Make the dictation setup button call a command that exists
The Download button on Sound → Dictation ran `panama-dictate download`. There
is no such command: the helper answered {"ok": false, "error":
"unknown-command"} and the button did nothing, every time, for anyone who
tried it. The real command is `setup`, which pulls the speech server image
and downloads the ~490 MB model together -- one trip, by design. And the card
told you the server needed `panama app whisper-vulkan`, an app that exists in
no package or setup/apps entry; the server is a prebuilt image the helper
pulls with podman.
So the card described three things and got all three wrong. It now shows the
real state of both pieces and offers one action -- "Set up dictation" -- wired
to the command that actually fetches them, with progress that names which half
is running (server image, then model). Verified end to end: `panama-dictate
setup` emits the pulling/downloading JSON the service parses.
Claude-Session: https://claude.ai/code/session_01Epx9ZC1gwm81K3jm9x9CKh
This commit is contained in:
@@ -37,41 +37,47 @@ SettingsPage {
|
|||||||
title: "Dictation"
|
title: "Dictation"
|
||||||
subtitle: Dictation.ready
|
subtitle: Dictation.ready
|
||||||
? "Hold Super+D, speak, and release. The words are typed where the cursor is."
|
? "Hold Super+D, speak, and release. The words are typed where the cursor is."
|
||||||
: "Speech to text, on the GPU. Two pieces have to be in place first — neither ships with Panama, because both are large and want the network."
|
: "Speech to text, on the GPU. Hold Super+D once it is set up. The one-time setup below fetches a speech server and a ~490 MB model — neither ships with Panama, because both are large and want the network."
|
||||||
|
|
||||||
// Not a toggle: the keybind exists either way, and a switch would imply
|
// Status of the two pieces, read from the machine rather than guessed.
|
||||||
// dictation can be turned off rather than simply not set up.
|
|
||||||
TextRow {
|
TextRow {
|
||||||
label: "Speech server"
|
label: "Speech server"
|
||||||
detail: Dictation.imageBuilt
|
detail: Dictation.serverReady
|
||||||
? (Dictation.serverReady
|
? "Running"
|
||||||
? "Built and running"
|
: (Dictation.imageBuilt ? "Ready — starts on the first dictation" : "Not set up yet")
|
||||||
: "Built. Starts on the first dictation and stays loaded.")
|
|
||||||
: "Not built — run: panama app whisper-vulkan"
|
|
||||||
value: Dictation.imageBuilt ? "Ready" : "Missing"
|
value: Dictation.imageBuilt ? "Ready" : "Missing"
|
||||||
}
|
}
|
||||||
|
|
||||||
ActionRow {
|
TextRow {
|
||||||
label: "Speech model"
|
label: "Speech model"
|
||||||
detail: Dictation.downloading
|
detail: Dictation.modelInstalled
|
||||||
? (Dictation.downloadTotalBytes > 0
|
? "Kept across rebuilds of the server"
|
||||||
? Math.round(Dictation.downloadFraction * 100) + "% of "
|
: "About 490 MB, downloaded once"
|
||||||
+ Math.round(Dictation.downloadTotalBytes / 1048576) + " MB"
|
value: Dictation.modelInstalled ? Math.round(Dictation.modelBytes / 1048576) + " MB" : "Missing"
|
||||||
: "Downloading…")
|
|
||||||
: (Dictation.modelInstalled
|
|
||||||
? Math.round(Dictation.modelBytes / 1048576) + " MB, in place"
|
|
||||||
: "About 490 MB, downloaded once and kept")
|
|
||||||
action: Dictation.downloading ? "Downloading…" : "Download"
|
|
||||||
enabled: !Dictation.downloading && !Dictation.modelInstalled
|
|
||||||
visible: !Dictation.modelInstalled || Dictation.downloading
|
|
||||||
onTriggered: Dictation.download()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TextRow {
|
// ONE action that actually works: panama-dictate setup pulls the
|
||||||
visible: Dictation.modelInstalled && !Dictation.downloading
|
// server image and downloads the model together. This card used to
|
||||||
label: "Speech model"
|
// offer a Download button wired to a command the helper does not have,
|
||||||
detail: "Downloaded once and kept across rebuilds of the server."
|
// and told you to build a "panama app whisper-vulkan" that does not
|
||||||
value: Math.round(Dictation.modelBytes / 1048576) + " MB"
|
// 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 {
|
TextRow {
|
||||||
@@ -79,6 +85,7 @@ SettingsPage {
|
|||||||
label: "Typing"
|
label: "Typing"
|
||||||
detail: "wtype is missing, so dictated text would go to the clipboard instead of being typed."
|
detail: "wtype is missing, so dictated text would go to the clipboard instead of being typed."
|
||||||
value: "Missing"
|
value: "Missing"
|
||||||
|
divider: Dictation.lastError !== ""
|
||||||
}
|
}
|
||||||
|
|
||||||
TextRow {
|
TextRow {
|
||||||
|
|||||||
@@ -33,6 +33,9 @@ Singleton {
|
|||||||
property int modelBytes: 0
|
property int modelBytes: 0
|
||||||
|
|
||||||
property bool downloading: false
|
property bool downloading: false
|
||||||
|
// "pulling" while the server image comes down, "downloading" while the
|
||||||
|
// model does. Empty when idle. Lets the button say which half is running.
|
||||||
|
property string phase: ""
|
||||||
property int downloadedBytes: 0
|
property int downloadedBytes: 0
|
||||||
property int downloadTotalBytes: 0
|
property int downloadTotalBytes: 0
|
||||||
property string lastError: ""
|
property string lastError: ""
|
||||||
@@ -76,7 +79,12 @@ Singleton {
|
|||||||
|
|
||||||
Process {
|
Process {
|
||||||
id: downloadRun
|
id: downloadRun
|
||||||
command: [root.helper, "download"]
|
// "setup", not "download": the helper has no "download" command -- it
|
||||||
|
// has one "setup" that pulls the server image AND downloads the model,
|
||||||
|
// because being told to fetch half a gigabyte only to hear an image is
|
||||||
|
// also needed would be two trips. Calling the wrong name is why this
|
||||||
|
// button did nothing for so long.
|
||||||
|
command: [root.helper, "setup"]
|
||||||
stdout: SplitParser {
|
stdout: SplitParser {
|
||||||
// One JSON object per line, roughly once a second, so the progress
|
// One JSON object per line, roughly once a second, so the progress
|
||||||
// bar moves without the helper flooding a pipe nobody drains.
|
// bar moves without the helper flooding a pipe nobody drains.
|
||||||
@@ -87,6 +95,8 @@ Singleton {
|
|||||||
root.lastError = "The model could not be downloaded.";
|
root.lastError = "The model could not be downloaded.";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (update.state !== undefined)
|
||||||
|
root.phase = String(update.state);
|
||||||
if (update.bytes !== undefined)
|
if (update.bytes !== undefined)
|
||||||
root.downloadedBytes = update.bytes;
|
root.downloadedBytes = update.bytes;
|
||||||
if (update.total !== undefined)
|
if (update.total !== undefined)
|
||||||
@@ -99,8 +109,9 @@ Singleton {
|
|||||||
}
|
}
|
||||||
onExited: (exitCode, exitStatus) => {
|
onExited: (exitCode, exitStatus) => {
|
||||||
root.downloading = false;
|
root.downloading = false;
|
||||||
|
root.phase = "";
|
||||||
if (exitCode !== 0 && root.lastError === "")
|
if (exitCode !== 0 && root.lastError === "")
|
||||||
root.lastError = "The model could not be downloaded.";
|
root.lastError = "Dictation setup did not finish. Check that podman is installed and the network is up.";
|
||||||
root.refresh();
|
root.refresh();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -110,10 +121,13 @@ Singleton {
|
|||||||
statusRun.running = true;
|
statusRun.running = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function download(): void {
|
// Pulls the speech server image and downloads the model, with progress.
|
||||||
|
// One action because the helper's one "setup" command does both.
|
||||||
|
function setup(): void {
|
||||||
if (downloadRun.running)
|
if (downloadRun.running)
|
||||||
return;
|
return;
|
||||||
root.lastError = "";
|
root.lastError = "";
|
||||||
|
root.phase = "";
|
||||||
root.downloadedBytes = 0;
|
root.downloadedBytes = 0;
|
||||||
root.downloadTotalBytes = 0;
|
root.downloadTotalBytes = 0;
|
||||||
root.downloading = true;
|
root.downloading = true;
|
||||||
|
|||||||
Reference in New Issue
Block a user