From 1786f265893fbce7564ceacbc1b4cf0c90d1a295 Mon Sep 17 00:00:00 2001 From: Martin Dvorak Date: Sat, 30 Mar 2024 15:10:15 +0100 Subject: [PATCH] Wingman config and preferences done - STATIC LLM choice only (list of models NOT loaded from services) #1539 --- app/src/qt/dialogs/configuration_dialog.cpp | 182 +++++++++++++++----- app/src/qt/dialogs/configuration_dialog.h | 23 ++- lib/src/config/configuration.cpp | 22 +-- lib/src/config/configuration.h | 7 + lib/src/mind/ai/llm/openai_wingman.cpp | 2 +- lib/src/mind/mind.cpp | 13 +- 6 files changed, 186 insertions(+), 63 deletions(-) diff --git a/app/src/qt/dialogs/configuration_dialog.cpp b/app/src/qt/dialogs/configuration_dialog.cpp index b0818dfc..173cb477 100644 --- a/app/src/qt/dialogs/configuration_dialog.cpp +++ b/app/src/qt/dialogs/configuration_dialog.cpp @@ -680,8 +680,9 @@ void ConfigurationDialog::MindTab::save() * Wingman Open AI tab */ -ConfigurationDialog::WingmanOpenAiTab::WingmanOpenAiTab(QWidget* parent) +ConfigurationDialog::WingmanOpenAiTab::WingmanOpenAiTab(QWidget* parent, QComboBox* parentLlmProvidersCombo) : QWidget(parent), + parentLlmProvidersCombo{parentLlmProvidersCombo}, config(Configuration::getInstance()) { helpLabel = new QLabel( @@ -689,21 +690,30 @@ ConfigurationDialog::WingmanOpenAiTab::WingmanOpenAiTab(QWidget* parent) "OpenAI LLM provider configuration:\n" "" ).arg(ENV_VAR_OPENAI_API_KEY)); - helpLabel->setVisible(!config.canWingmanOpenAi()); + helpLabel->setVisible(!config.canWingmanOpenAiFromEnv()); apiKeyLabel = new QLabel(tr("
API key:")); apiKeyLabel->setVisible(helpLabel->isVisible()); apiKeyEdit = new QLineEdit(this); apiKeyEdit->setVisible(helpLabel->isVisible()); - setOllamaButton = new QPushButton(tr("Set ollama"), this); // enabled on valid config > add ollama to drop down > choose it in drop down + // enabled on valid config > add ollama to drop down > choose it in drop down + setOpenAiButton = new QPushButton(tr("Set OpenAI as LLM Provider"), this); + setOpenAiButton->setToolTip( + tr("Add OpenAI to the dropdown with LLM providers (if not there) and set it for use with Wingman") + ); + setOpenAiButton->setVisible(helpLabel->isVisible()); clearApiKeyButton = new QPushButton(tr("Clear API Key"), this); clearApiKeyButton->setVisible(helpLabel->isVisible()); + llmModelsLabel = new QLabel(tr("LLM model:")); + llmModelsCombo = new QComboBox(); + llmModelsCombo->addItem(LLM_MODEL_NONE); + llmModelsCombo->addItem(LLM_MODEL_GPT35_TURBO); + llmModelsCombo->addItem(LLM_MODEL_GPT4); configuredLabel = new QLabel( tr("The OpenAI API key is configured using the environment variable."), this); @@ -713,8 +723,13 @@ ConfigurationDialog::WingmanOpenAiTab::WingmanOpenAiTab(QWidget* parent) llmProvidersLayout->addWidget(helpLabel); llmProvidersLayout->addWidget(apiKeyLabel); llmProvidersLayout->addWidget(apiKeyEdit); - llmProvidersLayout->addWidget(clearApiKeyButton); llmProvidersLayout->addWidget(configuredLabel); + llmProvidersLayout->addWidget(llmModelsLabel); + llmProvidersLayout->addWidget(llmModelsCombo); + QHBoxLayout* buttonsLayout = new QHBoxLayout{}; + buttonsLayout->addWidget(setOpenAiButton); + buttonsLayout->addWidget(clearApiKeyButton); + llmProvidersLayout->addLayout(buttonsLayout); llmProvidersLayout->addStretch(); QVBoxLayout* layout = new QVBoxLayout(); @@ -722,6 +737,9 @@ ConfigurationDialog::WingmanOpenAiTab::WingmanOpenAiTab(QWidget* parent) layout->addStretch(); setLayout(layout); + QObject::connect( + setOpenAiButton, SIGNAL(clicked()), + this, SLOT(setOpenAiSlot())); QObject::connect( clearApiKeyButton, SIGNAL(clicked()), this, SLOT(clearApiKeySlot())); @@ -734,29 +752,92 @@ ConfigurationDialog::WingmanOpenAiTab::~WingmanOpenAiTab() delete apiKeyLabel; delete apiKeyEdit; delete clearApiKeyButton; + delete llmModelsLabel; + delete llmModelsCombo; +} + +void refreshWingmanLlmProvidersComboBox( + QComboBox* parentLlmProvidersCombo, + Configuration& config +) +{ + parentLlmProvidersCombo->clear(); + parentLlmProvidersCombo->addItem(WINGMAN_NONE_COMBO_LABEL); // NO LLM provider - NO Wingman +#ifdef MF_WIP + if(config.canWingmanMock()) { + parentLlmProvidersCombo->addItem( + QString::fromStdString(WINGMAN_MOCK_COMBO_LABEL), + WingmanLlmProviders::WINGMAN_PROVIDER_MOCK); + } +#endif + if(config.canWingmanOpenAi()) { + parentLlmProvidersCombo->addItem( + QString::fromStdString(WINGMAN_OPENAI_COMBO_LABEL), + WingmanLlmProviders::WINGMAN_PROVIDER_OPENAI); + } + if(config.canWingmanOllama()) { + parentLlmProvidersCombo->addItem( + QString::fromStdString(WINGMAN_OLLAMA_COMBO_LABEL), + WingmanLlmProviders::WINGMAN_PROVIDER_OLLAMA); + } + + // select configure provider in the combo box + parentLlmProvidersCombo->setCurrentIndex( + parentLlmProvidersCombo->findData(config.getWingmanLlmProvider())); + +} + +void ConfigurationDialog::WingmanOpenAiTab::setOpenAiSlot() +{ + MF_DEBUG("Signal SLOT: set OpenAI" << endl); + + if(apiKeyEdit->text().size()==0 && !config.canWingmanOpenAiFromEnv()) { + QMessageBox::critical( + this, + tr("LLM Provider Config Error"), + tr( + "Unable to set OpenAI as LLM provider as neither API key is set in the configuration, " + "nor it is defined and environment variable") + ); + config.setWingmanLlmProvider(WingmanLlmProviders::WINGMAN_PROVIDER_NONE); + refreshWingmanLlmProvidersComboBox(parentLlmProvidersCombo, config); + } + + save(); + config.setWingmanLlmProvider(WingmanLlmProviders::WINGMAN_PROVIDER_OPENAI); + refreshWingmanLlmProvidersComboBox(parentLlmProvidersCombo, config); } void ConfigurationDialog::WingmanOpenAiTab::clearApiKeySlot() { apiKeyEdit->clear(); + + save(); + if(config.getWingmanLlmProvider() == WingmanLlmProviders::WINGMAN_PROVIDER_OPENAI) { + config.setWingmanLlmProvider(WingmanLlmProviders::WINGMAN_PROVIDER_NONE); + } + refreshWingmanLlmProvidersComboBox(parentLlmProvidersCombo, config); QMessageBox::information( this, tr("OpenAI API Key Cleared"), tr( - "API key has been cleared from the configuration. " - "Please close the configuration dialog with the OK button to finish " - "the reconfiguration") + "API key has been cleared from the configuration and " + "OpenAI is no longer the LLM provider.") ); } void ConfigurationDialog::WingmanOpenAiTab::refresh() { apiKeyEdit->setText(QString::fromStdString(config.getWingmanOpenAiApiKey())); + llmModelsCombo->setCurrentText( + QString::fromStdString(config.getWingmanOpenAiLlm())); } void ConfigurationDialog::WingmanOpenAiTab::save() { config.setWingmanOpenAiApiKey(apiKeyEdit->text().toStdString()); + config.setWingmanOpenAiLlm( + llmModelsCombo->itemText(llmModelsCombo->currentIndex()).toStdString()); } @@ -764,8 +845,9 @@ void ConfigurationDialog::WingmanOpenAiTab::save() * Wingman ollama */ -ConfigurationDialog::WingmanOllamaTab::WingmanOllamaTab(QWidget* parent) +ConfigurationDialog::WingmanOllamaTab::WingmanOllamaTab(QWidget* parent, QComboBox* parentLlmProvidersCombo) : QWidget(parent), + parentLlmProvidersCombo{parentLlmProvidersCombo}, config(Configuration::getInstance()) { helpLabel = new QLabel( @@ -778,13 +860,24 @@ ConfigurationDialog::WingmanOllamaTab::WingmanOllamaTab(QWidget* parent) helpLabel->setVisible(!config.canWingmanOllama()); urlLabel = new QLabel(tr("
ollama server URL:")); urlEdit = new QLineEdit(this); + // enabled on valid config > add ollama to drop down > choose it in drop down + setOllamaButton = new QPushButton(tr("Set ollama as LLM Provider"), this); + setOllamaButton->setToolTip( + tr("Add ollama to the dropdown with LLM providers (if not there) and set it for use with Wingman") + ); clearUrlButton = new QPushButton(tr("Clear URL"), this); + llmModelsLabel = new QLabel(tr("LLM model:")); + llmModelsCombo = new QComboBox(); + llmModelsCombo->addItem(LLM_MODEL_NONE); QVBoxLayout* llmProvidersLayout = new QVBoxLayout(); llmProvidersLayout->addWidget(helpLabel); llmProvidersLayout->addWidget(urlLabel); llmProvidersLayout->addWidget(urlEdit); + llmProvidersLayout->addWidget(setOllamaButton); llmProvidersLayout->addWidget(clearUrlButton); + llmProvidersLayout->addWidget(llmModelsLabel); + llmProvidersLayout->addWidget(llmModelsCombo); llmProvidersLayout->addStretch(); QVBoxLayout* layout = new QVBoxLayout(); @@ -792,6 +885,9 @@ ConfigurationDialog::WingmanOllamaTab::WingmanOllamaTab(QWidget* parent) layout->addStretch(); setLayout(layout); + QObject::connect( + setOllamaButton, SIGNAL(clicked()), + this, SLOT(setOllamaSlot())); QObject::connect( clearUrlButton, SIGNAL(clicked()), this, SLOT(clearUrlSlot())); @@ -803,19 +899,46 @@ ConfigurationDialog::WingmanOllamaTab::~WingmanOllamaTab() delete urlLabel; delete urlEdit; delete clearUrlButton; + delete llmModelsLabel; + delete llmModelsCombo; +} + +void ConfigurationDialog::WingmanOllamaTab::setOllamaSlot() +{ + MF_DEBUG("Signal SLOT: set ollama" << endl); + + if(urlEdit->text().size()==0) { + QMessageBox::critical( + this, + tr("LLM Provider Config Error"), + tr( + "Unable to set ollama as LLM provider as neither API key is set in the configuration, " + "nor it is defined and environment variable") + ); + config.setWingmanLlmProvider(WingmanLlmProviders::WINGMAN_PROVIDER_NONE); + refreshWingmanLlmProvidersComboBox(parentLlmProvidersCombo, config); + } + + save(); + config.setWingmanLlmProvider(WingmanLlmProviders::WINGMAN_PROVIDER_OLLAMA); + refreshWingmanLlmProvidersComboBox(parentLlmProvidersCombo, config); } void ConfigurationDialog::WingmanOllamaTab::clearUrlSlot() { urlEdit->clear(); + + save(); + if(config.getWingmanLlmProvider() == WingmanLlmProviders::WINGMAN_PROVIDER_OLLAMA) { + config.setWingmanLlmProvider(WingmanLlmProviders::WINGMAN_PROVIDER_NONE); + } + refreshWingmanLlmProvidersComboBox(parentLlmProvidersCombo, config); QMessageBox::information( this, - tr("ollama URL Cleared"), + tr("ollama Server URL Cleared"), tr( - "ollama URL has been cleared from the configuration. " - "Please close the configuration dialog with the OK button " - "to finish the reconfiguration.") - ); + "ollama server URL has been cleared from the configuration and " + "ollama is no longer the LLM provider.")); } void ConfigurationDialog::WingmanOllamaTab::refresh() @@ -838,8 +961,6 @@ void ConfigurationDialog::WingmanOllamaTab::save() ConfigurationDialog::WingmanTab::WingmanTab(QWidget* parent) : QWidget(parent), - openAiComboLabel{"OpenAI"}, - ollamaComboLabel{"ollama"}, config(Configuration::getInstance()) { llmProvidersLabel = new QLabel(tr("LLM provider:"), this); @@ -850,8 +971,8 @@ ConfigurationDialog::WingmanTab::WingmanTab(QWidget* parent) ); wingmanTabWidget = new QTabWidget; - wingmanOpenAiTab = new WingmanOpenAiTab{this}; - wingmanOllamaTab = new WingmanOllamaTab{this}; + wingmanOpenAiTab = new WingmanOpenAiTab{this, llmProvidersCombo}; + wingmanOllamaTab = new WingmanOllamaTab{this, llmProvidersCombo}; wingmanTabWidget->addTab(wingmanOpenAiTab, tr("OpenAI")); wingmanTabWidget->addTab(wingmanOllamaTab, tr("ollama")); @@ -875,7 +996,7 @@ ConfigurationDialog::WingmanTab::WingmanTab(QWidget* parent) void ConfigurationDialog::WingmanTab::handleComboBoxChanged(int index) { string comboItemLabel{llmProvidersCombo->itemText(index).toStdString()}; MF_DEBUG("WingmanTab::handleComboBoxChange: '" << comboItemLabel << "'" << endl); - if(this->isVisible() && comboItemLabel == openAiComboLabel) { + if(this->isVisible() && comboItemLabel == WINGMAN_OPENAI_COMBO_LABEL) { QMessageBox::warning( this, tr("Data Privacy Warning"), @@ -895,27 +1016,10 @@ ConfigurationDialog::WingmanTab::~WingmanTab() void ConfigurationDialog::WingmanTab::refresh() { // refresh LLM providers combo - llmProvidersCombo->clear(); - llmProvidersCombo->addItem(""); // disable Wingman -#ifdef MF_WIP - if(config.canWingmanMock()) { - llmProvidersCombo->addItem( - QString{"Mock"}, WingmanLlmProviders::WINGMAN_PROVIDER_MOCK); - } -#endif - if(config.canWingmanOpenAi()) { - llmProvidersCombo->addItem( - QString::fromStdString(openAiComboLabel), WingmanLlmProviders::WINGMAN_PROVIDER_OPENAI); - } - if(config.canWingmanOllama()) { - llmProvidersCombo->addItem( - QString::fromStdString(ollamaComboLabel), WingmanLlmProviders::WINGMAN_PROVIDER_OLLAMA); - } + refreshWingmanLlmProvidersComboBox(llmProvidersCombo, config); + wingmanOpenAiTab->refresh(); wingmanOllamaTab->refresh(); - // set the last selected provider - llmProvidersCombo->setCurrentIndex( - llmProvidersCombo->findData(config.getWingmanLlmProvider())); } void ConfigurationDialog::WingmanTab::save() diff --git a/app/src/qt/dialogs/configuration_dialog.h b/app/src/qt/dialogs/configuration_dialog.h index 5cabcc5c..140375e9 100644 --- a/app/src/qt/dialogs/configuration_dialog.h +++ b/app/src/qt/dialogs/configuration_dialog.h @@ -73,6 +73,11 @@ private slots: void saveConfigSignal(); }; +constexpr const auto WINGMAN_NONE_COMBO_LABEL = ""; +constexpr const auto WINGMAN_OPENAI_COMBO_LABEL = "OpenAI"; +constexpr const auto WINGMAN_OLLAMA_COMBO_LABEL = "ollama"; +constexpr const auto WINGMAN_MOCK_COMBO_LABEL = "mock"; + /** * @brief Wingman tab's OpenAI tab. */ @@ -81,22 +86,27 @@ class ConfigurationDialog::WingmanOpenAiTab : public QWidget Q_OBJECT private: + QComboBox* parentLlmProvidersCombo; Configuration& config; QLabel* helpLabel; QLabel* configuredLabel; QLabel* apiKeyLabel; QLineEdit* apiKeyEdit; + QPushButton* setOpenAiButton; QPushButton* clearApiKeyButton; + QLabel* llmModelsLabel; + QComboBox* llmModelsCombo; public: - explicit WingmanOpenAiTab(QWidget* parent); + explicit WingmanOpenAiTab(QWidget* parent, QComboBox* parentLlmProvidersCombo); ~WingmanOpenAiTab(); void refresh(); void save(); private slots: + void setOpenAiSlot(); void clearApiKeySlot(); }; @@ -108,21 +118,27 @@ class ConfigurationDialog::WingmanOllamaTab : public QWidget Q_OBJECT private: + QComboBox* parentLlmProvidersCombo; + Configuration& config; QLabel* helpLabel; QLabel* urlLabel; QLineEdit* urlEdit; + QPushButton* setOllamaButton; QPushButton* clearUrlButton; + QLabel* llmModelsLabel; + QComboBox* llmModelsCombo; public: - explicit WingmanOllamaTab(QWidget* parent); + explicit WingmanOllamaTab(QWidget* parent, QComboBox* parentLlmProvidersCombo); ~WingmanOllamaTab(); void refresh(); void save(); private slots: + void setOllamaSlot(); void clearUrlSlot(); }; @@ -134,9 +150,6 @@ class ConfigurationDialog::WingmanTab : public QWidget Q_OBJECT private: - const std::string openAiComboLabel; - const std::string ollamaComboLabel; - Configuration& config; QLabel* llmProvidersLabel; diff --git a/lib/src/config/configuration.cpp b/lib/src/config/configuration.cpp index dfbbbaa7..17307b9b 100644 --- a/lib/src/config/configuration.cpp +++ b/lib/src/config/configuration.cpp @@ -29,10 +29,6 @@ using namespace m8r::filesystem; namespace m8r { -const string LLM_MODEL_GPT35 = string{"gpt-3.5"}; -// TODO ollama does NOT have to host llama2 > it should NOT be offered as default model -const string LLM_MODEL_LLAMA2 = string{"llama2"}; - const string KnowledgeTool::TOOL_PHRASE = string{"<>"}; // non-primitive constants initializations @@ -42,8 +38,8 @@ const string Configuration::DEFAULT_UI_THEME_NAME = string{UI_DEFAULT_THEME}; const string Configuration::DEFAULT_UI_HTML_CSS_THEME = string{UI_DEFAULT_HTML_CSS_THEME}; const string Configuration::DEFAULT_EDITOR_FONT= string{UI_DEFAULT_EDITOR_FONT}; const string Configuration::DEFAULT_TIME_SCOPE = string{"0y0m0d0h0m"}; -const string Configuration::DEFAULT_WINGMAN_LLM_MODEL_OPENAI = LLM_MODEL_GPT35; -const string Configuration::DEFAULT_WINGMAN_LLM_MODEL_OLLAMA = LLM_MODEL_LLAMA2; +const string Configuration::DEFAULT_WINGMAN_LLM_MODEL_OPENAI = string{LLM_MODEL_GPT35_TURBO}; +const string Configuration::DEFAULT_WINGMAN_LLM_MODEL_OLLAMA = string{LLM_MODEL_LLAMA2}; Configuration::Configuration() : asyncMindThreshold{}, @@ -398,12 +394,18 @@ const char* Configuration::getEditorFromEnv() return editor; } +bool Configuration::canWingmanOpenAiFromEnv() +{ + if(std::getenv(ENV_VAR_OPENAI_API_KEY) != nullptr) { + return true; + } + + return false; +} + bool Configuration::canWingmanOpenAi() { - if ( - this->wingmanOpenAiApiKey.size() > 0 - || std::getenv(ENV_VAR_OPENAI_API_KEY) != nullptr - ) { + if(this->wingmanOpenAiApiKey.size() > 0 || canWingmanOpenAiFromEnv()) { return true; } diff --git a/lib/src/config/configuration.h b/lib/src/config/configuration.h index 347279cb..2797b2fb 100644 --- a/lib/src/config/configuration.h +++ b/lib/src/config/configuration.h @@ -52,6 +52,12 @@ enum WingmanLlmProviders { WINGMAN_PROVIDER_OLLAMA }; +constexpr const auto LLM_MODEL_NONE = ""; +constexpr const auto LLM_MODEL_GPT35_TURBO = "gpt-3.5-turbo"; +constexpr const auto LLM_MODEL_GPT4 = "gpt-4"; +// TODO ollama does NOT have to host llama2 > it should NOT be offered as default model +constexpr const auto LLM_MODEL_LLAMA2 = "llama2"; + // const in constexpr makes value const constexpr const auto ENV_VAR_HOME = "HOME"; constexpr const auto ENV_VAR_DISPLAY = "DISPLAY"; @@ -569,6 +575,7 @@ class Configuration { #else bool canWingmanMock() { return false; } #endif + bool canWingmanOpenAiFromEnv(); bool canWingmanOpenAi(); bool canWingmanOllama(); private: diff --git a/lib/src/mind/ai/llm/openai_wingman.cpp b/lib/src/mind/ai/llm/openai_wingman.cpp index 0fc16b62..f01bf64a 100644 --- a/lib/src/mind/ai/llm/openai_wingman.cpp +++ b/lib/src/mind/ai/llm/openai_wingman.cpp @@ -134,7 +134,7 @@ void OpenAiWingman::curlGet(CommandWingmanChat& command) { << "<<<" << endl); - // TODO this must be refactored to parent class so that all wingmans can use it + // TODO this must be refactored to parent class so that all Wingmans can use it #if defined(_WIN32) || defined(__APPLE__) /* Qt Networking examples: * diff --git a/lib/src/mind/mind.cpp b/lib/src/mind/mind.cpp index 2b58b9c9..02784063 100644 --- a/lib/src/mind/mind.cpp +++ b/lib/src/mind/mind.cpp @@ -1464,10 +1464,8 @@ void Mind::initWingman() delete wingman; wingman = nullptr; } - wingman = (Wingman*)new OpenAiWingman{ - config.getWingmanOpenAiApiKey() - // TODO config.getWingmanOpenAiLlm() - }; + wingman = (Wingman*)new OpenAiWingman{config.getWingmanOpenAiApiKey()}; + wingman->setLlmModel(config.getWingmanOpenAiLlm()); wingmanLlmProvider = config.getWingmanLlmProvider(); return; case WingmanLlmProviders::WINGMAN_PROVIDER_OLLAMA: @@ -1476,10 +1474,8 @@ void Mind::initWingman() delete wingman; wingman = nullptr; } - wingman = (Wingman*)new OllamaWingman{ - config.getWingmanOllamaUrl(), - // TODO config.getWingmanOllamaLlm() - }; + wingman = (Wingman*)new OllamaWingman{config.getWingmanOllamaUrl()}; + wingman->setLlmModel(config.getWingmanOllamaLlm()); wingmanLlmProvider = config.getWingmanLlmProvider(); return; case WingmanLlmProviders::WINGMAN_PROVIDER_MOCK: @@ -1491,6 +1487,7 @@ void Mind::initWingman() return; case WingmanLlmProviders::WINGMAN_PROVIDER_NONE: MF_DEBUG(" MIND Wingman init: set to NONE > deinitialize > NO Wingman" << endl); + break; default: MF_DEBUG(" MIND Wingman init: UNKNOWN > NO Wingman" << endl); break;