mirror of
https://github.com/nomic-ai/gpt4all
synced 2024-11-10 01:10:35 +00:00
693 lines
26 KiB
QML
693 lines
26 KiB
QML
import QtCore
|
|
import QtQuick
|
|
import QtQuick.Controls
|
|
import QtQuick.Controls.Basic
|
|
import QtQuick.Layouts
|
|
import modellist
|
|
import mysettings
|
|
|
|
MySettingsTab {
|
|
onRestoreDefaultsClicked: {
|
|
MySettings.restoreModelDefaults(root.currentModelInfo);
|
|
}
|
|
title: qsTr("Model/Character Settings")
|
|
contentItem: GridLayout {
|
|
id: root
|
|
columns: 3
|
|
rowSpacing: 10
|
|
columnSpacing: 10
|
|
|
|
property var currentModelName: comboBox.currentText
|
|
property var currentModelId: comboBox.currentValue
|
|
property var currentModelInfo: ModelList.modelInfo(root.currentModelId)
|
|
|
|
Label {
|
|
id: label
|
|
Layout.row: 0
|
|
Layout.column: 0
|
|
text: qsTr("Model/Character:")
|
|
color: theme.textColor
|
|
font.pixelSize: theme.fontSizeLarge
|
|
}
|
|
|
|
RowLayout {
|
|
Layout.fillWidth: true
|
|
Layout.row: 1
|
|
Layout.column: 0
|
|
Layout.columnSpan: 2
|
|
height: label.height + 20
|
|
spacing: 10
|
|
|
|
MyComboBox {
|
|
id: comboBox
|
|
Layout.fillWidth: true
|
|
model: ModelList.installedModels
|
|
valueRole: "id"
|
|
textRole: "name"
|
|
currentIndex: 0
|
|
contentItem: Text {
|
|
leftPadding: 10
|
|
rightPadding: 20
|
|
text: comboBox.currentText
|
|
font: comboBox.font
|
|
color: theme.textColor
|
|
verticalAlignment: Text.AlignVCenter
|
|
elide: Text.ElideRight
|
|
}
|
|
delegate: ItemDelegate {
|
|
width: comboBox.width
|
|
contentItem: Text {
|
|
text: name
|
|
color: theme.textColor
|
|
font: comboBox.font
|
|
elide: Text.ElideRight
|
|
verticalAlignment: Text.AlignVCenter
|
|
}
|
|
background: Rectangle {
|
|
color: highlighted ? theme.backgroundLight : theme.backgroundDark
|
|
}
|
|
highlighted: comboBox.highlightedIndex === index
|
|
}
|
|
}
|
|
|
|
MyButton {
|
|
id: cloneButton
|
|
text: qsTr("Clone")
|
|
onClicked: {
|
|
var id = ModelList.clone(root.currentModelInfo);
|
|
comboBox.currentIndex = comboBox.indexOfValue(id);
|
|
}
|
|
}
|
|
|
|
MyButton {
|
|
id: removeButton
|
|
enabled: root.currentModelInfo.isClone
|
|
text: qsTr("Remove")
|
|
onClicked: {
|
|
ModelList.remove(root.currentModelInfo);
|
|
comboBox.currentIndex = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
RowLayout {
|
|
Layout.row: 2
|
|
Layout.column: 0
|
|
Layout.topMargin: 15
|
|
spacing: 10
|
|
Label {
|
|
id: uniqueNameLabel
|
|
text: qsTr("Unique Name:")
|
|
color: theme.textColor
|
|
font.pixelSize: theme.fontSizeLarge
|
|
}
|
|
Label {
|
|
id: uniqueNameLabelHelp
|
|
visible: false
|
|
text: qsTr("Must contain a non-empty unique name that does not match any existing model/character.")
|
|
color: theme.textErrorColor
|
|
font.pixelSize: theme.fontSizeLarge
|
|
wrapMode: TextArea.Wrap
|
|
}
|
|
}
|
|
|
|
MyTextField {
|
|
id: uniqueNameField
|
|
text: root.currentModelName
|
|
font.pixelSize: theme.fontSizeLarge
|
|
enabled: root.currentModelInfo.isClone || root.currentModelInfo.description === ""
|
|
color: enabled ? theme.textColor : theme.mutedTextColor
|
|
Layout.row: 3
|
|
Layout.column: 0
|
|
Layout.columnSpan: 2
|
|
Layout.fillWidth: true
|
|
Connections {
|
|
target: MySettings
|
|
function onNameChanged() {
|
|
uniqueNameField.text = root.currentModelInfo.name;
|
|
}
|
|
}
|
|
Connections {
|
|
target: root
|
|
function onCurrentModelInfoChanged() {
|
|
uniqueNameField.text = root.currentModelInfo.name;
|
|
}
|
|
}
|
|
onTextChanged: {
|
|
if (text !== "" && ModelList.isUniqueName(text)) {
|
|
MySettings.setModelName(root.currentModelInfo, text);
|
|
}
|
|
uniqueNameLabelHelp.visible = root.currentModelInfo.name !== "" &&
|
|
(text === "" || (text !== root.currentModelInfo.name && !ModelList.isUniqueName(text)));
|
|
}
|
|
}
|
|
|
|
Label {
|
|
text: qsTr("Model File:")
|
|
color: theme.textColor
|
|
font.pixelSize: theme.fontSizeLarge
|
|
Layout.row: 4
|
|
Layout.column: 0
|
|
Layout.topMargin: 15
|
|
}
|
|
|
|
MyTextField {
|
|
text: root.currentModelInfo.filename
|
|
font.pixelSize: theme.fontSizeLarge
|
|
enabled: false
|
|
color: enabled ? theme.textColor : theme.mutedTextColor
|
|
Layout.row: 5
|
|
Layout.column: 0
|
|
Layout.columnSpan: 2
|
|
Layout.fillWidth: true
|
|
}
|
|
|
|
Label {
|
|
visible: !root.currentModelInfo.isChatGPT
|
|
text: qsTr("System Prompt:")
|
|
color: theme.textColor
|
|
font.pixelSize: theme.fontSizeLarge
|
|
Layout.row: 6
|
|
Layout.column: 0
|
|
Layout.topMargin: 15
|
|
}
|
|
|
|
Rectangle {
|
|
id: systemPrompt
|
|
visible: !root.currentModelInfo.isChatGPT
|
|
Layout.row: 7
|
|
Layout.column: 0
|
|
Layout.columnSpan: 2
|
|
Layout.fillWidth: true
|
|
color: "transparent"
|
|
Layout.minimumHeight: Math.max(100, systemPromptArea.contentHeight + 20)
|
|
TextArea {
|
|
id: systemPromptArea
|
|
anchors.fill: parent
|
|
text: root.currentModelInfo.systemPrompt
|
|
color: theme.textColor
|
|
background: Rectangle {
|
|
implicitWidth: 150
|
|
color: theme.backgroundDark
|
|
radius: 10
|
|
}
|
|
padding: 10
|
|
wrapMode: TextArea.Wrap
|
|
Connections {
|
|
target: MySettings
|
|
function onSystemPromptChanged() {
|
|
systemPromptArea.text = root.currentModelInfo.systemPrompt;
|
|
}
|
|
}
|
|
Connections {
|
|
target: root
|
|
function onCurrentModelInfoChanged() {
|
|
systemPromptArea.text = root.currentModelInfo.systemPrompt;
|
|
}
|
|
}
|
|
onTextChanged: {
|
|
MySettings.setModelSystemPrompt(root.currentModelInfo, text)
|
|
}
|
|
bottomPadding: 10
|
|
Accessible.role: Accessible.EditableText
|
|
ToolTip.text: qsTr("The systemPrompt allows instructions to the model at the beginning of a chat.\nNOTE: A longer, detailed system prompt can lead to higher quality answers, but can also slow down generation.")
|
|
ToolTip.visible: hovered
|
|
}
|
|
}
|
|
|
|
RowLayout {
|
|
Layout.row: 8
|
|
Layout.column: 0
|
|
Layout.columnSpan: 2
|
|
Layout.topMargin: 15
|
|
spacing: 10
|
|
Label {
|
|
id: promptTemplateLabel
|
|
text: qsTr("Prompt Template:")
|
|
color: theme.textColor
|
|
font.pixelSize: theme.fontSizeLarge
|
|
}
|
|
Label {
|
|
id: promptTemplateLabelHelp
|
|
text: qsTr("Must contain the string \"%1\" to be replaced with the user's input.")
|
|
color: theme.textErrorColor
|
|
font.pixelSize: theme.fontSizeLarge
|
|
visible: templateTextArea.text.indexOf("%1") === -1
|
|
wrapMode: TextArea.Wrap
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
id: promptTemplate
|
|
Layout.row: 9
|
|
Layout.column: 0
|
|
Layout.columnSpan: 2
|
|
Layout.fillWidth: true
|
|
Layout.minimumHeight: Math.max(100, templateTextArea.contentHeight + 20)
|
|
color: "transparent"
|
|
clip: true
|
|
TextArea {
|
|
id: templateTextArea
|
|
anchors.fill: parent
|
|
text: root.currentModelInfo.promptTemplate
|
|
color: theme.textColor
|
|
font.pixelSize: theme.fontSizeLarge
|
|
background: Rectangle {
|
|
implicitWidth: 150
|
|
color: theme.backgroundDark
|
|
radius: 10
|
|
}
|
|
padding: 10
|
|
wrapMode: TextArea.Wrap
|
|
Connections {
|
|
target: MySettings
|
|
function onPromptTemplateChanged() {
|
|
templateTextArea.text = root.currentModelInfo.promptTemplate;
|
|
}
|
|
}
|
|
Connections {
|
|
target: root
|
|
function onCurrentModelInfoChanged() {
|
|
templateTextArea.text = root.currentModelInfo.promptTemplate;
|
|
}
|
|
}
|
|
onTextChanged: {
|
|
if (templateTextArea.text.indexOf("%1") !== -1) {
|
|
MySettings.setModelPromptTemplate(root.currentModelInfo, text)
|
|
}
|
|
}
|
|
bottomPadding: 10
|
|
Accessible.role: Accessible.EditableText
|
|
Accessible.name: promptTemplateLabel.text
|
|
Accessible.description: promptTemplateLabelHelp.text
|
|
ToolTip.text: qsTr("The prompt template partially determines how models will respond to prompts.\nNOTE: A longer, detailed template can lead to higher quality answers, but can also slow down generation.")
|
|
ToolTip.visible: hovered
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
id: optionalImageRect
|
|
visible: false // FIXME: for later
|
|
Layout.row: 2
|
|
Layout.column: 1
|
|
Layout.rowSpan: 5
|
|
Layout.alignment: Qt.AlignHCenter
|
|
Layout.fillHeight: true
|
|
Layout.maximumWidth: height
|
|
Layout.topMargin: 35
|
|
Layout.bottomMargin: 35
|
|
Layout.leftMargin: 35
|
|
width: 3000
|
|
border.width: 1
|
|
border.color: theme.tabBorder
|
|
radius: 10
|
|
color: "transparent"
|
|
Item {
|
|
anchors.centerIn: parent
|
|
height: childrenRect.height
|
|
Image {
|
|
id: img
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
width: 100
|
|
height: 100
|
|
source: "qrc:/gpt4all/icons/image.svg"
|
|
}
|
|
Text {
|
|
text: qsTr("Add\noptional image")
|
|
font.pixelSize: theme.fontSizeLarge
|
|
anchors.top: img.bottom
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
wrapMode: TextArea.Wrap
|
|
horizontalAlignment: Qt.AlignHCenter
|
|
color: theme.mutedTextColor
|
|
}
|
|
}
|
|
}
|
|
|
|
Label {
|
|
text: qsTr("Generation Settings")
|
|
color: theme.textColor
|
|
Layout.row: 10
|
|
Layout.column: 0
|
|
Layout.columnSpan: 2
|
|
Layout.topMargin: 15
|
|
Layout.alignment: Qt.AlignHCenter
|
|
Layout.minimumWidth: promptTemplate.width
|
|
horizontalAlignment: Qt.AlignHCenter
|
|
font.pixelSize: theme.fontSizeLarger
|
|
font.bold: true
|
|
}
|
|
|
|
GridLayout {
|
|
Layout.row: 11
|
|
Layout.column: 0
|
|
Layout.columnSpan: 2
|
|
Layout.topMargin: 15
|
|
Layout.fillWidth: true
|
|
Layout.minimumWidth: promptTemplate.width
|
|
columns: 4
|
|
rowSpacing: 10
|
|
columnSpacing: 10
|
|
|
|
Label {
|
|
id: tempLabel
|
|
text: qsTr("Temperature:")
|
|
color: theme.textColor
|
|
font.pixelSize: theme.fontSizeLarge
|
|
Layout.row: 0
|
|
Layout.column: 0
|
|
}
|
|
|
|
MyTextField {
|
|
id: temperatureField
|
|
text: root.currentModelInfo.temperature
|
|
color: theme.textColor
|
|
font.pixelSize: theme.fontSizeLarge
|
|
ToolTip.text: qsTr("Temperature increases the chances of choosing less likely tokens.\nNOTE: Higher temperature gives more creative but less predictable outputs.")
|
|
ToolTip.visible: hovered
|
|
Layout.row: 0
|
|
Layout.column: 1
|
|
validator: DoubleValidator {
|
|
locale: "C"
|
|
}
|
|
Connections {
|
|
target: MySettings
|
|
function onTemperatureChanged() {
|
|
temperatureField.text = root.currentModelInfo.temperature;
|
|
}
|
|
}
|
|
Connections {
|
|
target: root
|
|
function onCurrentModelInfoChanged() {
|
|
temperatureField.text = root.currentModelInfo.temperature;
|
|
}
|
|
}
|
|
onEditingFinished: {
|
|
var val = parseFloat(text)
|
|
if (!isNaN(val)) {
|
|
MySettings.setModelTemperature(root.currentModelInfo, val)
|
|
focus = false
|
|
} else {
|
|
text = root.currentModelInfo.temperature
|
|
}
|
|
}
|
|
Accessible.role: Accessible.EditableText
|
|
Accessible.name: tempLabel.text
|
|
Accessible.description: ToolTip.text
|
|
}
|
|
Label {
|
|
id: topPLabel
|
|
text: qsTr("Top P:")
|
|
color: theme.textColor
|
|
font.pixelSize: theme.fontSizeLarge
|
|
Layout.row: 0
|
|
Layout.column: 2
|
|
}
|
|
MyTextField {
|
|
id: topPField
|
|
text: root.currentModelInfo.topP
|
|
color: theme.textColor
|
|
font.pixelSize: theme.fontSizeLarge
|
|
ToolTip.text: qsTr("Only the most likely tokens up to a total probability of top_p can be chosen.\nNOTE: Prevents choosing highly unlikely tokens, aka Nucleus Sampling")
|
|
ToolTip.visible: hovered
|
|
Layout.row: 0
|
|
Layout.column: 3
|
|
validator: DoubleValidator {
|
|
locale: "C"
|
|
}
|
|
Connections {
|
|
target: MySettings
|
|
function onTopPChanged() {
|
|
topPField.text = root.currentModelInfo.topP;
|
|
}
|
|
}
|
|
Connections {
|
|
target: root
|
|
function onCurrentModelInfoChanged() {
|
|
topPField.text = root.currentModelInfo.topP;
|
|
}
|
|
}
|
|
onEditingFinished: {
|
|
var val = parseFloat(text)
|
|
if (!isNaN(val)) {
|
|
MySettings.setModelTopP(root.currentModelInfo, val)
|
|
focus = false
|
|
} else {
|
|
text = root.currentModelInfo.topP
|
|
}
|
|
}
|
|
Accessible.role: Accessible.EditableText
|
|
Accessible.name: topPLabel.text
|
|
Accessible.description: ToolTip.text
|
|
}
|
|
Label {
|
|
id: topKLabel
|
|
visible: !root.currentModelInfo.isChatGPT
|
|
text: qsTr("Top K:")
|
|
color: theme.textColor
|
|
font.pixelSize: theme.fontSizeLarge
|
|
Layout.row: 1
|
|
Layout.column: 0
|
|
}
|
|
MyTextField {
|
|
id: topKField
|
|
visible: !root.currentModelInfo.isChatGPT
|
|
text: root.currentModelInfo.topK
|
|
color: theme.textColor
|
|
font.pixelSize: theme.fontSizeLarge
|
|
ToolTip.text: qsTr("Only the top K most likely tokens will be chosen from")
|
|
ToolTip.visible: hovered
|
|
Layout.row: 1
|
|
Layout.column: 1
|
|
validator: IntValidator {
|
|
bottom: 1
|
|
}
|
|
Connections {
|
|
target: MySettings
|
|
function onTopKChanged() {
|
|
topKField.text = root.currentModelInfo.topK;
|
|
}
|
|
}
|
|
Connections {
|
|
target: root
|
|
function onCurrentModelInfoChanged() {
|
|
topKField.text = root.currentModelInfo.topK;
|
|
}
|
|
}
|
|
onEditingFinished: {
|
|
var val = parseInt(text)
|
|
if (!isNaN(val)) {
|
|
MySettings.setModelTopK(root.currentModelInfo, val)
|
|
focus = false
|
|
} else {
|
|
text = root.currentModelInfo.topK
|
|
}
|
|
}
|
|
Accessible.role: Accessible.EditableText
|
|
Accessible.name: topKLabel.text
|
|
Accessible.description: ToolTip.text
|
|
}
|
|
Label {
|
|
id: maxLengthLabel
|
|
visible: !root.currentModelInfo.isChatGPT
|
|
text: qsTr("Max Length:")
|
|
color: theme.textColor
|
|
font.pixelSize: theme.fontSizeLarge
|
|
Layout.row: 1
|
|
Layout.column: 2
|
|
}
|
|
MyTextField {
|
|
id: maxLengthField
|
|
visible: !root.currentModelInfo.isChatGPT
|
|
text: root.currentModelInfo.maxLength
|
|
color: theme.textColor
|
|
font.pixelSize: theme.fontSizeLarge
|
|
ToolTip.text: qsTr("Maximum length of response in tokens")
|
|
ToolTip.visible: hovered
|
|
Layout.row: 1
|
|
Layout.column: 3
|
|
validator: IntValidator {
|
|
bottom: 1
|
|
}
|
|
Connections {
|
|
target: MySettings
|
|
function onMaxLengthChanged() {
|
|
maxLengthField.text = root.currentModelInfo.maxLength;
|
|
}
|
|
}
|
|
Connections {
|
|
target: root
|
|
function onCurrentModelInfoChanged() {
|
|
maxLengthField.text = root.currentModelInfo.maxLength;
|
|
}
|
|
}
|
|
onEditingFinished: {
|
|
var val = parseInt(text)
|
|
if (!isNaN(val)) {
|
|
MySettings.setModelMaxLength(root.currentModelInfo, val)
|
|
focus = false
|
|
} else {
|
|
text = root.currentModelInfo.maxLength
|
|
}
|
|
}
|
|
Accessible.role: Accessible.EditableText
|
|
Accessible.name: maxLengthLabel.text
|
|
Accessible.description: ToolTip.text
|
|
}
|
|
|
|
Label {
|
|
id: batchSizeLabel
|
|
visible: !root.currentModelInfo.isChatGPT
|
|
text: qsTr("Prompt Batch Size:")
|
|
font.pixelSize: theme.fontSizeLarge
|
|
color: theme.textColor
|
|
Layout.row: 2
|
|
Layout.column: 0
|
|
}
|
|
MyTextField {
|
|
id: batchSizeField
|
|
visible: !root.currentModelInfo.isChatGPT
|
|
text: root.currentModelInfo.promptBatchSize
|
|
color: theme.textColor
|
|
font.pixelSize: theme.fontSizeLarge
|
|
ToolTip.text: qsTr("Amount of prompt tokens to process at once.\nNOTE: Higher values can speed up reading prompts but will use more RAM")
|
|
ToolTip.visible: hovered
|
|
Layout.row: 2
|
|
Layout.column: 1
|
|
validator: IntValidator {
|
|
bottom: 1
|
|
}
|
|
Connections {
|
|
target: MySettings
|
|
function onPromptBatchSizeChanged() {
|
|
batchSizeField.text = root.currentModelInfo.promptBatchSize;
|
|
}
|
|
}
|
|
Connections {
|
|
target: root
|
|
function onCurrentModelInfoChanged() {
|
|
batchSizeField.text = root.currentModelInfo.promptBatchSize;
|
|
}
|
|
}
|
|
onEditingFinished: {
|
|
var val = parseInt(text)
|
|
if (!isNaN(val)) {
|
|
MySettings.setModelPromptBatchSize(root.currentModelInfo, val)
|
|
focus = false
|
|
} else {
|
|
text = root.currentModelInfo.promptBatchSize
|
|
}
|
|
}
|
|
Accessible.role: Accessible.EditableText
|
|
Accessible.name: batchSizeLabel.text
|
|
Accessible.description: ToolTip.text
|
|
}
|
|
Label {
|
|
id: repeatPenaltyLabel
|
|
visible: !root.currentModelInfo.isChatGPT
|
|
text: qsTr("Repeat Penalty:")
|
|
color: theme.textColor
|
|
font.pixelSize: theme.fontSizeLarge
|
|
Layout.row: 2
|
|
Layout.column: 2
|
|
}
|
|
MyTextField {
|
|
id: repeatPenaltyField
|
|
visible: !root.currentModelInfo.isChatGPT
|
|
text: root.currentModelInfo.repeatPenalty
|
|
color: theme.textColor
|
|
font.pixelSize: theme.fontSizeLarge
|
|
ToolTip.text: qsTr("Amount to penalize repetitiveness of the output")
|
|
ToolTip.visible: hovered
|
|
Layout.row: 2
|
|
Layout.column: 3
|
|
validator: DoubleValidator {
|
|
locale: "C"
|
|
}
|
|
Connections {
|
|
target: MySettings
|
|
function onRepeatPenaltyChanged() {
|
|
repeatPenaltyField.text = root.currentModelInfo.repeatPenalty;
|
|
}
|
|
}
|
|
Connections {
|
|
target: root
|
|
function onCurrentModelInfoChanged() {
|
|
repeatPenaltyField.text = root.currentModelInfo.repeatPenalty;
|
|
}
|
|
}
|
|
onEditingFinished: {
|
|
var val = parseFloat(text)
|
|
if (!isNaN(val)) {
|
|
MySettings.setModelRepeatPenalty(root.currentModelInfo, val)
|
|
focus = false
|
|
} else {
|
|
text = root.currentModelInfo.repeatPenalty
|
|
}
|
|
}
|
|
Accessible.role: Accessible.EditableText
|
|
Accessible.name: repeatPenaltyLabel.text
|
|
Accessible.description: ToolTip.text
|
|
}
|
|
Label {
|
|
id: repeatPenaltyTokensLabel
|
|
visible: !root.currentModelInfo.isChatGPT
|
|
text: qsTr("Repeat Penalty Tokens:")
|
|
color: theme.textColor
|
|
font.pixelSize: theme.fontSizeLarge
|
|
Layout.row: 3
|
|
Layout.column: 0
|
|
}
|
|
MyTextField {
|
|
id: repeatPenaltyTokenField
|
|
visible: !root.currentModelInfo.isChatGPT
|
|
text: root.currentModelInfo.repeatPenaltyTokens
|
|
color: theme.textColor
|
|
font.pixelSize: theme.fontSizeLarge
|
|
ToolTip.text: qsTr("How far back in output to apply repeat penalty")
|
|
ToolTip.visible: hovered
|
|
Layout.row: 3
|
|
Layout.column: 1
|
|
validator: IntValidator {
|
|
bottom: 1
|
|
}
|
|
Connections {
|
|
target: MySettings
|
|
function onRepeatPenaltyTokensChanged() {
|
|
repeatPenaltyTokenField.text = root.currentModelInfo.repeatPenaltyTokens;
|
|
}
|
|
}
|
|
Connections {
|
|
target: root
|
|
function onCurrentModelInfoChanged() {
|
|
repeatPenaltyTokenField.text = root.currentModelInfo.repeatPenaltyTokens;
|
|
}
|
|
}
|
|
onEditingFinished: {
|
|
var val = parseInt(text)
|
|
if (!isNaN(val)) {
|
|
MySettings.setModelRepeatPenaltyTokens(root.currentModelInfo, val)
|
|
focus = false
|
|
} else {
|
|
text = root.currentModelInfo.repeatPenaltyTokens
|
|
}
|
|
}
|
|
Accessible.role: Accessible.EditableText
|
|
Accessible.name: repeatPenaltyTokensLabel.text
|
|
Accessible.description: ToolTip.text
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
Layout.row: 12
|
|
Layout.column: 0
|
|
Layout.columnSpan: 2
|
|
Layout.topMargin: 15
|
|
Layout.fillWidth: true
|
|
Layout.minimumWidth: promptTemplate.width
|
|
height: 1
|
|
color: theme.tabBorder
|
|
}
|
|
}
|
|
}
|