The sidebar was a flat scan of thirty-one rows; now it reads like a settings app. Multi-subject categories (Input, Network & Sharing, Applications, Users & Accounts, Privacy & Security, System) carry an Appearance-style tab strip above the page, drawn by the shell so the leaf pages themselves are untouched. The taxonomy lives in one new file, services/SettingsRoutes.qml; the sidebar, the strip, route validation, search breadcrumbs, and both generators derive from it. ShellState.settingsPage still holds leaf ids, so every deep link, IPC call, and search result keeps working — and now lands on the exact tab. Dictation moves out of Sound onto its own page under Input, with a handoff back to Sound for the microphone. The strip scrolls when System's nine tabs outgrow a tiled window. All 161 contracts pass. Claude-Session: https://claude.ai/code/session_01Ms2FbjQy31TVf3CEvQhGM8
91 lines
3.7 KiB
QML
91 lines
3.7 KiB
QML
import QtQuick
|
|
import qs.config
|
|
import qs.services
|
|
|
|
// Dictation is an input method, so it lives under Input beside the keyboard —
|
|
// but it listens through whichever device the Sound page selects, so the
|
|
// microphone card below hands off there rather than duplicating the picker.
|
|
SettingsPage {
|
|
title: "Dictation"
|
|
lede: Dictation.ready
|
|
? "Hold Super+D, speak, and release. The words are typed where the cursor is."
|
|
: "Speech to text, on the GPU, once the one-time setup below has run."
|
|
|
|
SettingsCard {
|
|
title: "Dictation"
|
|
subtitle: Dictation.ready
|
|
? "Hold Super+D, speak, and release. The words are typed where the cursor is."
|
|
: "The one-time setup fetches a speech server and a ~490 MB model — neither ships with Panama, because both are large and want the network."
|
|
|
|
// Status of the two pieces, read from the machine rather than guessed.
|
|
TextRow {
|
|
label: "Speech server"
|
|
detail: Dictation.serverReady
|
|
? "Running"
|
|
: (Dictation.imageBuilt ? "Ready — starts on the first dictation" : "Not set up yet")
|
|
value: Dictation.imageBuilt ? "Ready" : "Missing"
|
|
}
|
|
|
|
TextRow {
|
|
label: "Speech model"
|
|
detail: Dictation.modelInstalled
|
|
? "Kept across rebuilds of the server"
|
|
: "About 490 MB, downloaded once"
|
|
value: Dictation.modelInstalled ? Math.round(Dictation.modelBytes / 1048576) + " MB" : "Missing"
|
|
}
|
|
|
|
// ONE action that actually works: panama-dictate setup pulls the
|
|
// server image and downloads the model together. This card used to
|
|
// offer a Download button wired to a command the helper does not have,
|
|
// and told you to build a "panama app whisper-vulkan" that does not
|
|
// exist -- so nothing here did anything. It does now.
|
|
ActionRow {
|
|
visible: !Dictation.ready || Dictation.downloading
|
|
label: "Set up dictation"
|
|
detail: {
|
|
if (!Dictation.downloading)
|
|
return "Fetches the speech server and the model. Runs once, keeps both.";
|
|
if (Dictation.phase === "pulling")
|
|
return "Fetching the speech server…";
|
|
if (Dictation.downloadTotalBytes > 0)
|
|
return "Downloading model — " + Math.round(Dictation.downloadFraction * 100)
|
|
+ "% of " + Math.round(Dictation.downloadTotalBytes / 1048576) + " MB";
|
|
return "Setting up…";
|
|
}
|
|
action: Dictation.downloading ? "Working…" : "Set up"
|
|
enabled: !Dictation.downloading
|
|
onTriggered: Dictation.setup()
|
|
divider: !Dictation.typingAvailable || Dictation.lastError !== ""
|
|
}
|
|
|
|
TextRow {
|
|
visible: !Dictation.typingAvailable
|
|
label: "Typing"
|
|
detail: "wtype is missing, so dictated text would go to the clipboard instead of being typed."
|
|
value: "Missing"
|
|
divider: Dictation.lastError !== ""
|
|
}
|
|
|
|
TextRow {
|
|
visible: Dictation.lastError !== ""
|
|
label: "Problem"
|
|
detail: Dictation.lastError
|
|
value: ""
|
|
divider: false
|
|
}
|
|
}
|
|
|
|
SettingsCard {
|
|
title: "Microphone"
|
|
subtitle: AudioDevices.current(false)?.description ?? "No input device"
|
|
|
|
ActionRow {
|
|
label: "Input device"
|
|
detail: "Dictation listens through the input device selected in Sound"
|
|
divider: false
|
|
action: "Open Sound"
|
|
onTriggered: ShellState.openSettings("sound")
|
|
}
|
|
}
|
|
}
|