Files
whisper_voice/src/ui/qml/Settings.qml
Your Name d509eb5efb WCAG: Review fixes - arrow keys, PageTab roles, AlertMessage
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.
2026-02-18 21:10:21 +02:00

1171 lines
62 KiB
QML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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)
}
}
}
}
}
}
}
}
}
}
}
}