@goodandready/dsh-voice 0.7.2 → 0.8.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 +3 -1
- package/lib/client.js +90 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -137,7 +137,9 @@ Put a normal provider after it: if the browser cannot do it, recording falls bac
|
|
|
137
137
|
|
|
138
138
|
## Configure (Web GUI)
|
|
139
139
|
|
|
140
|
-
Settings →
|
|
140
|
+
Settings → **Plugins → Plugin settings → Voice** — the plugin's own
|
|
141
|
+
collapsible card in the plugins tab; the sidebar keeps no separate row for it.
|
|
142
|
+
The card has four blocks:
|
|
141
143
|
|
|
142
144
|
- **Dictation** — fallback chain (provider + optional model per row, order is
|
|
143
145
|
the order of attempts), language, and the silence threshold that ends a
|
package/lib/client.js
CHANGED
|
@@ -23,6 +23,10 @@ window.__ModuleLoader__.load({
|
|
|
23
23
|
// пакет, не трогая код этого плагина. Английский — язык по умолчанию,
|
|
24
24
|
// на него же приходится откат, если перевода нет.
|
|
25
25
|
const en = {
|
|
26
|
+
'saveFailed': 'Some fields were not saved —',
|
|
27
|
+
'cardHint': 'Dictation and voice messages: providers, chains, local whisper',
|
|
28
|
+
'expand': 'Expand',
|
|
29
|
+
'collapse': 'Collapse',
|
|
26
30
|
'composerUnavailable': 'Composer unavailable',
|
|
27
31
|
'keySpace': 'Space',
|
|
28
32
|
'keyUnset': 'not set',
|
|
@@ -94,6 +98,10 @@ window.__ModuleLoader__.load({
|
|
|
94
98
|
'openrouterWarning': 'OpenRouter has no /audio/transcriptions \u2014 use the openai-chat-audio template there',
|
|
95
99
|
}
|
|
96
100
|
const ru = {
|
|
101
|
+
'saveFailed': 'Часть полей не сохранилась —',
|
|
102
|
+
'cardHint': 'Диктовка и голосовые сообщения: провайдеры, цепочки, локальный whisper',
|
|
103
|
+
'expand': 'Развернуть',
|
|
104
|
+
'collapse': 'Свернуть',
|
|
97
105
|
'composerUnavailable': 'Композер недоступен',
|
|
98
106
|
'keySpace': 'Пробел',
|
|
99
107
|
'keyUnset': 'не задана',
|
|
@@ -239,6 +247,8 @@ window.__ModuleLoader__.load({
|
|
|
239
247
|
React.createElement('path', { d: 'M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z' }),
|
|
240
248
|
React.createElement('line', { x1: 12, y1: 9, x2: 12, y2: 13 }),
|
|
241
249
|
React.createElement('line', { x1: 12, y1: 17, x2: 12.01, y2: 17 }))
|
|
250
|
+
const chevronIcon = () => React.createElement('svg', ic,
|
|
251
|
+
React.createElement('path', { d: 'M6 9l6 6 6-6' }))
|
|
242
252
|
|
|
243
253
|
// ------------------------------------------------------------ transport
|
|
244
254
|
function blobToBase64(blob) {
|
|
@@ -875,7 +885,15 @@ window.__ModuleLoader__.load({
|
|
|
875
885
|
'.dvs-card input,.dvs-card select{background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l1);color:var(--dsw-alias-label-primary);border-radius:6px;padding:6px 8px;font-size:13px}' +
|
|
876
886
|
'.dvs-wait{font-size:13px;color:var(--dsw-alias-label-secondary);line-height:1.5;max-width:520px}' +
|
|
877
887
|
'.dvs-ok{font-size:12px;color:var(--dsw-alias-state-success-primary)}' +
|
|
878
|
-
'.dvs-bad{font-size:12px;color:var(--dsw-alias-state-error-primary)}'
|
|
888
|
+
'.dvs-bad{font-size:12px;color:var(--dsw-alias-state-error-primary)}' +
|
|
889
|
+
'.dvo-pcard{list-style:none;background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l1);border-radius:10px}' +
|
|
890
|
+
'.dvo-phead{display:flex;align-items:center;gap:8px;width:100%;padding:12px 14px;background:transparent;border:none;border-radius:10px;cursor:pointer;text-align:left;font:inherit;color:inherit}' +
|
|
891
|
+
'.dvo-pheadtext{display:flex;flex-direction:column;gap:2px;flex:1;min-width:0}' +
|
|
892
|
+
'.dvo-ptitle{font-size:14px;font-weight:600;color:var(--dsw-alias-label-primary)}' +
|
|
893
|
+
'.dvo-pdesc{font-size:12px;color:var(--dsw-alias-label-secondary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}' +
|
|
894
|
+
'.dvo-pchev{flex:none;display:flex;color:var(--dsw-alias-label-secondary);transition:transform .15s ease}' +
|
|
895
|
+
'.dvo-pcardOpen .dvo-pchev{transform:rotate(180deg)}' +
|
|
896
|
+
'.dvo-pbody{border-top:1px solid var(--dsw-alias-border-l1);padding:12px 14px}'
|
|
879
897
|
const setCssId = 'dsh-voice/settings.module.css'
|
|
880
898
|
if (typeof document !== 'undefined' && !document.querySelector('style[data-plugin-css="' + setCssId + '"]')) {
|
|
881
899
|
const tag = document.createElement('style')
|
|
@@ -1080,17 +1098,31 @@ window.__ModuleLoader__.load({
|
|
|
1080
1098
|
const save = async () => {
|
|
1081
1099
|
setErr(''); setSaved(false)
|
|
1082
1100
|
if (!draft) return
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1101
|
+
|
|
1102
|
+
// Поля пишутся по одному, и раньше первая же неудача обрывала цикл:
|
|
1103
|
+
// остальные поля не записывались, композер не получал сигнала
|
|
1104
|
+
// перечитать клавишу, а на кнопку это было похоже на «ничего не
|
|
1105
|
+
// происходит». Теперь пишутся все, а неудачи собираются и называются
|
|
1106
|
+
// поимённо — иначе непонятно, какое поле виновато.
|
|
1107
|
+
const failed = []
|
|
1108
|
+
for (const k of Object.keys(draft)) {
|
|
1109
|
+
try {
|
|
1110
|
+
await scope.set(k, draft[k])
|
|
1111
|
+
} catch (e) {
|
|
1112
|
+
failed.push(k + ': ' + String(e && e.message ? e.message : e))
|
|
1088
1113
|
}
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
voice.settings = {
|
|
1117
|
+
vadSilenceMs: Number(draft.dictation && draft.dictation.vadSilenceMs) || 700,
|
|
1118
|
+
autoSendMs: Number(draft.message && draft.message.autoSendMs) || 4000,
|
|
1119
|
+
}
|
|
1120
|
+
// Композер держит обработчик клавиши: пусть перечитает настройку,
|
|
1121
|
+
// иначе новая клавиша заработает только после перезагрузки страницы.
|
|
1122
|
+
try { window.dispatchEvent(new CustomEvent('dsh-voice:settings-saved')) } catch (noEvents) { /* некому */ }
|
|
1123
|
+
|
|
1124
|
+
if (failed.length) { setErr(t('saveFailed') + ' ' + failed.join('; ')); return }
|
|
1125
|
+
setSaved(true); setTimeout(() => setSaved(false), 2000)
|
|
1094
1126
|
}
|
|
1095
1127
|
|
|
1096
1128
|
const modeVal = (mode, key, fallback) => {
|
|
@@ -1194,13 +1226,59 @@ window.__ModuleLoader__.load({
|
|
|
1194
1226
|
)
|
|
1195
1227
|
}
|
|
1196
1228
|
|
|
1229
|
+
// Карточка во вкладке «Настройки плагинов» (#18): ядро даёт только рамку
|
|
1230
|
+
// списка, поэтому заголовок, пояснение и сворачивание — наши.
|
|
1231
|
+
// Тело монтируется при первом раскрытии: снапшот настроек тогда и придёт.
|
|
1232
|
+
function PluginCard(props) {
|
|
1233
|
+
const [open, setOpen] = React.useState(false)
|
|
1234
|
+
const tt = (props && props.t) || t
|
|
1235
|
+
const title = tt('title')
|
|
1236
|
+
return React.createElement('li',
|
|
1237
|
+
{ className: open ? 'dvo-pcard dvo-pcardOpen' : 'dvo-pcard' },
|
|
1238
|
+
React.createElement('button',
|
|
1239
|
+
{
|
|
1240
|
+
type: 'button', className: 'dvo-phead',
|
|
1241
|
+
'aria-expanded': open ? 'true' : 'false',
|
|
1242
|
+
'aria-label': (open ? tt('collapse') : tt('expand')) + ': ' + title,
|
|
1243
|
+
onClick: () => setOpen((v) => !v),
|
|
1244
|
+
},
|
|
1245
|
+
React.createElement('span', { className: 'dvo-pheadtext' },
|
|
1246
|
+
React.createElement('span', { className: 'dvo-ptitle' }, title),
|
|
1247
|
+
React.createElement('span', { className: 'dvo-pdesc' }, tt('cardHint')),
|
|
1248
|
+
),
|
|
1249
|
+
React.createElement('span', { className: 'dvo-pchev' }, chevronIcon()),
|
|
1250
|
+
),
|
|
1251
|
+
open ? React.createElement('div', { className: 'dvo-pbody' }, React.createElement(VoiceSection, props)) : null,
|
|
1252
|
+
)
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1197
1255
|
function registerSettings(ctx) {
|
|
1256
|
+
// Слот объявлен пакетом настроек ядра; вкладка перебирает пространства
|
|
1257
|
+
// настроек, поэтому ключ регистрации обязан равняться NS, не id пакета:
|
|
1258
|
+
// не равен — карточка не появится вовсе, молча.
|
|
1259
|
+
const tryPluginItem = () => {
|
|
1260
|
+
try {
|
|
1261
|
+
ctx.slots.inject('settings.plugin.item', () => ctx.slots.register(
|
|
1262
|
+
{
|
|
1263
|
+
name: 'settings.plugin.item',
|
|
1264
|
+
key: NS,
|
|
1265
|
+
// locale в записи слота — то, из-за чего компонент получает props.t.
|
|
1266
|
+
locale: NS,
|
|
1267
|
+
inject: () => ({ ctx: ctx }),
|
|
1268
|
+
},
|
|
1269
|
+
PluginCard,
|
|
1270
|
+
))
|
|
1271
|
+
return true
|
|
1272
|
+
} catch (e) { return false }
|
|
1273
|
+
}
|
|
1274
|
+
if (tryPluginItem()) return
|
|
1275
|
+
// Запасной путь: в этой сборке слота нет — остаёмся боковым
|
|
1276
|
+
// разделом, чтобы настройки не пропали.
|
|
1198
1277
|
ctx.slots.inject('settings.section', () => ctx.slots.register(
|
|
1199
1278
|
{
|
|
1200
1279
|
name: 'settings.section',
|
|
1201
1280
|
id: '@goodandready/dsh-voice',
|
|
1202
1281
|
order: 30,
|
|
1203
|
-
// locale в записи слота — то, из-за чего компонент получает props.t.
|
|
1204
1282
|
locale: NS,
|
|
1205
1283
|
label: () => t('title'),
|
|
1206
1284
|
inject: () => ({ ctx: ctx }),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-voice",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.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",
|