@goodandready/dsh-voice 0.9.29 → 0.9.30

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 CHANGED
@@ -35,6 +35,9 @@
35
35
 
36
36
  ---
37
37
 
38
+ ## Requirements
39
+ ### Browser Microphone & Secure Context\nModern browsers require a **Secure Context** (`https://` or `http://localhost` / `http://127.0.0.1`) for microphone capture (`navigator.mediaDevices.getUserMedia`). When accessing DSH over a local network (e.g., `http://192.168.1.x:3080` via `dsh-lanmode`), browsers will block microphone access by default. Serve DSH behind an HTTPS reverse proxy or configure browser security flags (`chrome://flags/#unsafely-treat-insecure-origin-as-secure`) for LAN IP access.\n
40
+
38
41
  ## ⚡ Overview
39
42
 
40
43
  **`dsh-voice`** brings voice superpowers to the **DeepSeek Harness** Web UI. Whether you need hands-free real-time streaming dictation segmented on natural breath pauses or crisp voice notes with keyboard/mouse Push-to-Talk gestures, `dsh-voice` ensures your audio is never lost thanks to **automatic multi-provider fallback chains**.
package/README.ru.md CHANGED
@@ -35,6 +35,9 @@
35
35
 
36
36
  ---
37
37
 
38
+ ## Требования
39
+ ### Доступ к микрофону и Secure Context\nСовременные веб-браузеры требуют безопасный контекст (**Secure Context**: `https://` или `http://localhost` / `http://127.0.0.1`) для захвата аудио с микрофона (`navigator.mediaDevices.getUserMedia`). При открытии DSH в локальной сети по обычному HTTP (например, `http://192.168.1.x:3080` через `dsh-lanmode`) браузер блокирует микрофон. Для работы в LAN настройте HTTPS (через reverse proxy, например caddy/nginx) либо укажите адрес в настройках браузера (`chrome://flags/#unsafely-treat-insecure-origin-as-secure`).\n
40
+
38
41
  ## ⚡ Обзор
39
42
 
40
43
  **`dsh-voice`** добавляет полноценные голосовые возможности в веб-интерфейс **DeepSeek Harness**. Будь то непрерывная диктовка с нарезкой фраз по естественным паузам или голосовые сообщения с удобными жестами Push-to-Talk (мышь и клавиатура) — `dsh-voice` гарантирует сохранность каждой записи благодаря **автоматическим цепочкам отказоустойчивости**.
package/README.zh.md CHANGED
@@ -35,6 +35,9 @@
35
35
 
36
36
  ---
37
37
 
38
+ ## 运行要求
39
+ ### 浏览器麦克风与安全上下文 (Secure Context)\n现代浏览器要求在**安全上下文**(`https://` 或 `http://localhost` / `http://127.0.0.1`)下才允许调用麦克风接口(`navigator.mediaDevices.getUserMedia`)。当通过局域网以普通 HTTP 访问 DSH(如使用 `dsh-lanmode` 访问 `http://192.168.1.x:3080`)时,浏览器默认会阻止录音。在局域网环境下,请配置 HTTPS 反向代理,或在浏览器中启用标志(`chrome://flags/#unsafely-treat-insecure-origin-as-secure`)。\n
40
+
38
41
  ## ⚡ 插件概览
39
42
 
40
43
  **`dsh-voice`** 为 **DeepSeek Harness** Web 界面带来极速语音交互体验。无论是按自然停顿切分的流式听写,还是带撤回保护的语音消息以及键盘/鼠标 Push-to-Talk 对讲,`dsh-voice` 凭借**多服务商自动故障转移备用链**确保您的录音万无一失。
package/lib/client.js CHANGED
@@ -3087,11 +3087,13 @@ voice._chainsPromise = null
3087
3087
  const [updaterStatus, setUpdaterStatus] = React.useState(null)
3088
3088
  const [updating, setUpdating] = React.useState(false)
3089
3089
  const [updaterMsg, setUpdaterMsg] = React.useState('')
3090
+ const [updaterMsgKind, setUpdaterMsgKind] = React.useState('')
3090
3091
  const [updaterLoading, setUpdaterLoading] = React.useState(false)
3091
3092
 
