完善设置页连接测试
This commit is contained in:
@@ -14,6 +14,8 @@ find_package(Qt6 REQUIRED COMPONENTS Widgets Network)
|
|||||||
|
|
||||||
qt_add_executable(QtDesktopPet
|
qt_add_executable(QtDesktopPet
|
||||||
main.cpp
|
main.cpp
|
||||||
|
src/ai/AIProviderFactory.h
|
||||||
|
src/ai/AIProviderFactory.cpp
|
||||||
src/ai/ConversationManager.h
|
src/ai/ConversationManager.h
|
||||||
src/ai/ConversationManager.cpp
|
src/ai/ConversationManager.cpp
|
||||||
src/ai/LLMProvider.h
|
src/ai/LLMProvider.h
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ int main(int argc, char *argv[])
|
|||||||
window.applyAppConfig(configManager.loadAppConfig());
|
window.applyAppConfig(configManager.loadAppConfig());
|
||||||
|
|
||||||
TrayController trayController(&window);
|
TrayController trayController(&window);
|
||||||
|
window.setSettingsFallbackInContextMenuEnabled(!trayController.isAvailable());
|
||||||
trayController.show();
|
trayController.show();
|
||||||
|
|
||||||
QObject::connect(&app, &QCoreApplication::aboutToQuit, [&configManager, &window]() {
|
QObject::connect(&app, &QCoreApplication::aboutToQuit, [&configManager, &window]() {
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
#include "AIProviderFactory.h"
|
||||||
|
|
||||||
|
#include "../config/SecretStore.h"
|
||||||
|
#include "GoogleGeminiProvider.h"
|
||||||
|
#include "OpenAICompatibleProvider.h"
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
bool populateRuntimeApiKey(AIConfig &config, QString *errorMessage)
|
||||||
|
{
|
||||||
|
if (!config.apiKey.trimmed().isEmpty())
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.apiKeyStorage == QStringLiteral("windows-dpapi"))
|
||||||
|
{
|
||||||
|
if (config.apiKeyEncrypted.trimmed().isEmpty())
|
||||||
|
{
|
||||||
|
*errorMessage = QStringLiteral("请先在设置里配置 API Key。");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SecretStore::Result result = SecretStore::unprotectText(config.apiKeyEncrypted);
|
||||||
|
if (!result.success)
|
||||||
|
{
|
||||||
|
*errorMessage = QStringLiteral("API Key 解密失败:") + result.errorMessage;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
config.apiKey = result.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.apiKey.trimmed().isEmpty())
|
||||||
|
{
|
||||||
|
*errorMessage = QStringLiteral("请先在设置里配置 API Key。");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AIProviderFactory::prepareRuntimeConfig(AIConfig &config, QString *errorMessage)
|
||||||
|
{
|
||||||
|
const bool supportedProtocol = config.protocol == QStringLiteral("openai-compatible")
|
||||||
|
|| config.protocol == QStringLiteral("google-generative-language");
|
||||||
|
if (!supportedProtocol)
|
||||||
|
{
|
||||||
|
*errorMessage = QStringLiteral("当前 Provider 协议暂未接入。");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!populateRuntimeApiKey(config, errorMessage))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.baseUrl.trimmed().isEmpty())
|
||||||
|
{
|
||||||
|
*errorMessage = QStringLiteral("请先在设置里配置 Base URL。");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.model.trimmed().isEmpty())
|
||||||
|
{
|
||||||
|
*errorMessage = QStringLiteral("请先在设置里配置 Model。");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<LLMProvider> AIProviderFactory::createProvider(const AIConfig &config)
|
||||||
|
{
|
||||||
|
if (config.protocol == QStringLiteral("google-generative-language"))
|
||||||
|
{
|
||||||
|
return std::make_unique<GoogleGeminiProvider>(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.protocol == QStringLiteral("openai-compatible"))
|
||||||
|
{
|
||||||
|
return std::make_unique<OpenAICompatibleProvider>(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../config/AIConfig.h"
|
||||||
|
#include "LLMProvider.h"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
class AIProviderFactory
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static bool prepareRuntimeConfig(AIConfig &config, QString *errorMessage);
|
||||||
|
static std::unique_ptr<LLMProvider> createProvider(const AIConfig &config);
|
||||||
|
};
|
||||||
@@ -70,6 +70,11 @@ void TrayController::createMenu()
|
|||||||
hidePetWindow();
|
hidePetWindow();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
QAction *settingsAction = m_menu.addAction(QStringLiteral("设置..."));
|
||||||
|
QObject::connect(settingsAction, &QAction::triggered, [this]() {
|
||||||
|
openSettings();
|
||||||
|
});
|
||||||
|
|
||||||
m_menu.addSeparator();
|
m_menu.addSeparator();
|
||||||
|
|
||||||
QAction *quitAction = m_menu.addAction(QStringLiteral("退出"));
|
QAction *quitAction = m_menu.addAction(QStringLiteral("退出"));
|
||||||
@@ -117,6 +122,16 @@ void TrayController::togglePetWindowVisibility()
|
|||||||
showPetWindow();
|
showPetWindow();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TrayController::openSettings()
|
||||||
|
{
|
||||||
|
if (m_petWindow == nullptr)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_petWindow->openSettingsDialog();
|
||||||
|
}
|
||||||
|
|
||||||
void TrayController::quitApplication()
|
void TrayController::quitApplication()
|
||||||
{
|
{
|
||||||
if (m_petWindow != nullptr)
|
if (m_petWindow != nullptr)
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ private:
|
|||||||
void showPetWindow();
|
void showPetWindow();
|
||||||
void hidePetWindow();
|
void hidePetWindow();
|
||||||
void togglePetWindowVisibility();
|
void togglePetWindowVisibility();
|
||||||
|
void openSettings();
|
||||||
void quitApplication();
|
void quitApplication();
|
||||||
|
|
||||||
PetWindow *m_petWindow = nullptr;
|
PetWindow *m_petWindow = nullptr;
|
||||||
|
|||||||
+38
-101
@@ -1,11 +1,9 @@
|
|||||||
#include "PetWindow.h"
|
#include "PetWindow.h"
|
||||||
|
|
||||||
#include "../ai/ConversationManager.h"
|
#include "../ai/ConversationManager.h"
|
||||||
#include "../ai/GoogleGeminiProvider.h"
|
#include "../ai/AIProviderFactory.h"
|
||||||
#include "../ai/OpenAICompatibleProvider.h"
|
|
||||||
#include "../character/CharacterPackageLoader.h"
|
#include "../character/CharacterPackageLoader.h"
|
||||||
#include "../config/ConfigManager.h"
|
#include "../config/ConfigManager.h"
|
||||||
#include "../config/SecretStore.h"
|
|
||||||
#include "../util/Logger.h"
|
#include "../util/Logger.h"
|
||||||
#include "ChatBubble.h"
|
#include "ChatBubble.h"
|
||||||
#include "ChatHistoryPanel.h"
|
#include "ChatHistoryPanel.h"
|
||||||
@@ -62,85 +60,6 @@ AppConfig normalizedAppConfig(AppConfig config)
|
|||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool populateRuntimeApiKey(AIConfig &config, QString *errorMessage)
|
|
||||||
{
|
|
||||||
if (!config.apiKey.trimmed().isEmpty())
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.apiKeyStorage == QStringLiteral("windows-dpapi"))
|
|
||||||
{
|
|
||||||
if (config.apiKeyEncrypted.trimmed().isEmpty())
|
|
||||||
{
|
|
||||||
*errorMessage = QStringLiteral("请先在设置里配置 API Key。");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const SecretStore::Result result = SecretStore::unprotectText(config.apiKeyEncrypted);
|
|
||||||
if (!result.success)
|
|
||||||
{
|
|
||||||
*errorMessage = QStringLiteral("API Key 解密失败:") + result.errorMessage;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
config.apiKey = result.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.apiKey.trimmed().isEmpty())
|
|
||||||
{
|
|
||||||
*errorMessage = QStringLiteral("请先在设置里配置 API Key。");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool prepareRuntimeAIConfig(AIConfig &config, QString *errorMessage)
|
|
||||||
{
|
|
||||||
const bool supportedProtocol = config.protocol == QStringLiteral("openai-compatible")
|
|
||||||
|| config.protocol == QStringLiteral("google-generative-language");
|
|
||||||
if (!supportedProtocol)
|
|
||||||
{
|
|
||||||
*errorMessage = QStringLiteral("当前 Provider 协议暂未接入。");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!populateRuntimeApiKey(config, errorMessage))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.baseUrl.trimmed().isEmpty())
|
|
||||||
{
|
|
||||||
*errorMessage = QStringLiteral("请先在设置里配置 Base URL。");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.model.trimmed().isEmpty())
|
|
||||||
{
|
|
||||||
*errorMessage = QStringLiteral("请先在设置里配置 Model。");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unique_ptr<LLMProvider> createProvider(const AIConfig &config)
|
|
||||||
{
|
|
||||||
if (config.protocol == QStringLiteral("google-generative-language"))
|
|
||||||
{
|
|
||||||
return std::make_unique<GoogleGeminiProvider>(config);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.protocol == QStringLiteral("openai-compatible"))
|
|
||||||
{
|
|
||||||
return std::make_unique<OpenAICompatibleProvider>(config);
|
|
||||||
}
|
|
||||||
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString userVisibleErrorMessage(const ChatResponse &response)
|
QString userVisibleErrorMessage(const ChatResponse &response)
|
||||||
{
|
{
|
||||||
QString message = response.errorMessage.trimmed();
|
QString message = response.errorMessage.trimmed();
|
||||||
@@ -289,6 +208,34 @@ void PetWindow::showBubbleMessage(const QString &message)
|
|||||||
m_chatBubble->showMessage(message, bubbleAnchorPosition());
|
m_chatBubble->showMessage(message, bubbleAnchorPosition());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PetWindow::openSettingsDialog()
|
||||||
|
{
|
||||||
|
ConfigManager configManager;
|
||||||
|
SettingsDialog dialog(configManager.loadAIConfigStore(), currentAppConfig(), [this]() {
|
||||||
|
return isManualStateSwitchLocked();
|
||||||
|
}, this);
|
||||||
|
if (dialog.exec() != QDialog::Accepted)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!configManager.saveAIConfigStore(dialog.aiConfigStore()))
|
||||||
|
{
|
||||||
|
Logger::warning(QStringLiteral("Failed to save AI config from settings dialog."));
|
||||||
|
}
|
||||||
|
|
||||||
|
applyAppConfig(dialog.appConfig());
|
||||||
|
if (!configManager.saveAppConfig(currentAppConfig()))
|
||||||
|
{
|
||||||
|
Logger::warning(QStringLiteral("Failed to save app config from settings dialog."));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PetWindow::setSettingsFallbackInContextMenuEnabled(bool enabled)
|
||||||
|
{
|
||||||
|
m_settingsFallbackInContextMenuEnabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
void PetWindow::contextMenuEvent(QContextMenuEvent *event)
|
void PetWindow::contextMenuEvent(QContextMenuEvent *event)
|
||||||
{
|
{
|
||||||
resetBubbleAutoHideTimer();
|
resetBubbleAutoHideTimer();
|
||||||
@@ -307,7 +254,11 @@ void PetWindow::contextMenuEvent(QContextMenuEvent *event)
|
|||||||
cancelAIAction->setEnabled(aiRequestRunning);
|
cancelAIAction->setEnabled(aiRequestRunning);
|
||||||
QAction *clearConversationAction = menu.addAction(QStringLiteral("清空对话"));
|
QAction *clearConversationAction = menu.addAction(QStringLiteral("清空对话"));
|
||||||
clearConversationAction->setEnabled(m_conversationManager && m_conversationManager->hasHistory());
|
clearConversationAction->setEnabled(m_conversationManager && m_conversationManager->hasHistory());
|
||||||
QAction *settingsAction = menu.addAction(QStringLiteral("设置"));
|
QAction *settingsAction = nullptr;
|
||||||
|
if (m_settingsFallbackInContextMenuEnabled)
|
||||||
|
{
|
||||||
|
settingsAction = menu.addAction(QStringLiteral("设置"));
|
||||||
|
}
|
||||||
|
|
||||||
addStateTestActions(&menu);
|
addStateTestActions(&menu);
|
||||||
|
|
||||||
@@ -335,23 +286,9 @@ void PetWindow::contextMenuEvent(QContextMenuEvent *event)
|
|||||||
{
|
{
|
||||||
clearConversation();
|
clearConversation();
|
||||||
}
|
}
|
||||||
else if (selectedAction == settingsAction)
|
else if (settingsAction != nullptr && selectedAction == settingsAction)
|
||||||
{
|
{
|
||||||
ConfigManager configManager;
|
openSettingsDialog();
|
||||||
SettingsDialog dialog(configManager.loadAIConfigStore(), currentAppConfig(), this);
|
|
||||||
if (dialog.exec() == QDialog::Accepted)
|
|
||||||
{
|
|
||||||
if (!configManager.saveAIConfigStore(dialog.aiConfigStore()))
|
|
||||||
{
|
|
||||||
Logger::warning(QStringLiteral("Failed to save AI config from settings dialog."));
|
|
||||||
}
|
|
||||||
|
|
||||||
applyAppConfig(dialog.appConfig());
|
|
||||||
if (!configManager.saveAppConfig(currentAppConfig()))
|
|
||||||
{
|
|
||||||
Logger::warning(QStringLiteral("Failed to save app config from settings dialog."));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (selectedAction == exitAction)
|
else if (selectedAction == exitAction)
|
||||||
{
|
{
|
||||||
@@ -402,14 +339,14 @@ bool PetWindow::submitChatMessage(const QString &message)
|
|||||||
ConfigManager configManager;
|
ConfigManager configManager;
|
||||||
AIConfig config = configManager.loadAIConfig();
|
AIConfig config = configManager.loadAIConfig();
|
||||||
QString errorMessage;
|
QString errorMessage;
|
||||||
if (!prepareRuntimeAIConfig(config, &errorMessage))
|
if (!AIProviderFactory::prepareRuntimeConfig(config, &errorMessage))
|
||||||
{
|
{
|
||||||
playState(QStringLiteral("error"), false);
|
playState(QStringLiteral("error"), false);
|
||||||
showBubbleMessage(errorMessage);
|
showBubbleMessage(errorMessage);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<LLMProvider> provider = createProvider(config);
|
std::unique_ptr<LLMProvider> provider = AIProviderFactory::createProvider(config);
|
||||||
if (!provider)
|
if (!provider)
|
||||||
{
|
{
|
||||||
playState(QStringLiteral("error"), false);
|
playState(QStringLiteral("error"), false);
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ public:
|
|||||||
|
|
||||||
void applyAppConfig(const AppConfig &config);
|
void applyAppConfig(const AppConfig &config);
|
||||||
AppConfig currentAppConfig() const;
|
AppConfig currentAppConfig() const;
|
||||||
|
void openSettingsDialog();
|
||||||
|
void setSettingsFallbackInContextMenuEnabled(bool enabled);
|
||||||
void pauseAnimation();
|
void pauseAnimation();
|
||||||
void resumeAnimation();
|
void resumeAnimation();
|
||||||
void showBubbleMessage(const QString &message);
|
void showBubbleMessage(const QString &message);
|
||||||
@@ -98,4 +100,5 @@ private:
|
|||||||
bool m_returnToIdleAfterResume = false;
|
bool m_returnToIdleAfterResume = false;
|
||||||
bool m_streamingChatActive = false;
|
bool m_streamingChatActive = false;
|
||||||
bool m_streamingTalkStarted = false;
|
bool m_streamingTalkStarted = false;
|
||||||
|
bool m_settingsFallbackInContextMenuEnabled = true;
|
||||||
};
|
};
|
||||||
|
|||||||
+305
-25
@@ -1,5 +1,7 @@
|
|||||||
#include "SettingsDialog.h"
|
#include "SettingsDialog.h"
|
||||||
|
|
||||||
|
#include "../ai/AIProviderFactory.h"
|
||||||
|
#include "../ai/LLMTypes.h"
|
||||||
#include "../config/SecretStore.h"
|
#include "../config/SecretStore.h"
|
||||||
|
|
||||||
#include <QCheckBox>
|
#include <QCheckBox>
|
||||||
@@ -7,16 +9,25 @@
|
|||||||
#include <QDialogButtonBox>
|
#include <QDialogButtonBox>
|
||||||
#include <QDoubleSpinBox>
|
#include <QDoubleSpinBox>
|
||||||
#include <QFormLayout>
|
#include <QFormLayout>
|
||||||
|
#include <QFrame>
|
||||||
|
#include <QHBoxLayout>
|
||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
#include <QLineEdit>
|
#include <QLineEdit>
|
||||||
#include <QList>
|
#include <QList>
|
||||||
|
#include <QListWidget>
|
||||||
|
#include <QMessageBox>
|
||||||
#include <QPair>
|
#include <QPair>
|
||||||
|
#include <QPointer>
|
||||||
|
#include <QPushButton>
|
||||||
#include <QSpinBox>
|
#include <QSpinBox>
|
||||||
#include <QTabWidget>
|
#include <QStackedWidget>
|
||||||
|
#include <QStyle>
|
||||||
#include <QVBoxLayout>
|
#include <QVBoxLayout>
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
#include <QtGlobal>
|
#include <QtGlobal>
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
QString normalizedProviderName(const QString &provider)
|
QString normalizedProviderName(const QString &provider)
|
||||||
@@ -29,9 +40,29 @@ QString normalizedProviderName(const QString &provider)
|
|||||||
|
|
||||||
return normalized.isEmpty() ? QStringLiteral("custom") : normalized;
|
return normalized.isEmpty() ? QStringLiteral("custom") : normalized;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString userVisibleErrorMessage(const ChatResponse &response)
|
||||||
|
{
|
||||||
|
QString message = response.errorMessage.trimmed();
|
||||||
|
if (message.isEmpty())
|
||||||
|
{
|
||||||
|
message = QStringLiteral("未知错误。");
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingsDialog::SettingsDialog(const AIConfigStore &configStore, const AppConfig &appConfig, QWidget *parent)
|
if (response.httpStatus > 0)
|
||||||
|
{
|
||||||
|
message = QStringLiteral("HTTP ") + QString::number(response.httpStatus) + QStringLiteral(":") + message;
|
||||||
|
}
|
||||||
|
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsDialog::SettingsDialog(
|
||||||
|
const AIConfigStore &configStore,
|
||||||
|
const AppConfig &appConfig,
|
||||||
|
std::function<bool()> aiTestBlocked,
|
||||||
|
QWidget *parent)
|
||||||
: QDialog(parent)
|
: QDialog(parent)
|
||||||
, m_providerComboBox(new QComboBox(this))
|
, m_providerComboBox(new QComboBox(this))
|
||||||
, m_baseUrlEdit(new QLineEdit(this))
|
, m_baseUrlEdit(new QLineEdit(this))
|
||||||
@@ -41,17 +72,20 @@ SettingsDialog::SettingsDialog(const AIConfigStore &configStore, const AppConfig
|
|||||||
, m_timeoutSpinBox(new QSpinBox(this))
|
, m_timeoutSpinBox(new QSpinBox(this))
|
||||||
, m_temperatureSpinBox(new QDoubleSpinBox(this))
|
, m_temperatureSpinBox(new QDoubleSpinBox(this))
|
||||||
, m_maxTokensSpinBox(new QSpinBox(this))
|
, m_maxTokensSpinBox(new QSpinBox(this))
|
||||||
|
, m_testConnectionButton(new QPushButton(QStringLiteral("测试连接"), this))
|
||||||
, m_allowPlainApiKeyCheckBox(new QCheckBox(QStringLiteral("允许在非 Windows 环境明文保存 API Key"), this))
|
, m_allowPlainApiKeyCheckBox(new QCheckBox(QStringLiteral("允许在非 Windows 环境明文保存 API Key"), this))
|
||||||
, m_scaleSpinBox(new QSpinBox(this))
|
, m_scaleSpinBox(new QSpinBox(this))
|
||||||
, m_performanceModeComboBox(new QComboBox(this))
|
, m_performanceModeComboBox(new QComboBox(this))
|
||||||
, m_pauseWhenHiddenCheckBox(new QCheckBox(QStringLiteral("隐藏到托盘时暂停动画"), this))
|
, m_pauseWhenHiddenCheckBox(new QCheckBox(QStringLiteral("隐藏到托盘时暂停动画"), this))
|
||||||
, m_enableLazyLoadCheckBox(new QCheckBox(QStringLiteral("启用动画懒加载"), this))
|
, m_enableLazyLoadCheckBox(new QCheckBox(QStringLiteral("启用动画懒加载"), this))
|
||||||
|
, m_characterComboBox(new QComboBox(this))
|
||||||
, m_configStore(configStore)
|
, m_configStore(configStore)
|
||||||
, m_appConfig(appConfig)
|
, m_appConfig(appConfig)
|
||||||
|
, m_aiTestBlocked(std::move(aiTestBlocked))
|
||||||
{
|
{
|
||||||
setWindowTitle(QStringLiteral("设置"));
|
setWindowTitle(QStringLiteral("设置"));
|
||||||
setModal(true);
|
setModal(true);
|
||||||
resize(500, 430);
|
resize(760, 520);
|
||||||
|
|
||||||
const QList<QPair<QString, QString>> providers = {
|
const QList<QPair<QString, QString>> providers = {
|
||||||
{QStringLiteral("openai"), QStringLiteral("OpenAI")},
|
{QStringLiteral("openai"), QStringLiteral("OpenAI")},
|
||||||
@@ -97,51 +131,186 @@ SettingsDialog::SettingsDialog(const AIConfigStore &configStore, const AppConfig
|
|||||||
m_currentProvider = normalizedProviderName(m_providerComboBox->currentData().toString());
|
m_currentProvider = normalizedProviderName(m_providerComboBox->currentData().toString());
|
||||||
loadProviderConfig(m_currentProvider);
|
loadProviderConfig(m_currentProvider);
|
||||||
|
|
||||||
auto *formLayout = new QFormLayout();
|
m_testStatusLabel = new QLabel(QStringLiteral("未测试"), this);
|
||||||
formLayout->addRow(QStringLiteral("服务商"), m_providerComboBox);
|
m_testStatusLabel->setObjectName(QStringLiteral("TestStatusLabel"));
|
||||||
formLayout->addRow(QStringLiteral("Base URL"), m_baseUrlEdit);
|
m_testStatusLabel->setWordWrap(true);
|
||||||
formLayout->addRow(QStringLiteral("API Key"), m_apiKeyEdit);
|
|
||||||
formLayout->addRow(QStringLiteral("Model"), m_modelEdit);
|
auto *aiTitleLabel = new QLabel(QStringLiteral("AI 配置"), this);
|
||||||
formLayout->addRow(QStringLiteral("Path"), m_pathEdit);
|
aiTitleLabel->setObjectName(QStringLiteral("PageTitle"));
|
||||||
formLayout->addRow(QStringLiteral("Timeout"), m_timeoutSpinBox);
|
|
||||||
formLayout->addRow(QStringLiteral("Temperature"), m_temperatureSpinBox);
|
auto *aiFormLayout = new QFormLayout();
|
||||||
formLayout->addRow(QStringLiteral("Max Tokens"), m_maxTokensSpinBox);
|
aiFormLayout->setFieldGrowthPolicy(QFormLayout::ExpandingFieldsGrow);
|
||||||
formLayout->addRow(QString(), m_allowPlainApiKeyCheckBox);
|
aiFormLayout->setLabelAlignment(Qt::AlignRight);
|
||||||
|
aiFormLayout->setFormAlignment(Qt::AlignTop);
|
||||||
|
aiFormLayout->setHorizontalSpacing(18);
|
||||||
|
aiFormLayout->setVerticalSpacing(12);
|
||||||
|
aiFormLayout->addRow(QStringLiteral("服务商"), m_providerComboBox);
|
||||||
|
aiFormLayout->addRow(QStringLiteral("Base URL"), m_baseUrlEdit);
|
||||||
|
aiFormLayout->addRow(QStringLiteral("API Key"), m_apiKeyEdit);
|
||||||
|
aiFormLayout->addRow(QStringLiteral("Model"), m_modelEdit);
|
||||||
|
aiFormLayout->addRow(QStringLiteral("Path"), m_pathEdit);
|
||||||
|
aiFormLayout->addRow(QStringLiteral("Timeout"), m_timeoutSpinBox);
|
||||||
|
aiFormLayout->addRow(QStringLiteral("Temperature"), m_temperatureSpinBox);
|
||||||
|
aiFormLayout->addRow(QStringLiteral("Max Tokens"), m_maxTokensSpinBox);
|
||||||
|
aiFormLayout->addRow(QString(), m_allowPlainApiKeyCheckBox);
|
||||||
|
|
||||||
|
auto *storageHintLabel = new QLabel(this);
|
||||||
|
storageHintLabel->setObjectName(QStringLiteral("HintLabel"));
|
||||||
|
storageHintLabel->setWordWrap(true);
|
||||||
|
storageHintLabel->setText(SecretStore::isEncryptionAvailable()
|
||||||
|
? QStringLiteral("API Key 将使用 Windows DPAPI 加密后保存。")
|
||||||
|
: QStringLiteral("当前平台不支持内置加密。只有勾选确认后才会明文保存 API Key。"));
|
||||||
|
|
||||||
|
auto *aiActionLayout = new QHBoxLayout();
|
||||||
|
aiActionLayout->addWidget(m_testConnectionButton);
|
||||||
|
aiActionLayout->addWidget(m_testStatusLabel, 1);
|
||||||
|
|
||||||
|
auto *aiPageLayout = new QVBoxLayout();
|
||||||
|
aiPageLayout->setContentsMargins(24, 24, 24, 24);
|
||||||
|
aiPageLayout->setSpacing(16);
|
||||||
|
aiPageLayout->addWidget(aiTitleLabel);
|
||||||
|
aiPageLayout->addLayout(aiFormLayout);
|
||||||
|
aiPageLayout->addWidget(storageHintLabel);
|
||||||
|
aiPageLayout->addLayout(aiActionLayout);
|
||||||
|
aiPageLayout->addStretch();
|
||||||
|
|
||||||
auto *aiPage = new QWidget(this);
|
auto *aiPage = new QWidget(this);
|
||||||
aiPage->setLayout(formLayout);
|
aiPage->setLayout(aiPageLayout);
|
||||||
|
|
||||||
|
auto *appTitleLabel = new QLabel(QStringLiteral("应用设置"), this);
|
||||||
|
appTitleLabel->setObjectName(QStringLiteral("PageTitle"));
|
||||||
|
|
||||||
auto *appFormLayout = new QFormLayout();
|
auto *appFormLayout = new QFormLayout();
|
||||||
|
appFormLayout->setFieldGrowthPolicy(QFormLayout::ExpandingFieldsGrow);
|
||||||
|
appFormLayout->setLabelAlignment(Qt::AlignRight);
|
||||||
|
appFormLayout->setFormAlignment(Qt::AlignTop);
|
||||||
|
appFormLayout->setHorizontalSpacing(18);
|
||||||
|
appFormLayout->setVerticalSpacing(12);
|
||||||
appFormLayout->addRow(QStringLiteral("缩放"), m_scaleSpinBox);
|
appFormLayout->addRow(QStringLiteral("缩放"), m_scaleSpinBox);
|
||||||
appFormLayout->addRow(QStringLiteral("性能模式"), m_performanceModeComboBox);
|
appFormLayout->addRow(QStringLiteral("性能模式"), m_performanceModeComboBox);
|
||||||
appFormLayout->addRow(QString(), m_pauseWhenHiddenCheckBox);
|
appFormLayout->addRow(QString(), m_pauseWhenHiddenCheckBox);
|
||||||
appFormLayout->addRow(QString(), m_enableLazyLoadCheckBox);
|
appFormLayout->addRow(QString(), m_enableLazyLoadCheckBox);
|
||||||
|
|
||||||
|
auto *appPageLayout = new QVBoxLayout();
|
||||||
|
appPageLayout->setContentsMargins(24, 24, 24, 24);
|
||||||
|
appPageLayout->setSpacing(16);
|
||||||
|
appPageLayout->addWidget(appTitleLabel);
|
||||||
|
appPageLayout->addLayout(appFormLayout);
|
||||||
|
appPageLayout->addStretch();
|
||||||
|
|
||||||
auto *appPage = new QWidget(this);
|
auto *appPage = new QWidget(this);
|
||||||
appPage->setLayout(appFormLayout);
|
appPage->setLayout(appPageLayout);
|
||||||
|
|
||||||
auto *tabWidget = new QTabWidget(this);
|
m_characterComboBox->addItem(QStringLiteral("shiroko"), QStringLiteral("shiroko"));
|
||||||
tabWidget->addTab(aiPage, QStringLiteral("AI"));
|
m_characterComboBox->setEnabled(false);
|
||||||
tabWidget->addTab(appPage, QStringLiteral("应用"));
|
m_characterComboBox->setToolTip(QStringLiteral("角色选择将在多角色资源配置接入后启用。"));
|
||||||
|
|
||||||
auto *storageHintLabel = new QLabel(this);
|
auto *characterTitleLabel = new QLabel(QStringLiteral("角色"), this);
|
||||||
storageHintLabel->setWordWrap(true);
|
characterTitleLabel->setObjectName(QStringLiteral("PageTitle"));
|
||||||
storageHintLabel->setText(SecretStore::isEncryptionAvailable()
|
auto *characterHintLabel = new QLabel(QStringLiteral("当前版本使用内置角色资源。"), this);
|
||||||
? QStringLiteral("API Key 将使用 Windows DPAPI 加密后保存。")
|
characterHintLabel->setObjectName(QStringLiteral("HintLabel"));
|
||||||
: QStringLiteral("当前平台不支持内置加密。只有勾选确认后才会明文保存 API Key。"));
|
characterHintLabel->setWordWrap(true);
|
||||||
|
|
||||||
|
auto *characterFormLayout = new QFormLayout();
|
||||||
|
characterFormLayout->setFieldGrowthPolicy(QFormLayout::ExpandingFieldsGrow);
|
||||||
|
characterFormLayout->setLabelAlignment(Qt::AlignRight);
|
||||||
|
characterFormLayout->setHorizontalSpacing(18);
|
||||||
|
characterFormLayout->setVerticalSpacing(12);
|
||||||
|
characterFormLayout->addRow(QStringLiteral("当前角色"), m_characterComboBox);
|
||||||
|
|
||||||
|
auto *characterPageLayout = new QVBoxLayout();
|
||||||
|
characterPageLayout->setContentsMargins(24, 24, 24, 24);
|
||||||
|
characterPageLayout->setSpacing(16);
|
||||||
|
characterPageLayout->addWidget(characterTitleLabel);
|
||||||
|
characterPageLayout->addLayout(characterFormLayout);
|
||||||
|
characterPageLayout->addWidget(characterHintLabel);
|
||||||
|
characterPageLayout->addStretch();
|
||||||
|
|
||||||
|
auto *characterPage = new QWidget(this);
|
||||||
|
characterPage->setLayout(characterPageLayout);
|
||||||
|
|
||||||
|
auto *navigationList = new QListWidget(this);
|
||||||
|
navigationList->setObjectName(QStringLiteral("SettingsNavigation"));
|
||||||
|
navigationList->setFixedWidth(150);
|
||||||
|
navigationList->setFrameShape(QFrame::NoFrame);
|
||||||
|
navigationList->setSpacing(4);
|
||||||
|
navigationList->addItem(QStringLiteral("AI 配置"));
|
||||||
|
navigationList->addItem(QStringLiteral("应用"));
|
||||||
|
navigationList->addItem(QStringLiteral("角色"));
|
||||||
|
|
||||||
|
auto *pageStack = new QStackedWidget(this);
|
||||||
|
pageStack->setObjectName(QStringLiteral("SettingsPages"));
|
||||||
|
pageStack->addWidget(aiPage);
|
||||||
|
pageStack->addWidget(appPage);
|
||||||
|
pageStack->addWidget(characterPage);
|
||||||
|
|
||||||
|
connect(navigationList, &QListWidget::currentRowChanged, pageStack, &QStackedWidget::setCurrentIndex);
|
||||||
|
navigationList->setCurrentRow(0);
|
||||||
|
|
||||||
|
auto *contentLayout = new QHBoxLayout();
|
||||||
|
contentLayout->setContentsMargins(0, 0, 0, 0);
|
||||||
|
contentLayout->setSpacing(12);
|
||||||
|
contentLayout->addWidget(navigationList);
|
||||||
|
contentLayout->addWidget(pageStack, 1);
|
||||||
|
|
||||||
auto *buttonBox = new QDialogButtonBox(QDialogButtonBox::Save | QDialogButtonBox::Cancel, this);
|
auto *buttonBox = new QDialogButtonBox(QDialogButtonBox::Save | QDialogButtonBox::Cancel, this);
|
||||||
connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
||||||
connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||||
|
|
||||||
auto *layout = new QVBoxLayout(this);
|
auto *layout = new QVBoxLayout(this);
|
||||||
layout->addWidget(tabWidget);
|
layout->setContentsMargins(16, 16, 16, 16);
|
||||||
layout->addWidget(storageHintLabel);
|
layout->setSpacing(14);
|
||||||
|
layout->addLayout(contentLayout, 1);
|
||||||
layout->addWidget(buttonBox);
|
layout->addWidget(buttonBox);
|
||||||
|
|
||||||
|
setStyleSheet(QStringLiteral(
|
||||||
|
"QDialog { background: #f8fafc; }"
|
||||||
|
"QLabel#PageTitle { color: #111827; font-size: 18px; font-weight: 600; }"
|
||||||
|
"QLabel#HintLabel { color: #667085; }"
|
||||||
|
"QLabel#TestStatusLabel { color: #667085; padding-left: 8px; }"
|
||||||
|
"QLabel#TestStatusLabel[status=\"error\"] { color: #b42318; }"
|
||||||
|
"QLabel#TestStatusLabel[status=\"success\"] { color: #067647; }"
|
||||||
|
"QLabel#TestStatusLabel[status=\"info\"] { color: #175cd3; }"
|
||||||
|
"QLineEdit, QComboBox, QSpinBox, QDoubleSpinBox {"
|
||||||
|
" min-height: 30px; border: 1px solid #d0d5dd; border-radius: 6px;"
|
||||||
|
" padding: 4px 8px; background: #ffffff;"
|
||||||
|
"}"
|
||||||
|
"QLineEdit:focus, QComboBox:focus, QSpinBox:focus, QDoubleSpinBox:focus {"
|
||||||
|
" border: 1px solid #2e90fa;"
|
||||||
|
"}"
|
||||||
|
"QListWidget#SettingsNavigation {"
|
||||||
|
" background: transparent; border: none; padding: 4px;"
|
||||||
|
"}"
|
||||||
|
"QListWidget#SettingsNavigation::item {"
|
||||||
|
" min-height: 34px; padding: 6px 10px; border-radius: 6px; color: #475467;"
|
||||||
|
"}"
|
||||||
|
"QListWidget#SettingsNavigation::item:selected {"
|
||||||
|
" background: #eaf3ff; color: #175cd3; font-weight: 600;"
|
||||||
|
"}"
|
||||||
|
"QStackedWidget#SettingsPages {"
|
||||||
|
" background: #ffffff; border: 1px solid #eaecf0; border-radius: 8px;"
|
||||||
|
"}"
|
||||||
|
"QPushButton {"
|
||||||
|
" min-height: 30px; border: 1px solid #d0d5dd; border-radius: 6px;"
|
||||||
|
" padding: 4px 14px; background: #ffffff; color: #344054;"
|
||||||
|
"}"
|
||||||
|
"QPushButton:disabled { color: #98a2b3; background: #f2f4f7; }"
|
||||||
|
"QPushButton:hover:enabled { background: #f9fafb; }"));
|
||||||
|
|
||||||
connect(m_providerComboBox, &QComboBox::currentTextChanged, this, [this]() {
|
connect(m_providerComboBox, &QComboBox::currentTextChanged, this, [this]() {
|
||||||
switchProvider(m_providerComboBox->currentData().toString());
|
switchProvider(m_providerComboBox->currentData().toString());
|
||||||
});
|
});
|
||||||
|
connect(m_testConnectionButton, &QPushButton::clicked, this, [this]() {
|
||||||
|
testConnection();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsDialog::~SettingsDialog()
|
||||||
|
{
|
||||||
|
if (m_testProvider)
|
||||||
|
{
|
||||||
|
m_testProvider->cancel();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AIConfigStore SettingsDialog::aiConfigStore() const
|
AIConfigStore SettingsDialog::aiConfigStore() const
|
||||||
@@ -202,6 +371,13 @@ void SettingsDialog::switchProvider(const QString &provider)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (m_testProvider && m_testProvider->isBusy())
|
||||||
|
{
|
||||||
|
m_testProvider->cancel();
|
||||||
|
m_testConnectionButton->setEnabled(true);
|
||||||
|
setTestStatus(QStringLiteral("测试已取消。"), QStringLiteral("info"));
|
||||||
|
}
|
||||||
|
|
||||||
cacheCurrentProvider();
|
cacheCurrentProvider();
|
||||||
loadProviderConfig(normalizedProvider);
|
loadProviderConfig(normalizedProvider);
|
||||||
m_currentProvider = normalizedProvider;
|
m_currentProvider = normalizedProvider;
|
||||||
@@ -255,6 +431,38 @@ AIConfig SettingsDialog::configFromForm(const QString &provider) const
|
|||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AIConfig SettingsDialog::runtimeConfigFromForm(const QString &provider) const
|
||||||
|
{
|
||||||
|
const QString normalizedProvider = normalizedProviderName(provider);
|
||||||
|
AIConfig config = m_configStore.providers.value(normalizedProvider, defaultAIConfigForProvider(normalizedProvider));
|
||||||
|
config.provider = normalizedProvider;
|
||||||
|
config.protocol = defaultAIConfigForProvider(normalizedProvider).protocol;
|
||||||
|
config.baseUrl = m_baseUrlEdit->text().trimmed();
|
||||||
|
config.model = m_modelEdit->text().trimmed();
|
||||||
|
config.path = m_pathEdit->text().trimmed();
|
||||||
|
config.timeoutMs = m_timeoutSpinBox->value();
|
||||||
|
config.maxTokens = m_maxTokensSpinBox->value();
|
||||||
|
config.allowPlainApiKey = m_allowPlainApiKeyCheckBox->isChecked();
|
||||||
|
|
||||||
|
const QString apiKey = m_apiKeyEdit->text();
|
||||||
|
config.apiKey.clear();
|
||||||
|
config.apiKeyEncrypted.clear();
|
||||||
|
if (!apiKey.isEmpty())
|
||||||
|
{
|
||||||
|
config.apiKey = apiKey;
|
||||||
|
config.apiKeyStorage = QStringLiteral("runtime");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
config.apiKeyStorage = QStringLiteral("none");
|
||||||
|
}
|
||||||
|
|
||||||
|
config.stream = false;
|
||||||
|
config.temperature = 0.0;
|
||||||
|
config.maxTokens = qMin(config.maxTokens, 16);
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
QString SettingsDialog::decryptedApiKey(const AIConfig &config) const
|
QString SettingsDialog::decryptedApiKey(const AIConfig &config) const
|
||||||
{
|
{
|
||||||
if (config.apiKeyStorage == QStringLiteral("windows-dpapi") && !config.apiKeyEncrypted.isEmpty())
|
if (config.apiKeyStorage == QStringLiteral("windows-dpapi") && !config.apiKeyEncrypted.isEmpty())
|
||||||
@@ -270,3 +478,75 @@ QString SettingsDialog::decryptedApiKey(const AIConfig &config) const
|
|||||||
|
|
||||||
return config.apiKey;
|
return config.apiKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SettingsDialog::testConnection()
|
||||||
|
{
|
||||||
|
if (m_aiTestBlocked && m_aiTestBlocked())
|
||||||
|
{
|
||||||
|
QMessageBox::information(
|
||||||
|
this,
|
||||||
|
QStringLiteral("测试连接"),
|
||||||
|
QStringLiteral("当前 AI 对话正在进行,请等待对话结束后再测试连接。"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_testProvider && m_testProvider->isBusy())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString provider = normalizedProviderName(m_providerComboBox->currentData().toString());
|
||||||
|
AIConfig config = runtimeConfigFromForm(provider);
|
||||||
|
QString errorMessage;
|
||||||
|
if (!AIProviderFactory::prepareRuntimeConfig(config, &errorMessage))
|
||||||
|
{
|
||||||
|
setTestStatus(errorMessage, QStringLiteral("error"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<LLMProvider> providerInstance = AIProviderFactory::createProvider(config);
|
||||||
|
if (!providerInstance)
|
||||||
|
{
|
||||||
|
setTestStatus(QStringLiteral("当前 Provider 协议暂未接入。"), QStringLiteral("error"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_testProvider = std::move(providerInstance);
|
||||||
|
m_testConnectionButton->setEnabled(false);
|
||||||
|
setTestStatus(QStringLiteral("正在测试连接..."), QStringLiteral("info"));
|
||||||
|
|
||||||
|
ChatRequest request;
|
||||||
|
request.messages.append({
|
||||||
|
QStringLiteral("user"),
|
||||||
|
QStringLiteral("请只回复 OK,用于连接测试。")
|
||||||
|
});
|
||||||
|
|
||||||
|
QPointer<SettingsDialog> dialog(this);
|
||||||
|
m_testProvider->sendChatRequest(request, [dialog](const ChatResponse &response) {
|
||||||
|
if (dialog.isNull())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog->m_testConnectionButton->setEnabled(true);
|
||||||
|
if (response.success)
|
||||||
|
{
|
||||||
|
const QString status = response.httpStatus > 0
|
||||||
|
? QStringLiteral("连接成功,HTTP ") + QString::number(response.httpStatus)
|
||||||
|
: QStringLiteral("连接成功。");
|
||||||
|
dialog->setTestStatus(status, QStringLiteral("success"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog->setTestStatus(QStringLiteral("连接失败:") + userVisibleErrorMessage(response), QStringLiteral("error"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void SettingsDialog::setTestStatus(const QString &message, const QString &state)
|
||||||
|
{
|
||||||
|
m_testStatusLabel->setText(message);
|
||||||
|
m_testStatusLabel->setToolTip(message);
|
||||||
|
m_testStatusLabel->setProperty("status", state);
|
||||||
|
m_testStatusLabel->style()->unpolish(m_testStatusLabel);
|
||||||
|
m_testStatusLabel->style()->polish(m_testStatusLabel);
|
||||||
|
}
|
||||||
|
|||||||
+20
-1
@@ -5,16 +5,27 @@
|
|||||||
|
|
||||||
#include <QDialog>
|
#include <QDialog>
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
class QCheckBox;
|
class QCheckBox;
|
||||||
class QComboBox;
|
class QComboBox;
|
||||||
class QDoubleSpinBox;
|
class QDoubleSpinBox;
|
||||||
|
class QLabel;
|
||||||
class QLineEdit;
|
class QLineEdit;
|
||||||
|
class QPushButton;
|
||||||
class QSpinBox;
|
class QSpinBox;
|
||||||
|
class LLMProvider;
|
||||||
|
|
||||||
class SettingsDialog : public QDialog
|
class SettingsDialog : public QDialog
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit SettingsDialog(const AIConfigStore &configStore, const AppConfig &appConfig, QWidget *parent = nullptr);
|
explicit SettingsDialog(
|
||||||
|
const AIConfigStore &configStore,
|
||||||
|
const AppConfig &appConfig,
|
||||||
|
std::function<bool()> aiTestBlocked,
|
||||||
|
QWidget *parent = nullptr);
|
||||||
|
~SettingsDialog() override;
|
||||||
|
|
||||||
AIConfigStore aiConfigStore() const;
|
AIConfigStore aiConfigStore() const;
|
||||||
AppConfig appConfig() const;
|
AppConfig appConfig() const;
|
||||||
@@ -24,7 +35,10 @@ private:
|
|||||||
void loadProviderConfig(const QString &provider);
|
void loadProviderConfig(const QString &provider);
|
||||||
void switchProvider(const QString &provider);
|
void switchProvider(const QString &provider);
|
||||||
AIConfig configFromForm(const QString &provider) const;
|
AIConfig configFromForm(const QString &provider) const;
|
||||||
|
AIConfig runtimeConfigFromForm(const QString &provider) const;
|
||||||
QString decryptedApiKey(const AIConfig &config) const;
|
QString decryptedApiKey(const AIConfig &config) const;
|
||||||
|
void testConnection();
|
||||||
|
void setTestStatus(const QString &message, const QString &state);
|
||||||
|
|
||||||
QComboBox *m_providerComboBox = nullptr;
|
QComboBox *m_providerComboBox = nullptr;
|
||||||
QLineEdit *m_baseUrlEdit = nullptr;
|
QLineEdit *m_baseUrlEdit = nullptr;
|
||||||
@@ -34,12 +48,17 @@ private:
|
|||||||
QSpinBox *m_timeoutSpinBox = nullptr;
|
QSpinBox *m_timeoutSpinBox = nullptr;
|
||||||
QDoubleSpinBox *m_temperatureSpinBox = nullptr;
|
QDoubleSpinBox *m_temperatureSpinBox = nullptr;
|
||||||
QSpinBox *m_maxTokensSpinBox = nullptr;
|
QSpinBox *m_maxTokensSpinBox = nullptr;
|
||||||
|
QPushButton *m_testConnectionButton = nullptr;
|
||||||
|
QLabel *m_testStatusLabel = nullptr;
|
||||||
QCheckBox *m_allowPlainApiKeyCheckBox = nullptr;
|
QCheckBox *m_allowPlainApiKeyCheckBox = nullptr;
|
||||||
QSpinBox *m_scaleSpinBox = nullptr;
|
QSpinBox *m_scaleSpinBox = nullptr;
|
||||||
QComboBox *m_performanceModeComboBox = nullptr;
|
QComboBox *m_performanceModeComboBox = nullptr;
|
||||||
QCheckBox *m_pauseWhenHiddenCheckBox = nullptr;
|
QCheckBox *m_pauseWhenHiddenCheckBox = nullptr;
|
||||||
QCheckBox *m_enableLazyLoadCheckBox = nullptr;
|
QCheckBox *m_enableLazyLoadCheckBox = nullptr;
|
||||||
|
QComboBox *m_characterComboBox = nullptr;
|
||||||
AIConfigStore m_configStore;
|
AIConfigStore m_configStore;
|
||||||
AppConfig m_appConfig;
|
AppConfig m_appConfig;
|
||||||
QString m_currentProvider;
|
QString m_currentProvider;
|
||||||
|
std::function<bool()> m_aiTestBlocked;
|
||||||
|
std::unique_ptr<LLMProvider> m_testProvider;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user