@goodandready/dsh-tts 0.3.0 → 0.3.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/lib/client.js +151 -36
- 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,119 @@ 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
|
+
}
|
|
357
|
+
const ru = {
|
|
358
|
+
'general': 'Общее',
|
|
359
|
+
'loading': 'Загрузка…',
|
|
360
|
+
'chainHintCard': 'Выберите провайдера, вставьте ключ и нажмите «Сохранить» (или просто уйдите из поля). Ключ сразу попадает в хранилище учётных данных харнесса; браузер его обратно не читает.',
|
|
361
|
+
'speakRepliesHint': 'Каждый законченный ответ озвучивается на стороне харнесса и играет в этом браузере. Звук модели не отправляется.',
|
|
362
|
+
'chainHint': 'Порядок сверху вниз — порядок отката. Ключи облачных провайдеров попадают в хранилище учётных данных харнесса, а не в настройки плагина.',
|
|
363
|
+
'title': 'Озвучка',
|
|
364
|
+
'speakReplies': 'Читать ответы агента',
|
|
365
|
+
'speakAsItGoes': 'Читать по ходу',
|
|
366
|
+
'speakAsItGoesHint': 'Читать каждый ответ, как только он пришёл, предложениями, не дожидаясь конца хода.',
|
|
367
|
+
'rate': 'Скорость чтения',
|
|
368
|
+
'rateHint': 'От 0.5 до 2. Синтез не меняется — быстрее или медленнее играет браузер.',
|
|
369
|
+
'bargeIn': 'Замолкать, когда вы заговорили',
|
|
370
|
+
'bargeInHint': 'Прекращать чтение, как только открылся микрофон.',
|
|
371
|
+
'announce': 'Объявлять запрос подтверждения',
|
|
372
|
+
'announceHint': 'Говорить вслух и подавать сигнал, когда агент остановился ради подтверждения или задал вопрос.',
|
|
373
|
+
'approvalText': 'Что говорить о подтверждении',
|
|
374
|
+
'approvalHint': 'Произносится при запросе подтверждения; имя инструмента добавляется следом.',
|
|
375
|
+
'questionText': 'Что говорить о вопросе',
|
|
376
|
+
'questionHint': 'Произносится, когда агент задал вопрос. Пусто — не произносить.',
|
|
377
|
+
'chime': 'Сигнал',
|
|
378
|
+
'chimeHint': 'ding, beep или none.',
|
|
379
|
+
'language': 'Язык',
|
|
380
|
+
'languageHint': 'Подсказка для голосов, которым нужен язык (eSpeak, Azure SSML).',
|
|
381
|
+
'piperModel': 'Piper: путь к модели',
|
|
382
|
+
'piperModelHint': 'Абсолютный путь к модели ONNX. Пусто — Piper пропускается.',
|
|
383
|
+
'piperBin': 'Piper: бинарь',
|
|
384
|
+
'binHint': 'Ищется в PATH, если путь не абсолютный.',
|
|
385
|
+
'espeakBin': 'eSpeak: бинарь',
|
|
386
|
+
'espeakHint': 'Обычно espeak-ng.',
|
|
387
|
+
'edgeBin': 'edge-tts: бинарь',
|
|
388
|
+
'edgeHint': 'Консольная утилита из пакета edge-tts.',
|
|
389
|
+
'azureRegion': 'Azure: регион',
|
|
390
|
+
'azureHint': 'Нужен провайдеру azure, например eastus.',
|
|
391
|
+
'chainTitle': 'Цепочка провайдеров',
|
|
392
|
+
'addProvider': 'Добавить провайдера',
|
|
393
|
+
'localProvider': 'Локальный провайдер — ключ не нужен',
|
|
394
|
+
'configured': 'Ключ задан',
|
|
395
|
+
'notSet': 'Ключа нет',
|
|
396
|
+
'fromEnv': 'Задан в окружении',
|
|
397
|
+
'clear': 'Убрать',
|
|
398
|
+
'remove': 'Удалить',
|
|
399
|
+
'up': 'Выше',
|
|
400
|
+
'down': 'Ниже',
|
|
401
|
+
'save': 'Сохранить',
|
|
402
|
+
'saved': 'Сохранено',
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function TtsSection(props) {
|
|
406
|
+
// Переводчик приходит от слота, потому что в его записи указан locale.
|
|
407
|
+
const t = (props && props.t) || ((key) => key)
|
|
306
408
|
const [draft, setDraft] = React.useState(null)
|
|
307
409
|
const [credentials, setCredentials] = React.useState({})
|
|
308
410
|
const [keyDrafts, setKeyDrafts] = React.useState({})
|
|
@@ -326,7 +428,7 @@ window.__ModuleLoader__.load({
|
|
|
326
428
|
return () => { alive = false }
|
|
327
429
|
}, [])
|
|
328
430
|
|
|
329
|
-
if (!draft) return React.createElement('div', { className: 'dts-wrap' }, '
|
|
431
|
+
if (!draft) return React.createElement('div', { className: 'dts-wrap' }, t('loading'))
|
|
330
432
|
|
|
331
433
|
const setTop = (key, v) => setDraft((d) => Object.assign({}, d || {}, { [key]: v }))
|
|
332
434
|
const setDraftKey = (provider, value) => setKeyDrafts((d) => Object.assign({}, d, { [provider]: value }))
|
|
@@ -411,22 +513,23 @@ window.__ModuleLoader__.load({
|
|
|
411
513
|
|
|
412
514
|
return React.createElement('div', { className: 'dts-wrap' },
|
|
413
515
|
React.createElement('div', { className: 'dts-block' },
|
|
414
|
-
React.createElement('div', { className: 'dts-h' }, '
|
|
516
|
+
React.createElement('div', { className: 'dts-h' }, t('title')),
|
|
415
517
|
React.createElement('label', { className: 'dts-row' },
|
|
416
518
|
React.createElement('input', {
|
|
417
519
|
type: 'checkbox', checked: !!(draft && draft.speakReplies), disabled: !writable,
|
|
418
520
|
onChange: (e) => setTop('speakReplies', e.target.checked),
|
|
419
521
|
}),
|
|
420
|
-
React.createElement('span', null, '
|
|
522
|
+
React.createElement('span', null, t('speakReplies')),
|
|
421
523
|
),
|
|
422
524
|
React.createElement('div', { className: 'dts-sub' },
|
|
423
|
-
'
|
|
525
|
+
t('speakRepliesHint')),
|
|
424
526
|
),
|
|
425
527
|
React.createElement('div', { className: 'dts-block' },
|
|
426
|
-
React.createElement('div', { className: 'dts-h' }, '
|
|
528
|
+
React.createElement('div', { className: 'dts-h' }, t('chainTitle')),
|
|
427
529
|
React.createElement('div', { className: 'dts-sub' },
|
|
428
|
-
|
|
530
|
+
t('chainHintCard')),
|
|
429
531
|
React.createElement(ChainEditor, {
|
|
532
|
+
t: t,
|
|
430
533
|
value: draft && draft.chain ? draft.chain : [], writable: writable,
|
|
431
534
|
credentials: credentials, keyDrafts: keyDrafts,
|
|
432
535
|
onChange: (v) => setTop('chain', v),
|
|
@@ -436,27 +539,27 @@ window.__ModuleLoader__.load({
|
|
|
436
539
|
}),
|
|
437
540
|
),
|
|
438
541
|
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', '
|
|
542
|
+
React.createElement('div', { className: 'dts-h' }, t('general')),
|
|
543
|
+
boolField('speakAsItGoes', t('speakAsItGoes'),
|
|
544
|
+
t('speakAsItGoesHint')),
|
|
545
|
+
numberField('rate', t('rate'), t('rateHint'), 0.1),
|
|
546
|
+
boolField('bargeIn', t('bargeIn'),
|
|
547
|
+
t('bargeInHint')),
|
|
548
|
+
boolField('announceApproval', t('announce'),
|
|
549
|
+
t('announceHint')),
|
|
550
|
+
textField('approvalText', t('approvalText'), t('approvalHint')),
|
|
551
|
+
textField('questionText', t('questionText'), t('questionHint')),
|
|
552
|
+
textField('chime', t('chime'), t('chimeHint')),
|
|
553
|
+
textField('language', t('language'), t('languageHint')),
|
|
554
|
+
textField('piperModel', t('piperModel'), t('piperModelHint')),
|
|
555
|
+
textField('piperBin', t('piperBin'), t('binHint')),
|
|
556
|
+
textField('espeakBin', t('espeakBin'), t('espeakHint')),
|
|
557
|
+
textField('edgeBin', t('edgeBin'), t('edgeHint')),
|
|
558
|
+
textField('azureRegion', t('azureRegion'), t('azureHint')),
|
|
456
559
|
),
|
|
457
560
|
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' }, '
|
|
561
|
+
React.createElement('button', { type: 'button', className: 'dts-save', disabled: !writable, onClick: save }, t('save')),
|
|
562
|
+
saved ? React.createElement('span', { className: 'dts-ok' }, t('saved')) : null,
|
|
460
563
|
err ? React.createElement('span', { className: 'dts-bad' }, err) : null,
|
|
461
564
|
),
|
|
462
565
|
)
|
|
@@ -491,13 +594,25 @@ window.__ModuleLoader__.load({
|
|
|
491
594
|
}
|
|
492
595
|
|
|
493
596
|
function registerSettings(ctx) {
|
|
597
|
+
ctx.effect(() => ctx.locale.register(NS, { en, ru }), 'dsh-tts: словари')
|
|
598
|
+
// Подпись раздела рисует боковой список, а не наш компонент: props.t
|
|
599
|
+
// туда не доходит, поэтому берём переводчик, привязанный к namespace.
|
|
600
|
+
const t = ctx.locale.bind(NS)
|
|
494
601
|
ctx.slots.inject('settings.section', () => ctx.slots.register(
|
|
495
|
-
{
|
|
602
|
+
{
|
|
603
|
+
name: 'settings.section',
|
|
604
|
+
id: '@goodandready/dsh-tts',
|
|
605
|
+
order: 32,
|
|
606
|
+
// locale в записи слота — то, из-за чего компонент получает props.t.
|
|
607
|
+
locale: NS,
|
|
608
|
+
label: () => t('title'),
|
|
609
|
+
inject: () => ({ ctx: ctx }),
|
|
610
|
+
},
|
|
496
611
|
TtsSection,
|
|
497
612
|
))
|
|
498
613
|
}
|
|
499
614
|
|
|
500
|
-
exports.inject = ['slots']
|
|
615
|
+
exports.inject = ['slots', 'locale']
|
|
501
616
|
exports.apply = function apply(ctx) {
|
|
502
617
|
registerSettings(ctx)
|
|
503
618
|
registerSpeaker(ctx)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-tts",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
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",
|