diff --git a/download.cpp b/download.cpp index a35ed0e4..0663a4ab 100644 --- a/download.cpp +++ b/download.cpp @@ -27,7 +27,9 @@ Download::Download() &Download::handleHashAndSaveFinished, Qt::QueuedConnection); connect(&m_networkManager, &QNetworkAccessManager::sslErrors, this, &Download::handleSslErrors); + connect(this, &Download::downloadLocalModelsPathChanged, this, &Download::updateModelList); updateModelList(); + m_downloadLocalModelsPath = defaultLocalModelsPath(); } QList Download::modelList() const @@ -46,7 +48,22 @@ QList Download::modelList() const return values; } -QString Download::downloadLocalModelsPath() const +QString Download::downloadLocalModelsPath() const { + return m_downloadLocalModelsPath; +} + +void Download::setDownloadLocalModelsPath(const QString &modelPath) { + QString filePath = (modelPath.startsWith("file://") ? + QUrl(modelPath).toLocalFile() : modelPath); + QString canonical = QFileInfo(filePath).canonicalFilePath() + QDir::separator(); + qDebug() << "Set model path: " << canonical; + if (m_downloadLocalModelsPath != canonical) { + m_downloadLocalModelsPath = canonical; + emit downloadLocalModelsPathChanged(); + } +} + +QString Download::defaultLocalModelsPath() const { QString localPath = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + QDir::separator(); diff --git a/download.h b/download.h index 75bf3b2a..26a5e37c 100644 --- a/download.h +++ b/download.h @@ -50,6 +50,9 @@ class Download : public QObject { Q_OBJECT Q_PROPERTY(QList modelList READ modelList NOTIFY modelListChanged) + Q_PROPERTY(QString downloadLocalModelsPath READ downloadLocalModelsPath + WRITE setDownloadLocalModelsPath + NOTIFY downloadLocalModelsPathChanged) public: static Download *globalInstance(); @@ -58,7 +61,9 @@ public: Q_INVOKABLE void updateModelList(); Q_INVOKABLE void downloadModel(const QString &modelFile); Q_INVOKABLE void cancelDownload(const QString &modelFile); + Q_INVOKABLE QString defaultLocalModelsPath() const; Q_INVOKABLE QString downloadLocalModelsPath() const; + Q_INVOKABLE void setDownloadLocalModelsPath(const QString &modelPath); private Q_SLOTS: void handleSslErrors(QNetworkReply *reply, const QList &errors); @@ -73,6 +78,7 @@ Q_SIGNALS: void downloadProgress(qint64 bytesReceived, qint64 bytesTotal, const QString &modelFile); void downloadFinished(const QString &modelFile); void modelListChanged(); + void downloadLocalModelsPathChanged(); void requestHashAndSave(const QString &hash, const QString &saveFilePath, QTemporaryFile *tempFile, QNetworkReply *modelReply); @@ -83,6 +89,7 @@ private: QMap m_modelMap; QNetworkAccessManager m_networkManager; QMap m_activeDownloads; + QString m_downloadLocalModelsPath; private: explicit Download(); diff --git a/llm.cpp b/llm.cpp index ae0e4fa4..228e29da 100644 --- a/llm.cpp +++ b/llm.cpp @@ -282,7 +282,7 @@ bool LLMObject::handleRecalculate(bool isRecalc) } bool LLMObject::prompt(const QString &prompt, const QString &prompt_template, int32_t n_predict, int32_t top_k, float top_p, - float temp, int32_t n_batch) + float temp, int32_t n_batch, float repeat_penalty, int32_t repeat_penalty_tokens) { if (!isModelLoaded()) return false; @@ -300,6 +300,8 @@ bool LLMObject::prompt(const QString &prompt, const QString &prompt_template, in s_ctx.top_p = top_p; s_ctx.temp = temp; s_ctx.n_batch = n_batch; + s_ctx.repeat_penalty = repeat_penalty; + s_ctx.repeat_last_n = repeat_penalty_tokens; m_llmodel->prompt(instructPrompt.toStdString(), responseFunc, recalcFunc, s_ctx); m_responseLogits += s_ctx.logits.size() - logitsBefore; std::string trimmed = trim_whitespace(m_response); @@ -345,9 +347,9 @@ bool LLM::isModelLoaded() const } void LLM::prompt(const QString &prompt, const QString &prompt_template, int32_t n_predict, int32_t top_k, float top_p, - float temp, int32_t n_batch) + float temp, int32_t n_batch, float repeat_penalty, int32_t repeat_penalty_tokens) { - emit promptRequested(prompt, prompt_template, n_predict, top_k, top_p, temp, n_batch); + emit promptRequested(prompt, prompt_template, n_predict, top_k, top_p, temp, n_batch, repeat_penalty, repeat_penalty_tokens); } void LLM::regenerateResponse() diff --git a/llm.h b/llm.h index c2061eca..42028c9b 100644 --- a/llm.h +++ b/llm.h @@ -38,7 +38,7 @@ public: public Q_SLOTS: bool prompt(const QString &prompt, const QString &prompt_template, int32_t n_predict, int32_t top_k, float top_p, - float temp, int32_t n_batch); + float temp, int32_t n_batch, float repeat_penalty, int32_t repeat_penalty_tokens); bool loadModel(); void modelNameChangeRequested(const QString &modelName); @@ -85,7 +85,7 @@ public: Q_INVOKABLE bool isModelLoaded() const; Q_INVOKABLE void prompt(const QString &prompt, const QString &prompt_template, int32_t n_predict, int32_t top_k, float top_p, - float temp, int32_t n_batch); + float temp, int32_t n_batch, float repeat_penalty, int32_t repeat_penalty_tokens); Q_INVOKABLE void regenerateResponse(); Q_INVOKABLE void resetResponse(); Q_INVOKABLE void resetContext(); @@ -111,7 +111,7 @@ Q_SIGNALS: void responseChanged(); void responseInProgressChanged(); void promptRequested(const QString &prompt, const QString &prompt_template, int32_t n_predict, int32_t top_k, float top_p, - float temp, int32_t n_batch); + float temp, int32_t n_batch, float repeat_penalty, int32_t repeat_penalty_tokens); void regenerateResponseRequested(); void resetResponseRequested(); void resetContextRequested(); diff --git a/main.qml b/main.qml index 429e1b35..9485f3ac 100644 --- a/main.qml +++ b/main.qml @@ -824,7 +824,9 @@ Window { settingsDialog.maxLength, settingsDialog.topK, settingsDialog.topP, settingsDialog.temperature, - settingsDialog.promptBatchSize) + settingsDialog.promptBatchSize, + settingsDialog.repeatPenalty, + settingsDialog.repeatPenaltyTokens) } } } @@ -905,7 +907,9 @@ Window { settingsDialog.topK, settingsDialog.topP, settingsDialog.temperature, - settingsDialog.promptBatchSize) + settingsDialog.promptBatchSize, + settingsDialog.repeatPenalty, + settingsDialog.repeatPenaltyTokens) textInput.text = "" } } diff --git a/qml/ModelDownloaderDialog.qml b/qml/ModelDownloaderDialog.qml index c366dbac..16efda3d 100644 --- a/qml/ModelDownloaderDialog.qml +++ b/qml/ModelDownloaderDialog.qml @@ -293,7 +293,7 @@ Dialog { Label { Layout.alignment: Qt.AlignLeft Layout.fillWidth: true - text: qsTr("NOTE: models will be downloaded to\n") + Download.downloadLocalModelsPath() + text: qsTr("NOTE: models will be downloaded to\n") + Download.downloadLocalModelsPath wrapMode: Text.WrapAnywhere horizontalAlignment: Text.AlignHCenter color: theme.textColor diff --git a/qml/SettingsDialog.qml b/qml/SettingsDialog.qml index f81076c1..35ee4571 100644 --- a/qml/SettingsDialog.qml +++ b/qml/SettingsDialog.qml @@ -2,6 +2,7 @@ import QtCore import QtQuick import QtQuick.Controls import QtQuick.Controls.Basic +import QtQuick.Dialogs import QtQuick.Layouts import download import network @@ -31,12 +32,15 @@ Dialog { property int defaultTopK: 40 property int defaultMaxLength: 4096 property int defaultPromptBatchSize: 9 + property real defaultRepeatPenalty: 1.10 + property int defaultRepeatPenaltyTokens: 64 property int defaultThreadCount: 0 property string defaultPromptTemplate: "### Instruction: The prompt below is a question to answer, a task to complete, or a conversation to respond to; decide which and write an appropriate response. ### Prompt: %1 ### Response:\n" + property string defaultModelPath: Download.defaultLocalModelsPath() property alias temperature: settings.temperature property alias topP: settings.topP @@ -44,7 +48,10 @@ The prompt below is a question to answer, a task to complete, or a conversation property alias maxLength: settings.maxLength property alias promptBatchSize: settings.promptBatchSize property alias promptTemplate: settings.promptTemplate + property alias repeatPenalty: settings.repeatPenalty + property alias repeatPenaltyTokens: settings.repeatPenaltyTokens property alias threadCount: settings.threadCount + property alias modelPath: settings.modelPath Settings { id: settings @@ -54,23 +61,34 @@ The prompt below is a question to answer, a task to complete, or a conversation property int maxLength: settingsDialog.defaultMaxLength property int promptBatchSize: settingsDialog.defaultPromptBatchSize property int threadCount: settingsDialog.defaultThreadCount + property real repeatPenalty: settingsDialog.defaultRepeatPenalty + property int repeatPenaltyTokens: settingsDialog.defaultRepeatPenaltyTokens property string promptTemplate: settingsDialog.defaultPromptTemplate + property string modelPath: settingsDialog.defaultModelPath } - function restoreDefaults() { + + function restoreGenerationDefaults() { settings.temperature = defaultTemperature; settings.topP = defaultTopP; settings.topK = defaultTopK; settings.maxLength = defaultMaxLength; settings.promptBatchSize = defaultPromptBatchSize; settings.promptTemplate = defaultPromptTemplate; - settings.threadCount = defaultThreadCount settings.sync() + } + + function restoreApplicationDefaults() { + settings.modelPath = settingsDialog.defaultModelPath; + settings.threadCount = defaultThreadCount + Download.downloadLocalModelsPath = settings.modelPath; LLM.threadCount = settings.threadCount; + settings.sync() } Component.onCompleted: { LLM.threadCount = settings.threadCount; + Download.downloadLocalModelsPath = settings.modelPath; } Component.onDestruction: { @@ -80,260 +98,189 @@ The prompt below is a question to answer, a task to complete, or a conversation Item { Accessible.role: Accessible.Dialog Accessible.name: qsTr("Settings dialog") - Accessible.description: qsTr("Dialog containing various settings for model text generation") + Accessible.description: qsTr("Dialog containing various application settings") } + TabBar { + id: settingsTabBar + width: parent.width + TabButton { + text: qsTr("Generation") + Accessible.role: Accessible.Button + Accessible.name: qsTr("Generation settings") + Accessible.description: qsTr("Settings related to how the model generates text") + } - GridLayout { - columns: 2 - rowSpacing: 2 - columnSpacing: 10 - anchors.fill: parent - - Label { - id: tempLabel - text: qsTr("Temperature:") - color: theme.textColor - Layout.row: 0 - Layout.column: 0 + TabButton { + text: qsTr("Application") + Accessible.role: Accessible.Button + Accessible.name: qsTr("Application settings") + Accessible.description: qsTr("Settings related to general behavior of the application") } - TextField { - text: settings.temperature.toString() - color: theme.textColor - background: Rectangle { - implicitWidth: 150 - color: theme.backgroundLighter - radius: 10 - } - padding: 10 - ToolTip.text: qsTr("Temperature increases the chances of choosing less likely tokens - higher temperature gives more creative but less predictable outputs") - ToolTip.visible: hovered - Layout.row: 0 - Layout.column: 1 - validator: DoubleValidator { } - onAccepted: { - var val = parseFloat(text) - if (!isNaN(val)) { - settings.temperature = val - settings.sync() - focus = false - } else { - text = settings.temperature.toString() + } + + StackLayout { + anchors.top: settingsTabBar.bottom + anchors.bottom: parent.bottom + width: parent.width + currentIndex: settingsTabBar.currentIndex + Item { + id: generationSettingsTab + GridLayout { + columns: 2 + rowSpacing: 2 + columnSpacing: 10 + anchors.fill: parent + + Label { + id: tempLabel + text: qsTr("Temperature:") + color: theme.textColor + Layout.row: 0 + Layout.column: 0 } - } - Accessible.role: Accessible.EditableText - Accessible.name: tempLabel.text - Accessible.description: ToolTip.text - } - Label { - id: topPLabel - text: qsTr("Top P:") - color: theme.textColor - Layout.row: 1 - Layout.column: 0 - } - TextField { - text: settings.topP.toString() - color: theme.textColor - background: Rectangle { - implicitWidth: 150 - color: theme.backgroundLighter - radius: 10 - } - padding: 10 - ToolTip.text: qsTr("Only the most likely tokens up to a total probability of top_p can be chosen, prevents choosing highly unlikely tokens, aka Nucleus Sampling") - ToolTip.visible: hovered - Layout.row: 1 - Layout.column: 1 - validator: DoubleValidator {} - onAccepted: { - var val = parseFloat(text) - if (!isNaN(val)) { - settings.topP = val - settings.sync() - focus = false - } else { - text = settings.topP.toString() + TextField { + text: settings.temperature.toString() + color: theme.textColor + background: Rectangle { + implicitWidth: 150 + color: theme.backgroundLighter + radius: 10 + } + padding: 10 + ToolTip.text: qsTr("Temperature increases the chances of choosing less likely tokens - higher temperature gives more creative but less predictable outputs") + ToolTip.visible: hovered + Layout.row: 0 + Layout.column: 1 + validator: DoubleValidator { } + onAccepted: { + var val = parseFloat(text) + if (!isNaN(val)) { + settings.temperature = val + settings.sync() + focus = false + } else { + text = settings.temperature.toString() + } + } + Accessible.role: Accessible.EditableText + Accessible.name: tempLabel.text + Accessible.description: ToolTip.text } - } - Accessible.role: Accessible.EditableText - Accessible.name: topPLabel.text - Accessible.description: ToolTip.text - } - Label { - id: topKLabel - text: qsTr("Top K:") - color: theme.textColor - Layout.row: 2 - Layout.column: 0 - } - TextField { - text: settings.topK.toString() - color: theme.textColor - background: Rectangle { - implicitWidth: 150 - color: theme.backgroundLighter - radius: 10 - } - padding: 10 - ToolTip.text: qsTr("Only the top K most likely tokens will be chosen from") - ToolTip.visible: hovered - Layout.row: 2 - Layout.column: 1 - validator: IntValidator { bottom: 1 } - onAccepted: { - var val = parseInt(text) - if (!isNaN(val)) { - settings.topK = val - settings.sync() - focus = false - } else { - text = settings.topK.toString() + Label { + id: topPLabel + text: qsTr("Top P:") + color: theme.textColor + Layout.row: 1 + Layout.column: 0 + } + TextField { + text: settings.topP.toString() + color: theme.textColor + background: Rectangle { + implicitWidth: 150 + color: theme.backgroundLighter + radius: 10 + } + padding: 10 + ToolTip.text: qsTr("Only the most likely tokens up to a total probability of top_p can be chosen, prevents choosing highly unlikely tokens, aka Nucleus Sampling") + ToolTip.visible: hovered + Layout.row: 1 + Layout.column: 1 + validator: DoubleValidator {} + onAccepted: { + var val = parseFloat(text) + if (!isNaN(val)) { + settings.topP = val + settings.sync() + focus = false + } else { + text = settings.topP.toString() + } + } + Accessible.role: Accessible.EditableText + Accessible.name: topPLabel.text + Accessible.description: ToolTip.text + } + Label { + id: topKLabel + text: qsTr("Top K:") + color: theme.textColor + Layout.row: 2 + Layout.column: 0 } - } - Accessible.role: Accessible.EditableText - Accessible.name: topKLabel.text - Accessible.description: ToolTip.text - } - Label { - id: maxLengthLabel - text: qsTr("Max Length:") - color: theme.textColor - Layout.row: 3 - Layout.column: 0 - } - TextField { - text: settings.maxLength.toString() - color: theme.textColor - background: Rectangle { - implicitWidth: 150 - color: theme.backgroundLighter - radius: 10 - } - padding: 10 - ToolTip.text: qsTr("Maximum length of response in tokens") - ToolTip.visible: hovered - Layout.row: 3 - Layout.column: 1 - validator: IntValidator { bottom: 1 } - onAccepted: { - var val = parseInt(text) - if (!isNaN(val)) { - settings.maxLength = val - settings.sync() - focus = false - } else { - text = settings.maxLength.toString() + TextField { + text: settings.topK.toString() + color: theme.textColor + background: Rectangle { + implicitWidth: 150 + color: theme.backgroundLighter + radius: 10 + } + padding: 10 + ToolTip.text: qsTr("Only the top K most likely tokens will be chosen from") + ToolTip.visible: hovered + Layout.row: 2 + Layout.column: 1 + validator: IntValidator { bottom: 1 } + onAccepted: { + var val = parseInt(text) + if (!isNaN(val)) { + settings.topK = val + settings.sync() + focus = false + } else { + text = settings.topK.toString() + } + } + Accessible.role: Accessible.EditableText + Accessible.name: topKLabel.text + Accessible.description: ToolTip.text } - } - Accessible.role: Accessible.EditableText - Accessible.name: maxLengthLabel.text - Accessible.description: ToolTip.text - } - - Label { - id: batchSizeLabel - text: qsTr("Prompt Batch Size:") - color: theme.textColor - Layout.row: 4 - Layout.column: 0 - } - TextField { - text: settings.promptBatchSize.toString() - color: theme.textColor - background: Rectangle { - implicitWidth: 150 - color: theme.backgroundLighter - radius: 10 - } - padding: 10 - ToolTip.text: qsTr("Amount of prompt tokens to process at once, higher values can speed up reading prompts but will use more RAM") - ToolTip.visible: hovered - Layout.row: 4 - Layout.column: 1 - validator: IntValidator { bottom: 1 } - onAccepted: { - var val = parseInt(text) - if (!isNaN(val)) { - settings.promptBatchSize = val - settings.sync() - focus = false - } else { - text = settings.promptBatchSize.toString() + Label { + id: maxLengthLabel + text: qsTr("Max Length:") + color: theme.textColor + Layout.row: 3 + Layout.column: 0 } - } - Accessible.role: Accessible.EditableText - Accessible.name: batchSizeLabel.text - Accessible.description: ToolTip.text - } - - Label { - id: nThreadsLabel - text: qsTr("CPU Threads") - color: theme.textColor - Layout.row: 5 - Layout.column: 0 - } - TextField { - text: settingsDialog.threadCount.toString() - color: theme.textColor - background: Rectangle { - implicitWidth: 150 - color: theme.backgroundLighter - radius: 10 - } - padding: 10 - ToolTip.text: qsTr("Amount of processing threads to use, a setting of 0 will use the lesser of 4 or your number of CPU threads") - ToolTip.visible: hovered - Layout.row: 5 - Layout.column: 1 - validator: IntValidator { bottom: 1 } - onAccepted: { - var val = parseInt(text) - if (!isNaN(val)) { - settingsDialog.threadCount = val - LLM.threadCount = val - focus = false - } else { - text = settingsDialog.threadCount.toString() + TextField { + text: settings.maxLength.toString() + color: theme.textColor + background: Rectangle { + implicitWidth: 150 + color: theme.backgroundLighter + radius: 10 + } + padding: 10 + ToolTip.text: qsTr("Maximum length of response in tokens") + ToolTip.visible: hovered + Layout.row: 3 + Layout.column: 1 + validator: IntValidator { bottom: 1 } + onAccepted: { + var val = parseInt(text) + if (!isNaN(val)) { + settings.maxLength = val + settings.sync() + focus = false + } else { + text = settings.maxLength.toString() + } + } + Accessible.role: Accessible.EditableText + Accessible.name: maxLengthLabel.text + Accessible.description: ToolTip.text } - } - Accessible.role: Accessible.EditableText - Accessible.name: nThreadsLabel.text - Accessible.description: ToolTip.text - } - Label { - id: promptTemplateLabel - text: qsTr("Prompt Template:") - color: theme.textColor - Layout.row: 6 - Layout.column: 0 - } - Rectangle { - Layout.row: 6 - Layout.column: 1 - Layout.fillWidth: true - height: 200 - color: "transparent" - clip: true - Label { - id: promptTemplateLabelHelp - visible: settings.promptTemplate.indexOf("%1") === -1 - font.bold: true - color: theme.textErrorColor - text: qsTr("Prompt template must contain %1 to be replaced with the user's input.") - anchors.fill: templateScrollView - z: 200 - padding: 10 - wrapMode: TextArea.Wrap - Accessible.role: Accessible.EditableText - Accessible.name: text - } - ScrollView { - id: templateScrollView - anchors.fill: parent - TextArea { - text: settings.promptTemplate + Label { + id: batchSizeLabel + text: qsTr("Prompt Batch Size:") + color: theme.textColor + Layout.row: 4 + Layout.column: 0 + } + TextField { + text: settings.promptBatchSize.toString() color: theme.textColor background: Rectangle { implicitWidth: 150 @@ -341,42 +288,275 @@ The prompt below is a question to answer, a task to complete, or a conversation radius: 10 } padding: 10 - wrapMode: TextArea.Wrap - onTextChanged: { - settings.promptTemplate = text - settings.sync() + ToolTip.text: qsTr("Amount of prompt tokens to process at once, higher values can speed up reading prompts but will use more RAM") + ToolTip.visible: hovered + Layout.row: 4 + Layout.column: 1 + validator: IntValidator { bottom: 1 } + onAccepted: { + var val = parseInt(text) + if (!isNaN(val)) { + settings.promptBatchSize = val + settings.sync() + focus = false + } else { + text = settings.promptBatchSize.toString() + } + } + Accessible.role: Accessible.EditableText + Accessible.name: batchSizeLabel.text + Accessible.description: ToolTip.text + } + Label { + id: repeatPenaltyLabel + text: qsTr("Repeat Penalty:") + color: theme.textColor + Layout.row: 5 + Layout.column: 0 + } + TextField { + text: settings.repeatPenalty.toString() + color: theme.textColor + background: Rectangle { + implicitWidth: 150 + color: theme.backgroundLighter + radius: 10 + } + padding: 10 + ToolTip.text: qsTr("Amount to penalize reptetitiveness of the output") + ToolTip.visible: hovered + Layout.row: 5 + Layout.column: 1 + validator: DoubleValidator {} + onAccepted: { + var val = parseFloat(text) + if (!isNaN(val)) { + settings.repeatPenalty = val + settings.sync() + focus = false + } else { + text = settings.repeatPenalty.toString() + } } - bottomPadding: 10 Accessible.role: Accessible.EditableText - Accessible.name: promptTemplateLabel.text - Accessible.description: promptTemplateLabelHelp.text + Accessible.name: repeatPenaltyLabel.text + Accessible.description: ToolTip.text } - } - } - Button { - Layout.row: 7 - Layout.column: 1 - Layout.fillWidth: true - padding: 15 - contentItem: Text { - text: qsTr("Restore Defaults") - horizontalAlignment: Text.AlignHCenter - color: theme.textColor - Accessible.role: Accessible.Button - Accessible.name: text - Accessible.description: qsTr("Restores the settings dialog to a default state") - } + Label { + id: repeatPenaltyTokensLabel + text: qsTr("Repeat Penalty Tokens:") + color: theme.textColor + Layout.row: 6 + Layout.column: 0 + } + TextField { + text: settings.repeatPenaltyTokens.toString() + color: theme.textColor + background: Rectangle { + implicitWidth: 150 + color: theme.backgroundLighter + radius: 10 + } + padding: 10 + ToolTip.text: qsTr("How far back in output to apply repeat penalty") + ToolTip.visible: hovered + Layout.row: 6 + Layout.column: 1 + validator: IntValidator { bottom: 1 } + onAccepted: { + var val = parseInt(text) + if (!isNaN(val)) { + settings.repeatPenaltyTokens = val + settings.sync() + focus = false + } else { + text = settings.repeatPenaltyTokens.toString() + } + } + Accessible.role: Accessible.EditableText + Accessible.name: repeatPenaltyTokensLabel.text + Accessible.description: ToolTip.text + } - background: Rectangle { - opacity: .5 - border.color: theme.backgroundLightest - border.width: 1 - radius: 10 - color: theme.backgroundLight - } - onClicked: { - settingsDialog.restoreDefaults() - } - } + Label { + id: promptTemplateLabel + text: qsTr("Prompt Template:") + color: theme.textColor + Layout.row: 7 + Layout.column: 0 + } + Rectangle { + Layout.row: 7 + Layout.column: 1 + Layout.fillWidth: true + height: 200 + color: "transparent" + clip: true + Label { + id: promptTemplateLabelHelp + visible: settings.promptTemplate.indexOf("%1") === -1 + font.bold: true + color: theme.textErrorColor + text: qsTr("Prompt template must contain %1 to be replaced with the user's input.") + anchors.fill: templateScrollView + z: 200 + padding: 10 + wrapMode: TextArea.Wrap + Accessible.role: Accessible.EditableText + Accessible.name: text + } + ScrollView { + id: templateScrollView + anchors.fill: parent + TextArea { + text: settings.promptTemplate + color: theme.textColor + background: Rectangle { + implicitWidth: 150 + color: theme.backgroundLighter + radius: 10 + } + padding: 10 + wrapMode: TextArea.Wrap + onTextChanged: { + settings.promptTemplate = text + settings.sync() + } + bottomPadding: 10 + Accessible.role: Accessible.EditableText + Accessible.name: promptTemplateLabel.text + Accessible.description: promptTemplateLabelHelp.text + } + } + } + Button { + Layout.row: 8 + Layout.column: 1 + Layout.fillWidth: true + padding: 15 + contentItem: Text { + text: qsTr("Restore Defaults") + horizontalAlignment: Text.AlignHCenter + color: theme.textColor + Accessible.role: Accessible.Button + Accessible.name: text + Accessible.description: qsTr("Restores the settings dialog to a default state") + } + + background: Rectangle { + opacity: .5 + border.color: theme.backgroundLightest + border.width: 1 + radius: 10 + color: theme.backgroundLight + } + onClicked: { + settingsDialog.restoreGenerationDefaults() + } + } + } + } + Item { + id: systemSettingsTab + GridLayout { + columns: 3 + rowSpacing: 2 + columnSpacing: 10 + width: parent.width + anchors.top: parent.top + FolderDialog { + id: modelPathDialog + title: "Please choose a directory" + onAccepted: { + Download.downloadLocalModelsPath = selectedFolder + settings.modelPath = Download.downloadLocalModelsPath + settings.sync() + } + } + Label { + id: modelPathLabel + text: qsTr("Model file path:") + color: theme.textColor + Layout.row: 1 + Layout.column: 0 + } + TextField { + id: modelPathDisplayLabel + text: settings.modelPath + color: theme.textColor + readOnly: true + Layout.row: 1 + Layout.column: 1 + } + Button { + Layout.row: 1 + Layout.column: 2 + text: qsTr("Browse") + onClicked: modelPathDialog.open() + } + Label { + id: nThreadsLabel + text: qsTr("CPU Threads:") + color: theme.textColor + Layout.row: 2 + Layout.column: 0 + } + TextField { + text: settingsDialog.threadCount.toString() + color: theme.textColor + background: Rectangle { + implicitWidth: 150 + color: theme.backgroundLighter + radius: 10 + } + padding: 10 + ToolTip.text: qsTr("Amount of processing threads to use, a setting of 0 will use the lesser of 4 or your number of CPU threads") + ToolTip.visible: hovered + Layout.row: 2 + Layout.column: 1 + validator: IntValidator { bottom: 1 } + onAccepted: { + var val = parseInt(text) + if (!isNaN(val)) { + settingsDialog.threadCount = val + LLM.threadCount = val + settings.sync() + focus = false + } else { + text = settingsDialog.threadCount.toString() + } + } + Accessible.role: Accessible.EditableText + Accessible.name: nThreadsLabel.text + Accessible.description: ToolTip.text + } + Button { + Layout.row: 3 + Layout.column: 1 + Layout.fillWidth: true + padding: 15 + contentItem: Text { + text: qsTr("Restore Defaults") + horizontalAlignment: Text.AlignHCenter + color: theme.textColor + Accessible.role: Accessible.Button + Accessible.name: text + Accessible.description: qsTr("Restores the settings dialog to a default state") + } + + background: Rectangle { + opacity: .5 + border.color: theme.backgroundLightest + border.width: 1 + radius: 10 + color: theme.backgroundLight + } + onClicked: { + settingsDialog.restoreApplicationDefaults() + } + } + } + } } + }