opencode-voice-dictation 0.4.2 → 0.6.0
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/dist/gui-experimental.cjs +8 -8
- package/package.json +4 -4
- package/src/{gui/engines/edge-tts.ts → edge.ts} +9 -30
- package/src/gui/contract.ts +0 -18
- package/src/gui/definition.ts +50 -0
- package/src/gui/main.ts +0 -9
- package/src/gui/recorder.ts +51 -0
- package/src/gui/settings.tsx +105 -0
- package/src/gui/speak.ts +10 -2
- package/src/gui/window.tsx +114 -0
- package/src/index.ts +43 -12
- package/src/rpc.ts +90 -0
- package/src/whisper/server.ts +261 -0
- package/src/whisper/worker.py +53 -0
package/src/gui/settings.tsx
CHANGED
|
@@ -3,6 +3,7 @@ import { usePlugin } from "@opencode/plugin/gui"
|
|
|
3
3
|
import { Dictation, VoiceTyping } from "./contract"
|
|
4
4
|
import type { definition } from "./definition"
|
|
5
5
|
import type { Speaker } from "./speak"
|
|
6
|
+
import { VoiceRpc } from "../rpc"
|
|
6
7
|
|
|
7
8
|
const FALLBACK_LOCALES = [
|
|
8
9
|
"es-ES",
|
|
@@ -29,11 +30,13 @@ export function VoiceSettings(props: { speaker: Speaker }) {
|
|
|
29
30
|
const speechPrefs = ctx.stores.speech
|
|
30
31
|
const ttsPrefs = ctx.stores.tts
|
|
31
32
|
const enginePrefs = ctx.stores.engine
|
|
33
|
+
const whisperPrefs = ctx.stores.whisper
|
|
32
34
|
const dictation = ctx.main(Dictation)
|
|
33
35
|
const voiceTyping = ctx.main(VoiceTyping)
|
|
34
36
|
|
|
35
37
|
const [locales, setLocales] = createSignal<readonly string[]>([])
|
|
36
38
|
const [busy, setBusy] = createSignal(false)
|
|
39
|
+
const [whisperBusy, setWhisperBusy] = createSignal(false)
|
|
37
40
|
|
|
38
41
|
onMount(() => {
|
|
39
42
|
props.speaker.setConfig({ engine: ttsPrefs.value.engine, voice: ttsPrefs.value.voice })
|
|
@@ -96,6 +99,48 @@ export function VoiceSettings(props: { speaker: Speaker }) {
|
|
|
96
99
|
})
|
|
97
100
|
}
|
|
98
101
|
|
|
102
|
+
const patchWhisper = (change: { python?: string; model?: string; language?: string }) => {
|
|
103
|
+
whisperPrefs.update((draft) => {
|
|
104
|
+
if (change.python !== undefined) draft.python = change.python
|
|
105
|
+
if (change.model !== undefined) draft.model = change.model
|
|
106
|
+
if (change.language !== undefined) draft.language = change.language
|
|
107
|
+
})
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const testWhisper = async () => {
|
|
111
|
+
setWhisperBusy(true)
|
|
112
|
+
try {
|
|
113
|
+
const status = (await ctx.client.rpc(VoiceRpc).whisperStatus({
|
|
114
|
+
python: whisperPrefs.value.python,
|
|
115
|
+
model: whisperPrefs.value.model,
|
|
116
|
+
})) as { ready: boolean; model: string; device: string; error: string }
|
|
117
|
+
|
|
118
|
+
if (status.ready) {
|
|
119
|
+
ctx.toast.show({
|
|
120
|
+
variant: "success",
|
|
121
|
+
title: ctx.t("pill.title"),
|
|
122
|
+
description: ctx.t("whisper.test.ready", { model: status.model, device: status.device }),
|
|
123
|
+
})
|
|
124
|
+
} else {
|
|
125
|
+
ctx.toast.show({
|
|
126
|
+
variant: "error",
|
|
127
|
+
title: ctx.t("pill.title"),
|
|
128
|
+
description: ctx.t("whisper.test.error", { error: status.error || "?" }),
|
|
129
|
+
})
|
|
130
|
+
}
|
|
131
|
+
} catch (cause) {
|
|
132
|
+
ctx.toast.show({
|
|
133
|
+
variant: "error",
|
|
134
|
+
title: ctx.t("pill.title"),
|
|
135
|
+
description: ctx.t("whisper.test.error", {
|
|
136
|
+
error: cause instanceof Error ? cause.message : String(cause),
|
|
137
|
+
}),
|
|
138
|
+
})
|
|
139
|
+
} finally {
|
|
140
|
+
setWhisperBusy(false)
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
99
144
|
const patchTts = (engine: string) => {
|
|
100
145
|
ttsPrefs.update((draft) => {
|
|
101
146
|
draft.engine = engine
|
|
@@ -190,6 +235,7 @@ export function VoiceSettings(props: { speaker: Speaker }) {
|
|
|
190
235
|
options={[
|
|
191
236
|
{ value: "windows", label: ctx.t("engine.windows") },
|
|
192
237
|
{ value: "system", label: ctx.t("engine.system") },
|
|
238
|
+
{ value: "whisper", label: ctx.t("engine.whisper") },
|
|
193
239
|
]}
|
|
194
240
|
onChange={(value) => patchEngine(value)}
|
|
195
241
|
/>
|
|
@@ -205,6 +251,65 @@ export function VoiceSettings(props: { speaker: Speaker }) {
|
|
|
205
251
|
</ui.SettingsRow>
|
|
206
252
|
</Show>
|
|
207
253
|
|
|
254
|
+
<Show when={enginePrefs.value.mode === "whisper"}>
|
|
255
|
+
<ui.SettingsRow
|
|
256
|
+
id="whisper-python"
|
|
257
|
+
title={ctx.t("whisper.python")}
|
|
258
|
+
description={ctx.t("whisper.python.description")}
|
|
259
|
+
>
|
|
260
|
+
<ui.TextInput
|
|
261
|
+
label={ctx.t("whisper.python")}
|
|
262
|
+
value={whisperPrefs.value.python}
|
|
263
|
+
onInput={(value) => patchWhisper({ python: value })}
|
|
264
|
+
/>
|
|
265
|
+
</ui.SettingsRow>
|
|
266
|
+
|
|
267
|
+
<ui.SettingsRow
|
|
268
|
+
id="whisper-model"
|
|
269
|
+
title={ctx.t("whisper.model")}
|
|
270
|
+
description={ctx.t("whisper.model.description")}
|
|
271
|
+
>
|
|
272
|
+
<ui.Select
|
|
273
|
+
label={ctx.t("whisper.model")}
|
|
274
|
+
value={whisperPrefs.value.model}
|
|
275
|
+
options={[
|
|
276
|
+
{ value: "large-v3-turbo", label: "large-v3-turbo" },
|
|
277
|
+
{ value: "large-v3", label: "large-v3" },
|
|
278
|
+
{ value: "medium", label: "medium" },
|
|
279
|
+
]}
|
|
280
|
+
onChange={(value) => patchWhisper({ model: value })}
|
|
281
|
+
/>
|
|
282
|
+
</ui.SettingsRow>
|
|
283
|
+
|
|
284
|
+
<ui.SettingsRow
|
|
285
|
+
id="whisper-language"
|
|
286
|
+
title={ctx.t("whisper.language")}
|
|
287
|
+
description={ctx.t("whisper.language.description")}
|
|
288
|
+
>
|
|
289
|
+
<ui.Select
|
|
290
|
+
label={ctx.t("whisper.language")}
|
|
291
|
+
value={whisperPrefs.value.language}
|
|
292
|
+
options={[
|
|
293
|
+
{ value: "", label: ctx.t("whisper.language.auto") },
|
|
294
|
+
{ value: "es", label: "Español" },
|
|
295
|
+
{ value: "en", label: "English" },
|
|
296
|
+
{ value: "pt", label: "Português" },
|
|
297
|
+
]}
|
|
298
|
+
onChange={(value) => patchWhisper({ language: value })}
|
|
299
|
+
/>
|
|
300
|
+
</ui.SettingsRow>
|
|
301
|
+
|
|
302
|
+
<ui.SettingsRow
|
|
303
|
+
id="whisper-test"
|
|
304
|
+
title={ctx.t("whisper.test")}
|
|
305
|
+
description={ctx.t("whisper.test.description")}
|
|
306
|
+
>
|
|
307
|
+
<ui.Button busy={whisperBusy()} onClick={() => void testWhisper()}>
|
|
308
|
+
{ctx.t("whisper.test.run")}
|
|
309
|
+
</ui.Button>
|
|
310
|
+
</ui.SettingsRow>
|
|
311
|
+
</Show>
|
|
312
|
+
|
|
208
313
|
<Show when={enginePrefs.value.mode === "system"}>
|
|
209
314
|
<ui.SettingsRow
|
|
210
315
|
id="language"
|
package/src/gui/speak.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createSignal } from "solid-js"
|
|
2
2
|
import type { IpcClient, SetupContext } from "@opencode/plugin/gui"
|
|
3
|
+
import { VoiceRpc } from "../rpc"
|
|
3
4
|
import { Speech } from "./contract"
|
|
4
5
|
import type { definition } from "./definition"
|
|
5
6
|
|
|
@@ -153,7 +154,10 @@ export function createSpeaker(
|
|
|
153
154
|
const refreshVoices = async () => {
|
|
154
155
|
if (engine() === "edge") {
|
|
155
156
|
try {
|
|
156
|
-
const list = await
|
|
157
|
+
const list = (await ctx.client.rpc(VoiceRpc).edgeVoices({})) as unknown as Array<{
|
|
158
|
+
id: string
|
|
159
|
+
label: string
|
|
160
|
+
}>
|
|
157
161
|
if (list.length > 0) {
|
|
158
162
|
setVoices(list.map((voice) => ({ id: voice.id, label: voice.label })))
|
|
159
163
|
setError("")
|
|
@@ -279,7 +283,11 @@ export function createSpeaker(
|
|
|
279
283
|
|
|
280
284
|
try {
|
|
281
285
|
if (!encoded) {
|
|
282
|
-
encoded = await
|
|
286
|
+
encoded = (await ctx.client.rpc(VoiceRpc).edgeSpeak({
|
|
287
|
+
text: blocks[index] ?? "",
|
|
288
|
+
voice: selected,
|
|
289
|
+
rate,
|
|
290
|
+
})) as string
|
|
283
291
|
cached[index] = encoded
|
|
284
292
|
}
|
|
285
293
|
} catch {
|
package/src/gui/window.tsx
CHANGED
|
@@ -13,6 +13,8 @@ import { Dictation, Speech, VoiceTyping } from "./contract"
|
|
|
13
13
|
import type { definition } from "./definition"
|
|
14
14
|
import { createSpeaker, type Speaker } from "./speak"
|
|
15
15
|
import { VoiceSettings } from "./settings"
|
|
16
|
+
import { blobToBase64, startRecording, type Recording } from "./recorder"
|
|
17
|
+
import { VoiceRpc } from "../rpc"
|
|
16
18
|
|
|
17
19
|
interface ReadAction {
|
|
18
20
|
id: string
|
|
@@ -53,9 +55,12 @@ export const setup: Plugin.Setup<typeof definition> = (ctx) => {
|
|
|
53
55
|
const speaker = createSpeaker(ctx, speech)
|
|
54
56
|
const prefs = ctx.stores.prefs
|
|
55
57
|
const engine = ctx.stores.engine
|
|
58
|
+
const whisper = ctx.stores.whisper
|
|
56
59
|
const [windowsActive, setWindowsActive] = createSignal(false)
|
|
60
|
+
const [whisperState, setWhisperState] = createSignal<"idle" | "recording" | "transcribing">("idle")
|
|
57
61
|
|
|
58
62
|
let targetSession: string | undefined
|
|
63
|
+
let recording: Recording | undefined
|
|
59
64
|
|
|
60
65
|
const reason = (code: string, detail: string, available: readonly string[]) => {
|
|
61
66
|
switch (code) {
|
|
@@ -117,7 +122,83 @@ export const setup: Plugin.Setup<typeof definition> = (ctx) => {
|
|
|
117
122
|
})
|
|
118
123
|
}
|
|
119
124
|
|
|
125
|
+
const startWhisper = async () => {
|
|
126
|
+
if (whisperState() !== "idle") return
|
|
127
|
+
speaker.cancel()
|
|
128
|
+
|
|
129
|
+
try {
|
|
130
|
+
recording = await startRecording()
|
|
131
|
+
setWhisperState("recording")
|
|
132
|
+
} catch (cause) {
|
|
133
|
+
ctx.toast.show({
|
|
134
|
+
variant: "error",
|
|
135
|
+
title: ctx.t("pill.title"),
|
|
136
|
+
description: cause instanceof Error ? cause.message : ctx.t("error.whisper"),
|
|
137
|
+
})
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const cancelWhisper = () => {
|
|
142
|
+
const current = recording
|
|
143
|
+
recording = undefined
|
|
144
|
+
if (current) void current.stop().catch(() => {})
|
|
145
|
+
setWhisperState("idle")
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const stopWhisper = async () => {
|
|
149
|
+
const current = recording
|
|
150
|
+
recording = undefined
|
|
151
|
+
|
|
152
|
+
if (!current) {
|
|
153
|
+
setWhisperState("idle")
|
|
154
|
+
return
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
setWhisperState("transcribing")
|
|
158
|
+
|
|
159
|
+
try {
|
|
160
|
+
const blob = await current.stop()
|
|
161
|
+
const audio = await blobToBase64(blob)
|
|
162
|
+
const result = (await ctx.client.rpc(VoiceRpc).whisperTranscribe({
|
|
163
|
+
audio,
|
|
164
|
+
mime: blob.type,
|
|
165
|
+
language: whisper.value.language,
|
|
166
|
+
python: whisper.value.python,
|
|
167
|
+
model: whisper.value.model,
|
|
168
|
+
})) as { text: string; language: string }
|
|
169
|
+
|
|
170
|
+
const text = result.text.trim()
|
|
171
|
+
|
|
172
|
+
if (!text) {
|
|
173
|
+
ctx.toast.show({ variant: "default", title: ctx.t("pill.title"), description: ctx.t("whisper.empty") })
|
|
174
|
+
return
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const composer = ctx.screen.current()?.composer
|
|
178
|
+
if (composer) {
|
|
179
|
+
const existing = composer.text()
|
|
180
|
+
const separator = existing.length > 0 && !/\s$/.test(existing) ? " " : ""
|
|
181
|
+
composer.insert(separator + text)
|
|
182
|
+
composer.focus()
|
|
183
|
+
}
|
|
184
|
+
} catch (cause) {
|
|
185
|
+
ctx.toast.show({
|
|
186
|
+
variant: "error",
|
|
187
|
+
title: ctx.t("pill.title"),
|
|
188
|
+
description: cause instanceof Error ? cause.message : ctx.t("error.whisper"),
|
|
189
|
+
})
|
|
190
|
+
} finally {
|
|
191
|
+
setWhisperState("idle")
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
120
195
|
const toggle = () => {
|
|
196
|
+
if (engine.value.mode === "whisper") {
|
|
197
|
+
if (whisperState() === "recording") void stopWhisper()
|
|
198
|
+
else if (whisperState() === "idle") void startWhisper()
|
|
199
|
+
return
|
|
200
|
+
}
|
|
201
|
+
|
|
121
202
|
if (engine.value.mode === "windows") {
|
|
122
203
|
toggleWindows()
|
|
123
204
|
return
|
|
@@ -129,6 +210,8 @@ export const setup: Plugin.Setup<typeof definition> = (ctx) => {
|
|
|
129
210
|
}
|
|
130
211
|
|
|
131
212
|
const read = () => {
|
|
213
|
+
if (whisperState() === "recording") cancelWhisper()
|
|
214
|
+
|
|
132
215
|
if (speaker.speaking()) {
|
|
133
216
|
speaker.cancel()
|
|
134
217
|
return
|
|
@@ -235,6 +318,33 @@ export const setup: Plugin.Setup<typeof definition> = (ctx) => {
|
|
|
235
318
|
}
|
|
236
319
|
}
|
|
237
320
|
|
|
321
|
+
if (engine.value.mode === "whisper") {
|
|
322
|
+
if (whisperState() === "recording") {
|
|
323
|
+
return {
|
|
324
|
+
label: ctx.t("pill.recording"),
|
|
325
|
+
icon: "outline-wave-audio",
|
|
326
|
+
tone: "danger",
|
|
327
|
+
title: ctx.t("pill.stop"),
|
|
328
|
+
run: toggle,
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
if (whisperState() === "transcribing") {
|
|
333
|
+
return {
|
|
334
|
+
label: ctx.t("pill.transcribing"),
|
|
335
|
+
icon: "outline-audio",
|
|
336
|
+
title: ctx.t("pill.whisper"),
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
return {
|
|
341
|
+
label: ctx.t("pill.idle"),
|
|
342
|
+
icon: "outline-wave-audio",
|
|
343
|
+
title: ctx.t("pill.whisper"),
|
|
344
|
+
run: toggle,
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
238
348
|
const status = dictation.state()?.status ?? "idle"
|
|
239
349
|
|
|
240
350
|
if (status === "starting") {
|
|
@@ -350,6 +460,10 @@ export const setup: Plugin.Setup<typeof definition> = (ctx) => {
|
|
|
350
460
|
{ id: "test", title: ctx.t("settings.test"), keywords: "voice microphone microphone test microfono" },
|
|
351
461
|
{ id: "voice", title: ctx.t("speech.voice"), keywords: "read aloud voice voz lectura altavoz" },
|
|
352
462
|
{ id: "tts-engine", title: ctx.t("speech.engine"), keywords: "voice engine motor edge neural sistema" },
|
|
463
|
+
{ id: "whisper-python", title: ctx.t("whisper.python"), keywords: "whisper python venv entorno" },
|
|
464
|
+
{ id: "whisper-model", title: ctx.t("whisper.model"), keywords: "whisper model modelo" },
|
|
465
|
+
{ id: "whisper-language", title: ctx.t("whisper.language"), keywords: "whisper language idioma" },
|
|
466
|
+
{ id: "whisper-test", title: ctx.t("whisper.test"), keywords: "whisper check comprobar" },
|
|
353
467
|
{ id: "rate", title: ctx.t("speech.rate"), keywords: "read aloud speed velocidad lectura" },
|
|
354
468
|
{ id: "skip-code", title: ctx.t("speech.skipCode"), keywords: "read aloud code codigo lectura" },
|
|
355
469
|
{ id: "preview", title: ctx.t("speech.preview"), keywords: "read aloud test voice probar voz" },
|
package/src/index.ts
CHANGED
|
@@ -1,19 +1,50 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { Plugin } from "@opencode/plugin"
|
|
2
|
+
import { listEdgeVoices, synthesizeEdge } from "./edge.js"
|
|
3
|
+
import { VoiceRpc } from "./rpc.js"
|
|
4
|
+
import { WhisperEngine } from "./whisper/server.js"
|
|
2
5
|
|
|
3
6
|
/**
|
|
4
7
|
* Server part of the plugin.
|
|
5
8
|
*
|
|
6
|
-
* It
|
|
7
|
-
* the
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* `@opencode/plugin` is a type-only import on purpose: the server loads this
|
|
11
|
-
* file directly, and a runtime import would make the plugin depend on a
|
|
12
|
-
* `node_modules` tree next to it.
|
|
9
|
+
* It registers the Edge TTS and Whisper RPCs. Running them here (instead of
|
|
10
|
+
* in the Electron main process) lets their dependencies resolve from the
|
|
11
|
+
* plugin's own node_modules.
|
|
13
12
|
*/
|
|
14
|
-
|
|
13
|
+
export default Plugin.define({
|
|
15
14
|
id: "voice-dictation",
|
|
16
|
-
setup() {
|
|
17
|
-
|
|
15
|
+
async setup(ctx) {
|
|
16
|
+
const whisper = new WhisperEngine()
|
|
17
|
+
|
|
18
|
+
await ctx.rpc.register(VoiceRpc, {
|
|
19
|
+
edgeVoices: async () => listEdgeVoices(),
|
|
20
|
+
edgeSpeak: async (input) => {
|
|
21
|
+
const { text, voice, rate } = input as { text: string; voice: string; rate: number }
|
|
22
|
+
return synthesizeEdge({ text, voice, rate })
|
|
23
|
+
},
|
|
24
|
+
whisperTranscribe: async (input) => {
|
|
25
|
+
return whisper.transcribe(input as {
|
|
26
|
+
audio: string
|
|
27
|
+
mime: string
|
|
28
|
+
language: string
|
|
29
|
+
python: string
|
|
30
|
+
model: string
|
|
31
|
+
})
|
|
32
|
+
},
|
|
33
|
+
whisperStatus: async (input) => {
|
|
34
|
+
const { python, model } = input as { python: string; model: string }
|
|
35
|
+
try {
|
|
36
|
+
return await whisper.warm(python, model)
|
|
37
|
+
} catch (cause) {
|
|
38
|
+
return {
|
|
39
|
+
...whisper.statusOf(),
|
|
40
|
+
ready: false,
|
|
41
|
+
error: cause instanceof Error ? cause.message : String(cause),
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
return () => whisper.stop()
|
|
48
|
+
},
|
|
49
|
+
})
|
|
18
50
|
|
|
19
|
-
export default plugin
|
package/src/rpc.ts
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { Rpc } from "@opencode/plugin"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Edge TTS runs in the OpenCode server, where the plugin's own `node_modules`
|
|
5
|
+
* resolves. The GUI only calls these methods and plays the returned MP3.
|
|
6
|
+
*
|
|
7
|
+
* The definition is a plain object (Rpc.define is the identity function), so
|
|
8
|
+
* the GUI bundle does not need to load the RPC module at runtime.
|
|
9
|
+
*/
|
|
10
|
+
export const VoiceRpc = {
|
|
11
|
+
id: "voice-dictation.voice",
|
|
12
|
+
methods: {
|
|
13
|
+
edgeVoices: {
|
|
14
|
+
input: { type: "object", additionalProperties: false },
|
|
15
|
+
output: {
|
|
16
|
+
type: "array",
|
|
17
|
+
items: {
|
|
18
|
+
type: "object",
|
|
19
|
+
properties: {
|
|
20
|
+
id: { type: "string" },
|
|
21
|
+
label: { type: "string" },
|
|
22
|
+
locale: { type: "string" },
|
|
23
|
+
gender: { type: "string" },
|
|
24
|
+
},
|
|
25
|
+
required: ["id", "label", "locale", "gender"],
|
|
26
|
+
additionalProperties: false,
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
edgeSpeak: {
|
|
31
|
+
input: {
|
|
32
|
+
type: "object",
|
|
33
|
+
properties: {
|
|
34
|
+
text: { type: "string" },
|
|
35
|
+
voice: { type: "string" },
|
|
36
|
+
rate: { type: "number" },
|
|
37
|
+
},
|
|
38
|
+
required: ["text", "voice", "rate"],
|
|
39
|
+
additionalProperties: false,
|
|
40
|
+
},
|
|
41
|
+
output: { type: "string" },
|
|
42
|
+
},
|
|
43
|
+
whisperTranscribe: {
|
|
44
|
+
input: {
|
|
45
|
+
type: "object",
|
|
46
|
+
properties: {
|
|
47
|
+
audio: { type: "string" },
|
|
48
|
+
mime: { type: "string" },
|
|
49
|
+
language: { type: "string" },
|
|
50
|
+
python: { type: "string" },
|
|
51
|
+
model: { type: "string" },
|
|
52
|
+
},
|
|
53
|
+
required: ["audio", "mime", "language", "python", "model"],
|
|
54
|
+
additionalProperties: false,
|
|
55
|
+
},
|
|
56
|
+
output: {
|
|
57
|
+
type: "object",
|
|
58
|
+
properties: {
|
|
59
|
+
text: { type: "string" },
|
|
60
|
+
language: { type: "string" },
|
|
61
|
+
},
|
|
62
|
+
required: ["text", "language"],
|
|
63
|
+
additionalProperties: false,
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
whisperStatus: {
|
|
67
|
+
input: {
|
|
68
|
+
type: "object",
|
|
69
|
+
properties: {
|
|
70
|
+
python: { type: "string" },
|
|
71
|
+
model: { type: "string" },
|
|
72
|
+
},
|
|
73
|
+
required: ["python", "model"],
|
|
74
|
+
additionalProperties: false,
|
|
75
|
+
},
|
|
76
|
+
output: {
|
|
77
|
+
type: "object",
|
|
78
|
+
properties: {
|
|
79
|
+
ready: { type: "boolean" },
|
|
80
|
+
model: { type: "string" },
|
|
81
|
+
device: { type: "string" },
|
|
82
|
+
error: { type: "string" },
|
|
83
|
+
},
|
|
84
|
+
required: ["ready", "model", "device", "error"],
|
|
85
|
+
additionalProperties: false,
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
events: {},
|
|
90
|
+
} as const satisfies Rpc.PortableDefinition
|