@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.
Files changed (2) hide show
  1. package/lib/client.js +151 -36
  2. 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 ? 'Set in environment' : (cred.configured ? 'Configured' : 'Not set'))
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: 'Up', disabled: !props.writable, onClick: () => move(i, -1) }, '\u2191'),
278
- React.createElement('button', { type: 'button', className: 'dts-mini', title: 'Down', disabled: !props.writable, onClick: () => move(i, 1) }, '\u2193'),
279
- React.createElement('button', { type: 'button', className: 'dts-mini', title: 'Remove', disabled: !props.writable, onClick: () => remove(i) }, '\u00d7'),
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
- }, 'Clear') : null,
294
- ) : React.createElement('span', { className: 'dts-sub' }, 'Local / CLI provider — no API key'),
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: 'Add provider', disabled: !props.writable, onClick: add }, '+'),
300
- React.createElement('span', { className: 'dts-sub' }, 'Top to bottom is the fallback order. Cloud keys are saved to the host credentials store, never into plugin settings.'),
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
- function TtsSection() {
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' }, 'Loading\u2026')
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' }, 'Speech'),
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, 'Speak agent replies'),
522
+ React.createElement('span', null, t('speakReplies')),
421
523
  ),
422
524
  React.createElement('div', { className: 'dts-sub' },
423
- 'When on, each finished reply is synthesized on the host and played in this browser. Audio is not sent back to the model.'),
525
+ t('speakRepliesHint')),
424
526
  ),
425
527
  React.createElement('div', { className: 'dts-block' },
426
- React.createElement('div', { className: 'dts-h' }, 'Provider chain'),
528
+ React.createElement('div', { className: 'dts-h' }, t('chainTitle')),
427
529
  React.createElement('div', { className: 'dts-sub' },
428
- '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.'),
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' }, 'General'),
440
- boolField('speakAsItGoes', 'Speak as it goes',
441
- 'Read each reply the moment it lands, sentence by sentence, instead of waiting for the whole turn.'),
442
- numberField('rate', 'Playback speed', '0.5 to 2. Synthesis is untouched; the browser plays faster or slower.', 0.1),
443
- boolField('bargeIn', 'Fall silent when you speak',
444
- 'Stop reading the moment the microphone opens.'),
445
- boolField('announceApproval', 'Announce approvals',
446
- 'Say it out loud and play a chime when the agent stops for an approval or asks a question.'),
447
- textField('approvalText', 'Approval wording', 'Said when an approval is asked for; the tool name is appended.'),
448
- textField('questionText', 'Question wording', 'Said when the agent asks a question. Empty disables it.'),
449
- textField('chime', 'Chime', 'ding, beep or none.'),
450
- textField('language', 'Language', 'Hint for voices that need a locale (eSpeak, Azure SSML).'),
451
- textField('piperModel', 'Piper model path', 'Absolute path to an ONNX model. Leave empty to skip Piper.'),
452
- textField('piperBin', 'Piper binary', 'Looked up in PATH unless absolute.'),
453
- textField('espeakBin', 'eSpeak binary', 'Usually espeak-ng.'),
454
- textField('edgeBin', 'edge-tts binary', 'Python CLI from the edge-tts package.'),
455
- textField('azureRegion', 'Azure region', 'Required for the azure provider, e.g. eastus.'),
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 }, 'Save'),
459
- saved ? React.createElement('span', { className: 'dts-ok' }, 'Saved') : null,
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
- { name: 'settings.section', id: '@goodandready/dsh-tts', order: 32, label: () => 'Speech', inject: () => ({ ctx: ctx }) },
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.0",
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",