@goodandready/dsh-tts 0.3.0 → 0.3.2
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/lib/client.js +201 -37
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -239,6 +239,8 @@ window.__ModuleLoader__.load({
|
|
|
239
239
|
}
|
|
240
240
|
|
|
241
241
|
function ChainEditor(props) {
|
|
242
|
+
// Переводчик приходит от карточки: слот отдаёт props.t только ей.
|
|
243
|
+
const t = props.t || ((key) => key)
|
|
242
244
|
const rows = Array.isArray(props.value) ? props.value : []
|
|
243
245
|
const change = (i, patch) => {
|
|
244
246
|
const next = rows.map((r, k) => (k === i ? Object.assign({}, r, patch) : r))
|
|
@@ -259,7 +261,7 @@ window.__ModuleLoader__.load({
|
|
|
259
261
|
const cred = credOf(props.credentials, row.provider)
|
|
260
262
|
const draft = (props.keyDrafts && props.keyDrafts[row.provider]) || ''
|
|
261
263
|
const badge = !cloud ? null
|
|
262
|
-
: (!cred.writable ? '
|
|
264
|
+
: (!cred.writable ? t('fromEnv') : (cred.configured ? t('configured') : t('notSet')))
|
|
263
265
|
return React.createElement('div', { className: 'dts-entry', key: i },
|
|
264
266
|
React.createElement('div', { className: 'dts-row' },
|
|
265
267
|
React.createElement('select', {
|
|
@@ -274,9 +276,9 @@ window.__ModuleLoader__.load({
|
|
|
274
276
|
className: 'dts-model', value: row.voice || '', disabled: !props.writable,
|
|
275
277
|
placeholder: 'voice', onChange: (e) => change(i, { voice: e.target.value }),
|
|
276
278
|
}),
|
|
277
|
-
React.createElement('button', { type: 'button', className: 'dts-mini', title: '
|
|
278
|
-
React.createElement('button', { type: 'button', className: 'dts-mini', title: '
|
|
279
|
-
React.createElement('button', { type: 'button', className: 'dts-mini', title: '
|
|
279
|
+
React.createElement('button', { type: 'button', className: 'dts-mini', title: t('up'), disabled: !props.writable, onClick: () => move(i, -1) }, '\u2191'),
|
|
280
|
+
React.createElement('button', { type: 'button', className: 'dts-mini', title: t('down'), disabled: !props.writable, onClick: () => move(i, 1) }, '\u2193'),
|
|
281
|
+
React.createElement('button', { type: 'button', className: 'dts-mini', title: t('remove'), disabled: !props.writable, onClick: () => remove(i) }, '\u00d7'),
|
|
280
282
|
),
|
|
281
283
|
cloud ? React.createElement('div', { className: 'dts-row' },
|
|
282
284
|
React.createElement('input', {
|
|
@@ -290,19 +292,121 @@ window.__ModuleLoader__.load({
|
|
|
290
292
|
cred.configured && cred.writable ? React.createElement('button', {
|
|
291
293
|
type: 'button', className: 'dts-link', disabled: !props.writable,
|
|
292
294
|
onClick: () => props.onClearKey(row.provider),
|
|
293
|
-
}, '
|
|
294
|
-
) : React.createElement('span', { className: 'dts-sub' }, '
|
|
295
|
+
}, t('clear')) : null,
|
|
296
|
+
) : React.createElement('span', { className: 'dts-sub' }, t('localProvider')),
|
|
295
297
|
cloud && cred.ref ? React.createElement('span', { className: 'dts-sub' }, 'Stored as ' + cred.ref) : null,
|
|
296
298
|
)
|
|
297
299
|
}),
|
|
298
300
|
React.createElement('div', { className: 'dts-row' },
|
|
299
|
-
React.createElement('button', { type: 'button', className: 'dts-mini', title: '
|
|
300
|
-
React.createElement('span', { className: 'dts-sub' }, '
|
|
301
|
+
React.createElement('button', { type: 'button', className: 'dts-mini', title: t('addProvider'), disabled: !props.writable, onClick: add }, '+'),
|
|
302
|
+
React.createElement('span', { className: 'dts-sub' }, t('chainHint')),
|
|
301
303
|
),
|
|
302
304
|
)
|
|
303
305
|
}
|
|
304
306
|
|
|
305
|
-
|
|
307
|
+
// Строки карточки живут в реестре локалей: так их переводит отдельный
|
|
308
|
+
// пакет, не трогая код этого плагина. Английский — язык по умолчанию,
|
|
309
|
+
// на него же приходится откат, если перевода нет.
|
|
310
|
+
const en = {
|
|
311
|
+
'general': 'General',
|
|
312
|
+
'loading': 'Loading\u2026',
|
|
313
|
+
'chainHintCard': 'Pick a provider, paste its API key, and Save (or leave the key field). The key is written to the host credentials store immediately. The browser never reads it back.',
|
|
314
|
+
'speakRepliesHint': 'When on, each finished reply is synthesized on the host and played in this browser. Audio is not sent back to the model.',
|
|
315
|
+
'chainHint': 'Top to bottom is the fallback order. Cloud keys are saved to the host credentials store, never into plugin settings.',
|
|
316
|
+
'title': 'Speech',
|
|
317
|
+
'speakReplies': 'Speak agent replies',
|
|
318
|
+
'speakAsItGoes': 'Speak as it goes',
|
|
319
|
+
'speakAsItGoesHint': 'Read each reply the moment it lands, sentence by sentence, instead of waiting for the whole turn.',
|
|
320
|
+
'rate': 'Playback speed',
|
|
321
|
+
'rateHint': '0.5 to 2. Synthesis is untouched; the browser plays faster or slower.',
|
|
322
|
+
'bargeIn': 'Fall silent when you speak',
|
|
323
|
+
'bargeInHint': 'Stop reading the moment the microphone opens.',
|
|
324
|
+
'announce': 'Announce approvals',
|
|
325
|
+
'announceHint': 'Say it out loud and play a chime when the agent stops for an approval or asks a question.',
|
|
326
|
+
'approvalText': 'Approval wording',
|
|
327
|
+
'approvalHint': 'Said when an approval is asked for; the tool name is appended.',
|
|
328
|
+
'questionText': 'Question wording',
|
|
329
|
+
'questionHint': 'Said when the agent asks a question. Empty disables it.',
|
|
330
|
+
'chime': 'Chime',
|
|
331
|
+
'chimeHint': 'ding, beep or none.',
|
|
332
|
+
'language': 'Language',
|
|
333
|
+
'languageHint': 'Hint for voices that need a locale (eSpeak, Azure SSML).',
|
|
334
|
+
'piperModel': 'Piper model path',
|
|
335
|
+
'piperModelHint': 'Absolute path to an ONNX model. Leave empty to skip Piper.',
|
|
336
|
+
'piperBin': 'Piper binary',
|
|
337
|
+
'binHint': 'Looked up in PATH unless absolute.',
|
|
338
|
+
'espeakBin': 'eSpeak binary',
|
|
339
|
+
'espeakHint': 'Usually espeak-ng.',
|
|
340
|
+
'edgeBin': 'edge-tts binary',
|
|
341
|
+
'edgeHint': 'Python CLI from the edge-tts package.',
|
|
342
|
+
'azureRegion': 'Azure region',
|
|
343
|
+
'azureHint': 'Required for the azure provider, e.g. eastus.',
|
|
344
|
+
'chainTitle': 'Provider chain',
|
|
345
|
+
'addProvider': 'Add provider',
|
|
346
|
+
'localProvider': 'Local / CLI provider — no API key',
|
|
347
|
+
'configured': 'Configured',
|
|
348
|
+
'notSet': 'Not set',
|
|
349
|
+
'fromEnv': 'Set in environment',
|
|
350
|
+
'clear': 'Clear',
|
|
351
|
+
'remove': 'Remove',
|
|
352
|
+
'up': 'Up',
|
|
353
|
+
'down': 'Down',
|
|
354
|
+
'save': 'Save',
|
|
355
|
+
'saved': 'Saved',
|
|
356
|
+
'cardHint': 'Provider chain, voices and playback options.',
|
|
357
|
+
}
|
|
358
|
+
const ru = {
|
|
359
|
+
'general': 'Общее',
|
|
360
|
+
'loading': 'Загрузка…',
|
|
361
|
+
'chainHintCard': 'Выберите провайдера, вставьте ключ и нажмите «Сохранить» (или просто уйдите из поля). Ключ сразу попадает в хранилище учётных данных харнесса; браузер его обратно не читает.',
|
|
362
|
+
'speakRepliesHint': 'Каждый законченный ответ озвучивается на стороне харнесса и играет в этом браузере. Звук модели не отправляется.',
|
|
363
|
+
'chainHint': 'Порядок сверху вниз — порядок отката. Ключи облачных провайдеров попадают в хранилище учётных данных харнесса, а не в настройки плагина.',
|
|
364
|
+
'title': 'Озвучка',
|
|
365
|
+
'speakReplies': 'Читать ответы агента',
|
|
366
|
+
'speakAsItGoes': 'Читать по ходу',
|
|
367
|
+
'speakAsItGoesHint': 'Читать каждый ответ, как только он пришёл, предложениями, не дожидаясь конца хода.',
|
|
368
|
+
'rate': 'Скорость чтения',
|
|
369
|
+
'rateHint': 'От 0.5 до 2. Синтез не меняется — быстрее или медленнее играет браузер.',
|
|
370
|
+
'bargeIn': 'Замолкать, когда вы заговорили',
|
|
371
|
+
'bargeInHint': 'Прекращать чтение, как только открылся микрофон.',
|
|
372
|
+
'announce': 'Объявлять запрос подтверждения',
|
|
373
|
+
'announceHint': 'Говорить вслух и подавать сигнал, когда агент остановился ради подтверждения или задал вопрос.',
|
|
374
|
+
'approvalText': 'Что говорить о подтверждении',
|
|
375
|
+
'approvalHint': 'Произносится при запросе подтверждения; имя инструмента добавляется следом.',
|
|
376
|
+
'questionText': 'Что говорить о вопросе',
|
|
377
|
+
'questionHint': 'Произносится, когда агент задал вопрос. Пусто — не произносить.',
|
|
378
|
+
'chime': 'Сигнал',
|
|
379
|
+
'chimeHint': 'ding, beep или none.',
|
|
380
|
+
'language': 'Язык',
|
|
381
|
+
'languageHint': 'Подсказка для голосов, которым нужен язык (eSpeak, Azure SSML).',
|
|
382
|
+
'piperModel': 'Piper: путь к модели',
|
|
383
|
+
'piperModelHint': 'Абсолютный путь к модели ONNX. Пусто — Piper пропускается.',
|
|
384
|
+
'piperBin': 'Piper: бинарь',
|
|
385
|
+
'binHint': 'Ищется в PATH, если путь не абсолютный.',
|
|
386
|
+
'espeakBin': 'eSpeak: бинарь',
|
|
387
|
+
'espeakHint': 'Обычно espeak-ng.',
|
|
388
|
+
'edgeBin': 'edge-tts: бинарь',
|
|
389
|
+
'edgeHint': 'Консольная утилита из пакета edge-tts.',
|
|
390
|
+
'azureRegion': 'Azure: регион',
|
|
391
|
+
'azureHint': 'Нужен провайдеру azure, например eastus.',
|
|
392
|
+
'chainTitle': 'Цепочка провайдеров',
|
|
393
|
+
'addProvider': 'Добавить провайдера',
|
|
394
|
+
'localProvider': 'Локальный провайдер — ключ не нужен',
|
|
395
|
+
'configured': 'Ключ задан',
|
|
396
|
+
'notSet': 'Ключа нет',
|
|
397
|
+
'fromEnv': 'Задан в окружении',
|
|
398
|
+
'clear': 'Убрать',
|
|
399
|
+
'remove': 'Удалить',
|
|
400
|
+
'up': 'Выше',
|
|
401
|
+
'down': 'Ниже',
|
|
402
|
+
'save': 'Сохранить',
|
|
403
|
+
'saved': 'Сохранено',
|
|
404
|
+
'cardHint': 'Цепочка провайдеров, голоса и параметры чтения.',
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
function TtsSection(props) {
|
|
408
|
+
// Переводчик приходит от слота, потому что в его записи указан locale.
|
|
409
|
+
const t = (props && props.t) || ((key) => key)
|
|
306
410
|
const [draft, setDraft] = React.useState(null)
|
|
307
411
|
const [credentials, setCredentials] = React.useState({})
|
|
308
412
|
const [keyDrafts, setKeyDrafts] = React.useState({})
|
|
@@ -326,7 +430,7 @@ window.__ModuleLoader__.load({
|
|
|
326
430
|
return () => { alive = false }
|
|
327
431
|
}, [])
|
|
328
432
|
|
|
329
|
-
if (!draft) return React.createElement('div', { className: 'dts-wrap' }, '
|
|
433
|
+
if (!draft) return React.createElement('div', { className: 'dts-wrap' }, t('loading'))
|
|
330
434
|
|
|
331
435
|
const setTop = (key, v) => setDraft((d) => Object.assign({}, d || {}, { [key]: v }))
|
|
332
436
|
const setDraftKey = (provider, value) => setKeyDrafts((d) => Object.assign({}, d, { [provider]: value }))
|
|
@@ -411,22 +515,23 @@ window.__ModuleLoader__.load({
|
|
|
411
515
|
|
|
412
516
|
return React.createElement('div', { className: 'dts-wrap' },
|
|
413
517
|
React.createElement('div', { className: 'dts-block' },
|
|
414
|
-
React.createElement('div', { className: 'dts-h' }, '
|
|
518
|
+
React.createElement('div', { className: 'dts-h' }, t('title')),
|
|
415
519
|
React.createElement('label', { className: 'dts-row' },
|
|
416
520
|
React.createElement('input', {
|
|
417
521
|
type: 'checkbox', checked: !!(draft && draft.speakReplies), disabled: !writable,
|
|
418
522
|
onChange: (e) => setTop('speakReplies', e.target.checked),
|
|
419
523
|
}),
|
|
420
|
-
React.createElement('span', null, '
|
|
524
|
+
React.createElement('span', null, t('speakReplies')),
|
|
421
525
|
),
|
|
422
526
|
React.createElement('div', { className: 'dts-sub' },
|
|
423
|
-
'
|
|
527
|
+
t('speakRepliesHint')),
|
|
424
528
|
),
|
|
425
529
|
React.createElement('div', { className: 'dts-block' },
|
|
426
|
-
React.createElement('div', { className: 'dts-h' }, '
|
|
530
|
+
React.createElement('div', { className: 'dts-h' }, t('chainTitle')),
|
|
427
531
|
React.createElement('div', { className: 'dts-sub' },
|
|
428
|
-
|
|
532
|
+
t('chainHintCard')),
|
|
429
533
|
React.createElement(ChainEditor, {
|
|
534
|
+
t: t,
|
|
430
535
|
value: draft && draft.chain ? draft.chain : [], writable: writable,
|
|
431
536
|
credentials: credentials, keyDrafts: keyDrafts,
|
|
432
537
|
onChange: (v) => setTop('chain', v),
|
|
@@ -436,32 +541,58 @@ window.__ModuleLoader__.load({
|
|
|
436
541
|
}),
|
|
437
542
|
),
|
|
438
543
|
React.createElement('div', { className: 'dts-block' },
|
|
439
|
-
React.createElement('div', { className: 'dts-h' }, '
|
|
440
|
-
boolField('speakAsItGoes', '
|
|
441
|
-
'
|
|
442
|
-
numberField('rate', '
|
|
443
|
-
boolField('bargeIn', '
|
|
444
|
-
'
|
|
445
|
-
boolField('announceApproval', '
|
|
446
|
-
'
|
|
447
|
-
textField('approvalText', '
|
|
448
|
-
textField('questionText', '
|
|
449
|
-
textField('chime', '
|
|
450
|
-
textField('language', '
|
|
451
|
-
textField('piperModel', '
|
|
452
|
-
textField('piperBin', '
|
|
453
|
-
textField('espeakBin', '
|
|
454
|
-
textField('edgeBin', '
|
|
455
|
-
textField('azureRegion', '
|
|
544
|
+
React.createElement('div', { className: 'dts-h' }, t('general')),
|
|
545
|
+
boolField('speakAsItGoes', t('speakAsItGoes'),
|
|
546
|
+
t('speakAsItGoesHint')),
|
|
547
|
+
numberField('rate', t('rate'), t('rateHint'), 0.1),
|
|
548
|
+
boolField('bargeIn', t('bargeIn'),
|
|
549
|
+
t('bargeInHint')),
|
|
550
|
+
boolField('announceApproval', t('announce'),
|
|
551
|
+
t('announceHint')),
|
|
552
|
+
textField('approvalText', t('approvalText'), t('approvalHint')),
|
|
553
|
+
textField('questionText', t('questionText'), t('questionHint')),
|
|
554
|
+
textField('chime', t('chime'), t('chimeHint')),
|
|
555
|
+
textField('language', t('language'), t('languageHint')),
|
|
556
|
+
textField('piperModel', t('piperModel'), t('piperModelHint')),
|
|
557
|
+
textField('piperBin', t('piperBin'), t('binHint')),
|
|
558
|
+
textField('espeakBin', t('espeakBin'), t('espeakHint')),
|
|
559
|
+
textField('edgeBin', t('edgeBin'), t('edgeHint')),
|
|
560
|
+
textField('azureRegion', t('azureRegion'), t('azureHint')),
|
|
456
561
|
),
|
|
457
562
|
React.createElement('div', { className: 'dts-row' },
|
|
458
|
-
React.createElement('button', { type: 'button', className: 'dts-save', disabled: !writable, onClick: save }, '
|
|
459
|
-
saved ? React.createElement('span', { className: 'dts-ok' }, '
|
|
563
|
+
React.createElement('button', { type: 'button', className: 'dts-save', disabled: !writable, onClick: save }, t('save')),
|
|
564
|
+
saved ? React.createElement('span', { className: 'dts-ok' }, t('saved')) : null,
|
|
460
565
|
err ? React.createElement('span', { className: 'dts-bad' }, err) : null,
|
|
461
566
|
),
|
|
462
567
|
)
|
|
463
568
|
}
|
|
464
569
|
|
|
570
|
+
|
|
571
|
+
// Карточка настроек во вкладке «Плагины»: заголовок, пояснение и
|
|
572
|
+
// сворачивание рисуем сами — ядро даёт только рамку списка.
|
|
573
|
+
function TtsCard(props) {
|
|
574
|
+
const t = (props && props.t) || ((key) => key)
|
|
575
|
+
const [open, setOpen] = React.useState(false)
|
|
576
|
+
return React.createElement('div',
|
|
577
|
+
{
|
|
578
|
+
className: 'dts-wrap',
|
|
579
|
+
style: { border: '1px solid var(--dsw-alias-border-l2)', borderRadius: '8px', marginBottom: '10px' },
|
|
580
|
+
},
|
|
581
|
+
React.createElement('button',
|
|
582
|
+
{
|
|
583
|
+
type: 'button',
|
|
584
|
+
'aria-expanded': open ? 'true' : 'false',
|
|
585
|
+
onClick: () => setOpen((v) => !v),
|
|
586
|
+
style: { display: 'flex', width: '100%', gap: '8px', alignItems: 'baseline', background: 'none', border: 'none', padding: '10px 12px', cursor: 'pointer', textAlign: 'left' },
|
|
587
|
+
},
|
|
588
|
+
React.createElement('strong', null, t('title')),
|
|
589
|
+
React.createElement('span', { className: 'dts-sub' }, t('cardHint')),
|
|
590
|
+
React.createElement('span', { style: { marginLeft: 'auto', color: 'var(--dsw-alias-label-secondary)' } }, open ? '\u25be' : '\u25b8'),
|
|
591
|
+
),
|
|
592
|
+
open ? React.createElement(TtsSection, props) : null,
|
|
593
|
+
)
|
|
594
|
+
}
|
|
595
|
+
|
|
465
596
|
// Кнопка в строке ввода: видно, что сейчас читается, и можно остановить.
|
|
466
597
|
function SpeakerControl() {
|
|
467
598
|
const p = usePlayer()
|
|
@@ -491,18 +622,51 @@ window.__ModuleLoader__.load({
|
|
|
491
622
|
}
|
|
492
623
|
|
|
493
624
|
function registerSettings(ctx) {
|
|
625
|
+
ctx.effect(() => ctx.locale.register(NS, { en, ru }), 'dsh-tts: словари')
|
|
626
|
+
// Подпись раздела рисует боковой список, а не наш компонент: props.t
|
|
627
|
+
// туда не доходит, поэтому берём переводчик, привязанный к namespace.
|
|
628
|
+
const t = ctx.locale.bind(NS)
|
|
629
|
+
// Штатное место настроек — карточка во вкладке «Настройки плагинов».
|
|
630
|
+
// Вкладка перебирает пространства настроек и ищет слот по entryKey,
|
|
631
|
+
// равному имени пространства: ключ обязан равняться NS, иначе карточка
|
|
632
|
+
// не появится молча, без единой ошибки в журнале.
|
|
633
|
+
let moved = false
|
|
634
|
+
try {
|
|
635
|
+
moved = !!ctx.slots.inject('settings.plugin.item', () => ctx.slots.register(
|
|
636
|
+
{
|
|
637
|
+
name: 'settings.plugin.item',
|
|
638
|
+
key: NS,
|
|
639
|
+
locale: NS,
|
|
640
|
+
inject: () => ({ ctx: ctx }),
|
|
641
|
+
},
|
|
642
|
+
TtsCard,
|
|
643
|
+
))
|
|
644
|
+
} catch (noPluginItemSlot) { moved = false }
|
|
645
|
+
if (moved) return
|
|
646
|
+
// Запасной путь для сборок без settings.plugin.item: прежний раздел
|
|
647
|
+
// в боковом списке, чтобы настройки не пропали.
|
|
494
648
|
ctx.slots.inject('settings.section', () => ctx.slots.register(
|
|
495
|
-
{
|
|
649
|
+
{
|
|
650
|
+
name: 'settings.section',
|
|
651
|
+
id: '@goodandready/dsh-tts',
|
|
652
|
+
order: 32,
|
|
653
|
+
locale: NS,
|
|
654
|
+
label: () => t('title'),
|
|
655
|
+
inject: () => ({ ctx: ctx }),
|
|
656
|
+
},
|
|
496
657
|
TtsSection,
|
|
497
658
|
))
|
|
498
659
|
}
|
|
499
660
|
|
|
500
|
-
exports.inject = ['slots']
|
|
661
|
+
exports.inject = ['slots', 'locale']
|
|
501
662
|
exports.apply = function apply(ctx) {
|
|
502
663
|
registerSettings(ctx)
|
|
503
664
|
registerSpeaker(ctx)
|
|
504
665
|
ctx.effect(() => listenForVoice(), 'dsh-tts: замолкать, когда человек заговорил')
|
|
505
|
-
|
|
666
|
+
ctx.effect(() => {
|
|
667
|
+
const timer = setInterval(pollPending, 1000)
|
|
668
|
+
return () => clearInterval(timer)
|
|
669
|
+
}, 'dsh-tts: опрос готовых озвучек')
|
|
506
670
|
}
|
|
507
671
|
return module.exports
|
|
508
672
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-tts",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "Text-to-speech for DeepSeek Harness: speak agent replies in the Web UI with a provider fallback chain (OpenAI, ElevenLabs, Google, Azure, Groq, Deepgram, OpenRouter, Edge, Piper, eSpeak).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|