@iamsamuelrodda/dictate 2026.5.18-1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +175 -0
- package/assets/dictate-listening.ico +0 -0
- package/assets/dictate-listening.png +0 -0
- package/assets/dictate.ico +0 -0
- package/assets/dictate.png +0 -0
- package/config/default-config.yaml +17 -0
- package/install-windows-wizard.ps1 +392 -0
- package/install-windows.ps1 +330 -0
- package/install.ps1 +87 -0
- package/install.sh +233 -0
- package/npm/dictate-lifecycle.mjs +49 -0
- package/package.json +50 -0
- package/uninstall-windows.ps1 +83 -0
- package/uninstall.ps1 +51 -0
- package/uninstall.sh +92 -0
- package/update-windows.ps1 +70 -0
- package/update.ps1 +115 -0
- package/update.sh +34 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Samuel Rodda
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# 🎙️ Dictate
|
|
2
|
+
|
|
3
|
+
Desktop dictation that types into the focused app.
|
|
4
|
+
|
|
5
|
+
`dictate` runs as a small tray app. Press your configured push-to-talk shortcut,
|
|
6
|
+
speak, and it transcribes into whatever app you are already using.
|
|
7
|
+
|
|
8
|
+
Current status: early desktop app. Linux and Windows 11 installs, tray controls,
|
|
9
|
+
startup integration, recent history, model selection, API key storage, update,
|
|
10
|
+
and uninstall paths are implemented. Signed Windows installer packaging is still
|
|
11
|
+
future work.
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
Windows 11 normal install:
|
|
16
|
+
|
|
17
|
+
```powershell
|
|
18
|
+
powershell -ExecutionPolicy Bypass -Command "iwr -useb https://cdn.jsdelivr.net/npm/@iamsamuelrodda/dictate@latest/install.ps1 | iex"
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Open **Dictate** from the Start Menu after install.
|
|
22
|
+
|
|
23
|
+
Ubuntu/Debian source install:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
./install-ubuntu.sh
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Generic Linux source install:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
./install.sh
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Open **Dictate** from the app launcher after install.
|
|
36
|
+
|
|
37
|
+
Windows source install, from the repo/source directory:
|
|
38
|
+
|
|
39
|
+
```powershell
|
|
40
|
+
powershell -ExecutionPolicy Bypass -File .\install-windows-wizard.ps1
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
The local `.ps1` installer scripts must be run from a checkout or extracted
|
|
44
|
+
source directory. They will not work from `C:\Windows\System32`.
|
|
45
|
+
|
|
46
|
+
Node/npm users can also run:
|
|
47
|
+
|
|
48
|
+
```powershell
|
|
49
|
+
npx @iamsamuelrodda/dictate install
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Workflow
|
|
53
|
+
|
|
54
|
+
```text
|
|
55
|
+
Open Dictate
|
|
56
|
+
Select Model
|
|
57
|
+
Set API key if using a hosted model
|
|
58
|
+
Set push-to-talk shortcut if desired
|
|
59
|
+
Hold shortcut, speak, release
|
|
60
|
+
Review Recent History when needed
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
By default, Dictate installs a normal app launcher entry and starts on sign-in.
|
|
64
|
+
Startup can be changed from Settings.
|
|
65
|
+
|
|
66
|
+
## Update And Uninstall
|
|
67
|
+
|
|
68
|
+
Windows installed from the hosted installer:
|
|
69
|
+
|
|
70
|
+
```powershell
|
|
71
|
+
powershell -ExecutionPolicy Bypass -Command "iwr -useb https://cdn.jsdelivr.net/npm/@iamsamuelrodda/dictate@latest/update.ps1 | iex"
|
|
72
|
+
powershell -ExecutionPolicy Bypass -Command "iwr -useb https://cdn.jsdelivr.net/npm/@iamsamuelrodda/dictate@latest/uninstall.ps1 | iex"
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Windows from source:
|
|
76
|
+
|
|
77
|
+
```powershell
|
|
78
|
+
powershell -ExecutionPolicy Bypass -File .\update-windows.ps1
|
|
79
|
+
powershell -ExecutionPolicy Bypass -File .\uninstall-windows.ps1
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Linux:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
./update.sh
|
|
86
|
+
./uninstall.sh
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Use `-RemoveUserData` on Windows or `--remove-user-data` on Linux only when you
|
|
90
|
+
also want to remove config, logs, history, and downloaded model data.
|
|
91
|
+
|
|
92
|
+
## What It Does Today
|
|
93
|
+
|
|
94
|
+
- Starts from the Windows Start Menu or Linux app launcher
|
|
95
|
+
- Runs as a tray app
|
|
96
|
+
- Types dictated text into the focused app
|
|
97
|
+
- Supports configurable push-to-talk
|
|
98
|
+
- Shows selected model/status in the app UI
|
|
99
|
+
- Supports launch on startup
|
|
100
|
+
- Stores hosted-provider API keys in the OS secret store
|
|
101
|
+
- Keeps a small Recent History for copy/paste recovery
|
|
102
|
+
- Provides installer, updater, uninstaller, and doctor paths
|
|
103
|
+
|
|
104
|
+
## Models
|
|
105
|
+
|
|
106
|
+
Supported provider defaults:
|
|
107
|
+
|
|
108
|
+
- `faster-whisper/turbo` for local transcription
|
|
109
|
+
- `openai/gpt-4o-mini-transcribe`
|
|
110
|
+
- `xai/grok-speech-to-text`
|
|
111
|
+
- `gemini/gemini-3-flash-preview`
|
|
112
|
+
|
|
113
|
+
Local transcription can use CPU or GPU where supported. Hosted providers require
|
|
114
|
+
an API key before they can be selected.
|
|
115
|
+
|
|
116
|
+
## Commands
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
dictate
|
|
120
|
+
dictate --no-tray
|
|
121
|
+
dictate --once
|
|
122
|
+
dictate --once --copy
|
|
123
|
+
dictate doctor --quick
|
|
124
|
+
dictate doctor --quick --fix
|
|
125
|
+
dictate doctor --check-model-load
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Hotword and model options are available from Settings. CLI flags still exist for
|
|
129
|
+
automation and testing:
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
dictate --stt-backend faster-whisper --model turbo
|
|
133
|
+
dictate --stt-backend openai --model gpt-4o-mini-transcribe
|
|
134
|
+
dictate --stt-backend xai --model grok-speech-to-text
|
|
135
|
+
dictate --stt-backend gemini --model gemini-3-flash-preview
|
|
136
|
+
dictate --add-hotword AcmeWidget
|
|
137
|
+
dictate --list-hotwords
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## State
|
|
141
|
+
|
|
142
|
+
User state is local:
|
|
143
|
+
|
|
144
|
+
```text
|
|
145
|
+
Linux:
|
|
146
|
+
~/.config/dictate/config.yaml
|
|
147
|
+
~/.local/share/dictate/
|
|
148
|
+
|
|
149
|
+
Windows:
|
|
150
|
+
%APPDATA%\dictate\config.yaml
|
|
151
|
+
%LOCALAPPDATA%\dictate\
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Repo defaults intentionally ship with `hotwords: []`. Hotwords are user-specific
|
|
155
|
+
and should not be packaged into the public repo default config.
|
|
156
|
+
|
|
157
|
+
## Safety
|
|
158
|
+
|
|
159
|
+
- Dictate does not intentionally write raw API keys to `config.yaml`.
|
|
160
|
+
- API keys configured in the app use the OS secret store.
|
|
161
|
+
- Dictation text can be sensitive; check logs and issue reports before sharing.
|
|
162
|
+
- Important transcriptions should be verified before relying on them.
|
|
163
|
+
- Support and maintenance are best-effort.
|
|
164
|
+
|
|
165
|
+
## Docs
|
|
166
|
+
|
|
167
|
+
- [Windows 11 support](docs/windows-11.md)
|
|
168
|
+
- [Recent History spec](docs/recent-dictation-history-spec.md)
|
|
169
|
+
- [Release/versioning](docs/release-versioning.md)
|
|
170
|
+
- [Development streams](docs/development-streams.md)
|
|
171
|
+
- [Security policy](SECURITY.md)
|
|
172
|
+
|
|
173
|
+
## License
|
|
174
|
+
|
|
175
|
+
MIT. See [LICENSE](LICENSE).
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
hotwords: []
|
|
2
|
+
push_to_talk_combo: ctrl_r
|
|
3
|
+
stt_backend: faster-whisper
|
|
4
|
+
stt_compute_type: int8
|
|
5
|
+
stt_device: auto
|
|
6
|
+
# Local Whisper uses the single supported local model:
|
|
7
|
+
# stt_model: turbo
|
|
8
|
+
# To avoid local compute, set one hosted backend:
|
|
9
|
+
# stt_backend: openai
|
|
10
|
+
# stt_model: gpt-4o-mini-transcribe
|
|
11
|
+
# openai_api_key_command: /path/to/command/that/prints/the/key
|
|
12
|
+
# stt_backend: xai
|
|
13
|
+
# stt_model: grok-speech-to-text
|
|
14
|
+
# xai_api_key_command: /path/to/command/that/prints/the/key
|
|
15
|
+
# stt_backend: gemini
|
|
16
|
+
# stt_model: gemini-3-flash-preview
|
|
17
|
+
# gemini_api_key_command: /path/to/command/that/prints/the/key
|
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
param(
|
|
2
|
+
[string]$InitialAction = "Install"
|
|
3
|
+
)
|
|
4
|
+
|
|
5
|
+
$ErrorActionPreference = "Stop"
|
|
6
|
+
$TermsUrl = "https://arcforge.au/terms"
|
|
7
|
+
$DocumentationUrl = "https://github.com/arcforgelabs/dictate#readme"
|
|
8
|
+
$HostedWindowsUpdateUrl = "https://cdn.jsdelivr.net/npm/@iamsamuelrodda/dictate@latest/update.ps1"
|
|
9
|
+
|
|
10
|
+
Add-Type -AssemblyName System.Windows.Forms
|
|
11
|
+
Add-Type -AssemblyName System.Drawing
|
|
12
|
+
|
|
13
|
+
function New-ActionRadio {
|
|
14
|
+
param(
|
|
15
|
+
[string]$Text,
|
|
16
|
+
[string]$Tag,
|
|
17
|
+
[int]$Top,
|
|
18
|
+
[bool]$Checked = $false
|
|
19
|
+
)
|
|
20
|
+
$radio = New-Object System.Windows.Forms.RadioButton
|
|
21
|
+
$radio.Text = $Text
|
|
22
|
+
$radio.Tag = $Tag
|
|
23
|
+
$radio.Left = 18
|
|
24
|
+
$radio.Top = $Top
|
|
25
|
+
$radio.Width = 430
|
|
26
|
+
$radio.Checked = $Checked
|
|
27
|
+
return $radio
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function Get-SelectedAction {
|
|
31
|
+
foreach ($control in $actionsGroup.Controls) {
|
|
32
|
+
if ($control.Checked) {
|
|
33
|
+
return [string]$control.Tag
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return "Install"
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function Append-Log {
|
|
40
|
+
param([string]$Message)
|
|
41
|
+
$logBox.AppendText($Message + [Environment]::NewLine)
|
|
42
|
+
$logBox.SelectionStart = $logBox.TextLength
|
|
43
|
+
$logBox.ScrollToCaret()
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function Open-ExternalUrl {
|
|
47
|
+
param([string]$Url)
|
|
48
|
+
Start-Process $Url
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function Sync-RunButton {
|
|
52
|
+
if ($null -ne $runButton) {
|
|
53
|
+
$runButton.Enabled = $termsCheck.Checked
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function Sync-ShortcutOptions {
|
|
58
|
+
if ($null -ne $startupCheck) {
|
|
59
|
+
if ($shortcutCheck.Checked) {
|
|
60
|
+
$startupCheck.Enabled = $true
|
|
61
|
+
} else {
|
|
62
|
+
$startupCheck.Checked = $false
|
|
63
|
+
$startupCheck.Enabled = $false
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function Get-StartMenuProgramsDir {
|
|
69
|
+
$programsDir = Join-Path $env:APPDATA "Microsoft\Windows\Start Menu\Programs"
|
|
70
|
+
if (-not $env:APPDATA) {
|
|
71
|
+
$programsDir = Join-Path $HOME "AppData\Roaming\Microsoft\Windows\Start Menu\Programs"
|
|
72
|
+
}
|
|
73
|
+
return $programsDir
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function Get-StartupShortcutPath {
|
|
77
|
+
return (Join-Path (Join-Path (Get-StartMenuProgramsDir) "Startup") "Dictate.lnk")
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function Drain-LogQueue {
|
|
81
|
+
param($Queue)
|
|
82
|
+
$line = $null
|
|
83
|
+
while ($Queue.TryDequeue([ref]$line)) {
|
|
84
|
+
Append-Log $line
|
|
85
|
+
$line = $null
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function Invoke-LoggedProcess {
|
|
90
|
+
param(
|
|
91
|
+
[string]$Label,
|
|
92
|
+
[string]$FileName,
|
|
93
|
+
[string]$ArgumentString,
|
|
94
|
+
[string]$WorkingDirectory = $PSScriptRoot
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
Append-Log "==> $Label"
|
|
98
|
+
$psi = New-Object System.Diagnostics.ProcessStartInfo
|
|
99
|
+
$psi.FileName = $FileName
|
|
100
|
+
$psi.Arguments = $ArgumentString
|
|
101
|
+
$psi.WorkingDirectory = $WorkingDirectory
|
|
102
|
+
$psi.UseShellExecute = $false
|
|
103
|
+
$psi.RedirectStandardOutput = $true
|
|
104
|
+
$psi.RedirectStandardError = $true
|
|
105
|
+
$psi.CreateNoWindow = $true
|
|
106
|
+
|
|
107
|
+
$stdoutQueue = New-Object "System.Collections.Concurrent.ConcurrentQueue[string]"
|
|
108
|
+
$stderrQueue = New-Object "System.Collections.Concurrent.ConcurrentQueue[string]"
|
|
109
|
+
$stdoutHandler = [System.Diagnostics.DataReceivedEventHandler]{
|
|
110
|
+
param($sender, $eventArgs)
|
|
111
|
+
if ($null -ne $eventArgs.Data) {
|
|
112
|
+
$stdoutQueue.Enqueue($eventArgs.Data)
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
$stderrHandler = [System.Diagnostics.DataReceivedEventHandler]{
|
|
116
|
+
param($sender, $eventArgs)
|
|
117
|
+
if ($null -ne $eventArgs.Data) {
|
|
118
|
+
$stderrQueue.Enqueue($eventArgs.Data)
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
$process = New-Object System.Diagnostics.Process
|
|
123
|
+
$process.StartInfo = $psi
|
|
124
|
+
$process.add_OutputDataReceived($stdoutHandler)
|
|
125
|
+
$process.add_ErrorDataReceived($stderrHandler)
|
|
126
|
+
try {
|
|
127
|
+
[void]$process.Start()
|
|
128
|
+
$process.BeginOutputReadLine()
|
|
129
|
+
$process.BeginErrorReadLine()
|
|
130
|
+
while (-not $process.WaitForExit(100)) {
|
|
131
|
+
Drain-LogQueue $stdoutQueue
|
|
132
|
+
Drain-LogQueue $stderrQueue
|
|
133
|
+
[System.Windows.Forms.Application]::DoEvents()
|
|
134
|
+
}
|
|
135
|
+
$process.WaitForExit()
|
|
136
|
+
Drain-LogQueue $stdoutQueue
|
|
137
|
+
Drain-LogQueue $stderrQueue
|
|
138
|
+
if ($process.ExitCode -ne 0) {
|
|
139
|
+
throw "$Label failed with exit code $($process.ExitCode)."
|
|
140
|
+
}
|
|
141
|
+
} finally {
|
|
142
|
+
$process.remove_OutputDataReceived($stdoutHandler)
|
|
143
|
+
$process.remove_ErrorDataReceived($stderrHandler)
|
|
144
|
+
$process.Dispose()
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function Invoke-Step {
|
|
149
|
+
param(
|
|
150
|
+
[string]$Label,
|
|
151
|
+
[string]$FilePath,
|
|
152
|
+
[string[]]$Arguments,
|
|
153
|
+
[string]$WorkingDirectory = $PSScriptRoot
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
$argumentString = (
|
|
157
|
+
@("-NoProfile", "-ExecutionPolicy", "Bypass", "-File", "`"$FilePath`"") + $Arguments
|
|
158
|
+
) -join " "
|
|
159
|
+
Invoke-LoggedProcess -Label $Label -FileName "powershell" -ArgumentString $argumentString -WorkingDirectory $WorkingDirectory
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function Invoke-DictateUpdate {
|
|
163
|
+
param([string[]]$Arguments)
|
|
164
|
+
|
|
165
|
+
if (Test-Path (Join-Path $PSScriptRoot ".git")) {
|
|
166
|
+
Invoke-Step "Updating Dictate" (Join-Path $PSScriptRoot "update-windows.ps1") $Arguments
|
|
167
|
+
return
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
$tempUpdater = Join-Path ([System.IO.Path]::GetTempPath()) ("dictate-update-" + [guid]::NewGuid().ToString("N") + ".ps1")
|
|
171
|
+
try {
|
|
172
|
+
Append-Log "==> Fetching current hosted updater"
|
|
173
|
+
Invoke-WebRequest -UseBasicParsing -Uri $HostedWindowsUpdateUrl -OutFile $tempUpdater
|
|
174
|
+
$workingDirectory = Split-Path -Parent $PSScriptRoot
|
|
175
|
+
Invoke-Step -Label "Updating Dictate from hosted source" -FilePath $tempUpdater -Arguments $Arguments -WorkingDirectory $workingDirectory
|
|
176
|
+
} finally {
|
|
177
|
+
Remove-Item -Force -ErrorAction SilentlyContinue -Path $tempUpdater
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function Invoke-DictateDoctorFix {
|
|
182
|
+
$dictateExe = Join-Path $PSScriptRoot ".venv\Scripts\dictate.exe"
|
|
183
|
+
if (-not (Test-Path $dictateExe)) {
|
|
184
|
+
throw "Dictate is not installed in this source folder. Run Install first."
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
Invoke-LoggedProcess "Repairing Dictate with doctor --fix" $dictateExe "doctor --quick --fix --type-backend pynput"
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function Start-SelectedAction {
|
|
191
|
+
$runButton.Enabled = $false
|
|
192
|
+
$closeButton.Enabled = $false
|
|
193
|
+
$logBox.Clear()
|
|
194
|
+
try {
|
|
195
|
+
if (-not $termsCheck.Checked) {
|
|
196
|
+
throw "Please acknowledge the Dictate terms before continuing."
|
|
197
|
+
}
|
|
198
|
+
$action = Get-SelectedAction
|
|
199
|
+
$commonArgs = @()
|
|
200
|
+
if (-not $verifyCheck.Checked) { $commonArgs += "-NoVerify" }
|
|
201
|
+
if (-not $prepareCheck.Checked) { $commonArgs += "-NoPrepareTurbo" }
|
|
202
|
+
if (-not $shortcutCheck.Checked) { $commonArgs += "-NoShortcut" }
|
|
203
|
+
if (-not $startupCheck.Checked) { $commonArgs += "-NoStartup" }
|
|
204
|
+
|
|
205
|
+
if ($action -eq "Install") {
|
|
206
|
+
Invoke-Step "Installing Dictate" (Join-Path $PSScriptRoot "install-windows.ps1") $commonArgs
|
|
207
|
+
} elseif ($action -eq "Update") {
|
|
208
|
+
$updateArgs = @($commonArgs)
|
|
209
|
+
if ($startupCheck.Checked) { $updateArgs += "-ForceStartup" }
|
|
210
|
+
Invoke-DictateUpdate $updateArgs
|
|
211
|
+
} elseif ($action -eq "Repair") {
|
|
212
|
+
Invoke-DictateDoctorFix
|
|
213
|
+
} elseif ($action -eq "Uninstall") {
|
|
214
|
+
$uninstallArgs = @()
|
|
215
|
+
if ($removeUserDataCheck.Checked) { $uninstallArgs += "-RemoveUserData" }
|
|
216
|
+
Invoke-Step "Uninstalling Dictate" (Join-Path $PSScriptRoot "uninstall-windows.ps1") $uninstallArgs
|
|
217
|
+
}
|
|
218
|
+
Append-Log ""
|
|
219
|
+
Append-Log "Done."
|
|
220
|
+
[System.Windows.Forms.MessageBox]::Show(
|
|
221
|
+
"Dictate $($action.ToLowerInvariant()) completed.",
|
|
222
|
+
"Dictate Setup",
|
|
223
|
+
[System.Windows.Forms.MessageBoxButtons]::OK,
|
|
224
|
+
[System.Windows.Forms.MessageBoxIcon]::Information
|
|
225
|
+
) | Out-Null
|
|
226
|
+
} catch {
|
|
227
|
+
Append-Log ""
|
|
228
|
+
Append-Log "ERROR: $($_.Exception.Message)"
|
|
229
|
+
[System.Windows.Forms.MessageBox]::Show(
|
|
230
|
+
$_.Exception.Message,
|
|
231
|
+
"Dictate Setup",
|
|
232
|
+
[System.Windows.Forms.MessageBoxButtons]::OK,
|
|
233
|
+
[System.Windows.Forms.MessageBoxIcon]::Error
|
|
234
|
+
) | Out-Null
|
|
235
|
+
} finally {
|
|
236
|
+
Sync-RunButton
|
|
237
|
+
$closeButton.Enabled = $true
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
$form = New-Object System.Windows.Forms.Form
|
|
242
|
+
$form.Text = "Dictate Setup"
|
|
243
|
+
$form.StartPosition = "CenterScreen"
|
|
244
|
+
$form.Width = 720
|
|
245
|
+
$form.Height = 600
|
|
246
|
+
$form.FormBorderStyle = "FixedDialog"
|
|
247
|
+
$form.MaximizeBox = $false
|
|
248
|
+
|
|
249
|
+
$title = New-Object System.Windows.Forms.Label
|
|
250
|
+
$title.Text = "Dictate Setup"
|
|
251
|
+
$title.Font = New-Object System.Drawing.Font("Segoe UI", 16, [System.Drawing.FontStyle]::Bold)
|
|
252
|
+
$title.Left = 18
|
|
253
|
+
$title.Top = 14
|
|
254
|
+
$title.Width = 660
|
|
255
|
+
$title.Height = 34
|
|
256
|
+
$form.Controls.Add($title)
|
|
257
|
+
|
|
258
|
+
$subtitle = New-Object System.Windows.Forms.Label
|
|
259
|
+
$subtitle.Text = "Install, update, repair, or remove Dictate for this Windows user."
|
|
260
|
+
$subtitle.Left = 20
|
|
261
|
+
$subtitle.Top = 52
|
|
262
|
+
$subtitle.Width = 660
|
|
263
|
+
$subtitle.Height = 24
|
|
264
|
+
$form.Controls.Add($subtitle)
|
|
265
|
+
|
|
266
|
+
$actionsGroup = New-Object System.Windows.Forms.GroupBox
|
|
267
|
+
$actionsGroup.Text = "Action"
|
|
268
|
+
$actionsGroup.Left = 18
|
|
269
|
+
$actionsGroup.Top = 88
|
|
270
|
+
$actionsGroup.Width = 660
|
|
271
|
+
$actionsGroup.Height = 140
|
|
272
|
+
$form.Controls.Add($actionsGroup)
|
|
273
|
+
|
|
274
|
+
$actionsGroup.Controls.Add((New-ActionRadio "Install Dictate" "Install" 24 ($InitialAction -eq "Install")))
|
|
275
|
+
$actionsGroup.Controls.Add((New-ActionRadio "Update Dictate" "Update" 52 ($InitialAction -eq "Update")))
|
|
276
|
+
$actionsGroup.Controls.Add((New-ActionRadio "Repair launchers and runtime checks (doctor --fix)" "Repair" 80 ($InitialAction -eq "Repair")))
|
|
277
|
+
$actionsGroup.Controls.Add((New-ActionRadio "Uninstall Dictate" "Uninstall" 108 ($InitialAction -eq "Uninstall")))
|
|
278
|
+
|
|
279
|
+
$optionsGroup = New-Object System.Windows.Forms.GroupBox
|
|
280
|
+
$optionsGroup.Text = "Options"
|
|
281
|
+
$optionsGroup.Left = 18
|
|
282
|
+
$optionsGroup.Top = 238
|
|
283
|
+
$optionsGroup.Width = 660
|
|
284
|
+
$optionsGroup.Height = 142
|
|
285
|
+
$form.Controls.Add($optionsGroup)
|
|
286
|
+
|
|
287
|
+
$startupCheck = New-Object System.Windows.Forms.CheckBox
|
|
288
|
+
$startupCheck.Text = "Launch on startup"
|
|
289
|
+
$startupCheck.Left = 18
|
|
290
|
+
$startupCheck.Top = 24
|
|
291
|
+
$startupCheck.Width = 180
|
|
292
|
+
$startupCheck.Checked = $true
|
|
293
|
+
$optionsGroup.Controls.Add($startupCheck)
|
|
294
|
+
if ($InitialAction -eq "Update") {
|
|
295
|
+
$startupCheck.Checked = Test-Path (Get-StartupShortcutPath)
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
$shortcutCheck = New-Object System.Windows.Forms.CheckBox
|
|
299
|
+
$shortcutCheck.Text = "Create Start Menu entry"
|
|
300
|
+
$shortcutCheck.Left = 220
|
|
301
|
+
$shortcutCheck.Top = 24
|
|
302
|
+
$shortcutCheck.Width = 200
|
|
303
|
+
$shortcutCheck.Checked = $true
|
|
304
|
+
$shortcutCheck.Add_CheckedChanged({ Sync-ShortcutOptions })
|
|
305
|
+
$optionsGroup.Controls.Add($shortcutCheck)
|
|
306
|
+
|
|
307
|
+
$prepareCheck = New-Object System.Windows.Forms.CheckBox
|
|
308
|
+
$prepareCheck.Text = "Prepare default local model"
|
|
309
|
+
$prepareCheck.Left = 18
|
|
310
|
+
$prepareCheck.Top = 54
|
|
311
|
+
$prepareCheck.Width = 220
|
|
312
|
+
$prepareCheck.Checked = $true
|
|
313
|
+
$optionsGroup.Controls.Add($prepareCheck)
|
|
314
|
+
|
|
315
|
+
$verifyCheck = New-Object System.Windows.Forms.CheckBox
|
|
316
|
+
$verifyCheck.Text = "Run verification"
|
|
317
|
+
$verifyCheck.Left = 260
|
|
318
|
+
$verifyCheck.Top = 54
|
|
319
|
+
$verifyCheck.Width = 160
|
|
320
|
+
$verifyCheck.Checked = $true
|
|
321
|
+
$optionsGroup.Controls.Add($verifyCheck)
|
|
322
|
+
|
|
323
|
+
$removeUserDataCheck = New-Object System.Windows.Forms.CheckBox
|
|
324
|
+
$removeUserDataCheck.Text = "Also remove user config, logs, history, and models"
|
|
325
|
+
$removeUserDataCheck.Left = 18
|
|
326
|
+
$removeUserDataCheck.Top = 78
|
|
327
|
+
$removeUserDataCheck.Width = 360
|
|
328
|
+
$removeUserDataCheck.Checked = $false
|
|
329
|
+
$optionsGroup.Controls.Add($removeUserDataCheck)
|
|
330
|
+
|
|
331
|
+
$termsCheck = New-Object System.Windows.Forms.CheckBox
|
|
332
|
+
$termsCheck.Text = "I understand Dictate has real-world risks and agree to the Arc Forge terms"
|
|
333
|
+
$termsCheck.Left = 18
|
|
334
|
+
$termsCheck.Top = 104
|
|
335
|
+
$termsCheck.Width = 500
|
|
336
|
+
$termsCheck.Checked = $false
|
|
337
|
+
$termsCheck.Add_CheckedChanged({ Sync-RunButton })
|
|
338
|
+
$optionsGroup.Controls.Add($termsCheck)
|
|
339
|
+
|
|
340
|
+
$expectationLabel = New-Object System.Windows.Forms.Label
|
|
341
|
+
$expectationLabel.Text = "Support and maintenance are best-effort. Check important output and report issues."
|
|
342
|
+
$expectationLabel.Left = 36
|
|
343
|
+
$expectationLabel.Top = 124
|
|
344
|
+
$expectationLabel.Width = 500
|
|
345
|
+
$expectationLabel.Height = 18
|
|
346
|
+
$optionsGroup.Controls.Add($expectationLabel)
|
|
347
|
+
|
|
348
|
+
$termsLink = New-Object System.Windows.Forms.LinkLabel
|
|
349
|
+
$termsLink.Text = "Terms"
|
|
350
|
+
$termsLink.Left = 548
|
|
351
|
+
$termsLink.Top = 105
|
|
352
|
+
$termsLink.Width = 52
|
|
353
|
+
$termsLink.Add_Click({ Open-ExternalUrl $TermsUrl })
|
|
354
|
+
$optionsGroup.Controls.Add($termsLink)
|
|
355
|
+
|
|
356
|
+
$docsLink = New-Object System.Windows.Forms.LinkLabel
|
|
357
|
+
$docsLink.Text = "Documentation"
|
|
358
|
+
$docsLink.Left = 548
|
|
359
|
+
$docsLink.Top = 124
|
|
360
|
+
$docsLink.Width = 120
|
|
361
|
+
$docsLink.Add_Click({ Open-ExternalUrl $DocumentationUrl })
|
|
362
|
+
$optionsGroup.Controls.Add($docsLink)
|
|
363
|
+
|
|
364
|
+
$logBox = New-Object System.Windows.Forms.TextBox
|
|
365
|
+
$logBox.Left = 18
|
|
366
|
+
$logBox.Top = 394
|
|
367
|
+
$logBox.Width = 660
|
|
368
|
+
$logBox.Height = 116
|
|
369
|
+
$logBox.Multiline = $true
|
|
370
|
+
$logBox.ScrollBars = "Vertical"
|
|
371
|
+
$logBox.ReadOnly = $true
|
|
372
|
+
$logBox.Font = New-Object System.Drawing.Font("Consolas", 9)
|
|
373
|
+
$form.Controls.Add($logBox)
|
|
374
|
+
|
|
375
|
+
$runButton = New-Object System.Windows.Forms.Button
|
|
376
|
+
$runButton.Text = "Run"
|
|
377
|
+
$runButton.Left = 496
|
|
378
|
+
$runButton.Top = 524
|
|
379
|
+
$runButton.Width = 86
|
|
380
|
+
$runButton.Enabled = $false
|
|
381
|
+
$runButton.Add_Click({ Start-SelectedAction })
|
|
382
|
+
$form.Controls.Add($runButton)
|
|
383
|
+
|
|
384
|
+
$closeButton = New-Object System.Windows.Forms.Button
|
|
385
|
+
$closeButton.Text = "Close"
|
|
386
|
+
$closeButton.Left = 592
|
|
387
|
+
$closeButton.Top = 524
|
|
388
|
+
$closeButton.Width = 86
|
|
389
|
+
$closeButton.Add_Click({ $form.Close() })
|
|
390
|
+
$form.Controls.Add($closeButton)
|
|
391
|
+
|
|
392
|
+
[void]$form.ShowDialog()
|