Initial commit of WhisperVoice

This commit is contained in:
Your Name
2026-01-24 17:03:52 +02:00
commit 9ff0e8d108
118 changed files with 6102 additions and 0 deletions

905
src/ui/qml/Settings.qml Normal file
View File

@@ -0,0 +1,905 @@
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: "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
radius: 8
color: closeMa.containsMouse ? "#20ff4b4b" : "transparent"
border.color: closeMa.containsMouse ? "#40ff4b4b" : "transparent"
border.width: 1
Text {
anchors.centerIn: parent
text: "×"
color: closeMa.containsMouse ? "#ff4b4b" : 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 } }
}
}
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
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
}
}
}
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 {
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"
description: "Press to record a new shortcut (e.g. Ctrl+Space)"
control: ModernKeySequenceRecorder {
Layout.preferredWidth: 200
currentSequence: ui.getSetting("hotkey")
onSequenceChanged: (seq) => ui.setSetting("hotkey", 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: 6000
stepSize: 10
snapMode: Slider.SnapAlways
value: ui.getSetting("typing_speed")
onMoved: ui.setSetting("typing_speed", value)
}
}
}
}
}
}
// --- TAB: AUDIO ---
ScrollView {
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 {
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: false
control: ModernSlider {
Layout.preferredWidth: 200
from: 0.1; to: 1.0
value: ui.getSetting("opacity")
onMoved: ui.setSetting("opacity", Number(value.toFixed(2)))
}
}
}
}
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 {
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: "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: 10
opacity: 0.7
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: "Force language or Auto-detect"
control: ModernComboBox {
width: 140
model: ["auto", "en", "fr", "de", "es", "it", "ja", "zh", "ru"]
currentIndex: model.indexOf(ui.getSetting("language"))
onActivated: ui.setSetting("language", currentText)
}
}
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)
}
}
}
}
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 {
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: "#bd93f9" }
StatBox { label: "GPU VRAM"; value: ui.appVramMb; unit: "MB"; accent: "#ff79c6" }
StatBox { label: "GPU LOAD"; value: ui.appVramPercent; unit: "%"; accent: "#ff5555" }
}
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)
}
}
}
}
}
}
}
}
}
}
}
}