opencode-voice-dictation 0.6.2 → 0.7.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/README.md +79 -31
- package/dist/gui-experimental.cjs +8 -8
- package/docs/whispercpp.md +143 -0
- package/docs/whisperx.md +3 -2
- package/package.json +4 -2
- package/src/gui/definition.ts +69 -11
- package/src/gui/settings.tsx +186 -17
- package/src/gui/window.tsx +59 -20
- package/src/index.ts +34 -4
- package/src/rpc.ts +80 -0
- package/src/whispercpp/server.ts +277 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# Dictado con whisper.cpp nativo (Linux)
|
|
2
|
+
|
|
3
|
+
Guía para configurar el motor **whisper.cpp** del plugin en Ubuntu y otras
|
|
4
|
+
distros Linux. Transcribe **audio a texto** en la CPU, sin WSL, sin Python y sin
|
|
5
|
+
GPU.
|
|
6
|
+
|
|
7
|
+
> Es el motor por defecto en Linux. En Windows no está disponible: usa
|
|
8
|
+
> `Windows Voice Typing (Win+H)`, `System.Speech` o
|
|
9
|
+
> [WhisperX](whisperx.md).
|
|
10
|
+
|
|
11
|
+
## Requisitos
|
|
12
|
+
|
|
13
|
+
- **OpenCode Desktop** en Linux (el plugin GUI no se carga en la consola/TUI).
|
|
14
|
+
- **ffmpeg** en el `PATH`:
|
|
15
|
+
```bash
|
|
16
|
+
sudo apt update && sudo apt install -y ffmpeg
|
|
17
|
+
ffmpeg -version
|
|
18
|
+
```
|
|
19
|
+
- **glibc** compatible con el binario oficial: Ubuntu 22.04 o posterior. En
|
|
20
|
+
distros muy antiguas habría que compilar `whisper.cpp` a mano.
|
|
21
|
+
- **Internet la primera vez**: ~10 MB de binario y 57–180 MB de modelo.
|
|
22
|
+
- Micrófono accesible por la app.
|
|
23
|
+
|
|
24
|
+
No hace falta Python, `cmake` ni GPU.
|
|
25
|
+
|
|
26
|
+
## Instalación desde el plugin
|
|
27
|
+
|
|
28
|
+
1. Instala el plugin (`opencode plugin add opencode-voice-dictation`) y reinicia
|
|
29
|
+
OpenCode Desktop.
|
|
30
|
+
2. Abre **Ajustes → Dictado por voz**. En Linux el motor es
|
|
31
|
+
`whisper.cpp (local, nativo)`.
|
|
32
|
+
3. Elige el modelo:
|
|
33
|
+
- `base-q5_1` (57 MB): el más ligero; recomendado para portátiles modestos.
|
|
34
|
+
- `small-q5_1` (~180 MB): más preciso, consume más CPU y memoria.
|
|
35
|
+
4. Pulsa **Descargar motor**. Se bajan el binario oficial y el modelo.
|
|
36
|
+
5. Pulsa **Comprobar** para verificar que están instalados.
|
|
37
|
+
6. Pulsa la píldora del micrófono, habla y vuelve a pulsarla: el texto se
|
|
38
|
+
inserta en el prompt.
|
|
39
|
+
|
|
40
|
+
La descarga se guarda en:
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
~/.local/share/opencode-voice-dictation/whisper.cpp/
|
|
44
|
+
├── whisper-bin-ubuntu-x64/ # o arm64
|
|
45
|
+
│ ├── whisper-server
|
|
46
|
+
│ └── lib*.so
|
|
47
|
+
├── ggml-base-q5_1.bin
|
|
48
|
+
└── ggml-small-q5_1.bin # si lo descargaste
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Instalación manual (opcional)
|
|
52
|
+
|
|
53
|
+
Si prefieres preparar los archivos a mano:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
mkdir -p ~/.local/share/opencode-voice-dictation/whisper.cpp
|
|
57
|
+
cd ~/.local/share/opencode-voice-dictation/whisper.cpp
|
|
58
|
+
|
|
59
|
+
curl -L -o whisper-bin.tar.gz \
|
|
60
|
+
https://github.com/ggml-org/whisper.cpp/releases/download/v1.9.2/whisper-bin-ubuntu-x64.tar.gz
|
|
61
|
+
tar xzf whisper-bin.tar.gz
|
|
62
|
+
rm whisper-bin.tar.gz
|
|
63
|
+
|
|
64
|
+
curl -L -O \
|
|
65
|
+
https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-base-q5_1.bin
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
El plugin detecta el binario y el modelo en esa ruta; no necesitas configurar
|
|
69
|
+
nada más.
|
|
70
|
+
|
|
71
|
+
## Cómo funciona
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
Ventana (OpenCode Desktop)
|
|
75
|
+
getUserMedia + MediaRecorder → WebM/Opus base64
|
|
76
|
+
│ RPC whisperCppTranscribe
|
|
77
|
+
▼
|
|
78
|
+
Servidor del plugin
|
|
79
|
+
escribe /tmp/opencode-voice-dictation/whispercpp-*.webm
|
|
80
|
+
ffmpeg → WAV 16 kHz mono
|
|
81
|
+
POST http://127.0.0.1:<puerto>/inference
|
|
82
|
+
▼
|
|
83
|
+
whisper-server (proceso persistente)
|
|
84
|
+
modelo cargado una vez; responde {"text": "..."}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
- `whisper-server` se arranca la primera vez y se mantiene vivo. Cargar el
|
|
88
|
+
modelo es lo lento; las frases siguientes son rápidas.
|
|
89
|
+
- Si cambias de modelo o de idioma, el servidor se reinicia para aplicar el
|
|
90
|
+
cambio.
|
|
91
|
+
- El puerto se elige al azar entre 18081 y 18580 y solo escucha en
|
|
92
|
+
`127.0.0.1`.
|
|
93
|
+
- El audio temporal se borra después de cada transcripción.
|
|
94
|
+
|
|
95
|
+
## Ajustes
|
|
96
|
+
|
|
97
|
+
| Ajuste | Por defecto | Qué hace |
|
|
98
|
+
|---|---|---|
|
|
99
|
+
| Motor de dictado | `whisper.cpp` en Linux | Único motor disponible en Linux. |
|
|
100
|
+
| Modelo | `base-q5_1` | `base-q5_1` (57 MB) o `small-q5_1` (~180 MB). |
|
|
101
|
+
| Idioma | Automático | Autodetección o `es`, `en`, `pt`. |
|
|
102
|
+
| Descargar motor | — | Baja el binario y el modelo seleccionado. |
|
|
103
|
+
| Comprobar whisper.cpp | — | Indica si el servidor y el modelo están instalados. |
|
|
104
|
+
|
|
105
|
+
## Rendimiento orientativo
|
|
106
|
+
|
|
107
|
+
Depende de la CPU. Como referencia con `base-q5_1` y 4 hilos:
|
|
108
|
+
|
|
109
|
+
| Equipo | Una frase de 5–10 s |
|
|
110
|
+
|---|---|
|
|
111
|
+
| Portátil moderno (4–8 núcleos) | ~1–3 s |
|
|
112
|
+
| Portátil modesto (2 núcleos) | ~4–10 s |
|
|
113
|
+
|
|
114
|
+
`small-q5_1` mejora la precisión y multiplica el tiempo por ~2–3. Si el equipo
|
|
115
|
+
va justo, quédate en `base-q5_1`.
|
|
116
|
+
|
|
117
|
+
## Solución de problemas
|
|
118
|
+
|
|
119
|
+
| Síntoma | Causa / solución |
|
|
120
|
+
|---|---|
|
|
121
|
+
| «Whisper.cpp is not installed» | Pulsa **Descargar motor** en Ajustes. |
|
|
122
|
+
| La descarga falla | Revisa internet, proxy o firewall hacia GitHub y Hugging Face. |
|
|
123
|
+
| `ffmpeg` no encontrado | `sudo apt install ffmpeg` y comprueba `ffmpeg -version`. |
|
|
124
|
+
| El binario no arranca | `glibc` antigua o arquitectura distinta; compila `whisper.cpp` para tu sistema o usa Ubuntu 22.04+. |
|
|
125
|
+
| Se queda en «Transcribiendo…» | Mira el toast y el log del servidor de OpenCode; suele ser `ffmpeg` o el binario. |
|
|
126
|
+
| El RPC devuelve 400 | El plugin server no cargó: Ajustes → Extensiones → Plugins → **Reload**. |
|
|
127
|
+
| Tarda mucho | Usa `base-q5_1`; el primer dictado carga el modelo. |
|
|
128
|
+
| No hay píldora | La API GUI solo existe en OpenCode Desktop, no en la consola/TUI. |
|
|
129
|
+
|
|
130
|
+
## Privacidad
|
|
131
|
+
|
|
132
|
+
El audio se escribe en `/tmp`, se convierte en local y se transcribe en tu CPU.
|
|
133
|
+
No se envía a ningún servicio. Solo la primera descarga usa la red
|
|
134
|
+
(GitHub para el binario y Hugging Face para el modelo).
|
|
135
|
+
|
|
136
|
+
## Desktop y consola
|
|
137
|
+
|
|
138
|
+
- **OpenCode Desktop**: carga `./gui-experimental`, así que tiene píldora,
|
|
139
|
+
atajo `Ctrl+Shift+M`, `/dictado` y ajustes. whisper.cpp funciona completo.
|
|
140
|
+
- **OpenCode consola/TUI**: no carga la parte GUI. La parte servidor (RPC)
|
|
141
|
+
existe, pero no hay micrófono ni píldora. Para dictar en la consola haría
|
|
142
|
+
falta una parte `./tui` que grabara con `arecord`/`ffmpeg`; es un desarrollo
|
|
143
|
+
aparte y todavía no está incluido.
|
package/docs/whisperx.md
CHANGED
|
@@ -6,7 +6,8 @@ de lectura.
|
|
|
6
6
|
|
|
7
7
|
> Este motor es **avanzado**. Para un usuario sin WSL, Python y GPU NVIDIA, usa
|
|
8
8
|
> `Windows Voice Typing (Win+H)` o un motor en la nube. La integración con
|
|
9
|
-
> `whisper.cpp` nativo (sin WSL) está
|
|
9
|
+
> `whisper.cpp` nativo (sin WSL) ya está disponible para Linux; ver
|
|
10
|
+
> [la guía de whisper.cpp](whispercpp.md).
|
|
10
11
|
|
|
11
12
|
## Requisitos
|
|
12
13
|
|
|
@@ -164,7 +165,7 @@ La idea es añadir más motores de dictado con la misma interfaz:
|
|
|
164
165
|
| Windows Voice Typing (Win+H) | Disponible | Cero configuración, online |
|
|
165
166
|
| System.Speech | Disponible | Offline, calidad antigua |
|
|
166
167
|
| WhisperX local (WSL) | Disponible | Avanzado, NVIDIA + WSL |
|
|
167
|
-
| `whisper.cpp` nativo |
|
|
168
|
+
| `whisper.cpp` nativo | Disponible | Linux, sin WSL ni Python; ver [la guía](whispercpp.md) |
|
|
168
169
|
| Google Cloud Speech-to-Text | Planeado | API key, online |
|
|
169
170
|
| OpenAI (`gpt-4o-transcribe` / `whisper-1`) | Planeado | API key, online |
|
|
170
171
|
| Deepgram / AssemblyAI | Planeado | API key, online |
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-voice-dictation",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Offline voice dictation and read-aloud for OpenCode Desktop on Windows",
|
|
3
|
+
"version": "0.7.1",
|
|
4
|
+
"description": "Offline voice dictation and read-aloud for OpenCode Desktop on Windows and Linux",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Fran Carlos <francarlos803@gmail.com>",
|
|
7
7
|
"homepage": "https://github.com/evious/opencode-voice-dictation#readme",
|
|
@@ -20,6 +20,8 @@
|
|
|
20
20
|
"speech-to-text",
|
|
21
21
|
"text-to-speech",
|
|
22
22
|
"windows",
|
|
23
|
+
"linux",
|
|
24
|
+
"whisper.cpp",
|
|
23
25
|
"offline"
|
|
24
26
|
],
|
|
25
27
|
"type": "module",
|
package/src/gui/definition.ts
CHANGED
|
@@ -4,12 +4,12 @@ import { Plugin, Store } from "@opencode/plugin/gui"
|
|
|
4
4
|
/**
|
|
5
5
|
* Declarations read by the desktop app before either setup runs.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* Windows uses Win+H, System.Speech and WhisperX. Linux uses whisper.cpp
|
|
8
|
+
* natively; the window reads the server capabilities and offers only the
|
|
9
|
+
* engines that exist on each OS.
|
|
10
10
|
*/
|
|
11
11
|
export const definition = Plugin.define({
|
|
12
|
-
os: ["windows"],
|
|
12
|
+
os: ["windows", "linux"],
|
|
13
13
|
stores: {
|
|
14
14
|
prefs: Store.global(
|
|
15
15
|
Schema.Struct({
|
|
@@ -67,6 +67,16 @@ export const definition = Plugin.define({
|
|
|
67
67
|
language: "",
|
|
68
68
|
},
|
|
69
69
|
),
|
|
70
|
+
whispercpp: Store.global(
|
|
71
|
+
Schema.Struct({
|
|
72
|
+
model: Schema.String,
|
|
73
|
+
language: Schema.String,
|
|
74
|
+
}),
|
|
75
|
+
{
|
|
76
|
+
model: "base-q5_1",
|
|
77
|
+
language: "auto",
|
|
78
|
+
},
|
|
79
|
+
),
|
|
70
80
|
},
|
|
71
81
|
i18n: {
|
|
72
82
|
en: {
|
|
@@ -90,13 +100,14 @@ export const definition = Plugin.define({
|
|
|
90
100
|
"error.unknown": "Dictation failed ({code}).",
|
|
91
101
|
"toast.discarded": "Dictation discarded: the active session changed.",
|
|
92
102
|
"settings.title": "Voice dictation",
|
|
93
|
-
"settings.description": "Offline dictation
|
|
103
|
+
"settings.description": "Offline dictation on Windows and Linux. The text is inserted into the prompt; it is never sent automatically.",
|
|
94
104
|
"settings.section": "Dictation",
|
|
95
105
|
"settings.engine": "Dictation engine",
|
|
96
|
-
"settings.engine.description": "Windows
|
|
106
|
+
"settings.engine.description": "Windows offers Win+H and System.Speech; Linux uses whisper.cpp locally.",
|
|
97
107
|
"engine.windows": "Windows Voice Typing (Win+H)",
|
|
98
108
|
"engine.system": "System.Speech (offline)",
|
|
99
109
|
"engine.whisper": "WhisperX local (WSL)",
|
|
110
|
+
"engine.whispercpp": "whisper.cpp (local, native)",
|
|
100
111
|
"pill.recording": "Recording…",
|
|
101
112
|
"pill.transcribing": "Transcribing…",
|
|
102
113
|
"pill.whisper": "Record and transcribe with WhisperX",
|
|
@@ -115,6 +126,29 @@ export const definition = Plugin.define({
|
|
|
115
126
|
"whisper.test.ready": "WhisperX ready: {model} ({device})",
|
|
116
127
|
"whisper.test.error": "WhisperX is not ready: {error}",
|
|
117
128
|
"whisper.empty": "WhisperX returned no text.",
|
|
129
|
+
"pill.whispercpp": "Record and transcribe with whisper.cpp",
|
|
130
|
+
"error.whispercpp": "whisper.cpp could not transcribe the audio.",
|
|
131
|
+
"whispercpp.section": "whisper.cpp (local, native)",
|
|
132
|
+
"whispercpp.description": "Runs on the CPU without WSL or Python. The first time you need to download the server (~10 MB) and the model.",
|
|
133
|
+
"whispercpp.model": "Model",
|
|
134
|
+
"whispercpp.model.description": "base-q5_1 is the lightest model (57 MB) and works on modest laptops. small-q5_1 is more accurate but uses more CPU and memory.",
|
|
135
|
+
"whispercpp.language": "Language",
|
|
136
|
+
"whispercpp.language.description": "Automatic detects the language of each recording.",
|
|
137
|
+
"whispercpp.language.auto": "Automatic",
|
|
138
|
+
"whispercpp.install": "Download engine",
|
|
139
|
+
"whispercpp.install.description": "Downloads the official whisper.cpp server and the selected model to ~/.local/share/opencode-voice-dictation. Needs internet once.",
|
|
140
|
+
"whispercpp.install.run": "Download",
|
|
141
|
+
"whispercpp.install.busy": "Downloading…",
|
|
142
|
+
"whispercpp.install.ok": "whisper.cpp installed: {model}",
|
|
143
|
+
"whispercpp.install.error": "Could not install whisper.cpp: {error}",
|
|
144
|
+
"whispercpp.test": "Check whisper.cpp",
|
|
145
|
+
"whispercpp.test.description": "Reports whether the server and the selected model are installed.",
|
|
146
|
+
"whispercpp.test.run": "Check",
|
|
147
|
+
"whispercpp.test.ready": "whisper.cpp ready: {model}",
|
|
148
|
+
"whispercpp.test.missing": "whisper.cpp is not installed. Download the engine first.",
|
|
149
|
+
"whispercpp.test.error": "whisper.cpp is not ready: {error}",
|
|
150
|
+
"whispercpp.empty": "whisper.cpp returned no text.",
|
|
151
|
+
"whispercpp.linux": "whisper.cpp is only available on Linux. On Windows use Win+H, System.Speech or WhisperX.",
|
|
118
152
|
"settings.winh": "Test Win+H",
|
|
119
153
|
"settings.winh.description": "Sends the Windows voice typing shortcut. If the floating bar appears, it works.",
|
|
120
154
|
"settings.winh.run": "Test Win+H",
|
|
@@ -152,7 +186,7 @@ export const definition = Plugin.define({
|
|
|
152
186
|
"read.noSession": "Open a conversation to read its last message.",
|
|
153
187
|
"read.empty": "The last message has no text to read.",
|
|
154
188
|
"read.error": "The last message could not be read.",
|
|
155
|
-
"read.noVoices": "
|
|
189
|
+
"read.noVoices": "The system has no voices installed for reading.",
|
|
156
190
|
"speech.section": "Read aloud",
|
|
157
191
|
"speech.engine": "Voice engine",
|
|
158
192
|
"speech.engine.description": "Edge neural sounds much more natural but needs internet. System uses the installed Windows voices, offline.",
|
|
@@ -196,13 +230,14 @@ export const definition = Plugin.define({
|
|
|
196
230
|
"error.unknown": "El dictado falló ({code}).",
|
|
197
231
|
"toast.discarded": "Dictado descartado: la sesión activa cambió.",
|
|
198
232
|
"settings.title": "Dictado por voz",
|
|
199
|
-
"settings.description": "Dictado offline
|
|
233
|
+
"settings.description": "Dictado offline en Windows y Linux. El texto se inserta en el prompt; nunca se envía solo.",
|
|
200
234
|
"settings.section": "Dictado",
|
|
201
235
|
"settings.engine": "Motor de dictado",
|
|
202
|
-
"settings.engine.description": "Windows
|
|
236
|
+
"settings.engine.description": "Windows ofrece Win+H y System.Speech; Linux usa whisper.cpp en local.",
|
|
203
237
|
"engine.windows": "Windows Voice Typing (Win+H)",
|
|
204
238
|
"engine.system": "System.Speech (offline)",
|
|
205
239
|
"engine.whisper": "WhisperX local (WSL)",
|
|
240
|
+
"engine.whispercpp": "whisper.cpp (local, nativo)",
|
|
206
241
|
"pill.recording": "Grabando…",
|
|
207
242
|
"pill.transcribing": "Transcribiendo…",
|
|
208
243
|
"pill.whisper": "Grabar y transcribir con WhisperX",
|
|
@@ -221,6 +256,29 @@ export const definition = Plugin.define({
|
|
|
221
256
|
"whisper.test.ready": "WhisperX listo: {model} ({device})",
|
|
222
257
|
"whisper.test.error": "WhisperX no está listo: {error}",
|
|
223
258
|
"whisper.empty": "WhisperX no devolvió texto.",
|
|
259
|
+
"pill.whispercpp": "Grabar y transcribir con whisper.cpp",
|
|
260
|
+
"error.whispercpp": "whisper.cpp no pudo transcribir el audio.",
|
|
261
|
+
"whispercpp.section": "whisper.cpp (local, nativo)",
|
|
262
|
+
"whispercpp.description": "Funciona en CPU sin WSL ni Python. La primera vez hay que descargar el servidor (~10 MB) y el modelo.",
|
|
263
|
+
"whispercpp.model": "Modelo",
|
|
264
|
+
"whispercpp.model.description": "base-q5_1 es el más ligero (57 MB) y va bien en portátiles modestos. small-q5_1 es más preciso pero consume más CPU y memoria.",
|
|
265
|
+
"whispercpp.language": "Idioma",
|
|
266
|
+
"whispercpp.language.description": "Automático detecta el idioma de cada grabación.",
|
|
267
|
+
"whispercpp.language.auto": "Automático",
|
|
268
|
+
"whispercpp.install": "Descargar motor",
|
|
269
|
+
"whispercpp.install.description": "Descarga el servidor oficial de whisper.cpp y el modelo elegido en ~/.local/share/opencode-voice-dictation. Solo necesita internet una vez.",
|
|
270
|
+
"whispercpp.install.run": "Descargar",
|
|
271
|
+
"whispercpp.install.busy": "Descargando…",
|
|
272
|
+
"whispercpp.install.ok": "whisper.cpp instalado: {model}",
|
|
273
|
+
"whispercpp.install.error": "No se pudo instalar whisper.cpp: {error}",
|
|
274
|
+
"whispercpp.test": "Comprobar whisper.cpp",
|
|
275
|
+
"whispercpp.test.description": "Indica si el servidor y el modelo elegido están instalados.",
|
|
276
|
+
"whispercpp.test.run": "Comprobar",
|
|
277
|
+
"whispercpp.test.ready": "whisper.cpp listo: {model}",
|
|
278
|
+
"whispercpp.test.missing": "whisper.cpp no está instalado. Descarga el motor primero.",
|
|
279
|
+
"whispercpp.test.error": "whisper.cpp no está listo: {error}",
|
|
280
|
+
"whispercpp.empty": "whisper.cpp no devolvió texto.",
|
|
281
|
+
"whispercpp.linux": "whisper.cpp solo está disponible en Linux. En Windows usa Win+H, System.Speech o WhisperX.",
|
|
224
282
|
"settings.winh": "Probar Win+H",
|
|
225
283
|
"settings.winh.description": "Envía el atajo del dictado de Windows. Si aparece la barra flotante, funciona.",
|
|
226
284
|
"settings.winh.run": "Probar Win+H",
|
|
@@ -257,10 +315,10 @@ export const definition = Plugin.define({
|
|
|
257
315
|
"read.noSession": "Abre una conversación para leer su último mensaje.",
|
|
258
316
|
"read.empty": "El último mensaje no tiene texto para leer.",
|
|
259
317
|
"read.error": "No se pudo leer el último mensaje.",
|
|
260
|
-
"read.noVoices": "
|
|
318
|
+
"read.noVoices": "El sistema no tiene voces instaladas para la lectura.",
|
|
261
319
|
"speech.section": "Lectura en voz alta",
|
|
262
320
|
"speech.engine": "Motor de voz",
|
|
263
|
-
"speech.engine.description": "Edge neuronal suena mucho más natural pero necesita internet. Sistema usa las voces instaladas
|
|
321
|
+
"speech.engine.description": "Edge neuronal suena mucho más natural pero necesita internet. Sistema usa las voces instaladas del equipo, sin conexión.",
|
|
264
322
|
"speech.engine.edge": "Edge neuronal (online)",
|
|
265
323
|
"speech.engine.system": "Sistema (offline)",
|
|
266
324
|
"error.edge": "No se pudo usar la voz de Edge. Revisa la conexión o cambia a la voz del sistema.",
|
package/src/gui/settings.tsx
CHANGED
|
@@ -31,24 +31,57 @@ export function VoiceSettings(props: { speaker: Speaker }) {
|
|
|
31
31
|
const ttsPrefs = ctx.stores.tts
|
|
32
32
|
const enginePrefs = ctx.stores.engine
|
|
33
33
|
const whisperPrefs = ctx.stores.whisper
|
|
34
|
+
const whisperCppPrefs = ctx.stores.whispercpp
|
|
34
35
|
const dictation = ctx.main(Dictation)
|
|
35
36
|
const voiceTyping = ctx.main(VoiceTyping)
|
|
36
37
|
|
|
37
38
|
const [locales, setLocales] = createSignal<readonly string[]>([])
|
|
38
39
|
const [busy, setBusy] = createSignal(false)
|
|
39
40
|
const [whisperBusy, setWhisperBusy] = createSignal(false)
|
|
41
|
+
const [whisperCppBusy, setWhisperCppBusy] = createSignal(false)
|
|
42
|
+
const [whisperCppCheckBusy, setWhisperCppCheckBusy] = createSignal(false)
|
|
43
|
+
const [platform, setPlatform] = createSignal("")
|
|
40
44
|
|
|
41
45
|
onMount(() => {
|
|
42
46
|
props.speaker.setConfig({ engine: ttsPrefs.value.engine, voice: ttsPrefs.value.voice })
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
+
|
|
48
|
+
const loadLocales = () => {
|
|
49
|
+
void dictation
|
|
50
|
+
.diagnose()
|
|
51
|
+
.then((result) => {
|
|
52
|
+
if (result.locales.length > 0) setLocales(result.locales)
|
|
53
|
+
})
|
|
54
|
+
.catch(() => {})
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
void ctx.client
|
|
58
|
+
.rpc(VoiceRpc)
|
|
59
|
+
.capabilities({})
|
|
60
|
+
.then((value) => {
|
|
61
|
+
const caps = value as { platform: string; engines: string[] }
|
|
62
|
+
setPlatform(caps.platform)
|
|
63
|
+
if (!caps.engines.includes(enginePrefs.value.mode) && caps.engines.length > 0) {
|
|
64
|
+
patchEngine(caps.engines[0] ?? "windows")
|
|
65
|
+
}
|
|
66
|
+
if (caps.platform !== "linux") loadLocales()
|
|
47
67
|
})
|
|
48
|
-
.catch(() =>
|
|
68
|
+
.catch(() => loadLocales())
|
|
69
|
+
|
|
49
70
|
void props.speaker.refreshVoices()
|
|
50
71
|
})
|
|
51
72
|
|
|
73
|
+
const engineOptions = () => {
|
|
74
|
+
if (platform() === "linux") {
|
|
75
|
+
return [{ value: "whispercpp", label: ctx.t("engine.whispercpp") }]
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return [
|
|
79
|
+
{ value: "windows", label: ctx.t("engine.windows") },
|
|
80
|
+
{ value: "system", label: ctx.t("engine.system") },
|
|
81
|
+
{ value: "whisper", label: ctx.t("engine.whisper") },
|
|
82
|
+
]
|
|
83
|
+
}
|
|
84
|
+
|
|
52
85
|
const localeOptions = () => {
|
|
53
86
|
const list = locales()
|
|
54
87
|
const current = prefs.value.locale
|
|
@@ -107,6 +140,88 @@ export function VoiceSettings(props: { speaker: Speaker }) {
|
|
|
107
140
|
})
|
|
108
141
|
}
|
|
109
142
|
|
|
143
|
+
const patchWhisperCpp = (change: { model?: string; language?: string }) => {
|
|
144
|
+
whisperCppPrefs.update((draft) => {
|
|
145
|
+
if (change.model !== undefined) draft.model = change.model
|
|
146
|
+
if (change.language !== undefined) draft.language = change.language
|
|
147
|
+
})
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const installWhisperCpp = async () => {
|
|
151
|
+
setWhisperCppBusy(true)
|
|
152
|
+
try {
|
|
153
|
+
const result = (await ctx.client.rpc(VoiceRpc).whisperCppInstall({
|
|
154
|
+
model: whisperCppPrefs.value.model,
|
|
155
|
+
})) as { ok: boolean; model: string; error: string }
|
|
156
|
+
|
|
157
|
+
if (result.ok) {
|
|
158
|
+
ctx.toast.show({
|
|
159
|
+
variant: "success",
|
|
160
|
+
title: ctx.t("pill.title"),
|
|
161
|
+
description: ctx.t("whispercpp.install.ok", { model: result.model }),
|
|
162
|
+
})
|
|
163
|
+
} else {
|
|
164
|
+
ctx.toast.show({
|
|
165
|
+
variant: "error",
|
|
166
|
+
title: ctx.t("pill.title"),
|
|
167
|
+
description: ctx.t("whispercpp.install.error", { error: result.error || "?" }),
|
|
168
|
+
persistent: true,
|
|
169
|
+
})
|
|
170
|
+
}
|
|
171
|
+
} catch (cause) {
|
|
172
|
+
ctx.toast.show({
|
|
173
|
+
variant: "error",
|
|
174
|
+
title: ctx.t("pill.title"),
|
|
175
|
+
description: ctx.t("whispercpp.install.error", {
|
|
176
|
+
error: cause instanceof Error ? cause.message : String(cause),
|
|
177
|
+
}),
|
|
178
|
+
persistent: true,
|
|
179
|
+
})
|
|
180
|
+
} finally {
|
|
181
|
+
setWhisperCppBusy(false)
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const checkWhisperCpp = async () => {
|
|
186
|
+
setWhisperCppCheckBusy(true)
|
|
187
|
+
try {
|
|
188
|
+
const status = (await ctx.client.rpc(VoiceRpc).whisperCppStatus({
|
|
189
|
+
model: whisperCppPrefs.value.model,
|
|
190
|
+
})) as { installed: boolean; ready: boolean; model: string; error: string }
|
|
191
|
+
|
|
192
|
+
if (status.error) {
|
|
193
|
+
ctx.toast.show({
|
|
194
|
+
variant: "error",
|
|
195
|
+
title: ctx.t("pill.title"),
|
|
196
|
+
description: ctx.t("whispercpp.test.error", { error: status.error }),
|
|
197
|
+
persistent: true,
|
|
198
|
+
})
|
|
199
|
+
} else if (!status.installed) {
|
|
200
|
+
ctx.toast.show({
|
|
201
|
+
variant: "default",
|
|
202
|
+
title: ctx.t("pill.title"),
|
|
203
|
+
description: ctx.t("whispercpp.test.missing"),
|
|
204
|
+
})
|
|
205
|
+
} else {
|
|
206
|
+
ctx.toast.show({
|
|
207
|
+
variant: "success",
|
|
208
|
+
title: ctx.t("pill.title"),
|
|
209
|
+
description: ctx.t("whispercpp.test.ready", { model: status.model }),
|
|
210
|
+
})
|
|
211
|
+
}
|
|
212
|
+
} catch (cause) {
|
|
213
|
+
ctx.toast.show({
|
|
214
|
+
variant: "error",
|
|
215
|
+
title: ctx.t("pill.title"),
|
|
216
|
+
description: ctx.t("whispercpp.test.error", {
|
|
217
|
+
error: cause instanceof Error ? cause.message : String(cause),
|
|
218
|
+
}),
|
|
219
|
+
})
|
|
220
|
+
} finally {
|
|
221
|
+
setWhisperCppCheckBusy(false)
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
110
225
|
const testWhisper = async () => {
|
|
111
226
|
setWhisperBusy(true)
|
|
112
227
|
try {
|
|
@@ -232,11 +347,7 @@ export function VoiceSettings(props: { speaker: Speaker }) {
|
|
|
232
347
|
<ui.Select
|
|
233
348
|
label={ctx.t("settings.engine")}
|
|
234
349
|
value={enginePrefs.value.mode}
|
|
235
|
-
options={
|
|
236
|
-
{ value: "windows", label: ctx.t("engine.windows") },
|
|
237
|
-
{ value: "system", label: ctx.t("engine.system") },
|
|
238
|
-
{ value: "whisper", label: ctx.t("engine.whisper") },
|
|
239
|
-
]}
|
|
350
|
+
options={engineOptions()}
|
|
240
351
|
onChange={(value) => patchEngine(value)}
|
|
241
352
|
/>
|
|
242
353
|
</ui.SettingsRow>
|
|
@@ -310,6 +421,62 @@ export function VoiceSettings(props: { speaker: Speaker }) {
|
|
|
310
421
|
</ui.SettingsRow>
|
|
311
422
|
</Show>
|
|
312
423
|
|
|
424
|
+
<Show when={enginePrefs.value.mode === "whispercpp"}>
|
|
425
|
+
<ui.SettingsRow
|
|
426
|
+
id="whispercpp-model"
|
|
427
|
+
title={ctx.t("whispercpp.model")}
|
|
428
|
+
description={ctx.t("whispercpp.model.description")}
|
|
429
|
+
>
|
|
430
|
+
<ui.Select
|
|
431
|
+
label={ctx.t("whispercpp.model")}
|
|
432
|
+
value={whisperCppPrefs.value.model}
|
|
433
|
+
options={[
|
|
434
|
+
{ value: "base-q5_1", label: "base-q5_1 · 57 MB" },
|
|
435
|
+
{ value: "small-q5_1", label: "small-q5_1 · 180 MB" },
|
|
436
|
+
]}
|
|
437
|
+
onChange={(value) => patchWhisperCpp({ model: value })}
|
|
438
|
+
/>
|
|
439
|
+
</ui.SettingsRow>
|
|
440
|
+
|
|
441
|
+
<ui.SettingsRow
|
|
442
|
+
id="whispercpp-language"
|
|
443
|
+
title={ctx.t("whispercpp.language")}
|
|
444
|
+
description={ctx.t("whispercpp.language.description")}
|
|
445
|
+
>
|
|
446
|
+
<ui.Select
|
|
447
|
+
label={ctx.t("whispercpp.language")}
|
|
448
|
+
value={whisperCppPrefs.value.language}
|
|
449
|
+
options={[
|
|
450
|
+
{ value: "auto", label: ctx.t("whispercpp.language.auto") },
|
|
451
|
+
{ value: "es", label: "Español" },
|
|
452
|
+
{ value: "en", label: "English" },
|
|
453
|
+
{ value: "pt", label: "Português" },
|
|
454
|
+
]}
|
|
455
|
+
onChange={(value) => patchWhisperCpp({ language: value })}
|
|
456
|
+
/>
|
|
457
|
+
</ui.SettingsRow>
|
|
458
|
+
|
|
459
|
+
<ui.SettingsRow
|
|
460
|
+
id="whispercpp-install"
|
|
461
|
+
title={ctx.t("whispercpp.install")}
|
|
462
|
+
description={ctx.t("whispercpp.install.description")}
|
|
463
|
+
>
|
|
464
|
+
<ui.Button busy={whisperCppBusy()} onClick={() => void installWhisperCpp()}>
|
|
465
|
+
{ctx.t("whispercpp.install.run")}
|
|
466
|
+
</ui.Button>
|
|
467
|
+
</ui.SettingsRow>
|
|
468
|
+
|
|
469
|
+
<ui.SettingsRow
|
|
470
|
+
id="whispercpp-test"
|
|
471
|
+
title={ctx.t("whispercpp.test")}
|
|
472
|
+
description={ctx.t("whispercpp.test.description")}
|
|
473
|
+
>
|
|
474
|
+
<ui.Button busy={whisperCppCheckBusy()} onClick={() => void checkWhisperCpp()}>
|
|
475
|
+
{ctx.t("whispercpp.test.run")}
|
|
476
|
+
</ui.Button>
|
|
477
|
+
</ui.SettingsRow>
|
|
478
|
+
</Show>
|
|
479
|
+
|
|
313
480
|
<Show when={enginePrefs.value.mode === "system"}>
|
|
314
481
|
<ui.SettingsRow
|
|
315
482
|
id="language"
|
|
@@ -439,13 +606,15 @@ export function VoiceSettings(props: { speaker: Speaker }) {
|
|
|
439
606
|
</ui.SettingsRow>
|
|
440
607
|
</ui.SettingsSection>
|
|
441
608
|
|
|
442
|
-
<
|
|
443
|
-
<ui.
|
|
444
|
-
<ui.
|
|
445
|
-
{
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
609
|
+
<Show when={platform() !== "linux"}>
|
|
610
|
+
<ui.SettingsSection title={ctx.t("settings.test.section")}>
|
|
611
|
+
<ui.SettingsRow id="test" title={ctx.t("settings.test")} description={ctx.t("settings.test.description")}>
|
|
612
|
+
<ui.Button busy={busy()} onClick={() => void test()}>
|
|
613
|
+
{ctx.t("settings.test.run")}
|
|
614
|
+
</ui.Button>
|
|
615
|
+
</ui.SettingsRow>
|
|
616
|
+
</ui.SettingsSection>
|
|
617
|
+
</Show>
|
|
449
618
|
</ui.SettingsLayout>
|
|
450
619
|
)
|
|
451
620
|
}
|