3092
3093
  const checkUpdater = React.useCallback(async () => {
3093
3094
  setUpdaterLoading(true)
3094
3095
  setUpdaterMsg('')
3096
+ setUpdaterMsgKind('')
3095
3097
  try {
3096
3098
  const res = await fetch('/api/dsh-voice/update', {
3097
3099
  headers: { accept: 'application/json' },
@@ -3102,10 +3104,12 @@ voice._chainsPromise = null
3102
3104
  setUpdaterStatus(data)
3103
3105
  if (data && data.latestCheckFailed) {
3104
3106
  setUpdaterMsg(t('updateCheckFailed'))
3107
+ setUpdaterMsgKind('err')
3105
3108
  }
3106
3109
  } catch (e) {
3107
3110
  setUpdaterStatus(null)
3108
3111
  setUpdaterMsg(t('updateCheckFailed') + ': ' + (e?.message || String(e)))
3112
+ setUpdaterMsgKind('err')
3109
3113
  } finally {
3110
3114
  setUpdaterLoading(false)
3111
3115
  }
@@ -3115,6 +3119,7 @@ voice._chainsPromise = null
3115
3119
  if (updating) return
3116
3120
  setUpdating(true)
3117
3121
  setUpdaterMsg('')
3122
+ setUpdaterMsgKind('')
3118
3123
  try {
3119
3124
  const res = await fetch('/api/dsh-voice/update', {
3120
3125
  method: 'POST',
@@ -3140,15 +3145,17 @@ voice._chainsPromise = null
3140
3145
  throw new Error(data.error || `HTTP ${res.status}`)
3141
3146
  }
3142
3147
  setUpdaterMsg(data.message || t('updateSuccess'))
3148
+ setUpdaterMsgKind('ok')
3143
3149
  checkUpdater()
3144
3150
  } catch (err) {
3145
3151
  setUpdaterMsg(err && err.message ? err.message : String(err))
3152
+ setUpdaterMsgKind('err')
3146
3153
  } finally {
3147
3154
  setUpdating(false)
3148
3155
  }
3149
3156
  }
3150
3157
 
3151
- return { updaterStatus, updating, updaterMsg, updaterLoading, checkUpdater, onUpdateNow }
3158
+ return { updaterStatus, updating, updaterMsg, updaterMsgKind, updaterLoading, checkUpdater, onUpdateNow }
3152
3159
  }
3153
3160
  // Developer Lexicon & IT Jargon
3154
3161
  function JargonEditor(props) {
@@ -3285,6 +3292,7 @@ voice._chainsPromise = null
3285
3292
  const updaterLoading = props.updaterLoading
3286
3293
  const updating = props.updating
3287
3294
  const updaterMsg = props.updaterMsg
3295
+ const updaterMsgKind = props.updaterMsgKind
3288
3296
  const onCheck = props.onCheck
3289
3297
  const onUpdateNow = props.onUpdateNow
3290
3298
 
@@ -3328,7 +3336,7 @@ voice._chainsPromise = null
3328
3336
  style: {
3329
3337
  marginTop: '8px',
3330
3338
  fontSize: '12px',
3331
- color: updaterMsg.includes('fail') || updaterMsg.includes('失败')
3339
+ color: (updaterMsgKind === 'err' || (!updaterMsgKind && (updaterMsg.includes('fail') || updaterMsg.includes('失败'))))
3332
3340
  ? 'var(--dsw-alias-state-warning-primary)'
3333
3341
  : 'var(--dsw-alias-state-success-primary)',
3334
3342
  },
@@ -3531,7 +3539,7 @@ voice._chainsPromise = null
3531
3539
 
3532
3540
  const { testingMic, testLevel, toggleTestMic, gateDbVal, gateThresholdPercent } = useMicTester(draft, value, setErr)
3533
3541
 
3534
- const { updaterStatus, updating, updaterMsg, updaterLoading, checkUpdater, onUpdateNow } = usePluginUpdater()
3542
+ const { updaterStatus, updating, updaterMsg, updaterMsgKind, updaterLoading, checkUpdater, onUpdateNow } = usePluginUpdater()
3535
3543
 
3536
3544
  const save = async () => {
3537
3545
  setErr(''); setSaved(false)
@@ -3905,6 +3913,7 @@ voice._chainsPromise = null
3905
3913
  updaterLoading: updaterLoading,
3906
3914
  updating: updating,
3907
3915
  updaterMsg: updaterMsg,
3916
+ updaterMsgKind: updaterMsgKind,
3908
3917
  onCheck: checkUpdater,
3909
3918
  onUpdateNow: onUpdateNow,
3910
3919
  }),
@@ -37,7 +37,7 @@ export function createLocalDaemons({ live, ctx }) {
37
37
  }
38
38
  const command = `${JSON.stringify(cfg.whisperBin)} -m ${JSON.stringify(cfg.whisperModel)}`
39
39
  + ` --host 127.0.0.1 --port 8001 -t 8 -p 1 -l ${isAutoLang(cfg.dictation.language) ? 'auto' : cfg.dictation.language}`
40
- const specOpts = { command, onExpiry: 'none', timeoutMs: 0, stdoutMaxBytes: 4 * 1024 * 1024 }
40
+ const specOpts = { command, onExpiry: 'none', timeoutMs: 60000, stdoutMaxBytes: 4 * 1024 * 1024 }
41
41
  const spec = typeof ctx.shell?.resolve === 'function' ? ctx.shell.resolve(specOpts) : specOpts
42
42
 
43
43
  if (typeof ctx.shell?.execute === 'function') {
@@ -216,7 +216,7 @@ export function createLocalDaemons({ live, ctx }) {
216
216
  try { port = new URL(cfg.sensevoiceUrl).port || '6006' } catch { /* invalid URL */ }
217
217
  const cmdArgs = buildSensevoiceArgs(cfg, port)
218
218
  const commandStr = `${JSON.stringify(cfg.sensevoiceBin)}${cmdArgs}`
219
- const specOpts = { command: commandStr, onExpiry: 'none', timeoutMs: 0, stdoutMaxBytes: 4 * 1024 * 1024 }
219
+ const specOpts = { command: commandStr, onExpiry: 'none', timeoutMs: 60000, stdoutMaxBytes: 4 * 1024 * 1024 }
220
220
  const spec = typeof ctx.shell?.resolve === 'function' ? ctx.shell.resolve(specOpts) : specOpts
221
221
 
222
222
  if (typeof ctx.shell?.execute === 'function') {
package/lib/updater.js CHANGED
@@ -18,6 +18,17 @@ function header(request, name) {
18
18
  return Array.isArray(value) ? value[0] : value;
19
19
  }
20
20
 
21
+ function isPrivateLan(value) {
22
+ const address = value?.toLowerCase().replace(/^\[|\]$/g, '');
23
+ if (!address) return false;
24
+ const ipv4 = address.startsWith('::ffff:') ? address.slice(7) : address;
25
+ return (
26
+ ipv4.startsWith('192.168.') ||
27
+ ipv4.startsWith('10.') ||
28
+ /^172\.(1[6-9]|2[0-9]|3[0-1])\./.test(ipv4)
29
+ );
30
+ }
31
+
21
32
  function isLoopback(value) {
22
33
  const address = value?.toLowerCase().replace(/^\[|\]$/g, '');
23
34
  return address === 'localhost' || address === 'localhost.' || address === '::1'
@@ -27,7 +38,8 @@ function isLoopback(value) {
27
38
 
28
39
  export function isTrustedUpdateRequest(request) {
29
40
  if (header(request, UPDATE_HEADER) !== '1') return false;
30
- if (!isLoopback(request.socket?.remoteAddress)) return false;
41
+ const remote = request.socket?.remoteAddress;
42
+ if (!isLoopback(remote) && !isPrivateLan(remote)) return false;
31
43
  const site = header(request, 'sec-fetch-site');
32
44
  if (site !== undefined && site !== 'same-origin') return false;
33
45
  const origin = header(request, 'origin');
@@ -35,8 +47,11 @@ export function isTrustedUpdateRequest(request) {
35
47
  if (origin === undefined || host === undefined) return false;
36
48
  try {
37
49
  const url = new URL(origin);
38
- return (url.protocol === 'http:' || url.protocol === 'https:')
39
- && isLoopback(url.hostname) && url.host === host;
50
+ return (
51
+ (url.protocol === 'http:' || url.protocol === 'https:') &&
52
+ (isLoopback(url.hostname) || isPrivateLan(url.hostname)) &&
53
+ url.host === host
54
+ );
40
55
  } catch {
41
56
  return false;
42
57
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goodandready/dsh-voice",
3
- "version": "0.9.29",
3
+ "version": "0.9.30",
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",