Files
harbour-hermes/qml/pages/SettingsPage.qml
T
kaneda a5932f0025 refactor(config): niente configurazione cablata — tutto parametrizzato (v0.3.0)
Prima di pubblicare: eliminati dai sorgenti tutti i valori "cablati".

- settings: default neutri (URL, voce, engine vuoti = "decide il server");
  nuove chiavi ttsVoices/ttsEngines (preset dei selettori) e vadMaxMs
  (durata massima registrazione, prima fissa a 60 s nel codice)
- ApiClient: guard su baseUrl vuoto (checkAuth/login si comportano senza
  rete), voce/engine omessi nella richiesta TTS se vuoti, Content-Type
  audio coerente col formato reale del file (ogg/wav/mp3/m4a/opus)
- SettingsPage: selettori voce/engine costruiti dai preset configurabili
  (Repeater dentro ContextMenu = pattern provato su tide) + campi per
  modificare i preset; LoginPage richiede indirizzo server non vuoto
- VoiceRecorder: tetto di registrazione da configurazione
- README: sezione "Configuration" completa; docs neutralizzate (niente
  URL/valori personali: repo public-friendly)
2026-09-12 12:42:06 +02:00

209 lines
7.4 KiB
QML

import QtQuick 2.0
import Sailfish.Silica 1.0
Page {
id: page
// Nessun elenco cablato: le voci/gli engine dei selettori vengono dalle
// impostazioni (ttsVoices/ttsEngines) e il valore corrente è sempre
// incluso. loading evita di salvare durante il caricamento iniziale.
property bool loading: true
SilicaFlickable {
anchors.fill: parent
contentHeight: col.height + Theme.paddingLarge
Column {
id: col
width: parent.width
spacing: Theme.paddingMedium
PageHeader { title: qsTr("Settings") }
SectionHeader { text: qsTr("Server") }
TextField {
id: urlField
width: parent.width
label: qsTr("Web UI address")
placeholderText: "https://hermes.example.com"
inputMethodHints: Qt.ImhUrlCharactersOnly | Qt.ImhNoAutoUppercase
onTextChanged: {
if (text !== appSettings.baseUrl)
appSettings.baseUrl = text
}
}
SectionHeader { text: qsTr("Voice") }
ComboBox {
label: qsTr("Read-aloud voice")
currentIndex: page.voiceIndex()
// Repeater dentro ContextMenu = pattern provato sul device (tide)
menu: ContextMenu {
MenuItem {
text: qsTr("(server default)")
onClicked: appSettings.ttsVoice = ""
}
Repeater {
model: page.voiceChoices().length
delegate: MenuItem {
text: page.voiceChoices()[index]
onClicked: appSettings.ttsVoice = page.voiceChoices()[index]
}
}
}
}
TextField {
id: voicesField
width: parent.width
label: qsTr("Voice presets (comma-separated)")
onTextChanged: {
if (!page.loading)
appSettings.ttsVoices = text.split(",")
}
}
ComboBox {
label: qsTr("TTS engine")
currentIndex: page.engineIndex()
menu: ContextMenu {
MenuItem {
text: qsTr("(server default)")
onClicked: appSettings.ttsEngine = ""
}
Repeater {
model: page.engineChoices().length
delegate: MenuItem {
text: page.engineChoices()[index]
onClicked: appSettings.ttsEngine = page.engineChoices()[index]
}
}
}
}
TextField {
id: enginesField
width: parent.width
label: qsTr("Engine presets (comma-separated)")
onTextChanged: {
if (!page.loading)
appSettings.ttsEngines = text.split(",")
}
}
TextSwitch {
text: qsTr("Read replies aloud")
description: qsTr("Speak every completed reply automatically")
checked: appSettings.ttsAutoRead
onCheckedChanged: appSettings.ttsAutoRead = checked
}
SectionHeader { text: qsTr("Voice dialog") }
TextSwitch {
text: qsTr("Send right after dictation")
description: qsTr("Send the transcribed text automatically, without tapping Send")
checked: appSettings.dictationAutoSend
onCheckedChanged: appSettings.dictationAutoSend = checked
}
ComboBox {
label: qsTr("Auto-stop pause")
currentIndex: {
var v = appSettings.vadSilenceMs
return v <= 1000 ? 0 : (v <= 1500 ? 1 : (v <= 2000 ? 2 : 3))
}
menu: ContextMenu {
MenuItem { text: qsTr("1.0 second"); onClicked: appSettings.vadSilenceMs = 1000 }
MenuItem { text: qsTr("1.5 seconds"); onClicked: appSettings.vadSilenceMs = 1500 }
MenuItem { text: qsTr("2.0 seconds"); onClicked: appSettings.vadSilenceMs = 2000 }
MenuItem { text: qsTr("3.0 seconds"); onClicked: appSettings.vadSilenceMs = 3000 }
}
}
ComboBox {
label: qsTr("Mic sensitivity")
currentIndex: appSettings.vadThreshold <= 500 ? 0
: (appSettings.vadThreshold <= 1000 ? 1 : 2)
menu: ContextMenu {
MenuItem { text: qsTr("High (even soft voices)"); onClicked: appSettings.vadThreshold = 400 }
MenuItem { text: qsTr("Medium"); onClicked: appSettings.vadThreshold = 800 }
MenuItem { text: qsTr("Low (noisy rooms)"); onClicked: appSettings.vadThreshold = 1600 }
}
}
SectionHeader { text: qsTr("Account") }
Button {
anchors.horizontalCenter: parent.horizontalCenter
text: qsTr("Sign out")
onClicked: page.signOut()
}
SectionHeader { text: qsTr("About") }
Label {
x: Theme.horizontalPageMargin
width: parent.width - 2 * Theme.horizontalPageMargin
wrapMode: Text.WordWrap
font.pixelSize: Theme.fontSizeSmall
color: Theme.secondaryColor
text: "harbour-hermes " + appVersion + "\n\n" +
qsTr("Native client for the Hermes Web UI (hermes-webui): " +
"sessions, live streaming chat, server-side voice " +
"dictation and read-aloud.")
}
}
VerticalScrollDecorator {}
}
function signOut() {
api.logout()
pageStack.replace(Qt.resolvedUrl("LoginPage.qml"))
}
// Elenchi dei selettori: preset configurati + valore corrente (così resta
// selezionabile anche se non è tra i preset). Indice 0 = "(server default)".
function voiceChoices() {
var out = appSettings.ttsVoices.slice()
var cur = appSettings.ttsVoice
if (cur.length > 0 && out.indexOf(cur) < 0)
out.push(cur)
return out
}
function voiceIndex() {
var cur = appSettings.ttsVoice
if (cur.length === 0)
return 0
var i = page.voiceChoices().indexOf(cur)
return i < 0 ? 0 : i + 1
}
function engineChoices() {
var out = appSettings.ttsEngines.slice()
var cur = appSettings.ttsEngine
if (cur.length > 0 && out.indexOf(cur) < 0)
out.push(cur)
return out
}
function engineIndex() {
var cur = appSettings.ttsEngine
if (cur.length === 0)
return 0
var i = page.engineChoices().indexOf(cur)
return i < 0 ? 0 : i + 1
}
Component.onCompleted: {
urlField.text = appSettings.baseUrl
voicesField.text = appSettings.ttsVoices.join(", ")
enginesField.text = appSettings.ttsEngines.join(", ")
page.loading = false
}
}