@goodandready/dsh-voice 0.4.0 → 0.4.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.
Files changed (2) hide show
  1. package/lib/client.js +52 -8
  2. package/package.json +1 -1
package/lib/client.js CHANGED
@@ -425,6 +425,7 @@ window.__ModuleLoader__.load({
425
425
  '.dvs-save{background:var(--dsw-alias-brand-primary);color:#fff;border:none;border-radius:6px;padding:7px 14px;font-size:13px;cursor:pointer}' +
426
426
  '.dvs-card{display:flex;flex-direction:column;gap:6px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:10px}' +
427
427
  '.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}' +
428
+ '.dvs-wait{font-size:13px;color:var(--dsw-alias-label-secondary);line-height:1.5;max-width:520px}' +
428
429
  '.dvs-ok{font-size:12px;color:var(--dsw-alias-state-success-primary)}' +
429
430
  '.dvs-bad{font-size:12px;color:var(--dsw-alias-state-error-primary)}'
430
431
  const setCssId = 'dsh-voice/settings.module.css'
@@ -527,7 +528,13 @@ window.__ModuleLoader__.load({
527
528
 
528
529
  function VoiceSection(props) {
529
530
  const ctx = props.ctx
530
- const scope = ctx.settingsScope.bind({ namespace: NS })
531
+ // На странице, открытой не с localhost, ядро отключает настройки целиком:
532
+ // общий вид документа не читается, каждый раздел получает "unavailable",
533
+ // запись выбрасывается. Сервер это ограничение не разделяет, и плагин
534
+ // dsh-lanmode поднимает ту же механику поверх тех же вызовов. Если он
535
+ // установлен — берём его службу, иначе ядровую: на loopback-странице она
536
+ // работает штатно.
537
+ const scope = ((ctx.get && ctx.get('lanSettings')) || ctx.settingsScope).bind({ namespace: NS })
531
538
  const [snap, setSnap] = React.useState(null)
532
539
  const [draft, setDraft] = React.useState(null)
533
540
  const [saved, setSaved] = React.useState(false)
@@ -541,19 +548,56 @@ window.__ModuleLoader__.load({
541
548
  return () => { alive = false; off() }
542
549
  }, [])
543
550
 
544
- const value = snap && snap.value ? snap.value : {}
545
- const writable = snap ? snap.writable !== false : false
546
- React.useEffect(() => { if (snap && draft === null) setDraft(JSON.parse(JSON.stringify(value))) }, [snap, draft, value])
551
+ // Снимок приходит со статусом, и он важнее самого значения.
552
+ // loading — ответа хоста ещё нет;
553
+ // unavailable — хост ответил, но наш namespace ему пока неизвестен:
554
+ // так бывает, если страницу открыли в первые секунды
555
+ // после старта, пока плагины ещё регистрируются;
556
+ // ready — значения на месте.
557
+ // При unavailable поле writable берётся из документа и остаётся true,
558
+ // поэтому без проверки статуса карточка рисует пустую, но с виду рабочую
559
+ // форму — именно так этот баг и выглядел.
560
+ const ready = !!snap && snap.status === 'ready'
561
+ const value = ready && snap.value ? snap.value : {}
562
+ const writable = ready && snap.writable !== false
563
+
564
+ // Зеркало настроек в браузере перечитывается только на запись значения и
565
+ // на переподключение; появление namespace таким сигналом не считается.
566
+ // Значит само оно не починится — просим перечитать, пока не готово.
567
+ // Зеркало общее, так что это чинит и остальные разделы настроек.
568
+ React.useEffect(() => {
569
+ if (ready) return undefined
570
+ let tries = 0
571
+ const timer = setInterval(() => {
572
+ if (tries >= 15) { clearInterval(timer); return }
573
+ tries += 1
574
+ try { ((ctx.get && ctx.get('lanSettings')) || ctx.settingsScope).describe().load() } catch (e) { /* сервис ещё не поднялся */ }
575
+ }, 1000)
576
+ return () => clearInterval(timer)
577
+ }, [ready])
578
+
579
+ // Черновик сеем только из готового снимка. Раньше он заполнялся из
580
+ // первого пришедшего, и пустой замораживался навсегда: следующий снимок
581
+ // уже не пересевал его, и карточка оставалась пустой до перезагрузки.
582
+ React.useEffect(() => { if (ready && draft === null) setDraft(JSON.parse(JSON.stringify(value))) }, [ready, draft, value])
547
583
  // Клиент должен знать порог VAD и окно отмены — они живут в тех же настройках.
548
584
  React.useEffect(() => {
549
- if (!snap) return
585
+ if (!ready) return
550
586
  voice.settings = {
551
587
  vadSilenceMs: Number(value && value.dictation && value.dictation.vadSilenceMs) || 700,
552
588
  autoSendMs: Number(value && value.message && value.message.autoSendMs) || 4000,
553
589
  }
554
- }, [snap])
555
-
556
- if (!snap) return React.createElement('div', { className: 'dvs-wrap' }, 'Загрузка…')
590
+ }, [ready, value])
591
+
592
+ if (!ready) {
593
+ const waiting = !snap || snap.status === 'loading'
594
+ return React.createElement('div', { className: 'dvs-wrap' },
595
+ React.createElement('div', { className: 'dvs-wait' }, waiting
596
+ ? 'Загрузка настроек…'
597
+ : 'Харнесс ещё не объявил настройки плагина. Если он только что перезапустился, '
598
+ + 'раздел появится сам через несколько секунд.'),
599
+ )
600
+ }
557
601
 
558
602
  const setIn = (mode, key, v) => setDraft((d) => {
559
603
  const next = JSON.parse(JSON.stringify(d || {}))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goodandready/dsh-voice",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
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",