@goodandready/dsh-voice 0.5.0 → 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/README.md +26 -0
- package/lib/client.js +11 -1
- package/lib/index.js +6 -5
- package/lib/providers.js +65 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -40,6 +40,32 @@ Keys are read through the DSH credentials service (Settings → Credentials, or
|
|
|
40
40
|
`$DSH_HOME/.credentials.yaml`), falling back to the process environment. A
|
|
41
41
|
provider without a key is skipped, not fatal.
|
|
42
42
|
|
|
43
|
+
### Ready-made providers
|
|
44
|
+
|
|
45
|
+
Six providers are filled in already — put the name in a chain and add the key:
|
|
46
|
+
|
|
47
|
+
| Name | Model | Credential |
|
|
48
|
+
|---|---|---|
|
|
49
|
+
| `openai` | `whisper-1` | `OPENAI_API_KEY` |
|
|
50
|
+
| `siliconflow` | `FunAudioLLM/SenseVoiceSmall` | `SILICONFLOW_API_KEY` |
|
|
51
|
+
| `deepinfra` | `openai/whisper-large-v3-turbo` | `DEEPINFRA_API_KEY` |
|
|
52
|
+
| `fireworks` | `whisper-v3-turbo` | `FIREWORKS_API_KEY` |
|
|
53
|
+
| `mistral` | `voxtral-mini-latest` | `MISTRAL_API_KEY` |
|
|
54
|
+
| `openrouter` | `google/gemini-2.5-flash` | `OPENROUTER_API_KEY` |
|
|
55
|
+
|
|
56
|
+
```yaml
|
|
57
|
+
- id: dsh-voice
|
|
58
|
+
config:
|
|
59
|
+
message:
|
|
60
|
+
chain:
|
|
61
|
+
- provider: openai
|
|
62
|
+
- provider: local-whisper
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Every endpoint was probed without a key before being written down: all six answered `401`, the answer of a path that exists and wants credentials. The model ids are starting points — override `model` in a chain row to change one.
|
|
66
|
+
|
|
67
|
+
A preset is the same form as a custom provider with the fields filled in, so a `customProviders` entry under the same name replaces it outright.
|
|
68
|
+
|
|
43
69
|
### Your own providers
|
|
44
70
|
|
|
45
71
|
Any OpenAI-compatible API can be added as a provider and used in the chains
|
package/lib/client.js
CHANGED
|
@@ -636,10 +636,20 @@ window.__ModuleLoader__.load({
|
|
|
636
636
|
}
|
|
637
637
|
|
|
638
638
|
// ------------------------------------------------------- settings page
|
|
639
|
-
const BUILTIN = [
|
|
639
|
+
const BUILTIN = [
|
|
640
|
+
'browser', 'deepgram', 'groq', 'hf', 'local-whisper',
|
|
641
|
+
// Заготовки: адрес и модель уже прописаны на хосте, нужен только ключ.
|
|
642
|
+
'openai', 'siliconflow', 'deepinfra', 'fireworks', 'mistral', 'openrouter',
|
|
643
|
+
]
|
|
640
644
|
const TEMPLATES = ['openai-transcriptions', 'openai-chat-audio']
|
|
641
645
|
const MODEL_HINT = {
|
|
642
646
|
browser: 'распознаёт сам браузер, ключ не нужен',
|
|
647
|
+
openai: 'whisper-1 · ключ OPENAI_API_KEY',
|
|
648
|
+
siliconflow: 'FunAudioLLM/SenseVoiceSmall · ключ SILICONFLOW_API_KEY',
|
|
649
|
+
deepinfra: 'openai/whisper-large-v3-turbo · ключ DEEPINFRA_API_KEY',
|
|
650
|
+
fireworks: 'whisper-v3-turbo · ключ FIREWORKS_API_KEY',
|
|
651
|
+
mistral: 'voxtral-mini-latest · ключ MISTRAL_API_KEY',
|
|
652
|
+
openrouter: 'google/gemini-2.5-flash · ключ OPENROUTER_API_KEY',
|
|
643
653
|
deepgram: 'nova-2', groq: 'whisper-large-v3-turbo',
|
|
644
654
|
hf: 'openai/whisper-large-v3', 'local-whisper': 'задаётся при запуске сервера',
|
|
645
655
|
}
|
package/lib/index.js
CHANGED
|
@@ -18,7 +18,7 @@ import { credentialRef } from '@deepseek-ai/dsh-credentials'
|
|
|
18
18
|
import { readFile, stat } from 'node:fs/promises'
|
|
19
19
|
import path from 'node:path'
|
|
20
20
|
import { runChain } from './chain.js'
|
|
21
|
-
import { makeProviders, PROVIDER_KEYS, DEFAULT_MODELS, CUSTOM_TEMPLATES } from './providers.js'
|
|
21
|
+
import { makeProviders, PROVIDER_KEYS, PRESET_KEYS, KNOWN_KEYS, DEFAULT_MODELS, CUSTOM_TEMPLATES } from './providers.js'
|
|
22
22
|
import { toWav16k } from './wav.js'
|
|
23
23
|
|
|
24
24
|
export const name = 'dsh-voice'
|
|
@@ -26,8 +26,9 @@ export const inject = ['tools', 'credentials', 'webServer', 'shell', 'settings']
|
|
|
26
26
|
|
|
27
27
|
const ChainEntry = z.object({
|
|
28
28
|
provider: z.string().default('local-whisper')
|
|
29
|
-
.description(`Provider key
|
|
30
|
-
+
|
|
29
|
+
.description(`Provider key. Built in: ${PROVIDER_KEYS.join(', ')}. `
|
|
30
|
+
+ `Ready-made, just add the key: ${PRESET_KEYS.join(', ')}. `
|
|
31
|
+
+ 'Or the name of an entry from customProviders. '
|
|
31
32
|
+ '"browser" recognises speech in the page itself — no key, no upload to this host, '
|
|
32
33
|
+ 'text appears while you speak; put a normal provider after it as a fallback.'),
|
|
33
34
|
model: z.string().default('')
|
|
@@ -187,7 +188,7 @@ export function apply(ctx, baseConfig) {
|
|
|
187
188
|
const models = {}
|
|
188
189
|
const order = []
|
|
189
190
|
for (const entry of Array.isArray(modeCfg.chain) ? modeCfg.chain : []) {
|
|
190
|
-
if (!
|
|
191
|
+
if (!KNOWN_KEYS.includes(entry.provider) && !customKeys.includes(entry.provider)) continue
|
|
191
192
|
order.push(entry.provider)
|
|
192
193
|
// Для своего провайдера модель по умолчанию живёт в его описании,
|
|
193
194
|
// подставит makeProviders — здесь пусто означает «бери оттуда».
|
|
@@ -211,7 +212,7 @@ export function apply(ctx, baseConfig) {
|
|
|
211
212
|
whisperRunning: await whisperAlive(),
|
|
212
213
|
// Клавиша нужна браузерной половине: она вешает обработчик удержания.
|
|
213
214
|
hotkey: cfg.hotkey,
|
|
214
|
-
providers:
|
|
215
|
+
providers: KNOWN_KEYS.concat(
|
|
215
216
|
(Array.isArray(cfg.customProviders) ? cfg.customProviders : [])
|
|
216
217
|
.map((c) => String(c && c.key || '').trim()).filter(Boolean),
|
|
217
218
|
),
|
package/lib/providers.js
CHANGED
|
@@ -13,6 +13,60 @@ export const PROVIDER_KEYS = ['browser', 'deepgram', 'groq', 'hf', 'local-whispe
|
|
|
13
13
|
// /audio/transcriptions вовсе, и распознавание там идёт через чат.
|
|
14
14
|
export const CUSTOM_TEMPLATES = ['openai-transcriptions', 'openai-chat-audio']
|
|
15
15
|
|
|
16
|
+
// Готовые провайдеры: то же самое, что свой провайдер, только адрес, модель и
|
|
17
|
+
// имя ключа уже проставлены. Нужен лишь ключ.
|
|
18
|
+
//
|
|
19
|
+
// Каждый адрес проверен запросом без ключа: все отвечают 401 «дайте ключ», то
|
|
20
|
+
// есть путь существует. Модель — разумная отправная точка, её можно заменить
|
|
21
|
+
// в строке цепочки, не трогая остальное.
|
|
22
|
+
//
|
|
23
|
+
// Своим провайдером с тем же именем можно перекрыть любую заготовку целиком:
|
|
24
|
+
// это не встроенный движок, а всего лишь заранее заполненная анкета.
|
|
25
|
+
export const PRESET_PROVIDERS = {
|
|
26
|
+
openai: {
|
|
27
|
+
template: 'openai-transcriptions',
|
|
28
|
+
baseURL: 'https://api.openai.com/v1',
|
|
29
|
+
model: 'whisper-1',
|
|
30
|
+
keyEnv: 'OPENAI_API_KEY',
|
|
31
|
+
},
|
|
32
|
+
siliconflow: {
|
|
33
|
+
template: 'openai-transcriptions',
|
|
34
|
+
baseURL: 'https://api.siliconflow.cn/v1',
|
|
35
|
+
model: 'FunAudioLLM/SenseVoiceSmall',
|
|
36
|
+
keyEnv: 'SILICONFLOW_API_KEY',
|
|
37
|
+
},
|
|
38
|
+
deepinfra: {
|
|
39
|
+
template: 'openai-transcriptions',
|
|
40
|
+
baseURL: 'https://api.deepinfra.com/v1/openai',
|
|
41
|
+
model: 'openai/whisper-large-v3-turbo',
|
|
42
|
+
keyEnv: 'DEEPINFRA_API_KEY',
|
|
43
|
+
},
|
|
44
|
+
fireworks: {
|
|
45
|
+
template: 'openai-transcriptions',
|
|
46
|
+
baseURL: 'https://api.fireworks.ai/inference/v1',
|
|
47
|
+
model: 'whisper-v3-turbo',
|
|
48
|
+
keyEnv: 'FIREWORKS_API_KEY',
|
|
49
|
+
},
|
|
50
|
+
mistral: {
|
|
51
|
+
template: 'openai-transcriptions',
|
|
52
|
+
baseURL: 'https://api.mistral.ai/v1',
|
|
53
|
+
model: 'voxtral-mini-latest',
|
|
54
|
+
keyEnv: 'MISTRAL_API_KEY',
|
|
55
|
+
},
|
|
56
|
+
// У OpenRouter нет /audio/transcriptions вовсе — только через чат.
|
|
57
|
+
openrouter: {
|
|
58
|
+
template: 'openai-chat-audio',
|
|
59
|
+
baseURL: 'https://openrouter.ai/api/v1',
|
|
60
|
+
model: 'google/gemini-2.5-flash',
|
|
61
|
+
keyEnv: 'OPENROUTER_API_KEY',
|
|
62
|
+
},
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export const PRESET_KEYS = Object.keys(PRESET_PROVIDERS)
|
|
66
|
+
|
|
67
|
+
/** Все имена, которые можно ставить в цепочку без объявления своего провайдера. */
|
|
68
|
+
export const KNOWN_KEYS = PROVIDER_KEYS.concat(PRESET_KEYS)
|
|
69
|
+
|
|
16
70
|
const CHAT_AUDIO_PROMPT =
|
|
17
71
|
'Transcribe the audio verbatim. Reply with the transcript text only, '
|
|
18
72
|
+ 'without comments, quotes or formatting.'
|
|
@@ -225,11 +279,19 @@ export function makeProviders(deps, req) {
|
|
|
225
279
|
}
|
|
226
280
|
|
|
227
281
|
const out = { browser, deepgram, groq, hf, 'local-whisper': localWhisper }
|
|
282
|
+
|
|
283
|
+
// Заготовки: те же свои провайдеры, только анкета заполнена заранее.
|
|
284
|
+
for (const key of PRESET_KEYS) {
|
|
285
|
+
if (out[key]) continue
|
|
286
|
+
out[key] = customProvider({ ...PRESET_PROVIDERS[key], key })
|
|
287
|
+
}
|
|
288
|
+
|
|
228
289
|
for (const spec of Array.isArray(cfg.customProviders) ? cfg.customProviders : []) {
|
|
229
290
|
const key = String(spec && spec.key || '').trim()
|
|
230
|
-
// Встроенные не перекрываем:
|
|
231
|
-
// провайдера в чужой цепочке.
|
|
232
|
-
|
|
291
|
+
// Встроенные движки не перекрываем: опечатка в имени тихо подменила бы
|
|
292
|
+
// рабочего провайдера в чужой цепочке. А заготовку перекрыть можно — она
|
|
293
|
+
// для того и заготовка, чтобы её правили.
|
|
294
|
+
if (!key || PROVIDER_KEYS.includes(key)) continue
|
|
233
295
|
out[key] = customProvider({ ...spec, key })
|
|
234
296
|
}
|
|
235
297
|
return out
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-voice",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Voice input for DeepSeek Harness: dictation chunked by pauses and voice messages, each with its own provider fallback chain (Deepgram, Groq, HuggingFace, local whisper.cpp, plus any OpenAI-compatible API of your own).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|