Add Up/Down arrow key navigation to Settings sidebar. Add Accessible.role: Accessible.PageTab to all 5 tab pages. Fix Loader status to use AlertMessage role per WCAG 4.1.3.
1171 lines
62 KiB
QML
1171 lines
62 KiB
QML
import QtQuick
|
||
import QtQuick.Controls
|
||
import QtQuick.Layouts
|
||
import QtQuick.Effects
|
||
|
||
Window {
|
||
id: root
|
||
|
||
width: 850 * (ui ? ui.uiScale : 1.0)
|
||
height: 620 * (ui ? ui.uiScale : 1.0)
|
||
|
||
visible: false
|
||
flags: Qt.FramelessWindowHint | Qt.Window
|
||
color: "transparent"
|
||
title: "WhisperVoice Settings"
|
||
Accessible.name: "WhisperVoice Settings"
|
||
|
||
// Explicit sizing for Python to read
|
||
|
||
// Prevent destruction on close
|
||
onClosing: (close) => {
|
||
close.accepted = false
|
||
root.visible = false
|
||
}
|
||
|
||
// Load Font
|
||
FontLoader {
|
||
id: jetBrainsMono
|
||
source: "fonts/ttf/JetBrainsMono-Bold.ttf"
|
||
}
|
||
|
||
readonly property string mainFont: jetBrainsMono.name
|
||
property bool isLoaded: false
|
||
|
||
Component.onCompleted: {
|
||
isLoaded = true
|
||
}
|
||
|
||
// --- REUSABLE COMPONENTS ---
|
||
component StatBox: Rectangle {
|
||
property string label: ""
|
||
property string value: ""
|
||
property string unit: ""
|
||
property color accent: SettingsStyle.accent
|
||
Layout.fillWidth: true
|
||
Layout.preferredHeight: 80
|
||
color: "#16161a"
|
||
radius: 12
|
||
border.color: SettingsStyle.borderSubtle
|
||
border.width: 1
|
||
|
||
Column {
|
||
anchors.centerIn: parent
|
||
spacing: 4
|
||
Text { text: label; color: SettingsStyle.textSecondary; font.pixelSize: 11; font.bold: true; anchors.horizontalCenter: parent.horizontalCenter }
|
||
Row {
|
||
anchors.horizontalCenter: parent.horizontalCenter
|
||
spacing: 2
|
||
Text { text: value; color: accent; font.pixelSize: 22; font.bold: true; font.family: "JetBrains Mono" }
|
||
Text { text: unit; color: SettingsStyle.textSecondary; font.pixelSize: 12; anchors.baseline: parent.bottom; anchors.baselineOffset: -4 }
|
||
}
|
||
}
|
||
}
|
||
|
||
// Main Container
|
||
Rectangle {
|
||
id: mainContainer
|
||
anchors.fill: parent
|
||
radius: 16
|
||
color: SettingsStyle.background
|
||
border.color: SettingsStyle.borderSubtle
|
||
border.width: 1
|
||
|
||
opacity: 0
|
||
transform: Translate {
|
||
id: entryTranslate
|
||
y: 20
|
||
}
|
||
|
||
Component.onCompleted: {
|
||
entryAnim.start()
|
||
}
|
||
|
||
ParallelAnimation {
|
||
id: entryAnim
|
||
NumberAnimation { target: mainContainer; property: "opacity"; to: 1; duration: 400; easing.type: Easing.OutCubic }
|
||
NumberAnimation { target: entryTranslate; property: "y"; to: 0; duration: 500; easing.type: Easing.OutBack; easing.overshoot: 0.8 }
|
||
}
|
||
|
||
// --- TITLE BAR ---
|
||
Item {
|
||
id: titleBar
|
||
height: 60
|
||
anchors.top: parent.top
|
||
anchors.left: parent.left
|
||
anchors.right: parent.right
|
||
|
||
MouseArea {
|
||
anchors.fill: parent
|
||
onPressed: root.startSystemMove()
|
||
}
|
||
|
||
RowLayout {
|
||
anchors.fill: parent
|
||
anchors.leftMargin: 24
|
||
anchors.rightMargin: 24
|
||
|
||
Image {
|
||
source: "microphone.svg"
|
||
sourceSize.width: 18
|
||
sourceSize.height: 18
|
||
Layout.alignment: Qt.AlignVCenter
|
||
opacity: 0.7
|
||
// Colorize the icon
|
||
layer.enabled: true
|
||
layer.effect: MultiEffect {
|
||
colorization: 1.0
|
||
colorizationColor: SettingsStyle.accent
|
||
}
|
||
}
|
||
|
||
Text {
|
||
text: "SETTINGS"
|
||
color: SettingsStyle.textSecondary
|
||
font.family: mainFont
|
||
font.pixelSize: 13
|
||
font.letterSpacing: 2
|
||
font.bold: true
|
||
Layout.alignment: Qt.AlignVCenter
|
||
}
|
||
|
||
Item { Layout.fillWidth: true }
|
||
|
||
// Improved Close Button
|
||
Rectangle {
|
||
width: 32; height: 32
|
||
activeFocusOnTab: true
|
||
Accessible.name: "Close settings"
|
||
Accessible.role: Accessible.Button
|
||
Keys.onReturnPressed: root.close()
|
||
Keys.onSpacePressed: root.close()
|
||
radius: 8
|
||
color: closeMa.containsMouse ? "#20FF8A8A" : "transparent"
|
||
border.color: closeMa.containsMouse ? "#40FF8A8A" : "transparent"
|
||
border.width: 1
|
||
|
||
Text {
|
||
anchors.centerIn: parent
|
||
text: "×"
|
||
color: closeMa.containsMouse ? "#FF8A8A" : SettingsStyle.textSecondary
|
||
font.family: mainFont
|
||
font.pixelSize: 20
|
||
font.bold: true
|
||
}
|
||
|
||
MouseArea {
|
||
id: closeMa
|
||
anchors.fill: parent
|
||
hoverEnabled: true
|
||
cursorShape: Qt.PointingHandCursor
|
||
onClicked: root.close()
|
||
}
|
||
|
||
Behavior on color { ColorAnimation { duration: 150 } }
|
||
Behavior on border.color { ColorAnimation { duration: 150 } }
|
||
// Focus ring
|
||
Rectangle {
|
||
anchors.fill: parent
|
||
radius: 8
|
||
color: "transparent"
|
||
border.width: SettingsStyle.focusRingWidth
|
||
border.color: SettingsStyle.accent
|
||
visible: parent.activeFocus
|
||
}
|
||
}
|
||
}
|
||
|
||
Rectangle {
|
||
anchors.bottom: parent.bottom
|
||
width: parent.width
|
||
height: 1
|
||
color: SettingsStyle.borderSubtle
|
||
}
|
||
}
|
||
|
||
// --- CONTENT AREA ---
|
||
RowLayout {
|
||
anchors.top: titleBar.bottom
|
||
anchors.bottom: parent.bottom
|
||
anchors.left: parent.left
|
||
anchors.right: parent.right
|
||
spacing: 0
|
||
|
||
// --- SIDEBAR ---
|
||
Rectangle {
|
||
Layout.fillHeight: true
|
||
Layout.preferredWidth: 220
|
||
Layout.minimumWidth: 220
|
||
Layout.maximumWidth: 220
|
||
color: Qt.rgba(1, 1, 1, 0.02) // Very subtle separation
|
||
|
||
ColumnLayout {
|
||
anchors.fill: parent
|
||
anchors.margins: 16
|
||
spacing: 8
|
||
|
||
ListModel {
|
||
id: navModel
|
||
ListElement { name: "General"; icon: "settings.svg" }
|
||
ListElement { name: "Audio"; icon: "microphone.svg" }
|
||
ListElement { name: "Visuals"; icon: "visibility.svg" }
|
||
ListElement { name: "AI Engine"; icon: "smart_toy.svg" }
|
||
ListElement { name: "Debug"; icon: "terminal.svg" }
|
||
}
|
||
|
||
Repeater {
|
||
model: navModel
|
||
delegate: Rectangle {
|
||
id: navBtnRoot
|
||
Layout.fillWidth: true
|
||
height: 38
|
||
color: stack.currentIndex === index ? SettingsStyle.surfaceHover : (ma.containsMouse ? Qt.rgba(1,1,1,0.03) : "transparent")
|
||
radius: 6
|
||
activeFocusOnTab: true
|
||
Accessible.name: name
|
||
Accessible.role: Accessible.Tab
|
||
Keys.onReturnPressed: stack.currentIndex = index
|
||
Keys.onSpacePressed: stack.currentIndex = index
|
||
Keys.onDownPressed: {
|
||
if (index < navModel.count - 1) {
|
||
var nextItem = navBtnRoot.parent.children[index + 2]
|
||
if (nextItem && nextItem.forceActiveFocus) nextItem.forceActiveFocus()
|
||
}
|
||
}
|
||
Keys.onUpPressed: {
|
||
if (index > 0) {
|
||
var prevItem = navBtnRoot.parent.children[index]
|
||
if (prevItem && prevItem.forceActiveFocus) prevItem.forceActiveFocus()
|
||
}
|
||
}
|
||
|
||
Behavior on color { ColorAnimation { duration: 150 } }
|
||
|
||
// Left active stripe
|
||
Rectangle {
|
||
width: 3
|
||
height: 20
|
||
anchors.left: parent.left
|
||
anchors.verticalCenter: parent.verticalCenter
|
||
radius: 2
|
||
color: SettingsStyle.accent
|
||
visible: stack.currentIndex === index
|
||
}
|
||
|
||
RowLayout {
|
||
anchors.fill: parent
|
||
anchors.leftMargin: 12
|
||
spacing: 12
|
||
|
||
Image {
|
||
source: icon
|
||
sourceSize.width: 16
|
||
sourceSize.height: 16
|
||
fillMode: Image.PreserveAspectFit
|
||
Layout.alignment: Qt.AlignVCenter
|
||
opacity: stack.currentIndex === index ? 1.0 : 0.5
|
||
|
||
layer.enabled: true
|
||
layer.effect: MultiEffect {
|
||
colorization: 1.0
|
||
colorizationColor: stack.currentIndex === index ? SettingsStyle.accent : SettingsStyle.textSecondary
|
||
}
|
||
}
|
||
|
||
Text {
|
||
text: name
|
||
color: stack.currentIndex === index ? SettingsStyle.textPrimary : SettingsStyle.textSecondary
|
||
font.family: mainFont
|
||
font.pixelSize: 13
|
||
font.weight: stack.currentIndex === index ? Font.Bold : Font.Normal
|
||
}
|
||
}
|
||
|
||
MouseArea {
|
||
id: ma
|
||
anchors.fill: parent
|
||
hoverEnabled: true
|
||
cursorShape: Qt.PointingHandCursor
|
||
onClicked: stack.currentIndex = index
|
||
}
|
||
// Focus ring
|
||
Rectangle {
|
||
anchors.fill: parent
|
||
radius: 6
|
||
color: "transparent"
|
||
border.width: SettingsStyle.focusRingWidth
|
||
border.color: SettingsStyle.accent
|
||
visible: parent.activeFocus
|
||
}
|
||
}
|
||
}
|
||
|
||
Item { Layout.fillHeight: true }
|
||
}
|
||
|
||
// Vertical Divider
|
||
Rectangle {
|
||
anchors.right: parent.right
|
||
anchors.top: parent.top
|
||
anchors.bottom: parent.bottom
|
||
width: 1
|
||
color: SettingsStyle.borderSubtle
|
||
}
|
||
}
|
||
|
||
// --- MAIN CONTENT STACK ---
|
||
Rectangle {
|
||
Layout.fillWidth: true
|
||
Layout.fillHeight: true
|
||
color: "transparent"
|
||
clip: true
|
||
|
||
StackLayout {
|
||
id: stack
|
||
anchors.fill: parent
|
||
currentIndex: 0
|
||
|
||
// --- TAB: GENERAL ---
|
||
ScrollView {
|
||
Accessible.role: Accessible.PageTab
|
||
ScrollBar.vertical.policy: ScrollBar.AsNeeded
|
||
contentWidth: availableWidth
|
||
|
||
ColumnLayout {
|
||
width: parent.width
|
||
spacing: 24
|
||
anchors.margins: 32
|
||
|
||
// Header
|
||
ColumnLayout {
|
||
spacing: 4
|
||
Layout.topMargin: 32
|
||
Layout.leftMargin: 32
|
||
Layout.rightMargin: 32
|
||
Text { text: "General"; color: SettingsStyle.textPrimary; font.family: mainFont; font.pixelSize: 24; font.bold: true }
|
||
Text { text: "System behavior and shortcuts"; color: SettingsStyle.textSecondary; font.family: mainFont; font.pixelSize: 14 }
|
||
}
|
||
|
||
ModernSettingsSection {
|
||
title: "Application"
|
||
Layout.margins: 32
|
||
Layout.topMargin: 0
|
||
|
||
content: ColumnLayout {
|
||
width: parent.width
|
||
spacing: 0
|
||
|
||
ModernSettingsItem {
|
||
label: "Global Hotkey (Transcribe)"
|
||
description: "Standard: Raw transcription"
|
||
control: ModernKeySequenceRecorder {
|
||
implicitWidth: 240
|
||
currentSequence: ui.getSetting("hotkey")
|
||
onSequenceChanged: (seq) => ui.setSetting("hotkey", seq)
|
||
}
|
||
}
|
||
|
||
ModernSettingsItem {
|
||
label: "Global Hotkey (Correct)"
|
||
description: "Enhanced: Transcribe + AI Correction"
|
||
control: ModernKeySequenceRecorder {
|
||
implicitWidth: 240
|
||
currentSequence: ui.getSetting("hotkey_correct")
|
||
onSequenceChanged: (seq) => ui.setSetting("hotkey_correct", seq)
|
||
}
|
||
}
|
||
|
||
ModernSettingsItem {
|
||
label: "Global Hotkey (Translate)"
|
||
description: "Press to record a new shortcut (e.g. F10)"
|
||
control: ModernKeySequenceRecorder {
|
||
implicitWidth: 240
|
||
currentSequence: ui.getSetting("hotkey_translate")
|
||
onSequenceChanged: (seq) => ui.setSetting("hotkey_translate", seq)
|
||
}
|
||
}
|
||
|
||
ModernSettingsItem {
|
||
label: "Run on Startup"
|
||
description: "Automatically launch when you log in"
|
||
control: ModernSwitch {
|
||
checked: ui.getSetting("run_on_startup")
|
||
onToggled: ui.setSetting("run_on_startup", checked)
|
||
}
|
||
}
|
||
|
||
ModernSettingsItem {
|
||
label: "Input Method"
|
||
description: "How text is sent to the active window"
|
||
control: ModernComboBox {
|
||
width: 160
|
||
model: ["Clipboard Paste", "Simulate Typing"]
|
||
currentIndex: model.indexOf(ui.getSetting("input_method"))
|
||
onActivated: ui.setSetting("input_method", currentText)
|
||
}
|
||
}
|
||
|
||
ModernSettingsItem {
|
||
label: "Typing Speed"
|
||
description: "Chars/min"
|
||
showSeparator: false
|
||
control: ModernSlider {
|
||
Layout.preferredWidth: 200
|
||
from: 10; to: 20000
|
||
stepSize: 100
|
||
snapMode: Slider.SnapAlways
|
||
value: ui.getSetting("typing_speed")
|
||
onMoved: ui.setSetting("typing_speed", value)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// --- TAB: AUDIO ---
|
||
ScrollView {
|
||
Accessible.role: Accessible.PageTab
|
||
ScrollBar.vertical.policy: ScrollBar.AsNeeded
|
||
contentWidth: availableWidth
|
||
|
||
ColumnLayout {
|
||
width: parent.width
|
||
spacing: 24
|
||
anchors.margins: 32
|
||
|
||
ColumnLayout {
|
||
spacing: 4
|
||
Layout.topMargin: 32
|
||
Layout.leftMargin: 32
|
||
Layout.rightMargin: 32
|
||
Text { text: "Audio"; color: SettingsStyle.textPrimary; font.family: mainFont; font.pixelSize: 24; font.bold: true }
|
||
Text { text: "Input devices and signal processing"; color: SettingsStyle.textSecondary; font.family: mainFont; font.pixelSize: 14 }
|
||
}
|
||
|
||
ModernSettingsSection {
|
||
title: "Devices"
|
||
Layout.margins: 32
|
||
Layout.topMargin: 0
|
||
|
||
content: ColumnLayout {
|
||
width: parent.width
|
||
spacing: 0
|
||
|
||
ModernSettingsItem {
|
||
label: "Microphone"
|
||
description: "Select your primary input device"
|
||
control: ModernComboBox {
|
||
Layout.preferredWidth: 280
|
||
textRole: "name"
|
||
valueRole: "id"
|
||
model: ui.getAudioDevices()
|
||
onActivated: ui.setSetting("input_device", model[currentIndex].id) // Explicitly use model index
|
||
}
|
||
}
|
||
|
||
ModernSettingsItem {
|
||
label: "Save Recordings"
|
||
description: "Save .wav files to ./recordings folder"
|
||
showSeparator: false
|
||
control: ModernSwitch {
|
||
checked: ui.getSetting("save_recordings")
|
||
onToggled: ui.setSetting("save_recordings", checked)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
ModernSettingsSection {
|
||
title: "Signal Processing"
|
||
Layout.margins: 32
|
||
Layout.topMargin: 0
|
||
|
||
content: ColumnLayout {
|
||
width: parent.width
|
||
spacing: 0
|
||
|
||
ModernSettingsItem {
|
||
label: "VAD Threshold"
|
||
description: "Silence detection sensitivity (" + (ui.getSetting("silence_threshold") * 100).toFixed(0) + "%)"
|
||
control: ModernSlider {
|
||
Layout.preferredWidth: 200
|
||
from: 1; to: 100
|
||
value: ui.getSetting("silence_threshold") * 100
|
||
onMoved: ui.setSetting("silence_threshold", Number((value / 100.0).toFixed(2)))
|
||
}
|
||
}
|
||
|
||
ModernSettingsItem {
|
||
label: "Silence Timeout"
|
||
description: "Stop recording after " + (ui.getSetting("silence_duration")).toFixed(1) + "s of silence"
|
||
showSeparator: false
|
||
control: ModernSlider {
|
||
Layout.preferredWidth: 200
|
||
from: 0.5; to: 5.0
|
||
value: ui.getSetting("silence_duration")
|
||
onMoved: ui.setSetting("silence_duration", Number(value.toFixed(1)))
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// --- TAB: VISUALS ---
|
||
ScrollView {
|
||
Accessible.role: Accessible.PageTab
|
||
ScrollBar.vertical.policy: ScrollBar.AsNeeded
|
||
contentWidth: availableWidth
|
||
|
||
ColumnLayout {
|
||
width: parent.width
|
||
spacing: 24
|
||
anchors.margins: 32
|
||
|
||
ColumnLayout {
|
||
spacing: 4
|
||
Layout.topMargin: 32
|
||
Layout.leftMargin: 32
|
||
Layout.rightMargin: 32
|
||
Text { text: "Visuals"; color: SettingsStyle.textPrimary; font.family: mainFont; font.pixelSize: 24; font.bold: true }
|
||
Text { text: "Customize the overlay appearance"; color: SettingsStyle.textSecondary; font.family: mainFont; font.pixelSize: 14 }
|
||
}
|
||
|
||
ModernSettingsSection {
|
||
title: "Overlay"
|
||
Layout.margins: 32
|
||
Layout.topMargin: 0
|
||
|
||
content: ColumnLayout {
|
||
width: parent.width
|
||
spacing: 0
|
||
|
||
ModernSettingsItem {
|
||
label: "UI Scale"
|
||
description: "Global interface scaling factor (" + ui.getSetting("ui_scale").toFixed(2) + "x)"
|
||
control: ModernSlider {
|
||
Layout.preferredWidth: 200
|
||
from: 0.75; to: 1.5
|
||
value: ui.getSetting("ui_scale")
|
||
onMoved: ui.setSetting("ui_scale", Number(value.toFixed(2)))
|
||
}
|
||
}
|
||
|
||
ModernSettingsItem {
|
||
label: "Always on Top"
|
||
description: "Keep the overlay visible above other windows"
|
||
control: ModernSwitch {
|
||
checked: ui.getSetting("always_on_top")
|
||
onToggled: ui.setSetting("always_on_top", checked)
|
||
}
|
||
}
|
||
|
||
ModernSettingsItem {
|
||
label: "Window Opacity"
|
||
description: "Transparency level"
|
||
showSeparator: true
|
||
control: ModernSlider {
|
||
Layout.preferredWidth: 200
|
||
from: 0.1; to: 1.0
|
||
value: ui.getSetting("opacity")
|
||
onMoved: ui.setSetting("opacity", Number(value.toFixed(2)))
|
||
}
|
||
}
|
||
ModernSettingsItem {
|
||
label: "Reduce Motion"
|
||
description: "Disable animations for accessibility"
|
||
showSeparator: false
|
||
control: ModernSwitch {
|
||
checked: ui.getSetting("reduce_motion")
|
||
onToggled: ui.setSetting("reduce_motion", checked)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
ModernSettingsSection {
|
||
title: "Window Position"
|
||
Layout.margins: 32
|
||
Layout.topMargin: 0
|
||
|
||
content: ColumnLayout {
|
||
width: parent.width
|
||
spacing: 0
|
||
|
||
ModernSettingsItem {
|
||
label: "Anchor Position"
|
||
description: "Where the overlay snaps to on screen"
|
||
control: ModernComboBox {
|
||
width: 160
|
||
model: ["Bottom Center", "Top Center", "Bottom Right", "Top Right", "Bottom Left", "Top Left"]
|
||
currentIndex: model.indexOf(ui.getSetting("overlay_position"))
|
||
onActivated: ui.setSetting("overlay_position", currentText)
|
||
}
|
||
}
|
||
|
||
ModernSettingsItem {
|
||
label: "Horizontal Offset"
|
||
description: "Fine-tune X position (" + ui.getSetting("overlay_offset_x") + "px)"
|
||
control: ModernSlider {
|
||
Layout.preferredWidth: 200
|
||
from: -500; to: 500
|
||
value: ui.getSetting("overlay_offset_x")
|
||
onMoved: ui.setSetting("overlay_offset_x", value)
|
||
}
|
||
}
|
||
|
||
ModernSettingsItem {
|
||
label: "Vertical Offset"
|
||
description: "Fine-tune Y position (" + ui.getSetting("overlay_offset_y") + "px)"
|
||
showSeparator: false
|
||
control: ModernSlider {
|
||
Layout.preferredWidth: 200
|
||
from: -500; to: 500
|
||
value: ui.getSetting("overlay_offset_y")
|
||
onMoved: ui.setSetting("overlay_offset_y", value)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// --- TAB: AI ENGINE ---
|
||
ScrollView {
|
||
Accessible.role: Accessible.PageTab
|
||
ScrollBar.vertical.policy: ScrollBar.AsNeeded
|
||
contentWidth: availableWidth
|
||
|
||
ColumnLayout {
|
||
width: parent.width
|
||
spacing: 24
|
||
anchors.margins: 32
|
||
|
||
ColumnLayout {
|
||
spacing: 4
|
||
Layout.topMargin: 32
|
||
Layout.leftMargin: 32
|
||
Layout.rightMargin: 32
|
||
Text { text: "AI Engine"; color: SettingsStyle.textPrimary; font.family: mainFont; font.pixelSize: 24; font.bold: true }
|
||
Text { text: "Model configuration and performance"; color: SettingsStyle.textSecondary; font.family: mainFont; font.pixelSize: 14 }
|
||
}
|
||
|
||
ModernSettingsSection {
|
||
title: "Style & Prompting"
|
||
Layout.margins: 32
|
||
Layout.topMargin: 0
|
||
|
||
content: ColumnLayout {
|
||
width: parent.width
|
||
spacing: 0
|
||
|
||
ModernSettingsItem {
|
||
label: "Punctuation Style"
|
||
description: "Hint for how to format text"
|
||
control: ModernComboBox {
|
||
id: styleCombo
|
||
width: 180
|
||
model: ["Standard (Proper)", "Casual (Lowercase)", "Custom"]
|
||
|
||
// Logic to determine initial index based on config string
|
||
Component.onCompleted: {
|
||
let current = ui.getSetting("initial_prompt")
|
||
if (current === "Mm-hmm. Okay, let's go. I speak in full sentences.") currentIndex = 0
|
||
else if (current === "um, okay... i guess so.") currentIndex = 1
|
||
else currentIndex = 2
|
||
}
|
||
|
||
onActivated: {
|
||
if (index === 0) ui.setSetting("initial_prompt", "Mm-hmm. Okay, let's go. I speak in full sentences.")
|
||
else if (index === 1) ui.setSetting("initial_prompt", "um, okay... i guess so.")
|
||
// Custom: Don't change string immediately, let user type
|
||
}
|
||
}
|
||
}
|
||
|
||
ModernSettingsItem {
|
||
label: "Custom Prompt"
|
||
description: "Advanced: Define your own style hint"
|
||
visible: styleCombo.currentIndex === 2
|
||
control: ModernTextField {
|
||
Layout.preferredWidth: 280
|
||
placeholderText: "e.g. 'Hello, World.'"
|
||
text: ui.getSetting("initial_prompt") || ""
|
||
onEditingFinished: ui.setSetting("initial_prompt", text === "" ? null : text)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
ModernSettingsSection {
|
||
title: "Model Config"
|
||
Layout.margins: 32
|
||
Layout.topMargin: 0
|
||
|
||
content: ColumnLayout {
|
||
width: parent.width
|
||
spacing: 0
|
||
|
||
ListModel {
|
||
id: modelDetailsModel
|
||
ListElement { name: "tiny"; info: "39M params • <1GB VRAM • 32x speed. Fastest, best for weak hardware." }
|
||
ListElement { name: "base"; info: "74M params • ~1GB VRAM • 16x speed. Efficient for simple commands." }
|
||
ListElement { name: "small"; info: "244M params • ~2GB VRAM • 6x speed. Recommended for most users." }
|
||
ListElement { name: "medium"; info: "769M params • ~5GB VRAM • Accurate, high fidelity. Mid-range GPU req." }
|
||
ListElement { name: "large-v3"; info: "1.5B params • ~10GB VRAM • Pro quality. Requires high-end hardware." }
|
||
ListElement { name: "turbo"; info: "800M params • ~6GB VRAM • Large-v3 quality with 8x speed boost." }
|
||
}
|
||
|
||
ModernSettingsItem {
|
||
label: "Model Size"
|
||
description: "Larger models are smarter but slower"
|
||
control: ModernComboBox {
|
||
id: modelSizeCombo
|
||
width: 140
|
||
model: ["tiny", "base", "small", "medium", "large-v3", "turbo"]
|
||
currentIndex: model.indexOf(ui.getSetting("model_size"))
|
||
onActivated: ui.setSetting("model_size", currentText)
|
||
}
|
||
}
|
||
|
||
// Model Info Card
|
||
Rectangle {
|
||
id: modelInfoCard
|
||
Layout.fillWidth: true
|
||
Layout.margins: 12
|
||
Layout.topMargin: 0
|
||
Layout.bottomMargin: 16
|
||
height: 54
|
||
color: "#0a0a0f"
|
||
radius: 6
|
||
border.color: SettingsStyle.borderSubtle
|
||
border.width: 1
|
||
|
||
// Improved reactive check
|
||
property bool isDownloaded: false
|
||
|
||
Timer {
|
||
id: checkTimer
|
||
interval: 1000
|
||
running: true
|
||
repeat: true
|
||
onTriggered: parent.checkStatus()
|
||
}
|
||
|
||
function checkStatus() {
|
||
if (modelSizeCombo && modelSizeCombo.currentText) {
|
||
isDownloaded = ui.isModelDownloaded(modelSizeCombo.currentText)
|
||
}
|
||
}
|
||
|
||
// Refresh when notified by Python
|
||
Connections {
|
||
target: ui
|
||
function onModelStatesChanged() {
|
||
checkStatus()
|
||
}
|
||
}
|
||
|
||
Component.onCompleted: checkStatus()
|
||
|
||
// Also check when selection changes
|
||
Connections {
|
||
target: modelSizeCombo
|
||
function onActivated() { modelInfoCard.checkStatus() }
|
||
}
|
||
|
||
RowLayout {
|
||
anchors.fill: parent
|
||
anchors.leftMargin: 12
|
||
anchors.rightMargin: 12
|
||
spacing: 12
|
||
|
||
Image {
|
||
source: "smart_toy.svg"
|
||
sourceSize: Qt.size(16, 16)
|
||
layer.enabled: true
|
||
layer.effect: MultiEffect {
|
||
colorization: 1.0
|
||
colorizationColor: modelInfoCard.isDownloaded ? SettingsStyle.accent : "#808080"
|
||
}
|
||
}
|
||
|
||
ColumnLayout {
|
||
Layout.fillWidth: true
|
||
spacing: 2
|
||
Text {
|
||
text: {
|
||
if (ui.isDownloading) return "Downloading AI Core..."
|
||
for (var i = 0; i < modelDetailsModel.count; i++) {
|
||
if (modelDetailsModel.get(i).name === modelSizeCombo.currentText) {
|
||
return modelDetailsModel.get(i).info
|
||
}
|
||
}
|
||
return "Select a model."
|
||
}
|
||
color: "#ffffff"
|
||
font.family: "JetBrains Mono"
|
||
font.pixelSize: 11
|
||
opacity: 1.0
|
||
elide: Text.ElideRight
|
||
Layout.fillWidth: true
|
||
}
|
||
|
||
Rectangle {
|
||
id: downloaderBar
|
||
visible: ui.isDownloading
|
||
Layout.fillWidth: true
|
||
height: 2
|
||
color: "#20ffffff"
|
||
Rectangle {
|
||
width: downloaderBar.width * 0.5
|
||
height: downloaderBar.height
|
||
color: SettingsStyle.accent
|
||
SequentialAnimation on x {
|
||
loops: Animation.Infinite
|
||
NumberAnimation { from: -width; to: downloaderBar.width; duration: 1500 }
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
Button {
|
||
id: downloadBtn
|
||
text: "Download"
|
||
visible: !modelInfoCard.isDownloaded && !ui.isDownloading
|
||
Layout.preferredHeight: 24
|
||
Layout.preferredWidth: 80
|
||
|
||
contentItem: Text {
|
||
text: "DOWNLOAD"
|
||
font.pixelSize: 10; font.bold: true; color: "#000000"; horizontalAlignment: Text.AlignHCenter; verticalAlignment: Text.AlignVCenter
|
||
}
|
||
background: Rectangle {
|
||
color: downloadBtn.hovered ? "#ffffff" : SettingsStyle.accent; radius: 4
|
||
}
|
||
onClicked: ui.downloadModel(modelSizeCombo.currentText)
|
||
}
|
||
|
||
// Status tag
|
||
Rectangle {
|
||
id: statusTag
|
||
visible: modelInfoCard.isDownloaded && !ui.isDownloading
|
||
height: 18; width: 64; radius: 4; color: "#1000f2ff"
|
||
border.color: "#3000f2ff"; border.width: 1
|
||
Text {
|
||
anchors.centerIn: statusTag; text: "INSTALLED"; font.pixelSize: 9
|
||
font.bold: true; color: SettingsStyle.accent; opacity: 0.9
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
ModernSettingsItem {
|
||
label: "Language"
|
||
description: "Spoken language to transcribe"
|
||
control: ModernComboBox {
|
||
Layout.preferredWidth: 200
|
||
model: ui.get_supported_languages()
|
||
currentIndex: model.indexOf(ui.get_current_language_name())
|
||
onActivated: (index) => ui.set_language_by_name(currentText)
|
||
}
|
||
}
|
||
|
||
// Task selector removed as per user request (Hotkeys handle this now)
|
||
|
||
ModernSettingsItem {
|
||
label: "Compute Device"
|
||
description: "Hardware acceleration (CUDA requires NVidia GPU)"
|
||
control: ModernComboBox {
|
||
width: 140
|
||
model: ["auto", "cuda", "cpu"]
|
||
currentIndex: model.indexOf(ui.getSetting("compute_device"))
|
||
onActivated: ui.setSetting("compute_device", currentText)
|
||
}
|
||
}
|
||
|
||
ModernSettingsItem {
|
||
label: "Precision"
|
||
description: "Quantization type (int8 is faster, float16 is accurate)"
|
||
showSeparator: false
|
||
control: ModernComboBox {
|
||
width: 140
|
||
model: ["int8", "float16", "float32"]
|
||
currentIndex: model.indexOf(ui.getSetting("compute_type"))
|
||
onActivated: ui.setSetting("compute_type", currentText)
|
||
}
|
||
}
|
||
|
||
ModernSettingsItem {
|
||
label: "Low VRAM Mode"
|
||
description: "Unload models immediately after use (Saves VRAM, Adds Delay)"
|
||
showSeparator: false
|
||
control: ModernSwitch {
|
||
checked: ui.getSetting("unload_models_after_use")
|
||
onToggled: ui.setSetting("unload_models_after_use", checked)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
ModernSettingsSection {
|
||
title: "Correction & Rewriting"
|
||
Layout.margins: 32
|
||
Layout.topMargin: 0
|
||
|
||
content: ColumnLayout {
|
||
width: parent.width
|
||
spacing: 0
|
||
|
||
ModernSettingsItem {
|
||
label: "Enable Correction"
|
||
description: "Post-process text with Llama 3.2 1B (Adds latency)"
|
||
control: ModernSwitch {
|
||
checked: ui.getSetting("llm_enabled")
|
||
onToggled: ui.setSetting("llm_enabled", checked)
|
||
}
|
||
}
|
||
|
||
ModernSettingsItem {
|
||
label: "Correction Mode"
|
||
description: "Grammar Fix vs. Complete Rewrite"
|
||
visible: ui.getSetting("llm_enabled")
|
||
control: ModernComboBox {
|
||
width: 140
|
||
model: ["Grammar", "Standard", "Rewrite"]
|
||
currentIndex: model.indexOf(ui.getSetting("llm_mode"))
|
||
onActivated: ui.setSetting("llm_mode", currentText)
|
||
}
|
||
}
|
||
|
||
// LLM Model Status Card
|
||
Rectangle {
|
||
Layout.fillWidth: true
|
||
Layout.margins: 12
|
||
Layout.topMargin: 0
|
||
Layout.bottomMargin: 16
|
||
height: 54
|
||
color: "#0a0a0f"
|
||
visible: ui.getSetting("llm_enabled")
|
||
radius: 6
|
||
border.color: SettingsStyle.borderSubtle
|
||
border.width: 1
|
||
|
||
property bool isDownloaded: false
|
||
property bool isDownloading: ui.isDownloading && ui.statusText.indexOf("LLM") !== -1
|
||
|
||
Timer {
|
||
interval: 2000
|
||
running: visible
|
||
repeat: true
|
||
onTriggered: parent.checkStatus()
|
||
}
|
||
|
||
function checkStatus() {
|
||
isDownloaded = ui.isLLMModelDownloaded()
|
||
}
|
||
|
||
Component.onCompleted: checkStatus()
|
||
|
||
Connections {
|
||
target: ui
|
||
function onModelStatesChanged() { parent.checkStatus() }
|
||
function onIsDownloadingChanged() { parent.checkStatus() }
|
||
}
|
||
|
||
RowLayout {
|
||
anchors.fill: parent
|
||
anchors.leftMargin: 12
|
||
anchors.rightMargin: 12
|
||
spacing: 12
|
||
|
||
Image {
|
||
source: "smart_toy.svg"
|
||
sourceSize: Qt.size(16, 16)
|
||
layer.enabled: true
|
||
layer.effect: MultiEffect {
|
||
colorization: 1.0
|
||
colorizationColor: parent.parent.isDownloaded ? SettingsStyle.accent : "#808080"
|
||
}
|
||
}
|
||
|
||
ColumnLayout {
|
||
Layout.fillWidth: true
|
||
spacing: 2
|
||
Text {
|
||
text: "Llama 3.2 1B (Instruct)"
|
||
color: "#ffffff"
|
||
font.family: "JetBrains Mono"; font.bold: true
|
||
font.pixelSize: 11
|
||
}
|
||
Text {
|
||
text: parent.parent.isDownloaded ? "Ready." : "Model missing (~1.2GB)"
|
||
color: SettingsStyle.textSecondary
|
||
font.family: "JetBrains Mono"; font.pixelSize: 10
|
||
}
|
||
}
|
||
|
||
Button {
|
||
id: dlBtn
|
||
text: "Download"
|
||
visible: !parent.parent.isDownloaded && !parent.parent.isDownloading
|
||
Layout.preferredHeight: 24
|
||
Layout.preferredWidth: 80
|
||
|
||
contentItem: Text {
|
||
text: "DOWNLOAD"
|
||
font.pixelSize: 10; font.bold: true; color: "#000000"; horizontalAlignment: Text.AlignHCenter; verticalAlignment: Text.AlignVCenter
|
||
}
|
||
background: Rectangle {
|
||
color: dlBtn.hovered ? "#ffffff" : SettingsStyle.accent; radius: 4
|
||
}
|
||
onClicked: ui.downloadLLM()
|
||
}
|
||
|
||
// Progress Bar
|
||
Rectangle {
|
||
visible: parent.parent.isDownloading
|
||
Layout.fillWidth: true
|
||
height: 4
|
||
color: "#30ffffff"
|
||
Rectangle {
|
||
width: parent.width * (ui.downloadProgress / 100)
|
||
height: parent.height
|
||
color: SettingsStyle.accent
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
ModernSettingsSection {
|
||
title: "Advanced Decoding"
|
||
Layout.margins: 32
|
||
Layout.topMargin: 0
|
||
|
||
content: ColumnLayout {
|
||
width: parent.width
|
||
spacing: 0
|
||
|
||
ModernSettingsItem {
|
||
label: "Beam Size"
|
||
description: "Search width (Higher = Better Accuracy, Slower)"
|
||
control: ModernSlider {
|
||
Layout.preferredWidth: 200
|
||
from: 1; to: 10
|
||
value: ui.getSetting("beam_size")
|
||
onMoved: ui.setSetting("beam_size", value)
|
||
}
|
||
}
|
||
|
||
ModernSettingsItem {
|
||
label: "VAD Filter"
|
||
description: "Skip silent audio segments (Speeds up processing)"
|
||
control: ModernSwitch {
|
||
checked: ui.getSetting("vad_filter")
|
||
onToggled: ui.setSetting("vad_filter", checked)
|
||
}
|
||
}
|
||
|
||
ModernSettingsItem {
|
||
label: "Hallucination Check"
|
||
description: "Prevent repetitive text loops (No Repeat N-Gram)"
|
||
control: ModernSwitch {
|
||
checked: ui.getSetting("no_repeat_ngram_size") > 0
|
||
onToggled: ui.setSetting("no_repeat_ngram_size", checked ? 3 : 0)
|
||
}
|
||
}
|
||
|
||
ModernSettingsItem {
|
||
label: "Context History"
|
||
description: "Use previous text to improve coherence"
|
||
showSeparator: false
|
||
control: ModernSwitch {
|
||
checked: ui.getSetting("condition_on_previous_text")
|
||
onToggled: ui.setSetting("condition_on_previous_text", checked)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// --- TAB: DEBUG ---
|
||
ScrollView {
|
||
Accessible.role: Accessible.PageTab
|
||
ScrollBar.vertical.policy: ScrollBar.AsNeeded
|
||
contentWidth: availableWidth
|
||
|
||
ColumnLayout {
|
||
width: parent.width
|
||
spacing: 16
|
||
anchors.margins: 32
|
||
|
||
ColumnLayout {
|
||
spacing: 4
|
||
Layout.topMargin: 32
|
||
Layout.leftMargin: 32
|
||
Layout.rightMargin: 32
|
||
Text { text: "System Diagnostics"; color: SettingsStyle.textPrimary; font.family: mainFont; font.pixelSize: 24; font.bold: true }
|
||
Text { text: "Live performance and logs"; color: SettingsStyle.textSecondary; font.family: mainFont; font.pixelSize: 14 }
|
||
}
|
||
|
||
// --- PERFORMANCE STATS ---
|
||
RowLayout {
|
||
Layout.fillWidth: true
|
||
Layout.leftMargin: 32
|
||
Layout.rightMargin: 32
|
||
spacing: 16
|
||
|
||
StatBox { label: "APP CPU"; value: ui.appCpu; unit: "%"; accent: "#00f2ff" }
|
||
StatBox { label: "APP RAM"; value: ui.appRamMb; unit: "MB"; accent: "#CAA9FF" }
|
||
StatBox { label: "GPU VRAM"; value: ui.appVramMb; unit: "MB"; accent: "#FF8FD0" }
|
||
StatBox { label: "GPU LOAD"; value: ui.appVramPercent; unit: "%"; accent: "#FF8A8A" }
|
||
}
|
||
|
||
Rectangle {
|
||
Layout.fillWidth: true
|
||
Layout.preferredHeight: 300
|
||
Layout.margins: 32
|
||
Layout.topMargin: 0
|
||
color: "#0d0d10"
|
||
radius: 8
|
||
border.color: SettingsStyle.borderSubtle
|
||
border.width: 1
|
||
clip: true
|
||
|
||
ScrollView {
|
||
anchors.fill: parent
|
||
anchors.margins: 12
|
||
ScrollBar.vertical.policy: ScrollBar.AlwaysOn
|
||
|
||
TextArea {
|
||
id: logArea
|
||
text: ui.allLogs
|
||
readOnly: true
|
||
color: "#cccccc"
|
||
font.family: "JetBrains Mono"
|
||
font.pixelSize: 11
|
||
wrapMode: TextArea.Wrap
|
||
background: null
|
||
selectByMouse: true
|
||
|
||
Connections {
|
||
target: ui
|
||
function onLogAppended(line) {
|
||
logArea.append(line)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|