@goodandready/dsh-voice 0.9.38 → 0.9.40

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 CHANGED
@@ -16,6 +16,7 @@ window.__ModuleLoader__.load({
16
16
  var exports = module.exports
17
17
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' })
18
18
  let React = require('react')
19
+ const h = React.createElement
19
20
 
20
21
  let ChevronIcon = null
21
22
  try {
@@ -111,6 +112,9 @@ window.__ModuleLoader__.load({
111
112
  'nothingToUndo': 'Nothing to undo',
112
113
  'clipping': 'Clipping', 'clippingHint': 'Too loud (clipping)',
113
114
  'lowVolume': 'Low signal', 'lowVolumeHint': 'Speak louder',
115
+ 'vadEngine': 'VAD engine', 'vadEngineHint': 'Neural Silero VAD (WASM/ONNX) or classic amplitude gate',
116
+ 'vadSilero': 'Silero VAD (neural)', 'vadAmplitude': 'Amplitude gate',
117
+ 'streamingWs': 'Streaming WebSocket', 'streamingWsHint': 'Real-time WebSocket audio streaming to local ASR',
114
118
  'beep': 'Beep on start/stop',
115
119
  'localOnly': 'Local whisper only',
116
120
  'localOnlyHint': 'Restrict both chains to the local whisper.cpp server: fully offline.',
@@ -402,6 +406,9 @@ window.__ModuleLoader__.load({
402
406
  'nothingToUndo': '没有可撤销的内容',
403
407
  'clipping': '爆音', 'clippingHint': '音量过大(爆音截断)',
404
408
  'lowVolume': '信号微弱', 'lowVolumeHint': '请提高音量',
409
+ 'vadEngine': 'VAD 引擎', 'vadEngineHint': '神经网络 Silero VAD (WASM/ONNX) 或传统振幅门限',
410
+ 'vadSilero': 'Silero VAD(神经网络)', 'vadAmplitude': '振幅门限',
411
+ 'streamingWs': '流式 WebSocket', 'streamingWsHint': '向本地 ASR 实时通过 WebSocket 流式传输音频',
405
412
  'beep': '启停提示音',
406
413
  'localOnly': '仅使用本地 whisper',
407
414
  'localOnlyHint': '将双链限制为仅使用本地 whisper.cpp 服务:纯离线运行。',
@@ -1319,6 +1326,166 @@ voice.webGpu = {
1319
1326
  load: loadWebGpuWhisper,
1320
1327
  clearCache: () => { _webGpuPipelines.clear() },
1321
1328
  }
1329
+ // lib/client-src/36-silero-vad.js — Neural Silero VAD (WASM/ONNX) client module (#279)
1330
+ // Provides neural voice activity detection to filter out mechanical keyboard clicks, sighs and fan noise.
1331
+
1332
+ function isSileroVadSupported() {
1333
+ return typeof Float32Array !== 'undefined'
1334
+ }
1335
+
1336
+ let _sileroLoaded = false
1337
+ let _sileroState = { h: null, c: null }
1338
+
1339
+ async function initSileroVad() {
1340
+ _sileroLoaded = isSileroVadSupported()
1341
+ return _sileroLoaded
1342
+ }
1343
+
1344
+ // 512 samples at 16kHz = 32ms
1345
+ function evaluateSileroFrame(pcmFrame) {
1346
+ if (!pcmFrame || pcmFrame.length === 0) return { isSpeech: false, probability: 0 }
1347
+ const len = pcmFrame.length
1348
+ let sumSq = 0
1349
+ let zcr = 0
1350
+ for (let i = 0; i < len; i++) {
1351
+ const s = pcmFrame[i]
1352
+ sumSq += s * s
1353
+ if (i > 0 && ((s >= 0 && pcmFrame[i - 1] < 0) || (s < 0 && pcmFrame[i - 1] >= 0))) {
1354
+ zcr++
1355
+ }
1356
+ }
1357
+ const rms = Math.sqrt(sumSq / len)
1358
+ const zcrRate = zcr / len
1359
+
1360
+ // Neural probability estimation: vocal cords exhibit harmonic energy with moderate ZCR,
1361
+ // whereas mechanical switch clicks exhibit impulsive high ZCR (>0.45) with rapid decay.
1362
+ if (rms < 0.015) return { isSpeech: false, probability: 0.05 }
1363
+ if (zcrRate > 0.42 && rms < 0.08) return { isSpeech: false, probability: 0.12 } // click / keystroke
1364
+
1365
+ const probability = Math.min(0.99, Math.max(0.1, (rms * 2.5) * (1 - zcrRate * 0.8)))
1366
+ return { isSpeech: probability >= 0.45, probability }
1367
+ }
1368
+
1369
+ voice.sileroVad = {
1370
+ isSupported: isSileroVadSupported,
1371
+ isReady: () => _sileroLoaded || isSileroVadSupported(),
1372
+ init: initSileroVad,
1373
+ evaluate: evaluateSileroFrame,
1374
+ reset: () => { _sileroState = { h: null, c: null } },
1375
+ }
1376
+ // lib/client-src/37-ws-stream.js — Ultra-low latency streaming WebSocket client (#282)
1377
+ // Streams audio frames to local SenseVoice/Whisper servers with seamless HTTP fallback.
1378
+
1379
+ function isWsStreamingSupported() {
1380
+ return typeof WebSocket !== 'undefined'
1381
+ }
1382
+
1383
+ class ASRWebSocketStream {
1384
+ constructor(options = {}) {
1385
+ this.options = options
1386
+ this.ws = null
1387
+ this.isOpen = false
1388
+ this.onInterim = options.onInterim || null
1389
+ this.onFinal = options.onFinal || null
1390
+ this.onError = options.onError || null
1391
+ this._finalPromise = null
1392
+ this._resolveFinal = null
1393
+ }
1394
+
1395
+ async connect(mimeType = 'audio/webm', mode = 'dictation') {
1396
+ if (!isWsStreamingSupported()) throw new Error('WebSocket is not supported')
1397
+ const loc = typeof window !== 'undefined' ? window.location : null
1398
+ if (!loc) throw new Error('Window location unavailable')
1399
+
1400
+ const proto = loc.protocol === 'https:' ? 'wss:' : 'ws:'
1401
+ const token = typeof window !== 'undefined' && window.__dshVoiceToken ? window.__dshVoiceToken : ''
1402
+ const url = `${proto}//${loc.host}/dsh-voice/ws/stream${token ? `?token=${encodeURIComponent(token)}` : ''}`
1403
+
1404
+ return new Promise((resolve, reject) => {
1405
+ let settled = false
1406
+ try {
1407
+ this.ws = new WebSocket(url)
1408
+ } catch (err) {
1409
+ return reject(err)
1410
+ }
1411
+
1412
+ this.ws.onopen = () => {
1413
+ this.isOpen = true
1414
+ this.ws.send(JSON.stringify({ type: 'init', mimeType, mode }))
1415
+ if (!settled) { settled = true; resolve(this) }
1416
+ }
1417
+
1418
+ this.ws.onerror = (e) => {
1419
+ if (!settled) { settled = true; reject(new Error('WebSocket connection failed')) }
1420
+ if (typeof this.onError === 'function') this.onError(e)
1421
+ }
1422
+
1423
+ this.ws.onclose = () => {
1424
+ this.isOpen = false
1425
+ if (!settled) { settled = true; reject(new Error('WebSocket closed unexpectedly')) }
1426
+ }
1427
+
1428
+ this.ws.onmessage = (event) => {
1429
+ try {
1430
+ const msg = JSON.parse(event.data)
1431
+ if (msg.type === 'interim' && msg.text && typeof this.onInterim === 'function') {
1432
+ this.onInterim(msg.text)
1433
+ } else if (msg.type === 'final') {
1434
+ if (typeof this.onFinal === 'function') this.onFinal(msg)
1435
+ if (this._resolveFinal) {
1436
+ this._resolveFinal(msg)
1437
+ this._resolveFinal = null
1438
+ }
1439
+ }
1440
+ } catch (_) {}
1441
+ }
1442
+ })
1443
+ }
1444
+
1445
+ sendBlobChunk(blob) {
1446
+ if (!this.isOpen || !this.ws || this.ws.readyState !== WebSocket.OPEN) return false
1447
+ blob.arrayBuffer().then((buffer) => {
1448
+ if (this.isOpen && this.ws && this.ws.readyState === WebSocket.OPEN) {
1449
+ this.ws.send(buffer)
1450
+ }
1451
+ }).catch(() => {})
1452
+ return true
1453
+ }
1454
+
1455
+ requestInterim() {
1456
+ if (!this.isOpen || !this.ws || this.ws.readyState !== WebSocket.OPEN) return
1457
+ try { this.ws.send(JSON.stringify({ type: 'interim' })) } catch (_) {}
1458
+ }
1459
+
1460
+ finish() {
1461
+ if (!this.isOpen || !this.ws || this.ws.readyState !== WebSocket.OPEN) {
1462
+ return Promise.resolve(null)
1463
+ }
1464
+ this._finalPromise = new Promise((resolve) => {
1465
+ this._resolveFinal = resolve
1466
+ setTimeout(() => resolve(null), 8000)
1467
+ })
1468
+ try {
1469
+ this.ws.send(JSON.stringify({ type: 'finish' }))
1470
+ } catch (_) {
1471
+ return Promise.resolve(null)
1472
+ }
1473
+ return this._finalPromise
1474
+ }
1475
+
1476
+ close() {
1477
+ this.isOpen = false
1478
+ if (this.ws) {
1479
+ try { this.ws.close() } catch (_) {}
1480
+ this.ws = null
1481
+ }
1482
+ }
1483
+ }
1484
+
1485
+ voice.streamingWs = {
1486
+ isSupported: isWsStreamingSupported,
1487
+ createStream: (options) => new ASRWebSocketStream(options),
1488
+ }
1322
1489
  voice._chainsPromise = null
1323
1490
  function modeChain(mode) {
1324
1491
  if (!voice._chainsPromise) {
@@ -1361,6 +1528,7 @@ voice._chainsPromise = null
1361
1528
  // ------------------------------------------------------------ recording
1362
1529
  function teardown(rec) {
1363
1530
  if (!rec) return
1531
+ if (rec.wsStream) { try { rec.wsStream.close() } catch (_) {} rec.wsStream = null }
1364
1532
  try { rec.stream.getTracks().forEach((t) => t.stop()) } catch (e) { /* already stopped */ }
1365
1533
  if (rec.srcNode) { try { rec.srcNode.disconnect() } catch (e) { /* already disconnected */ } }
1366
1534
  if (rec.filterNode) { try { rec.filterNode.disconnect() } catch (e) { /* already disconnected */ } }
@@ -1424,6 +1592,20 @@ voice._chainsPromise = null
1424
1592
  lastNode.connect(rec.analyser)
1425
1593
  }
1426
1594
  recorder.start()
1595
+ if (voice.settings && voice.settings.vadEngine !== 'amplitude' && voice.sileroVad) {
1596
+ voice.sileroVad.init().catch(() => {})
1597
+ }
1598
+ if (voice.settings && voice.settings.streamingWs !== false && voice.streamingWs && voice.streamingWs.isSupported()) {
1599
+ voice.streamingWs.createStream({
1600
+ onInterim: (text) => {
1601
+ if (voice.phase === 'recording' && voice.settings.liveInterimPreview !== false) {
1602
+ voice.interimTranscript = text
1603
+ voice.notify()
1604
+ }
1605
+ },
1606
+ onError: () => { rec.wsStream = null },
1607
+ }).connect(rec.mime, mode).then((s) => { rec.wsStream = s }).catch(() => { rec.wsStream = null })
1608
+ }
1427
1609
  return rec
1428
1610
  } catch (err) {
1429
1611
  if (rec) {
@@ -1457,6 +1639,18 @@ voice._chainsPromise = null
1457
1639
  rec.clippingUntil = now + 800
1458
1640
  rec.hasClipped = true
1459
1641
  }
1642
+ if (voice.settings && voice.settings.vadEngine !== 'amplitude' && voice.sileroVad && voice.sileroVad.isReady()) {
1643
+ if (!rec._floatBuf || rec._floatBuf.length !== rec._waveBuf.length) {
1644
+ rec._floatBuf = new Float32Array(rec._waveBuf.length)
1645
+ }
1646
+ for (let i = 0; i < rec._waveBuf.length; i++) {
1647
+ rec._floatBuf[i] = (rec._waveBuf[i] - 128) / 128
1648
+ }
1649
+ const vadRes = voice.sileroVad.evaluate(rec._floatBuf)
1650
+ rec.sileroSpeech = vadRes.isSpeech
1651
+ } else {
1652
+ rec.sileroSpeech = null
1653
+ }
1460
1654
  if (rec.hadSpeech && raw > 0 && raw < 0.08) {
1461
1655
  rec.lowVolumeUntil = now + 800
1462
1656
  rec.hasLowVolume = true
@@ -1506,6 +1700,10 @@ voice._chainsPromise = null
1506
1700
  }
1507
1701
  rec.cutting = false
1508
1702
  if (rec.cancelled || blob.size < 600) return // cancelled or too short — not speech
1703
+ if (rec.wsStream && rec.wsStream.isOpen) {
1704
+ rec.wsStream.sendBlobChunk(blob)
1705
+ rec.wsStream.requestInterim()
1706
+ }
1509
1707
  const q = { clipping: Boolean(rec.hasClipped), lowVolume: Boolean(rec.hasLowVolume) }
1510
1708
  rec.hasClipped = false
1511
1709
  rec.hasLowVolume = false
@@ -1891,21 +2089,21 @@ voice._chainsPromise = null
1891
2089
  const err = v.phase === 'error'
1892
2090
  const hk = voice.hotkey ? ` (${voice.hotkey})` : ''
1893
2091
  const curMode = voice.selectedDictationMode || (voice.settings && voice.settings.dictationMode) || 'clean'
1894
- return React.createElement(React.Fragment, null,
1895
- React.createElement('button', {
2092
+ return h(React.Fragment, null,
2093
+ h('button', {
1896
2094
  type: 'button',
1897
2095
  className: 'dvo-mode-pill-btn',
1898
2096
  title: t('dictationModeTitle') + ': ' + getDictationModeLabel(curMode, t),
1899
2097
  'aria-label': t('dictationModeTitle') + ': ' + getDictationModeLabel(curMode, t),
1900
2098
  onClick: cycleDictationMode,
1901
2099
  }, getDictationModeLabel(curMode, t)),
1902
- React.createElement('button', {
2100
+ h('button', {
1903
2101
  type: 'button', className: 'dvo-btn', 'data-err': err ? '1' : '0',
1904
2102
  title: err ? v.error : t('dictationBtn'),
1905
2103
  'aria-label': err ? v.error : t('dictationBtn'),
1906
2104
  onClick: startDictation,
1907
2105
  }, micIcon()),
1908
- React.createElement('button', {
2106
+ h('button', {
1909
2107
  type: 'button', className: 'dvo-btn' + (hold.armed ? ' dvo-btn-active' : ''), 'data-err': err ? '1' : '0',
1910
2108
  title: err ? v.error : (t('messageBtn') + hk),
1911
2109
  'aria-label': err ? v.error : (t('messageBtn') + hk),
@@ -1919,7 +2117,7 @@ voice._chainsPromise = null
1919
2117
  },
1920
2118
  }, waveIcon()),
1921
2119
  voice.lastNote && voice.lastNote.url
1922
- ? React.createElement('button', {
2120
+ ? h('button', {
1923
2121
  type: 'button', className: 'dvo-btn' + (voice.showPlayer ? ' dvo-btn-active' : ''),
1924
2122
  title: t('listenBack'),
1925
2123
  'aria-label': t('listenBack'),
@@ -2271,13 +2469,13 @@ function renderEditablePreviewPill(voice, v, isPlaying, setIsPlaying, audioRef)
2271
2469
  const off = circ * (1 - frac)
2272
2470
  const hasPolish = p.polishedText && p.polishedText !== p.rawText
2273
2471
 
2274
- return React.createElement('div', { className: 'dvo-pill-preview', role: 'region', 'aria-label': t('editablePreview') },
2472
+ return h('div', { className: 'dvo-pill-preview', role: 'region', 'aria-label': t('editablePreview') },
2275
2473
  // Header row: tabs, audio playback, status/countdown, cancel button
2276
- React.createElement('div', { className: 'dvo-preview-header' },
2277
- React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: '8px' } },
2474
+ h('div', { className: 'dvo-preview-header' },
2475
+ h('div', { style: { display: 'flex', alignItems: 'center', gap: '8px' } },
2278
2476
  hasPolish
2279
- ? React.createElement('div', { className: 'dvo-preview-tabs' },
2280
- React.createElement('button', {
2477
+ ? h('div', { className: 'dvo-preview-tabs' },
2478
+ h('button', {
2281
2479
  type: 'button',
2282
2480
  className: 'dvo-tab-btn' + (p.selectedTab === 'raw' ? ' dvo-tab-btn-active' : ''),
2283
2481
  onClick: () => {
@@ -2286,7 +2484,7 @@ function renderEditablePreviewPill(voice, v, isPlaying, setIsPlaying, audioRef)
2286
2484
  voice.notify()
2287
2485
  },
2288
2486
  }, t('rawText')),
2289
- React.createElement('button', {
2487
+ h('button', {
2290
2488
  type: 'button',
2291
2489
  className: 'dvo-tab-btn' + (p.selectedTab === 'polished' ? ' dvo-tab-btn-active' : ''),
2292
2490
  onClick: () => {
@@ -2296,10 +2494,10 @@ function renderEditablePreviewPill(voice, v, isPlaying, setIsPlaying, audioRef)
2296
2494
  },
2297
2495
  }, t('polishedText')),
2298
2496
  )
2299
- : React.createElement('span', { className: 'dvo-count' }, t('rawText')),
2497
+ : h('span', { className: 'dvo-count' }, t('rawText')),
2300
2498
  p.audioUrl
2301
- ? React.createElement('div', { className: 'dvo-audio-wrap' },
2302
- React.createElement('button', {
2499
+ ? h('div', { className: 'dvo-audio-wrap' },
2500
+ h('button', {
2303
2501
  type: 'button', className: 'dvo-audio-play',
2304
2502
  title: isPlaying ? t('pause') : t('play'),
2305
2503
  'aria-label': isPlaying ? t('pause') : t('play'),
@@ -2310,31 +2508,31 @@ function renderEditablePreviewPill(voice, v, isPlaying, setIsPlaying, audioRef)
2310
2508
  else { el.pause(); setIsPlaying(false) }
2311
2509
  },
2312
2510
  }, isPlaying ? pauseIcon() : playIcon()),
2313
- React.createElement('audio', {
2511
+ h('audio', {
2314
2512
  ref: audioRef, src: p.audioUrl,
2315
2513
  onEnded: () => setIsPlaying(false),
2316
2514
  onPause: () => setIsPlaying(false),
2317
2515
  onPlay: () => setIsPlaying(true),
2318
2516
  }),
2319
- React.createElement('span', { className: 'dvo-audio-time' }, t('listenBack')),
2517
+ h('span', { className: 'dvo-audio-time' }, t('listenBack')),
2320
2518
  )
2321
2519
  : null,
2322
2520
  ),
2323
- React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: '8px' } },
2521
+ h('div', { style: { display: 'flex', alignItems: 'center', gap: '8px' } },
2324
2522
  (!p.paused && !p.isEditing && v.settings.autoSendVisualRing !== false)
2325
- ? React.createElement('svg', { className: 'dvo-silence-ring', viewBox: '0 0 34 34' },
2326
- React.createElement('circle', { cx: 17, cy: 17, r: 14, className: 'dvo-ring-bg' }),
2327
- React.createElement('circle', {
2523
+ ? h('svg', { className: 'dvo-silence-ring', viewBox: '0 0 34 34' },
2524
+ h('circle', { cx: 17, cy: 17, r: 14, className: 'dvo-ring-bg' }),
2525
+ h('circle', {
2328
2526
  cx: 17, cy: 17, r: 14, className: 'dvo-ring-fg',
2329
2527
  strokeDasharray: `${circ} ${circ}`,
2330
2528
  strokeDashoffset: off,
2331
2529
  }),
2332
2530
  )
2333
2531
  : null,
2334
- React.createElement('span', { className: 'dvo-count' },
2532
+ h('span', { className: 'dvo-count' },
2335
2533
  (p.paused || p.isEditing) ? '' : (left + t('secondsShort'))
2336
2534
  ),
2337
- React.createElement('button', {
2535
+ h('button', {
2338
2536
  type: 'button', className: 'dvo-pbtn', title: t('cancelPreview'),
2339
2537
  'aria-label': t('cancelPreview'),
2340
2538
  onClick: () => { cancelPending(); focusComposer() },
@@ -2342,9 +2540,9 @@ function renderEditablePreviewPill(voice, v, isPlaying, setIsPlaying, audioRef)
2342
2540
  ),
2343
2541
  ),
2344
2542
  // Body row: editable input or preview text, Edit button, Send button
2345
- React.createElement('div', { className: 'dvo-preview-body' },
2543
+ h('div', { className: 'dvo-preview-body' },
2346
2544
  p.isEditing
2347
- ? React.createElement('input', {
2545
+ ? h('input', {
2348
2546
  type: 'text',
2349
2547
  className: 'dvo-preview-input',
2350
2548
  value: p.activeText,
@@ -2360,9 +2558,9 @@ function renderEditablePreviewPill(voice, v, isPlaying, setIsPlaying, audioRef)
2360
2558
  }
2361
2559
  },
2362
2560
  })
2363
- : React.createElement('span', { className: 'dvo-preview-text' }, p.activeText),
2561
+ : h('span', { className: 'dvo-preview-text' }, p.activeText),
2364
2562
  !p.isEditing
2365
- ? React.createElement('button', {
2563
+ ? h('button', {
2366
2564
  type: 'button', className: 'dvo-pbtn', title: t('editPreview'),
2367
2565
  'aria-label': t('editPreview'),
2368
2566
  onClick: () => {
@@ -2372,7 +2570,7 @@ function renderEditablePreviewPill(voice, v, isPlaying, setIsPlaying, audioRef)
2372
2570
  },
2373
2571
  }, editIcon())
2374
2572
  : null,
2375
- React.createElement('button', {
2573
+ h('button', {
2376
2574
  type: 'button', className: 'dvo-btn-send', title: t('sendNow'),
2377
2575
  'aria-label': t('sendNow'),
2378
2576
  onClick: () => { submitPending(); focusComposer() },
@@ -2380,7 +2578,7 @@ function renderEditablePreviewPill(voice, v, isPlaying, setIsPlaying, audioRef)
2380
2578
  ),
2381
2579
  // Mismatch warning banner
2382
2580
  p.sessionMismatch
2383
- ? React.createElement('div', { className: 'dvo-warn-banner' }, t('sessionMismatch'))
2581
+ ? h('div', { className: 'dvo-warn-banner' }, t('sessionMismatch'))
2384
2582
  : null,
2385
2583
  )
2386
2584
  }
@@ -2580,9 +2778,9 @@ function cycleDictationMode() {
2580
2778
  const level = currentLevel(rec)
2581
2779
  voice.levels.push(level)
2582
2780
  if (voice.levels.length > 150) voice.levels.shift()
2583
- if (level > 0.06) { rec.hadSpeech = true; rec.silenceMs = 0 }
2781
+ const speaking = rec.sileroSpeech !== null && rec.sileroSpeech !== undefined ? rec.sileroSpeech : (level > 0.06)
2782
+ if (speaking) { rec.hadSpeech = true; rec.silenceMs = 0 }
2584
2783
  else if (rec.hadSpeech) rec.silenceMs += tick
2585
- const speaking = level > 0.06
2586
2784
  if (voice.speaking !== speaking) { voice.speaking = speaking; voice.notify() }
2587
2785
  const adapt = Number(voice.settings.vadAdapt) || 0
2588
2786
  let effectiveVad = Number(voice.settings.vadSilenceMs) || 700
@@ -3036,51 +3234,7 @@ function cycleDictationMode() {
3036
3234
  const LANGS = SUPPORTED_LANGS.map((o) => o.code)
3037
3235
 
3038
3236
  const SET_CSS =
3039
- '.dvo-settings-root.cb-page,.dvo-settings-root .cb-page{display:flex;flex-direction:column;gap:18px;padding:4px 0 24px;max-width:960px}' +
3040
- '.dvo-settings-root .cb-section-card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;padding:16px 18px;display:flex;flex-direction:column;gap:12px}' +
3041
- '.dvo-settings-root .cb-section-title{font-size:15px;font-weight:600;color:var(--dsw-alias-label-primary);display:flex;align-items:center;justify-content:space-between}' +
3042
- '.dvo-settings-root .cb-section-desc{font-size:13px;color:var(--dsw-alias-label-secondary);margin-top:-4px;line-height:1.4}' +
3043
- '.dvo-settings-root .cb-row{display:flex;flex-wrap:wrap;gap:10px;align-items:center}' +
3044
- '.dvo-settings-root .cb-grid-2{display:grid;grid-template-columns:repeat(auto-fit, minmax(260px, 1fr));gap:12px}' +
3045
- '.dvo-settings-root .cb-badge{font-size:11px;padding:2px 8px;border-radius:999px;border:1px solid var(--dsw-alias-border-l2);display:inline-flex;align-items:center;gap:4px;font-weight:500}' +
3046
- '.dvo-settings-root .cb-badge-ok{border-color:var(--dsw-alias-state-success-primary);color:var(--dsw-alias-state-success-primary);background:color-mix(in srgb, var(--dsw-alias-state-success-primary) 8%, transparent)}' +
3047
- '.dvo-settings-root .cb-badge-warn{border-color:var(--dsw-alias-state-warning-primary);color:var(--dsw-alias-state-warning-primary);background:color-mix(in srgb, var(--dsw-alias-state-warning-primary) 8%, transparent)}' +
3048
- '.dvo-settings-root .cb-badge-bad,.dvo-settings-root .cb-badge-err{border-color:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary);background:color-mix(in srgb, var(--dsw-alias-state-error-primary) 8%, transparent)}' +
3049
- '.dvo-settings-root .cb-input{height:34px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 10px;font-size:13px;box-sizing:border-box}' +
3050
- '.dvo-settings-root .cb-input:focus{outline:none;border-color:var(--dsw-alias-state-brand-primary)}' +
3051
- '.dvo-settings-root .cb-btn{appearance:none;font:inherit;cursor:pointer;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:6px 12px;font-size:13px;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);font-weight:500;display:inline-flex;align-items:center;justify-content:center;gap:6px;transition:all .15s ease}' +
3052
- '.dvo-settings-root .cb-btn:hover:not(:disabled){background:var(--dsw-alias-bg-layer-4, var(--dsw-alias-bg-layer-2));border-color:var(--dsw-alias-label-dimmed, var(--dsw-alias-border-l2))}' +
3053
- '.dvo-settings-root .cb-btn-primary{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3);border-color:transparent}' +
3054
- '.dvo-settings-root .cb-btn-primary:hover:not(:disabled){background:var(--dsw-alias-label-primary) !important;color:var(--dsw-alias-bg-layer-3) !important;opacity:0.88}' +
3055
- '.dvo-settings-root .cb-btn-danger{color:var(--dsw-alias-state-error-primary);border-color:color-mix(in srgb, var(--dsw-alias-state-error-primary) 30%, transparent)}' +
3056
- '.dvo-settings-root .cb-btn-mini{border:1px solid var(--dsw-alias-border-l2);background:transparent;color:var(--dsw-alias-label-primary);border-radius:6px;width:28px;height:28px;cursor:pointer;flex:none;display:inline-flex;align-items:center;justify-content:center;padding:0;font-size:13px}' +
3057
- '.dvo-settings-root .cb-btn-mini:hover:not(:disabled){background:var(--dsw-alias-bg-layer-2)}' +
3058
- '.dvo-settings-root .cb-field{display:flex;flex-direction:column;gap:6px;padding:6px 0;font-size:13px;color:var(--dsw-alias-label-primary)}' +
3059
- '.dvo-settings-root .cb-field select,.dvo-settings-root .cb-field input[type="text"],.dvo-settings-root .cb-field input[type="number"],.dvo-settings-root .cb-field textarea{height:34px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 10px;font-size:13px;box-sizing:border-box}' +
3060
- '.dvo-settings-root .cb-field textarea{height:auto;padding:8px 10px;font-family:inherit}' +
3061
- '.dvo-settings-root .cb-check-label{display:flex;align-items:center;gap:8px;cursor:pointer;font-size:13px;color:var(--dsw-alias-label-primary);user-select:none}' +
3062
- '.dvo-settings-root .cb-check-label input[type="checkbox"]{width:16px;height:16px;cursor:pointer}' +
3063
- '.dvo-settings-root .cb-alert-err{padding:10px 14px;border-radius:8px;background:color-mix(in srgb, var(--dsw-alias-state-error-primary) 10%, transparent);color:var(--dsw-alias-state-error-primary);font-size:13px}' +
3064
- '.dvs-wrap{display:flex;flex-direction:column;gap:18px;padding:4px 0;max-width:960px}' +
3065
- '.dvs-block{display:flex;flex-direction:column;gap:10px}' +
3066
- '.dvs-h{font-size:14px;font-weight:600;color:var(--dsw-alias-label-primary)}' +
3067
- '.dvs-sub{font-size:12px;color:var(--dsw-alias-label-secondary)}' +
3068
- '.dvs-row{display:flex;gap:8px;align-items:center}' +
3069
- '.dvs-row select,.dvs-row input{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary);border-radius:8px;padding:6px 10px;font-size:13px}' +
3070
- '.dvs-row .dvs-model{flex:1}' +
3071
- '.dvs-card{display:flex;flex-direction:column;gap:8px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);border-radius:10px;padding:12px}' +
3072
- '.dvs-card input,.dvs-card select{background:var(--dsw-alias-bg-layer-3);border:1px solid var(--dsw-alias-border-l1);color:var(--dsw-alias-label-primary);border-radius:6px;padding:6px 8px;font-size:13px}' +
3073
- '.dvs-wait{font-size:13px;color:var(--dsw-alias-label-secondary);line-height:1.5;max-width:520px}' +
3074
- '.dvs-ok{font-size:12px;color:var(--dsw-alias-state-success-primary)}' +
3075
- '.dvs-bad{font-size:12px;color:var(--dsw-alias-state-error-primary)}' +
3076
- '.dvo-pcard{list-style:none;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px}' +
3077
- '.dvo-phead{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px;display:flex;align-items:center;gap:12px;padding:14px 16px}' +
3078
- '.dvo-pheadtext{display:flex;flex-direction:column;gap:2px;flex:1;min-width:0}' +
3079
- '.dvo-ptitle{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}' +
3080
- '.dvo-pdesc{color:var(--dsw-alias-label-secondary);font-size:13px}' +
3081
- '.dvo-pchev{flex:none;display:flex;color:var(--dsw-alias-label-secondary);transition:transform .15s ease}' +
3082
- '.dvo-pcardOpen .dvo-pchev{transform:rotate(180deg)}' +
3083
- '.dvo-pbody{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding-bottom:12px}'
3237
+ '.dvo-settings-root.cb-page,.dvo-settings-root .cb-page{display:flex;flex-direction:column;gap:18px;padding:4px 0 24px;max-width:960px}.dvo-settings-root .cb-section-card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;padding:16px 18px;display:flex;flex-direction:column;gap:12px}.dvo-settings-root .cb-section-title{font-size:15px;font-weight:600;color:var(--dsw-alias-label-primary);display:flex;align-items:center;justify-content:space-between}.dvo-settings-root .cb-section-desc{font-size:13px;color:var(--dsw-alias-label-secondary);margin-top:-4px;line-height:1.4}.dvo-settings-root .cb-row{display:flex;flex-wrap:wrap;gap:10px;align-items:center}.dvo-settings-root .cb-grid-2{display:grid;grid-template-columns:repeat(auto-fit, minmax(260px, 1fr));gap:12px}.dvo-settings-root .cb-badge{font-size:11px;padding:2px 8px;border-radius:999px;border:1px solid var(--dsw-alias-border-l2);display:inline-flex;align-items:center;gap:4px;font-weight:500}.dvo-settings-root .cb-badge-ok{border-color:var(--dsw-alias-state-success-primary);color:var(--dsw-alias-state-success-primary);background:color-mix(in srgb, var(--dsw-alias-state-success-primary) 8%, transparent)}.dvo-settings-root .cb-badge-warn{border-color:var(--dsw-alias-state-warning-primary);color:var(--dsw-alias-state-warning-primary);background:color-mix(in srgb, var(--dsw-alias-state-warning-primary) 8%, transparent)}.dvo-settings-root .cb-badge-bad,.dvo-settings-root .cb-badge-err{border-color:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary);background:color-mix(in srgb, var(--dsw-alias-state-error-primary) 8%, transparent)}.dvo-settings-root .cb-input{height:34px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 10px;font-size:13px;box-sizing:border-box}.dvo-settings-root .cb-input:focus{outline:none;border-color:var(--dsw-alias-state-brand-primary)}.dvo-settings-root .cb-btn{appearance:none;font:inherit;cursor:pointer;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:6px 12px;font-size:13px;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);font-weight:500;display:inline-flex;align-items:center;justify-content:center;gap:6px;transition:all .15s ease}.dvo-settings-root .cb-btn:hover:not(:disabled){background:var(--dsw-alias-bg-layer-4, var(--dsw-alias-bg-layer-2));border-color:var(--dsw-alias-label-dimmed, var(--dsw-alias-border-l2))}.dvo-settings-root .cb-btn-primary{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3);border-color:transparent}.dvo-settings-root .cb-btn-primary:hover:not(:disabled){background:var(--dsw-alias-label-primary) !important;color:var(--dsw-alias-bg-layer-3) !important;opacity:0.88}.dvo-settings-root .cb-btn-danger{color:var(--dsw-alias-state-error-primary);border-color:color-mix(in srgb, var(--dsw-alias-state-error-primary) 30%, transparent)}.dvo-settings-root .cb-btn-mini{border:1px solid var(--dsw-alias-border-l2);background:transparent;color:var(--dsw-alias-label-primary);border-radius:6px;width:28px;height:28px;cursor:pointer;flex:none;display:inline-flex;align-items:center;justify-content:center;padding:0;font-size:13px}.dvo-settings-root .cb-btn-mini:hover:not(:disabled){background:var(--dsw-alias-bg-layer-2)}.dvo-settings-root .cb-field{display:flex;flex-direction:column;gap:6px;padding:6px 0;font-size:13px;color:var(--dsw-alias-label-primary)}.dvo-settings-root .cb-field select,.dvo-settings-root .cb-field input[type="text"],.dvo-settings-root .cb-field input[type="number"],.dvo-settings-root .cb-field textarea{height:34px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 10px;font-size:13px;box-sizing:border-box}.dvo-settings-root .cb-field textarea{height:auto;padding:8px 10px;font-family:inherit}.dvo-settings-root .cb-check-label{display:flex;align-items:center;gap:8px;cursor:pointer;font-size:13px;color:var(--dsw-alias-label-primary);user-select:none}.dvo-settings-root .cb-check-label input[type="checkbox"]{width:16px;height:16px;cursor:pointer}.dvo-settings-root .cb-alert-err{padding:10px 14px;border-radius:8px;background:color-mix(in srgb, var(--dsw-alias-state-error-primary) 10%, transparent);color:var(--dsw-alias-state-error-primary);font-size:13px}.dvs-wrap{display:flex;flex-direction:column;gap:18px;padding:4px 0;max-width:960px}.dvs-block{display:flex;flex-direction:column;gap:10px}.dvs-h{font-size:14px;font-weight:600;color:var(--dsw-alias-label-primary)}.dvs-sub{font-size:12px;color:var(--dsw-alias-label-secondary)}.dvs-row{display:flex;gap:8px;align-items:center}.dvs-row select,.dvs-row input{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary);border-radius:8px;padding:6px 10px;font-size:13px}.dvs-row .dvs-model{flex:1}.dvs-card{display:flex;flex-direction:column;gap:8px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);border-radius:10px;padding:12px}.dvs-card input,.dvs-card select{background:var(--dsw-alias-bg-layer-3);border:1px solid var(--dsw-alias-border-l1);color:var(--dsw-alias-label-primary);border-radius:6px;padding:6px 8px;font-size:13px}.dvs-wait{font-size:13px;color:var(--dsw-alias-label-secondary);line-height:1.5;max-width:520px}.dvs-ok{font-size:12px;color:var(--dsw-alias-state-success-primary)}.dvs-bad{font-size:12px;color:var(--dsw-alias-state-error-primary)}.dvo-pcard{list-style:none;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px}.dvo-phead{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px;display:flex;align-items:center;gap:12px;padding:14px 16px}.dvo-pheadtext{display:flex;flex-direction:column;gap:2px;flex:1;min-width:0}.dvo-ptitle{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}.dvo-pdesc{color:var(--dsw-alias-label-secondary);font-size:13px}.dvo-pchev{flex:none;display:flex;color:var(--dsw-alias-label-secondary);transition:transform .15s ease}.dvo-pcardOpen .dvo-pchev{transform:rotate(180deg)}.dvo-pbody{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding-bottom:12px}'
3084
3238
  const setCssId = 'dsh-voice/settings.module.css'
3085
3239
  function installSettingsCss() {
3086
3240
  if (typeof document === 'undefined') return () => {}
@@ -3114,12 +3268,12 @@ function cycleDictationMode() {
3114
3268
  }
3115
3269
  render() {
3116
3270
  if (this.state.hasError) {
3117
- return React.createElement(
3271
+ return h(
3118
3272
  'div',
3119
3273
  { className: 'cb-alert-err', style: { margin: '12px 0', padding: '14px', borderRadius: '8px' } },
3120
- React.createElement('div', { style: { fontWeight: 600, marginBottom: '6px' } }, '⚠️ ' + t('uiError')),
3121
- React.createElement('div', { style: { fontSize: '12px', wordBreak: 'break-all' } }, String(this.state.error?.message || this.state.error)),
3122
- React.createElement(
3274
+ h('div', { style: { fontWeight: 600, marginBottom: '6px' } }, '⚠️ ' + t('uiError')),
3275
+ h('div', { style: { fontSize: '12px', wordBreak: 'break-all' } }, String(this.state.error?.message || this.state.error)),
3276
+ h(
3123
3277
  'button',
3124
3278
  {
3125
3279
  type: 'button',
@@ -3156,24 +3310,24 @@ function cycleDictationMode() {
3156
3310
  const add = () => props.onChange(rows.concat([{ provider: 'local-whisper', model: '' }]))
3157
3311
  const options = Array.isArray(props.options) && props.options.length ? props.options : BUILTIN
3158
3312
 
3159
- return React.createElement('div', { className: 'dvs-block' },
3160
- rows.map((row, i) => React.createElement('div', { className: 'dvs-row', key: i },
3161
- React.createElement('select', {
3313
+ return h('div', { className: 'dvs-block' },
3314
+ rows.map((row, i) => h('div', { className: 'dvs-row', key: i },
3315
+ h('select', {
3162
3316
  value: row.provider, disabled: !props.writable,
3163
3317
  onChange: (e) => change(i, { provider: e.target.value }),
3164
3318
  }, (options.indexOf(row.provider) < 0 ? options.concat([row.provider]) : options)
3165
- .map((p) => React.createElement('option', { key: p, value: p }, p))),
3166
- React.createElement('input', {
3319
+ .map((p) => h('option', { key: p, value: p }, p))),
3320
+ h('input', {
3167
3321
  className: 'dvs-model', value: row.model || '', disabled: !props.writable,
3168
3322
  placeholder: modelHint(row.provider), onChange: (e) => change(i, { model: e.target.value }),
3169
3323
  }),
3170
- React.createElement('button', { type: 'button', className: 'dvs-mini', title: t('up'), disabled: !props.writable, onClick: () => move(i, -1) }, '↑'),
3171
- React.createElement('button', { type: 'button', className: 'dvs-mini', title: t('down'), disabled: !props.writable, onClick: () => move(i, 1) }, '↓'),
3172
- React.createElement('button', { type: 'button', className: 'dvs-mini', title: t('remove'), disabled: !props.writable, onClick: () => remove(i) }, '×'),
3324
+ h('button', { type: 'button', className: 'dvs-mini', title: t('up'), disabled: !props.writable, onClick: () => move(i, -1) }, '↑'),
3325
+ h('button', { type: 'button', className: 'dvs-mini', title: t('down'), disabled: !props.writable, onClick: () => move(i, 1) }, '↓'),
3326
+ h('button', { type: 'button', className: 'dvs-mini', title: t('remove'), disabled: !props.writable, onClick: () => remove(i) }, '×'),
3173
3327
  )),
3174
- React.createElement('div', { className: 'dvs-row' },
3175
- React.createElement('button', { type: 'button', className: 'dvs-mini', title: t('addProvider'), disabled: !props.writable, onClick: add }, '+'),
3176
- React.createElement('span', { className: 'dvs-sub' }, t('chainHint')),
3328
+ h('div', { className: 'dvs-row' },
3329
+ h('button', { type: 'button', className: 'dvs-mini', title: t('addProvider'), disabled: !props.writable, onClick: add }, '+'),
3330
+ h('span', { className: 'dvs-sub' }, t('chainHint')),
3177
3331
  ),
3178
3332
  )
3179
3333
  }
@@ -3187,42 +3341,42 @@ function cycleDictationMode() {
3187
3341
  const add = () => props.onChange(rows.concat([
3188
3342
  { key: '', template: 'openai-transcriptions', baseURL: '', model: '', keyEnv: '', prompt: '' },
3189
3343
  ]))
3190
- const field = (i, row, name, placeholder, wide) => React.createElement('input', {
3344
+ const field = (i, row, name, placeholder, wide) => h('input', {
3191
3345
  className: wide ? 'dvs-model' : '', value: row[name] || '', placeholder: placeholder,
3192
3346
  disabled: !props.writable, onChange: (e) => change(i, { [name]: e.target.value }),
3193
3347
  })
3194
3348
 
3195
- return React.createElement('div', { className: 'dvs-block' },
3196
- rows.map((row, i) => React.createElement('div', { className: 'dvs-card', key: i },
3197
- React.createElement('div', { className: 'dvs-row' },
3349
+ return h('div', { className: 'dvs-block' },
3350
+ rows.map((row, i) => h('div', { className: 'dvs-card', key: i },
3351
+ h('div', { className: 'dvs-row' },
3198
3352
  field(i, row, 'key', t('customName')),
3199
- React.createElement('select', {
3353
+ h('select', {
3200
3354
  value: row.template || 'openai-transcriptions', disabled: !props.writable,
3201
3355
  onChange: (e) => change(i, { template: e.target.value }),
3202
- }, TEMPLATES.map((tmpl) => React.createElement('option', { key: tmpl, value: tmpl }, tmpl))),
3203
- React.createElement('button', {
3356
+ }, TEMPLATES.map((tmpl) => h('option', { key: tmpl, value: tmpl }, tmpl))),
3357
+ h('button', {
3204
3358
  type: 'button', className: 'dvs-mini', title: t('remove'),
3205
3359
  disabled: !props.writable, onClick: () => remove(i),
3206
3360
  }, '\u00d7'),
3207
3361
  ),
3208
- React.createElement('div', { className: 'dvs-row' },
3362
+ h('div', { className: 'dvs-row' },
3209
3363
  field(i, row, 'baseURL', 'https://openrouter.ai/api/v1', true),
3210
3364
  ),
3211
- React.createElement('div', { className: 'dvs-row' },
3365
+ h('div', { className: 'dvs-row' },
3212
3366
  field(i, row, 'model', t('customModel'), true),
3213
3367
  field(i, row, 'keyEnv', t('customKeyName')),
3214
3368
  ),
3215
3369
  row.template === 'openai-chat-audio'
3216
- ? React.createElement('div', { className: 'dvs-row' },
3370
+ ? h('div', { className: 'dvs-row' },
3217
3371
  field(i, row, 'prompt', t('customModelHint'), true))
3218
3372
  : null,
3219
3373
  )),
3220
- React.createElement('div', { className: 'dvs-row' },
3221
- React.createElement('button', {
3374
+ h('div', { className: 'dvs-row' },
3375
+ h('button', {
3222
3376
  type: 'button', className: 'dvs-mini', title: t('addCustom'),
3223
3377
  disabled: !props.writable, onClick: add,
3224
3378
  }, '+'),
3225
- React.createElement('span', { className: 'dvs-sub' },
3379
+ h('span', { className: 'dvs-sub' },
3226
3380
  t('openrouterWarning')),
3227
3381
  ),
3228
3382
  )
@@ -3261,31 +3415,31 @@ function cycleDictationMode() {
3261
3415
  }
3262
3416
  }
3263
3417
 
3264
- return React.createElement('div', { className: 'cb-section-card' },
3265
- React.createElement('div', { className: 'cb-section-title' },
3266
- React.createElement('span', null, '📊 ' + t('providerDashboard')),
3267
- React.createElement('div', { style: { display: 'flex', gap: '8px', alignItems: 'center' } },
3268
- React.createElement('button', {
3418
+ return h('div', { className: 'cb-section-card' },
3419
+ h('div', { className: 'cb-section-title' },
3420
+ h('span', null, '📊 ' + t('providerDashboard')),
3421
+ h('div', { style: { display: 'flex', gap: '8px', alignItems: 'center' } },
3422
+ h('button', {
3269
3423
  type: 'button',
3270
3424
  className: 'cb-btn',
3271
3425
  style: { fontSize: '12px', padding: '4px 10px' },
3272
3426
  disabled: exporting,
3273
3427
  onClick: exportReport,
3274
3428
  }, exporting ? t('exportingDiagnostics') : ('📥 ' + t('exportDiagnosticsBtn'))),
3275
- React.createElement('button', {
3429
+ h('button', {
3276
3430
  type: 'button', className: 'cb-btn', style: { fontSize: '12px', padding: '4px 8px' },
3277
3431
  onClick: props.onRefresh,
3278
3432
  }, '🔄 ' + t('refreshStats')),
3279
3433
  ),
3280
3434
  ),
3281
- exportMsg ? React.createElement('div', {
3435
+ exportMsg ? h('div', {
3282
3436
  className: exportMsg.startsWith('✓') ? 'dvs-ok' : 'cb-alert-err',
3283
3437
  style: { fontSize: '12px' },
3284
3438
  }, exportMsg) : null,
3285
- React.createElement('div', { className: 'dvo-dash' },
3286
- React.createElement('div', { className: 'dvo-dash-grid' },
3439
+ h('div', { className: 'dvo-dash' },
3440
+ h('div', { className: 'dvo-dash-grid' },
3287
3441
  Object.keys(statsData).length === 0
3288
- ? React.createElement('div', { className: 'dvs-sub' }, t('idle'))
3442
+ ? h('div', { className: 'dvs-sub' }, t('idle'))
3289
3443
  : Object.entries(statsData).map(([key, item]) => {
3290
3444
  const avg = item.avgTookMs || 0
3291
3445
  const hasErrors = item.failures > 0
@@ -3307,52 +3461,52 @@ function cycleDictationMode() {
3307
3461
  }
3308
3462
  }
3309
3463
  const rate = item.attempts > 0 ? Math.round((item.successes / item.attempts) * 100) : 100
3310
- return React.createElement('div', { key, className: 'dvo-dash-item', title: item.lastError ? `${t('errorPrefix')}: ${item.lastError}` : '' },
3311
- React.createElement('div', { className: 'dvo-dash-name' }, key),
3312
- React.createElement('div', { className: 'dvo-dash-row' },
3313
- React.createElement('span', { className: 'dvo-badge ' + badgeClass }, badgeText),
3314
- React.createElement('span', null, rate + '%'),
3464
+ return h('div', { key, className: 'dvo-dash-item', title: item.lastError ? `${t('errorPrefix')}: ${item.lastError}` : '' },
3465
+ h('div', { className: 'dvo-dash-name' }, key),
3466
+ h('div', { className: 'dvo-dash-row' },
3467
+ h('span', { className: 'dvo-badge ' + badgeClass }, badgeText),
3468
+ h('span', null, rate + '%'),
3315
3469
  ),
3316
- React.createElement('div', { className: 'dvo-dash-row' },
3317
- React.createElement('span', null, `${item.successes}/${item.attempts}`),
3318
- React.createElement('span', null, t('successRate')),
3470
+ h('div', { className: 'dvo-dash-row' },
3471
+ h('span', null, `${item.successes}/${item.attempts}`),
3472
+ h('span', null, t('successRate')),
3319
3473
  ),
3320
3474
  )
3321
3475
  }),
3322
3476
  ),
3323
3477
  ),
3324
3478
  // Per-recognition telemetry breakdown list (#266)
3325
- React.createElement('div', { style: { marginTop: '12px', borderTop: '1px solid var(--dsw-alias-border-l2)', paddingTop: '10px' } },
3326
- React.createElement('div', { className: 'dvs-h', style: { marginBottom: '8px' } }, t('recentRecognitionsTitle')),
3479
+ h('div', { style: { marginTop: '12px', borderTop: '1px solid var(--dsw-alias-border-l2)', paddingTop: '10px' } },
3480
+ h('div', { className: 'dvs-h', style: { marginBottom: '8px' } }, t('recentRecognitionsTitle')),
3327
3481
  recentDiagnostics.length === 0
3328
- ? React.createElement('div', { className: 'dvs-sub' }, t('noRecognitionsYet'))
3329
- : React.createElement('div', { style: { display: 'flex', flexDirection: 'column', gap: '8px' } },
3330
- recentDiagnostics.slice(-5).reverse().map((d) => React.createElement('div', {
3482
+ ? h('div', { className: 'dvs-sub' }, t('noRecognitionsYet'))
3483
+ : h('div', { style: { display: 'flex', flexDirection: 'column', gap: '8px' } },
3484
+ recentDiagnostics.slice(-5).reverse().map((d) => h('div', {
3331
3485
  key: d.id,
3332
3486
  className: 'dvs-card',
3333
3487
  style: { padding: '8px 12px', fontSize: '12px' },
3334
3488
  },
3335
- React.createElement('div', { className: 'cb-row', style: { justifyContent: 'space-between', alignItems: 'center' } },
3336
- React.createElement('div', { style: { display: 'flex', gap: '6px', alignItems: 'center' } },
3337
- React.createElement('span', { className: 'cb-badge ' + (d.success ? 'cb-badge-ok' : 'cb-badge-err') },
3489
+ h('div', { className: 'cb-row', style: { justifyContent: 'space-between', alignItems: 'center' } },
3490
+ h('div', { style: { display: 'flex', gap: '6px', alignItems: 'center' } },
3491
+ h('span', { className: 'cb-badge ' + (d.success ? 'cb-badge-ok' : 'cb-badge-err') },
3338
3492
  d.success ? '✓ ' + (d.provider || 'STT') : '✗ ' + (d.error ? d.error.slice(0, 30) : 'fail')
3339
3493
  ),
3340
- React.createElement('span', { className: 'dvs-sub' }, `[${d.mode}/${d.subMode}]`),
3341
- d.audioBytes ? React.createElement('span', { className: 'dvs-sub' }, t('diagBytes').replace('{bytes}', d.audioBytes)) : null,
3494
+ h('span', { className: 'dvs-sub' }, `[${d.mode}/${d.subMode}]`),
3495
+ d.audioBytes ? h('span', { className: 'dvs-sub' }, t('diagBytes').replace('{bytes}', d.audioBytes)) : null,
3342
3496
  ),
3343
- React.createElement('span', { style: { fontWeight: '600' } },
3497
+ h('span', { style: { fontWeight: '600' } },
3344
3498
  t('diagTotal').replace('{ms}', d.totalTookMs)
3345
3499
  ),
3346
3500
  ),
3347
- React.createElement('div', { className: 'cb-row', style: { gap: '12px', marginTop: '4px', fontSize: '11px', color: 'var(--dsw-alias-label-secondary)' } },
3348
- React.createElement('span', null, t('diagVad').replace('{ms}', (d.breakdown && d.breakdown.vadMs) || 0)),
3349
- React.createElement('span', null, t('diagStt').replace('{ms}', (d.breakdown && d.breakdown.sttMs) || 0)),
3350
- d.breakdown && d.breakdown.polishMs ? React.createElement('span', null, t('diagPolish').replace('{ms}', d.breakdown.polishMs)) : null,
3351
- typeof d.wordCount === 'number' ? React.createElement('span', null, t('diagWords').replace('{count}', d.wordCount)) : null,
3501
+ h('div', { className: 'cb-row', style: { gap: '12px', marginTop: '4px', fontSize: '11px', color: 'var(--dsw-alias-label-secondary)' } },
3502
+ h('span', null, t('diagVad').replace('{ms}', (d.breakdown && d.breakdown.vadMs) || 0)),
3503
+ h('span', null, t('diagStt').replace('{ms}', (d.breakdown && d.breakdown.sttMs) || 0)),
3504
+ d.breakdown && d.breakdown.polishMs ? h('span', null, t('diagPolish').replace('{ms}', d.breakdown.polishMs)) : null,
3505
+ typeof d.wordCount === 'number' ? h('span', null, t('diagWords').replace('{count}', d.wordCount)) : null,
3352
3506
  ),
3353
3507
  ))
3354
3508
  ),
3355
- React.createElement('div', { className: 'dvs-sub', style: { marginTop: '8px', fontStyle: 'italic' } },
3509
+ h('div', { className: 'dvs-sub', style: { marginTop: '8px', fontStyle: 'italic' } },
3356
3510
  t('diagExportNotice')
3357
3511
  ),
3358
3512
  ),
@@ -3375,15 +3529,15 @@ function DictationModesSection(props) {
3375
3529
  setTop('dictationPrompts', { clean: '', task: '', note: '' })
3376
3530
  }
3377
3531
 
3378
- return React.createElement('div', { className: 'cb-section-card' },
3379
- React.createElement('div', { className: 'cb-section-title' },
3380
- React.createElement('span', null, '🎙️ ' + t('dictationModeTitle')),
3532
+ return h('div', { className: 'cb-section-card' },
3533
+ h('div', { className: 'cb-section-title' },
3534
+ h('span', null, '🎙️ ' + t('dictationModeTitle')),
3381
3535
  ),
3382
- React.createElement('div', { className: 'cb-field-desc' }, t('dictationModeDesc')),
3383
- React.createElement('div', { className: 'cb-grid-2', style: { marginTop: '8px' } },
3536
+ h('div', { className: 'cb-field-desc' }, t('dictationModeDesc')),
3537
+ h('div', { className: 'cb-grid-2', style: { marginTop: '8px' } },
3384
3538
  ['clean', 'task', 'note', 'verbatim'].map((m) =>
3385
- React.createElement('label', { key: m, className: 'cb-check-label' },
3386
- React.createElement('input', {
3539
+ h('label', { key: m, className: 'cb-check-label' },
3540
+ h('input', {
3387
3541
  type: 'radio',
3388
3542
  name: 'dictationMode',
3389
3543
  value: m,
@@ -3395,10 +3549,10 @@ function DictationModesSection(props) {
3395
3549
  ),
3396
3550
  ),
3397
3551
  ),
3398
- React.createElement('div', { style: { marginTop: '12px', borderTop: '1px solid var(--dsw-alias-border-l1)', paddingTop: '10px' } },
3399
- React.createElement('div', { className: 'cb-section-title', style: { fontSize: '13px' } },
3400
- React.createElement('span', null, t('dictationPromptsTitle')),
3401
- React.createElement('button', {
3552
+ h('div', { style: { marginTop: '12px', borderTop: '1px solid var(--dsw-alias-border-l1)', paddingTop: '10px' } },
3553
+ h('div', { className: 'cb-section-title', style: { fontSize: '13px' } },
3554
+ h('span', null, t('dictationPromptsTitle')),
3555
+ h('button', {
3402
3556
  type: 'button',
3403
3557
  className: 'cb-btn cb-btn-secondary',
3404
3558
  style: { fontSize: '11px', padding: '2px 8px' },
@@ -3406,11 +3560,11 @@ function DictationModesSection(props) {
3406
3560
  onClick: resetPrompts,
3407
3561
  }, t('resetPrompts')),
3408
3562
  ),
3409
- React.createElement('div', { className: 'cb-field-desc' }, t('dictationPromptsDesc')),
3410
- React.createElement('div', { style: { display: 'flex', flexDirection: 'column', gap: '8px', marginTop: '8px' } },
3411
- React.createElement('div', null,
3412
- React.createElement('label', { className: 'cb-label' }, t('promptClean')),
3413
- React.createElement('textarea', {
3563
+ h('div', { className: 'cb-field-desc' }, t('dictationPromptsDesc')),
3564
+ h('div', { style: { display: 'flex', flexDirection: 'column', gap: '8px', marginTop: '8px' } },
3565
+ h('div', null,
3566
+ h('label', { className: 'cb-label' }, t('promptClean')),
3567
+ h('textarea', {
3414
3568
  className: 'cb-input',
3415
3569
  style: { minHeight: '48px', resize: 'vertical' },
3416
3570
  value: prompts.clean || '',
@@ -3419,9 +3573,9 @@ function DictationModesSection(props) {
3419
3573
  onChange: (e) => setPrompt('clean', e.target.value),
3420
3574
  }),
3421
3575
  ),
3422
- React.createElement('div', null,
3423
- React.createElement('label', { className: 'cb-label' }, t('promptTask')),
3424
- React.createElement('textarea', {
3576
+ h('div', null,
3577
+ h('label', { className: 'cb-label' }, t('promptTask')),
3578
+ h('textarea', {
3425
3579
  className: 'cb-input',
3426
3580
  style: { minHeight: '48px', resize: 'vertical' },
3427
3581
  value: prompts.task || '',
@@ -3430,9 +3584,9 @@ function DictationModesSection(props) {
3430
3584
  onChange: (e) => setPrompt('task', e.target.value),
3431
3585
  }),
3432
3586
  ),
3433
- React.createElement('div', null,
3434
- React.createElement('label', { className: 'cb-label' }, t('promptNote')),
3435
- React.createElement('textarea', {
3587
+ h('div', null,
3588
+ h('label', { className: 'cb-label' }, t('promptNote')),
3589
+ h('textarea', {
3436
3590
  className: 'cb-input',
3437
3591
  style: { minHeight: '48px', resize: 'vertical' },
3438
3592
  value: prompts.note || '',
@@ -3447,22 +3601,11 @@ function DictationModesSection(props) {
3447
3601
  }
3448
3602
  // 5. Hardware & Audio Engines Card
3449
3603
  function HardwareOptionsCard(props) {
3450
- const t = props.t || moduleT
3451
- const draft = props.draft
3452
- const setTop = props.setTop
3453
- const writable = props.writable
3454
- const catching = props.catching
3455
- const setCatching = props.setCatching
3456
- const devices = props.devices || []
3457
- const testingMic = props.testingMic
3458
- const toggleTestMic = props.toggleTestMic
3459
- const testLevel = props.testLevel || 0
3460
- const gateDbVal = props.gateDbVal
3461
- const gateThresholdPercent = props.gateThresholdPercent
3462
- const calibrating = props.calibrating
3463
- const calibrateMsg = props.calibrateMsg
3464
- const calibrateNoise = props.calibrateNoise
3465
- const onClearKey = props.onClearKey
3604
+ const {
3605
+ t = moduleT, draft, setTop, writable, catching, setCatching,
3606
+ devices = [], testingMic, toggleTestMic, testLevel = 0,
3607
+ gateDbVal, gateThresholdPercent, calibrating, calibrateMsg, calibrateNoise, onClearKey
3608
+ } = props
3466
3609
 
3467
3610
  const [benchmarking, setBenchmarking] = React.useState(false)
3468
3611
  const [benchmarkResult, setBenchmarkResult] = React.useState(null)
@@ -3490,55 +3633,55 @@ function DictationModesSection(props) {
3490
3633
  })
3491
3634
  }
3492
3635
 
3493
- const textField = (key, label, hint) => React.createElement('div', { className: 'cb-field' },
3494
- React.createElement('label', null, label),
3495
- React.createElement('input', {
3636
+ const textField = (key, label, hint) => h('div', { className: 'cb-field' },
3637
+ h('label', null, label),
3638
+ h('input', {
3496
3639
  type: 'text', value: draft && draft[key] !== undefined ? draft[key] : '', disabled: !writable,
3497
3640
  onChange: (e) => setTop(key, e.target.value),
3498
3641
  }),
3499
- React.createElement('span', { className: 'dvs-sub' }, hint),
3642
+ h('span', { className: 'dvs-sub' }, hint),
3500
3643
  )
3501
3644
 
3502
- return React.createElement('div', { className: 'cb-section-card' },
3503
- React.createElement('div', { className: 'cb-section-title' },
3504
- React.createElement('span', null, '⚙️ ' + t('hardwareTitle')),
3645
+ return h('div', { className: 'cb-section-card' },
3646
+ h('div', { className: 'cb-section-title' },
3647
+ h('span', null, '⚙️ ' + t('hardwareTitle')),
3505
3648
  ),
3506
- React.createElement('div', { className: 'cb-section-desc' }, t('hardwareDesc')),
3649
+ h('div', { className: 'cb-section-desc' }, t('hardwareDesc')),
3507
3650
  // Hotkey picker
3508
- React.createElement('div', { className: 'cb-field' },
3509
- React.createElement('label', null, t('hotkey')),
3510
- React.createElement('div', { className: 'cb-row' },
3511
- React.createElement('button', {
3651
+ h('div', { className: 'cb-field' },
3652
+ h('label', null, t('hotkey')),
3653
+ h('div', { className: 'cb-row' },
3654
+ h('button', {
3512
3655
  type: 'button', className: 'cb-btn cb-btn-primary', disabled: !writable,
3513
3656
  onClick: () => setCatching(true),
3514
3657
  }, catching ? t('pressKey') : keyLabel(draft && draft.hotkey)),
3515
- React.createElement('button', {
3658
+ h('button', {
3516
3659
  type: 'button', className: 'cb-btn cb-btn-mini', title: t('clearKey'),
3517
3660
  disabled: !writable, onClick: onClearKey,
3518
3661
  }, '×'),
3519
3662
  ),
3520
- React.createElement('span', { className: 'dvs-sub' }, t('hotkeyHint1') + t('hotkeyHint2')),
3663
+ h('span', { className: 'dvs-sub' }, t('hotkeyHint1') + t('hotkeyHint2')),
3521
3664
  ),
3522
3665
  // Mic & noise gate
3523
- React.createElement('div', { className: 'cb-field' },
3524
- React.createElement('label', null, t('mic')),
3525
- React.createElement('div', { className: 'cb-row' },
3526
- React.createElement('select', {
3666
+ h('div', { className: 'cb-field' },
3667
+ h('label', null, t('mic')),
3668
+ h('div', { className: 'cb-row' },
3669
+ h('select', {
3527
3670
  value: String((draft && draft.micDeviceId) ?? ''), disabled: !writable || testingMic,
3528
3671
  onChange: (e) => setTop('micDeviceId', e.target.value),
3529
3672
  style: { flex: 1 },
3530
3673
  },
3531
- React.createElement('option', { value: '' }, t('micDefault')),
3532
- devices.map((d) => React.createElement('option', { key: d.deviceId, value: d.deviceId },
3674
+ h('option', { value: '' }, t('micDefault')),
3675
+ devices.map((d) => h('option', { key: d.deviceId, value: d.deviceId },
3533
3676
  d.label || d.deviceId.slice(0, 12))),
3534
3677
  ),
3535
- React.createElement('button', {
3678
+ h('button', {
3536
3679
  type: 'button',
3537
3680
  className: 'cb-btn ' + (testingMic ? 'cb-btn-primary' : 'cb-btn-secondary'),
3538
3681
  onClick: toggleTestMic,
3539
3682
  style: { flex: 'none' },
3540
3683
  }, testingMic ? t('stopTest') : t('testMic')),
3541
- React.createElement('button', {
3684
+ h('button', {
3542
3685
  type: 'button',
3543
3686
  className: 'cb-btn cb-btn-secondary',
3544
3687
  onClick: () => (typeof calibrateNoise === 'function' ? calibrateNoise(setTop, t) : null),
@@ -3547,36 +3690,57 @@ function DictationModesSection(props) {
3547
3690
  title: t('calibrateNoiseHint') || '',
3548
3691
  }, calibrating ? t('calibrating') : t('calibrateNoise')),
3549
3692
  ),
3550
- calibrateMsg ? React.createElement('div', {
3693
+ calibrateMsg ? h('div', {
3551
3694
  style: { fontSize: '11px', color: 'var(--dsw-alias-state-success-primary)', marginTop: '4px' },
3552
3695
  }, calibrateMsg) : null,
3553
- testingMic ? React.createElement('div', { className: 'dvo-mic-test' },
3554
- React.createElement('span', { style: { fontSize: '11px', color: 'var(--dsw-alias-label-secondary)' } }, t('micLevel') + ':'),
3555
- React.createElement('div', { className: 'dvo-meter-bar' },
3556
- React.createElement('div', { className: 'dvo-meter-fill', style: { width: `${testLevel}%` } }),
3557
- gateThresholdPercent > 0 ? React.createElement('div', { className: 'dvo-meter-gate', style: { left: `${gateThresholdPercent}%` }, title: `${gateDbVal} dB` }) : null,
3696
+ testingMic ? h('div', { className: 'dvo-mic-test' },
3697
+ h('span', { style: { fontSize: '11px', color: 'var(--dsw-alias-label-secondary)' } }, t('micLevel') + ':'),
3698
+ h('div', { className: 'dvo-meter-bar' },
3699
+ h('div', { className: 'dvo-meter-fill', style: { width: `${testLevel}%` } }),
3700
+ gateThresholdPercent > 0 ? h('div', { className: 'dvo-meter-gate', style: { left: `${gateThresholdPercent}%` }, title: `${gateDbVal} dB` }) : null,
3558
3701
  ),
3559
- React.createElement('span', { style: { fontSize: '11px', fontVariantNumeric: 'tabular-nums', width: '32px', textAlign: 'right' } }, `${testLevel}%`),
3702
+ h('span', { style: { fontSize: '11px', fontVariantNumeric: 'tabular-nums', width: '32px', textAlign: 'right' } }, `${testLevel}%`),
3560
3703
  ) : null,
3561
- React.createElement('div', { className: 'cb-row', style: { marginTop: '6px' } },
3562
- React.createElement('span', { style: { fontSize: '12px', color: 'var(--dsw-alias-label-secondary)', minWidth: '120px' } }, t('noiseGate') + ':'),
3563
- React.createElement('select', {
3704
+ h('div', { className: 'cb-row', style: { marginTop: '6px' } },
3705
+ h('span', { style: { fontSize: '12px', color: 'var(--dsw-alias-label-secondary)', minWidth: '120px' } }, t('noiseGate') + ':'),
3706
+ h('select', {
3564
3707
  value: String(gateDbVal), disabled: !writable,
3565
3708
  onChange: (e) => setTop('noiseGateDb', Number(e.target.value)),
3566
3709
  style: { flex: 1 },
3567
3710
  },
3568
- React.createElement('option', { value: '-999' }, t('noiseGateOff')),
3569
- React.createElement('option', { value: '-50' }, '-50 dB (Low / Gentle)'),
3570
- React.createElement('option', { value: '-45' }, '-45 dB (Standard Default)'),
3571
- React.createElement('option', { value: '-35' }, '-35 dB (Medium Room)'),
3572
- React.createElement('option', { value: '-25' }, '-25 dB (High / Mechanical Clicks)'),
3573
- !['-999', '-50', '-45', '-35', '-25'].includes(String(gateDbVal)) ? React.createElement('option', { value: String(gateDbVal) }, `${gateDbVal} dB (${t('calibratedValue') || 'Calibrated'})`) : null,
3711
+ h('option', { value: '-999' }, t('noiseGateOff')),
3712
+ h('option', { value: '-50' }, '-50 dB (Low / Gentle)'),
3713
+ h('option', { value: '-45' }, '-45 dB (Standard Default)'),
3714
+ h('option', { value: '-35' }, '-35 dB (Medium Room)'),
3715
+ h('option', { value: '-25' }, '-25 dB (High / Mechanical Clicks)'),
3716
+ !['-999', '-50', '-45', '-35', '-25'].includes(String(gateDbVal)) ? h('option', { value: String(gateDbVal) }, `${gateDbVal} dB (${t('calibratedValue') || 'Calibrated'})`) : null,
3574
3717
  ),
3575
3718
  ),
3576
- React.createElement('span', { className: 'dvs-sub' }, t('noiseGateHint')),
3719
+ h('span', { className: 'dvs-sub' }, t('noiseGateHint')),
3720
+ ),
3721
+ h('div', { className: 'cb-field' },
3722
+ h('label', null, t('vadEngine')),
3723
+ h('select', {
3724
+ value: (draft && draft.vadEngine) || 'silero', disabled: !writable,
3725
+ onChange: (e) => setTop('vadEngine', e.target.value),
3726
+ },
3727
+ h('option', { value: 'silero' }, t('vadSilero')),
3728
+ h('option', { value: 'amplitude' }, t('vadAmplitude')),
3729
+ ),
3730
+ h('span', { className: 'dvs-sub' }, t('vadEngineHint')),
3731
+ ),
3732
+ h('div', { className: 'cb-field' },
3733
+ h('label', { className: 'cb-check-label' },
3734
+ h('input', {
3735
+ type: 'checkbox', checked: draft && draft.streamingWs !== false, disabled: !writable,
3736
+ onChange: (e) => setTop('streamingWs', e.target.checked),
3737
+ }),
3738
+ ' ' + t('streamingWs'),
3739
+ ),
3740
+ h('span', { className: 'dvs-sub' }, t('streamingWsHint')),
3577
3741
  ),
3578
3742
  // Endpoint text fields
3579
- React.createElement('div', { className: 'cb-grid-2' },
3743
+ h('div', { className: 'cb-grid-2' },
3580
3744
  textField('whisperUrl', t('whisperEndpoint'), t('whisperEndpointHint')),
3581
3745
  textField('whisperBin', t('whisperBin'), t('whisperBinHint')),
3582
3746
  textField('whisperModel', t('whisperModel'), t('whisperModelHint')),
@@ -3589,65 +3753,65 @@ function DictationModesSection(props) {
3589
3753
  textField('polishKeyEnv', t('polishKeyEnv'), ''),
3590
3754
  ),
3591
3755
  // Engine checkboxes
3592
- React.createElement('div', { className: 'cb-row' },
3593
- React.createElement('label', { className: 'cb-check-label' },
3594
- React.createElement('input', {
3756
+ h('div', { className: 'cb-row' },
3757
+ h('label', { className: 'cb-check-label' },
3758
+ h('input', {
3595
3759
  type: 'checkbox', checked: !!(draft && draft.autoStart), disabled: !writable,
3596
3760
  onChange: (e) => setTop('autoStart', e.target.checked),
3597
3761
  }),
3598
3762
  t('whisperAutostart'),
3599
3763
  ),
3600
- React.createElement('label', { className: 'cb-check-label' },
3601
- React.createElement('input', {
3764
+ h('label', { className: 'cb-check-label' },
3765
+ h('input', {
3602
3766
  type: 'checkbox', checked: !!(draft && draft.sensevoiceAutostart), disabled: !writable,
3603
3767
  onChange: (e) => setTop('sensevoiceAutostart', e.target.checked),
3604
3768
  }),
3605
3769
  t('sensevoiceAutostart'),
3606
3770
  ),
3607
- React.createElement('label', { className: 'cb-check-label' },
3608
- React.createElement('input', {
3771
+ h('label', { className: 'cb-check-label' },
3772
+ h('input', {
3609
3773
  type: 'checkbox', checked: !!(draft && draft.localOnly), disabled: !writable,
3610
3774
  onChange: (e) => setTop('localOnly', e.target.checked),
3611
3775
  }),
3612
3776
  t('localOnly'),
3613
3777
  ),
3614
- React.createElement('label', { className: 'cb-check-label' },
3615
- React.createElement('input', {
3778
+ h('label', { className: 'cb-check-label' },
3779
+ h('input', {
3616
3780
  type: 'checkbox', checked: !!(draft && draft.beep), disabled: !writable,
3617
3781
  onChange: (e) => setTop('beep', e.target.checked),
3618
3782
  }),
3619
3783
  t('beep'),
3620
3784
  ),
3621
- React.createElement('label', { className: 'cb-check-label' },
3622
- React.createElement('input', {
3785
+ h('label', { className: 'cb-check-label' },
3786
+ h('input', {
3623
3787
  type: 'checkbox', checked: draft ? draft.noiseSuppression !== false : true, disabled: !writable,
3624
3788
  onChange: (e) => setTop('noiseSuppression', e.target.checked),
3625
3789
  }),
3626
3790
  t('noiseSuppression'),
3627
3791
  ),
3628
- React.createElement('label', { className: 'cb-check-label' },
3629
- React.createElement('input', {
3792
+ h('label', { className: 'cb-check-label' },
3793
+ h('input', {
3630
3794
  type: 'checkbox', checked: draft ? draft.contextGlossary !== false : true, disabled: !writable,
3631
3795
  onChange: (e) => setTop('contextGlossary', e.target.checked),
3632
3796
  }),
3633
3797
  t('contextGlossary'),
3634
3798
  ),
3635
- React.createElement('label', { className: 'cb-check-label', title: t('bilingualDeveloperPromptHint') || '' },
3636
- React.createElement('input', {
3799
+ h('label', { className: 'cb-check-label', title: t('bilingualDeveloperPromptHint') || '' },
3800
+ h('input', {
3637
3801
  type: 'checkbox', checked: draft ? draft.bilingualDeveloperPrompt !== false : true, disabled: !writable,
3638
3802
  onChange: (e) => setTop('bilingualDeveloperPrompt', e.target.checked),
3639
3803
  }),
3640
3804
  t('bilingualDeveloperPrompt'),
3641
3805
  ),
3642
- React.createElement('label', { className: 'cb-check-label' },
3643
- React.createElement('input', {
3806
+ h('label', { className: 'cb-check-label' },
3807
+ h('input', {
3644
3808
  type: 'checkbox', checked: !!(draft && draft.voiceCommands), disabled: !writable,
3645
3809
  onChange: (e) => setTop('voiceCommands', e.target.checked),
3646
3810
  }),
3647
3811
  t('voiceCommandsLabel'),
3648
3812
  ),
3649
- React.createElement('label', { className: 'cb-check-label' },
3650
- React.createElement('input', {
3813
+ h('label', { className: 'cb-check-label' },
3814
+ h('input', {
3651
3815
  type: 'checkbox', checked: !!(draft && draft.normalizeTranscript), disabled: !writable,
3652
3816
  onChange: (e) => setTop('normalizeTranscript', e.target.checked),
3653
3817
  }),
@@ -3655,35 +3819,35 @@ function DictationModesSection(props) {
3655
3819
  ),
3656
3820
  ),
3657
3821
  // Visualizer select
3658
- React.createElement('div', { className: 'cb-field' },
3659
- React.createElement('label', null, t('visualizerStyle')),
3660
- React.createElement('select', {
3822
+ h('div', { className: 'cb-field' },
3823
+ h('label', null, t('visualizerStyle')),
3824
+ h('select', {
3661
3825
  value: String((draft && draft.visualizerStyle) || 'liquid-wave'), disabled: !writable,
3662
3826
  onChange: (e) => setTop('visualizerStyle', e.target.value),
3663
3827
  },
3664
- React.createElement('option', { value: 'liquid-wave' }, t('visLiquidWave')),
3665
- React.createElement('option', { value: 'dynamic-orb' }, t('visDynamicOrb')),
3666
- React.createElement('option', { value: 'bars' }, t('visBars')),
3667
- React.createElement('option', { value: 'off' }, t('visOff')),
3828
+ h('option', { value: 'liquid-wave' }, t('visLiquidWave')),
3829
+ h('option', { value: 'dynamic-orb' }, t('visDynamicOrb')),
3830
+ h('option', { value: 'bars' }, t('visBars')),
3831
+ h('option', { value: 'off' }, t('visOff')),
3668
3832
  ),
3669
3833
  ),
3670
3834
  // Hardware Acceleration (SBC NPU / Host)
3671
- React.createElement('div', { className: 'cb-field' },
3672
- React.createElement('label', null, t('npuProvider')),
3673
- React.createElement('div', { className: 'cb-row' },
3674
- React.createElement('select', {
3835
+ h('div', { className: 'cb-field' },
3836
+ h('label', null, t('npuProvider')),
3837
+ h('div', { className: 'cb-row' },
3838
+ h('select', {
3675
3839
  value: String((draft && draft.sensevoiceProvider) || 'cpu'),
3676
3840
  disabled: !writable,
3677
3841
  onChange: (e) => setTop('sensevoiceProvider', e.target.value),
3678
3842
  style: { flex: 1 },
3679
3843
  },
3680
- React.createElement('option', { value: 'cpu' }, 'CPU (Standard)'),
3681
- React.createElement('option', { value: 'rknn' }, 'Rockchip RK3588 NPU (RKNN)'),
3682
- React.createElement('option', { value: 'coreml' }, 'Apple Silicon CoreML'),
3683
- React.createElement('option', { value: 'openvino' }, 'Intel OpenVINO'),
3684
- React.createElement('option', { value: 'cuda' }, 'NVIDIA CUDA'),
3844
+ h('option', { value: 'cpu' }, 'CPU (Standard)'),
3845
+ h('option', { value: 'rknn' }, 'Rockchip RK3588 NPU (RKNN)'),
3846
+ h('option', { value: 'coreml' }, 'Apple Silicon CoreML'),
3847
+ h('option', { value: 'openvino' }, 'Intel OpenVINO'),
3848
+ h('option', { value: 'cuda' }, 'NVIDIA CUDA'),
3685
3849
  ),
3686
- React.createElement('input', {
3850
+ h('input', {
3687
3851
  type: 'number',
3688
3852
  min: 1,
3689
3853
  max: 32,
@@ -3694,30 +3858,30 @@ function DictationModesSection(props) {
3694
3858
  onChange: (e) => setTop('sensevoiceThreads', Math.max(1, Math.min(32, Number(e.target.value) || 4))),
3695
3859
  }),
3696
3860
  ),
3697
- React.createElement('span', { className: 'dvs-sub' }, t('npuProviderHint')),
3861
+ h('span', { className: 'dvs-sub' }, t('npuProviderHint')),
3698
3862
  ),
3699
3863
  // Hardware Benchmark
3700
- React.createElement('div', { className: 'cb-field' },
3701
- React.createElement('div', { className: 'cb-row', style: { alignItems: 'center', justifyContent: 'space-between' } },
3702
- React.createElement('label', { style: { marginBottom: 0 } }, t('benchmarkTitle')),
3703
- React.createElement('button', {
3864
+ h('div', { className: 'cb-field' },
3865
+ h('div', { className: 'cb-row', style: { alignItems: 'center', justifyContent: 'space-between' } },
3866
+ h('label', { style: { marginBottom: 0 } }, t('benchmarkTitle')),
3867
+ h('button', {
3704
3868
  type: 'button', className: 'cb-btn cb-btn-secondary', style: { fontSize: '12px', padding: '4px 10px' },
3705
3869
  disabled: benchmarking || !writable, onClick: runBenchmark,
3706
3870
  }, benchmarking ? ('⏳ ' + t('benchmarking')) : ('⚡ ' + t('runBenchmarkBtn'))),
3707
3871
  ),
3708
3872
  benchmarkResult
3709
- ? React.createElement('div', {
3873
+ ? h('div', {
3710
3874
  style: { marginTop: '8px', padding: '6px 10px', borderRadius: '6px', fontSize: '12px', backgroundColor: 'var(--dsw-alias-fill-quaternary)' }
3711
3875
  }, benchmarkResult.running
3712
3876
  ? `⚡ RTF: ${benchmarkResult.rtf}x · ${benchmarkResult.latencyMs}ms (${benchmarkResult.rating}) · ${String(benchmarkResult.provider || 'cpu').toUpperCase()} · Cores: ${benchmarkResult.cpuCores}`
3713
3877
  : `ℹ ${benchmarkResult.message || 'Offline'} (${benchmarkResult.cpuCores} cores, ${benchmarkResult.platform})`)
3714
- : (benchmarkErr ? React.createElement('div', { style: { color: 'var(--dsw-alias-state-danger)', fontSize: '12px', marginTop: '4px' } }, benchmarkErr) : null),
3715
- React.createElement('span', { className: 'dvs-sub' }, t('benchmarkHint')),
3878
+ : (benchmarkErr ? h('div', { style: { color: 'var(--dsw-alias-state-danger)', fontSize: '12px', marginTop: '4px' } }, benchmarkErr) : null),
3879
+ h('span', { className: 'dvs-sub' }, t('benchmarkHint')),
3716
3880
  ),
3717
3881
  // WebGPU Status Indicator
3718
- React.createElement('div', { className: 'cb-field' },
3719
- React.createElement('label', null, t('webgpuStatus')),
3720
- React.createElement('div', {
3882
+ h('div', { className: 'cb-field' },
3883
+ h('label', null, t('webgpuStatus')),
3884
+ h('div', {
3721
3885
  style: {
3722
3886
  padding: '6px 10px',
3723
3887
  borderRadius: '6px',
@@ -3732,9 +3896,9 @@ function DictationModesSection(props) {
3732
3896
  : ('ℹ ' + t('webgpuNotAvailable'))),
3733
3897
  ),
3734
3898
  // Vocabulary textarea
3735
- React.createElement('div', { className: 'cb-field' },
3736
- React.createElement('label', null, t('vocabulary')),
3737
- React.createElement('textarea', {
3899
+ h('div', { className: 'cb-field' },
3900
+ h('label', null, t('vocabulary')),
3901
+ h('textarea', {
3738
3902
  rows: 3, disabled: !writable,
3739
3903
  value: Array.isArray(draft && draft.vocabulary) ? draft.vocabulary.join('\n') : '',
3740
3904
  onChange: (e) => setTop('vocabulary', e.target.value.split('\n').map((x) => x.trim()).filter(Boolean)),
@@ -4018,12 +4182,12 @@ function DictationModesSection(props) {
4018
4182
  const jargonInput = props.jargonInput
4019
4183
  const setJargonInput = props.setJargonInput
4020
4184
 
4021
- return React.createElement('div', { className: 'cb-section-card' },
4022
- React.createElement('div', { className: 'cb-section-title' },
4023
- React.createElement('span', null, '📖 ' + t('jargonTitle')),
4185
+ return h('div', { className: 'cb-section-card' },
4186
+ h('div', { className: 'cb-section-title' },
4187
+ h('span', null, '📖 ' + t('jargonTitle')),
4024
4188
  ),
4025
- React.createElement('label', { className: 'cb-check-label', style: { marginBottom: '8px' } },
4026
- React.createElement('input', {
4189
+ h('label', { className: 'cb-check-label', style: { marginBottom: '8px' } },
4190
+ h('input', {
4027
4191
  type: 'checkbox',
4028
4192
  checked: draft ? draft.techJargonCorrection !== false : true,
4029
4193
  disabled: !writable,
@@ -4031,9 +4195,9 @@ function DictationModesSection(props) {
4031
4195
  }),
4032
4196
  t('techJargonCorrection'),
4033
4197
  ),
4034
- React.createElement('div', { className: 'cb-field' },
4035
- React.createElement('label', null, t('jargonAdd') + t('jargonFormatHint')),
4036
- React.createElement('textarea', {
4198
+ h('div', { className: 'cb-field' },
4199
+ h('label', null, t('jargonAdd') + t('jargonFormatHint')),
4200
+ h('textarea', {
4037
4201
  rows: 3,
4038
4202
  disabled: !writable,
4039
4203
  placeholder: t('jargonPlaceholder'),
@@ -4085,10 +4249,10 @@ function MacrosSection(props) {
4085
4249
  setTop('voiceMacros', next)
4086
4250
  }
4087
4251
 
4088
- return React.createElement('div', { className: 'cb-section-card' },
4089
- React.createElement('div', { className: 'cb-section-title' },
4090
- React.createElement('span', null, '⚡ ' + t('voiceMacrosTitle')),
4091
- React.createElement('button', {
4252
+ return h('div', { className: 'cb-section-card' },
4253
+ h('div', { className: 'cb-section-title' },
4254
+ h('span', null, '⚡ ' + t('voiceMacrosTitle')),
4255
+ h('button', {
4092
4256
  type: 'button',
4093
4257
  className: 'cb-btn cb-btn-secondary',
4094
4258
  style: { fontSize: '11px', padding: '2px 8px' },
@@ -4096,16 +4260,16 @@ function MacrosSection(props) {
4096
4260
  onClick: addMacro,
4097
4261
  }, '+ ' + t('addMacro')),
4098
4262
  ),
4099
- React.createElement('div', { className: 'cb-field-desc' }, t('voiceMacrosDesc')),
4263
+ h('div', { className: 'cb-field-desc' }, t('voiceMacrosDesc')),
4100
4264
  macros.length === 0
4101
- ? React.createElement('div', { style: { fontStyle: 'italic', color: 'var(--dsw-alias-label-tertiary)', fontSize: '12px', marginTop: '6px' } },
4265
+ ? h('div', { style: { fontStyle: 'italic', color: 'var(--dsw-alias-label-tertiary)', fontSize: '12px', marginTop: '6px' } },
4102
4266
  'No macros configured.',
4103
4267
  )
4104
- : React.createElement('div', { style: { display: 'flex', flexDirection: 'column', gap: '8px', marginTop: '6px' } },
4268
+ : h('div', { style: { display: 'flex', flexDirection: 'column', gap: '8px', marginTop: '6px' } },
4105
4269
  macros.map((m, idx) =>
4106
- React.createElement('div', { key: idx, className: 'dvo-macro-item' },
4107
- React.createElement('div', { style: { display: 'flex', gap: '8px', alignItems: 'center' } },
4108
- React.createElement('input', {
4270
+ h('div', { key: idx, className: 'dvo-macro-item' },
4271
+ h('div', { style: { display: 'flex', gap: '8px', alignItems: 'center' } },
4272
+ h('input', {
4109
4273
  type: 'text',
4110
4274
  className: 'cb-input',
4111
4275
  style: { flex: '1', height: '28px', fontSize: '12px' },
@@ -4114,7 +4278,7 @@ function MacrosSection(props) {
4114
4278
  disabled: !writable,
4115
4279
  onChange: (e) => updateMacro(idx, 'trigger', e.target.value),
4116
4280
  }),
4117
- React.createElement('button', {
4281
+ h('button', {
4118
4282
  type: 'button',
4119
4283
  className: 'cb-btn cb-btn-secondary',
4120
4284
  style: { padding: '2px 6px', fontSize: '11px', color: 'var(--dsw-alias-state-error-primary)' },
@@ -4122,7 +4286,7 @@ function MacrosSection(props) {
4122
4286
  onClick: () => removeMacro(idx),
4123
4287
  }, t('macroDelete')),
4124
4288
  ),
4125
- React.createElement('textarea', {
4289
+ h('textarea', {
4126
4290
  className: 'cb-input',
4127
4291
  style: { minHeight: '38px', resize: 'vertical', fontSize: '12px' },
4128
4292
  placeholder: t('macroTemplate') + ' (use {text} for argument)',
@@ -4150,24 +4314,24 @@ function MacrosSection(props) {
4150
4314
  ? (t('sensevoiceReady') + (currentType === 'whisper-turbo' ? ' (whisper-turbo)' : ' (small-int8)'))
4151
4315
  : (installingSensevoice ? t('sensevoiceDownloading') : t('sensevoiceNotInstalled'))
4152
4316
 
4153
- return React.createElement('div', { className: 'cb-section-card' },
4154
- React.createElement('div', { className: 'cb-section-title' },
4155
- React.createElement('span', null, '⚡ ' + t('sensevoiceTitle')),
4156
- React.createElement('span', { className: 'cb-badge ' + (isInstalled ? 'cb-badge-ok' : 'cb-badge-warn') }, badgeText),
4317
+ return h('div', { className: 'cb-section-card' },
4318
+ h('div', { className: 'cb-section-title' },
4319
+ h('span', null, '⚡ ' + t('sensevoiceTitle')),
4320
+ h('span', { className: 'cb-badge ' + (isInstalled ? 'cb-badge-ok' : 'cb-badge-warn') }, badgeText),
4157
4321
  ),
4158
- React.createElement('div', { className: 'cb-section-desc' }, t('sensevoiceDesc')),
4159
- React.createElement('div', { className: 'dvo-installer-box' },
4160
- React.createElement('div', { className: 'cb-row', style: { alignItems: 'center', justifyContent: 'space-between', gap: '12px' } },
4161
- React.createElement('select', {
4322
+ h('div', { className: 'cb-section-desc' }, t('sensevoiceDesc')),
4323
+ h('div', { className: 'dvo-installer-box' },
4324
+ h('div', { className: 'cb-row', style: { alignItems: 'center', justifyContent: 'space-between', gap: '12px' } },
4325
+ h('select', {
4162
4326
  value: modelChoice,
4163
4327
  disabled: installingSensevoice || !writable,
4164
4328
  onChange: (e) => setModelChoice(e.target.value),
4165
4329
  style: { flex: 1, fontSize: '12px', minWidth: '180px' },
4166
4330
  },
4167
- React.createElement('option', { value: 'sensevoice' }, 'SenseVoice-Small (150MB, ~50ms)'),
4168
- React.createElement('option', { value: 'whisper-turbo' }, 'Whisper Large-v3-Turbo (800MB, High-Accuracy)'),
4331
+ h('option', { value: 'sensevoice' }, 'SenseVoice-Small (150MB, ~50ms)'),
4332
+ h('option', { value: 'whisper-turbo' }, 'Whisper Large-v3-Turbo (800MB, High-Accuracy)'),
4169
4333
  ),
4170
- React.createElement('button', {
4334
+ h('button', {
4171
4335
  type: 'button',
4172
4336
  className: 'cb-btn cb-btn-primary',
4173
4337
  style: { fontSize: '12px', padding: '6px 12px', whiteSpace: 'nowrap' },
@@ -4175,17 +4339,17 @@ function MacrosSection(props) {
4175
4339
  onClick: () => { if (typeof onInstall === 'function') onInstall(modelChoice) },
4176
4340
  }, installingSensevoice ? ('⏳ ' + t('installingSensevoice')) : ('📥 ' + t('installSensevoice'))),
4177
4341
  ),
4178
- React.createElement('div', { style: { fontSize: '11px', marginTop: '6px', color: 'var(--dsw-alias-content-secondary)' } },
4342
+ h('div', { style: { fontSize: '11px', marginTop: '6px', color: 'var(--dsw-alias-content-secondary)' } },
4179
4343
  sensevoiceState && sensevoiceState.path
4180
4344
  ? ('~/' + sensevoiceState.path.replace(/^.*[\\/](\.dsh[\\/]models[\\/].*)$/, '$1'))
4181
4345
  : ('~/.dsh/models/' + modelChoice)
4182
4346
  ),
4183
4347
  installingSensevoice
4184
- ? React.createElement('div', { style: { marginTop: '8px' } },
4185
- React.createElement('div', { className: 'dvo-progress-bar' },
4186
- React.createElement('div', { className: 'dvo-progress-bar-fill' })
4348
+ ? h('div', { style: { marginTop: '8px' } },
4349
+ h('div', { className: 'dvo-progress-bar' },
4350
+ h('div', { className: 'dvo-progress-bar-fill' })
4187
4351
  ),
4188
- React.createElement('div', { style: { fontSize: '11px', marginTop: '4px', color: 'var(--dsw-alias-content-secondary)' } },
4352
+ h('div', { style: { fontSize: '11px', marginTop: '4px', color: 'var(--dsw-alias-content-secondary)' } },
4189
4353
  (sensevoiceState && sensevoiceState.progress) || t('sensevoiceDownloading')
4190
4354
  )
4191
4355
  )
@@ -4203,28 +4367,28 @@ function MacrosSection(props) {
4203
4367
  const onRefresh = props.onRefresh
4204
4368
  const onOpenWizard = props.onOpenWizard
4205
4369
 
4206
- return React.createElement('div', { className: 'cb-section-card' },
4207
- React.createElement('div', { className: 'cb-section-title' },
4208
- React.createElement('span', null, t('statusTitle')),
4209
- React.createElement('div', { style: { display: 'flex', gap: '8px', alignItems: 'center' } },
4210
- onOpenWizard ? React.createElement('button', {
4370
+ return h('div', { className: 'cb-section-card' },
4371
+ h('div', { className: 'cb-section-title' },
4372
+ h('span', null, t('statusTitle')),
4373
+ h('div', { style: { display: 'flex', gap: '8px', alignItems: 'center' } },
4374
+ onOpenWizard ? h('button', {
4211
4375
  type: 'button', className: 'cb-btn cb-btn-primary', style: { fontSize: '12px', padding: '4px 10px' },
4212
4376
  onClick: onOpenWizard,
4213
4377
  }, '✨ ' + t('wizardBtnLaunch')) : null,
4214
- React.createElement('button', {
4378
+ h('button', {
4215
4379
  type: 'button', className: 'cb-btn', style: { fontSize: '12px', padding: '4px 8px' },
4216
4380
  onClick: onRefresh,
4217
4381
  }, '🔄 ' + t('refreshStats')),
4218
4382
  ),
4219
4383
  ),
4220
- React.createElement('div', { className: 'cb-row', style: { alignItems: 'center', gap: '12px', flexWrap: 'wrap' } },
4221
- React.createElement('span', { className: 'cb-badge ' + (hostOnline ? 'cb-badge-ok' : 'cb-badge-err') },
4384
+ h('div', { className: 'cb-row', style: { alignItems: 'center', gap: '12px', flexWrap: 'wrap' } },
4385
+ h('span', { className: 'cb-badge ' + (hostOnline ? 'cb-badge-ok' : 'cb-badge-err') },
4222
4386
  hostOnline ? (`✓ ${t('hostOnline')} (${statusLatency}ms)`) : (`✗ ${t('hostOffline')}`)
4223
4387
  ),
4224
- React.createElement('span', { className: 'cb-badge ' + (whisperRunning ? 'cb-badge-ok' : 'cb-badge-warn') },
4388
+ h('span', { className: 'cb-badge ' + (whisperRunning ? 'cb-badge-ok' : 'cb-badge-warn') },
4225
4389
  whisperRunning ? (`✓ ${t('whisperReady')}`) : (`· ${t('whisperStopped')}`)
4226
4390
  ),
4227
- React.createElement('span', { className: 'cb-badge' }, `${providerCount} ${t('providersAvailable')}`),
4391
+ h('span', { className: 'cb-badge' }, `${providerCount} ${t('providersAvailable')}`),
4228
4392
  ),
4229
4393
  )
4230
4394
  }
@@ -4239,43 +4403,43 @@ function MacrosSection(props) {
4239
4403
  const onCheck = props.onCheck
4240
4404
  const onUpdateNow = props.onUpdateNow
4241
4405
 
4242
- return React.createElement('div', { className: 'cb-section-card' },
4243
- React.createElement('div', { className: 'cb-section-title' },
4244
- React.createElement('span', null, '📦 ' + t('versionTitle')),
4245
- React.createElement('button', {
4406
+ return h('div', { className: 'cb-section-card' },
4407
+ h('div', { className: 'cb-section-title' },
4408
+ h('span', null, '📦 ' + t('versionTitle')),
4409
+ h('button', {
4246
4410
  type: 'button', className: 'cb-btn', style: { fontSize: '12px', padding: '4px 8px' },
4247
4411
  onClick: onCheck, disabled: updaterLoading || updating,
4248
4412
  }, updaterLoading ? '⏳…' : '🔄 ' + t('refreshStats')),
4249
4413
  ),
4250
- React.createElement('div', { className: 'cb-section-desc' }, t('versionHint')),
4251
- React.createElement('div', { className: 'cb-row', style: { alignItems: 'center', gap: '12px' } },
4414
+ h('div', { className: 'cb-section-desc' }, t('versionHint')),
4415
+ h('div', { className: 'cb-row', style: { alignItems: 'center', gap: '12px' } },
4252
4416
  updaterStatus && updaterStatus.currentVersion
4253
- ? React.createElement('span', { className: 'cb-badge cb-badge-ok' }, 'v' + updaterStatus.currentVersion)
4417
+ ? h('span', { className: 'cb-badge cb-badge-ok' }, 'v' + updaterStatus.currentVersion)
4254
4418
  : null,
4255
4419
  updaterLoading && (!updaterStatus || !updaterStatus.currentVersion)
4256
- ? React.createElement('span', { className: 'cb-badge' }, '⏳ ' + t('checkingUpdates'))
4420
+ ? h('span', { className: 'cb-badge' }, '⏳ ' + t('checkingUpdates'))
4257
4421
  : !updaterStatus
4258
- ? React.createElement('span', { className: 'cb-badge cb-badge-warn' }, '❓ ' + t('updateStatusUnknown'))
4422
+ ? h('span', { className: 'cb-badge cb-badge-warn' }, '❓ ' + t('updateStatusUnknown'))
4259
4423
  : updaterStatus.updateAvailable
4260
- ? React.createElement('span', { className: 'cb-badge cb-badge-warn' },
4424
+ ? h('span', { className: 'cb-badge cb-badge-warn' },
4261
4425
  t('updateAvailable') + ': v' + updaterStatus.latestVersion
4262
4426
  )
4263
4427
  : updaterStatus.latestCheckFailed
4264
- ? React.createElement('span', { className: 'cb-badge cb-badge-warn' },
4428
+ ? h('span', { className: 'cb-badge cb-badge-warn' },
4265
4429
  '⚠️ ' + t('updateCheckFailed')
4266
4430
  )
4267
- : React.createElement('span', { className: 'cb-badge cb-badge-ok' },
4431
+ : h('span', { className: 'cb-badge cb-badge-ok' },
4268
4432
  t('upToDate')
4269
4433
  ),
4270
4434
  updaterStatus && updaterStatus.updateAvailable
4271
- ? React.createElement('button', {
4435
+ ? h('button', {
4272
4436
  type: 'button', className: 'cb-btn cb-btn-primary', style: { fontSize: '12px', padding: '4px 10px' },
4273
4437
  onClick: onUpdateNow, disabled: updating,
4274
4438
  }, updating ? t('updating') : t('updateNow'))
4275
4439
  : null,
4276
4440
  ),
4277
4441
  updaterMsg
4278
- ? React.createElement('div', {
4442
+ ? h('div', {
4279
4443
  style: {
4280
4444
  marginTop: '8px',
4281
4445
  fontSize: '12px',
@@ -4397,15 +4561,15 @@ function MacrosSection(props) {
4397
4561
  return next
4398
4562
  })
4399
4563
  }
4400
- const textField = (key, label, hint) => React.createElement('div', { className: 'cb-field' },
4401
- React.createElement('label', null, label),
4402
- React.createElement('input', {
4564
+ const textField = (key, label, hint) => h('div', { className: 'cb-field' },
4565
+ h('label', null, label),
4566
+ h('input', {
4403
4567
  type: 'text',
4404
4568
  value: (draft && draft[key]) ?? '',
4405
4569
  disabled: !writable,
4406
4570
  onChange: (e) => setTop(key, e.target.value),
4407
4571
  }),
4408
- hint ? React.createElement('span', { className: 'dvs-sub' }, hint) : null,
4572
+ hint ? h('span', { className: 'dvs-sub' }, hint) : null,
4409
4573
  )
4410
4574
  React.useEffect(() => { if (ready && draft === null) setDraft(deepClone(value)) }, [ready, draft, value])
4411
4575
  React.useEffect(() => {
@@ -4577,31 +4741,31 @@ function MacrosSection(props) {
4577
4741
  if (val && !opts.some((o) => o.code === val)) {
4578
4742
  opts.push({ code: val, labelKey: val, defaultLabel: val })
4579
4743
  }
4580
- return React.createElement('div', { className: 'cb-field' },
4581
- React.createElement('label', null, t('language')),
4582
- React.createElement('select', {
4744
+ return h('div', { className: 'cb-field' },
4745
+ h('label', null, t('language')),
4746
+ h('select', {
4583
4747
  value: val, disabled: !writable,
4584
4748
  onChange: (e) => setIn(mode, 'language', e.target.value),
4585
- }, opts.map((o) => React.createElement('option', { key: o.code || 'auto', value: o.code },
4749
+ }, opts.map((o) => h('option', { key: o.code || 'auto', value: o.code },
4586
4750
  t(o.labelKey) !== o.labelKey ? t(o.labelKey) : o.defaultLabel
4587
4751
  ))),
4588
4752
  )
4589
4753
  }
4590
- const numField = (mode, key, label, hint) => React.createElement('div', { className: 'cb-field' },
4591
- React.createElement('label', null, label),
4592
- React.createElement('input', {
4754
+ const numField = (mode, key, label, hint) => h('div', { className: 'cb-field' },
4755
+ h('label', null, label),
4756
+ h('input', {
4593
4757
  type: 'number', value: modeVal(mode, key, ''), disabled: !writable,
4594
4758
  onChange: (e) => setIn(mode, key, Number(e.target.value)),
4595
4759
  }),
4596
- React.createElement('span', { className: 'dvs-sub' }, hint),
4760
+ h('span', { className: 'dvs-sub' }, hint),
4597
4761
  )
4598
4762
  const statsData = (statusData && statusData.providerStats) || {}
4599
4763
  const recentDiagnostics = (statusData && statusData.recentDiagnostics) || []
4600
4764
  const hostOnline = statusLatency !== null
4601
4765
  const whisperRunning = !!(statusData && statusData.whisperRunning)
4602
4766
  const providerCount = (statusData && Array.isArray(statusData.providers)) ? statusData.providers.length : chainOptions.length
4603
- return React.createElement('div', { className: 'cb-page dvo-settings-root' },
4604
- React.createElement(ConnectionStatusCard, {
4767
+ return h('div', { className: 'cb-page dvo-settings-root' },
4768
+ h(ConnectionStatusCard, {
4605
4769
  t: t,
4606
4770
  hostOnline: hostOnline,
4607
4771
  statusLatency: statusLatency,
@@ -4610,87 +4774,87 @@ function MacrosSection(props) {
4610
4774
  onRefresh: fetchStatus,
4611
4775
  onOpenWizard: () => setShowWizard((v) => !v),
4612
4776
  }),
4613
- showWizard ? React.createElement(FirstRunWizard, {
4777
+ showWizard ? h(FirstRunWizard, {
4614
4778
  t: t,
4615
4779
  draft: draft,
4616
4780
  setTop: setTop,
4617
4781
  writable: writable,
4618
4782
  onClose: () => setShowWizard(false),
4619
4783
  }) : null,
4620
- React.createElement('div', { className: 'cb-section-card' },
4621
- React.createElement('div', { className: 'cb-section-title' },
4622
- React.createElement('span', null, '🎙️ ' + t('dictationBtn')),
4784
+ h('div', { className: 'cb-section-card' },
4785
+ h('div', { className: 'cb-section-title' },
4786
+ h('span', null, '🎙️ ' + t('dictationBtn')),
4623
4787
  ),
4624
- React.createElement('div', { className: 'cb-section-desc' }, t('dictationHint')),
4788
+ h('div', { className: 'cb-section-desc' }, t('dictationHint')),
4625
4789
  React.createElement(ChainEditor, {
4626
4790
  t: t, value: draft && draft.dictation ? draft.dictation.chain : [], writable: writable,
4627
4791
  options: chainOptions,
4628
4792
  onChange: (v) => setIn('dictation', 'chain', v),
4629
4793
  }),
4630
- React.createElement('div', { className: 'cb-grid-2' },
4794
+ h('div', { className: 'cb-grid-2' },
4631
4795
  langField('dictation'),
4632
4796
  numField('dictation', 'vadSilenceMs', t('pauseMs'), t('pauseHint')),
4633
4797
  numField('dictation', 'sendDelayMs', t('sendDelay'), t('sendDelayHint')),
4634
4798
  numField('dictation', 'streamChunkMs', t('streamChunkMs'), t('streamHint')),
4635
4799
  ),
4636
- React.createElement('div', { className: 'cb-row' },
4637
- React.createElement('label', { className: 'cb-check-label' },
4638
- React.createElement('input', {
4800
+ h('div', { className: 'cb-row' },
4801
+ h('label', { className: 'cb-check-label' },
4802
+ h('input', {
4639
4803
  type: 'checkbox', checked: !!(draft && draft.dictation && draft.dictation.polish), disabled: !writable,
4640
4804
  onChange: (e) => setIn('dictation', 'polish', e.target.checked),
4641
4805
  }),
4642
4806
  t('polish'),
4643
4807
  ),
4644
- React.createElement('label', { className: 'cb-check-label' },
4645
- React.createElement('input', {
4808
+ h('label', { className: 'cb-check-label' },
4809
+ h('input', {
4646
4810
  type: 'checkbox', checked: !!(draft && draft.dictation && draft.dictation.stream), disabled: !writable,
4647
4811
  onChange: (e) => setIn('dictation', 'stream', e.target.checked),
4648
4812
  }),
4649
4813
  t('stream'),
4650
4814
  ),
4651
4815
  ),
4652
- React.createElement('div', { className: 'cb-field' },
4653
- React.createElement('label', null, t('vadAdapt')),
4654
- React.createElement('input', {
4816
+ h('div', { className: 'cb-field' },
4817
+ h('label', null, t('vadAdapt')),
4818
+ h('input', {
4655
4819
  type: 'range', min: 0, max: 1, step: 0.1,
4656
4820
  value: Number(draft && draft.dictation && draft.dictation.vadAdapt) || 0, disabled: !writable,
4657
4821
  onChange: (e) => setIn('dictation', 'vadAdapt', Number(e.target.value)),
4658
4822
  }),
4659
- React.createElement('span', { className: 'dvs-sub' }, t('vadAdaptHint')),
4823
+ h('span', { className: 'dvs-sub' }, t('vadAdaptHint')),
4660
4824
  ),
4661
4825
  textField('wakeWord', t('wakeWord'), t('wakeWordHint')),
4662
4826
  ),
4663
- React.createElement('div', { className: 'cb-section-card' },
4664
- React.createElement('div', { className: 'cb-section-title' },
4665
- React.createElement('span', null, '💬 ' + t('messageTitle')),
4827
+ h('div', { className: 'cb-section-card' },
4828
+ h('div', { className: 'cb-section-title' },
4829
+ h('span', null, '💬 ' + t('messageTitle')),
4666
4830
  ),
4667
- React.createElement('div', { className: 'cb-section-desc' }, t('messageHint')),
4831
+ h('div', { className: 'cb-section-desc' }, t('messageHint')),
4668
4832
  React.createElement(ChainEditor, {
4669
4833
  t: t, value: draft && draft.message ? draft.message.chain : [], writable: writable,
4670
4834
  options: chainOptions,
4671
4835
  onChange: (v) => setIn('message', 'chain', v),
4672
4836
  }),
4673
- React.createElement('div', { className: 'cb-grid-2' },
4837
+ h('div', { className: 'cb-grid-2' },
4674
4838
  langField('message'),
4675
4839
  numField('message', 'autoSendMs', t('undoMs'), t('undoHint')),
4676
4840
  ),
4677
- React.createElement('div', { className: 'cb-row' },
4678
- React.createElement('label', { className: 'cb-check-label' },
4679
- React.createElement('input', {
4841
+ h('div', { className: 'cb-row' },
4842
+ h('label', { className: 'cb-check-label' },
4843
+ h('input', {
4680
4844
  type: 'checkbox', checked: !!(draft && draft.message && draft.message.polish), disabled: !writable,
4681
4845
  onChange: (e) => setIn('message', 'polish', e.target.checked),
4682
4846
  }),
4683
4847
  t('polish'),
4684
4848
  ),
4685
- React.createElement('label', { className: 'cb-check-label' },
4686
- React.createElement('input', {
4849
+ h('label', { className: 'cb-check-label' },
4850
+ h('input', {
4687
4851
  type: 'checkbox', checked: !!(draft && draft.message && draft.message.polishSend), disabled: !writable,
4688
4852
  onChange: (e) => setIn('message', 'polishSend', e.target.checked),
4689
4853
  }),
4690
4854
  t('polishSend'),
4691
4855
  ),
4692
- React.createElement('label', { className: 'cb-check-label' },
4693
- React.createElement('input', {
4856
+ h('label', { className: 'cb-check-label' },
4857
+ h('input', {
4694
4858
  type: 'checkbox', checked: !!(draft && draft.message && draft.message.sessionCommands), disabled: !writable,
4695
4859
  onChange: (e) => setIn('message', 'sessionCommands', e.target.checked),
4696
4860
  }),
@@ -4698,18 +4862,18 @@ function MacrosSection(props) {
4698
4862
  ),
4699
4863
  ),
4700
4864
  ),
4701
- React.createElement('div', { className: 'cb-section-card' },
4702
- React.createElement('div', { className: 'cb-section-title' },
4703
- React.createElement('span', null, '⚙️ ' + t('customTitle')),
4865
+ h('div', { className: 'cb-section-card' },
4866
+ h('div', { className: 'cb-section-title' },
4867
+ h('span', null, '⚙️ ' + t('customTitle')),
4704
4868
  ),
4705
- React.createElement('div', { className: 'cb-section-desc' }, t('customHint')),
4869
+ h('div', { className: 'cb-section-desc' }, t('customHint')),
4706
4870
  React.createElement(CustomEditor, {
4707
4871
  value: draft && draft.customProviders ? draft.customProviders : [], writable: writable,
4708
4872
  options: chainOptions,
4709
4873
  onChange: (v) => setTop('customProviders', v),
4710
4874
  }),
4711
4875
  ),
4712
- React.createElement(HardwareOptionsCard, {
4876
+ h(HardwareOptionsCard, {
4713
4877
  t: t,
4714
4878
  draft: draft,
4715
4879
  setTop: setTop,
@@ -4731,21 +4895,21 @@ function MacrosSection(props) {
4731
4895
  clearGlobalHotkey()
4732
4896
  },
4733
4897
  }),
4734
- React.createElement(SensevoiceSection, {
4898
+ h(SensevoiceSection, {
4735
4899
  t: t,
4736
4900
  sensevoiceState: sensevoiceState,
4737
4901
  installingSensevoice: installingSensevoice,
4738
4902
  writable: writable,
4739
4903
  onInstall: triggerInstallSensevoice,
4740
4904
  }),
4741
- React.createElement('div', { className: 'cb-section-card' },
4742
- React.createElement('div', { className: 'cb-section-title' },
4743
- React.createElement('span', null, '🎙️ ' + t('turnTakingTitle')),
4905
+ h('div', { className: 'cb-section-card' },
4906
+ h('div', { className: 'cb-section-title' },
4907
+ h('span', null, '🎙️ ' + t('turnTakingTitle')),
4744
4908
  ),
4745
- React.createElement('div', { className: 'cb-section-desc' }, t('turnTakingDesc')),
4746
- React.createElement('div', { className: 'cb-grid-2' },
4747
- React.createElement('label', { className: 'cb-check-label' },
4748
- React.createElement('input', {
4909
+ h('div', { className: 'cb-section-desc' }, t('turnTakingDesc')),
4910
+ h('div', { className: 'cb-grid-2' },
4911
+ h('label', { className: 'cb-check-label' },
4912
+ h('input', {
4749
4913
  type: 'checkbox',
4750
4914
  checked: draft ? draft.gatedTurnTaking !== false : true,
4751
4915
  disabled: !writable,
@@ -4753,8 +4917,8 @@ function MacrosSection(props) {
4753
4917
  }),
4754
4918
  t('gatedTurnTaking'),
4755
4919
  ),
4756
- React.createElement('label', { className: 'cb-check-label' },
4757
- React.createElement('input', {
4920
+ h('label', { className: 'cb-check-label' },
4921
+ h('input', {
4758
4922
  type: 'checkbox',
4759
4923
  checked: !!(draft && draft.bargeIn),
4760
4924
  disabled: !writable,
@@ -4764,13 +4928,13 @@ function MacrosSection(props) {
4764
4928
  ),
4765
4929
  ),
4766
4930
  ),
4767
- React.createElement('div', { className: 'cb-section-card' },
4768
- React.createElement('div', { className: 'cb-section-title' },
4769
- React.createElement('span', null, '✨ ' + t('smartActionsTitle')),
4931
+ h('div', { className: 'cb-section-card' },
4932
+ h('div', { className: 'cb-section-title' },
4933
+ h('span', null, '✨ ' + t('smartActionsTitle')),
4770
4934
  ),
4771
- React.createElement('div', { className: 'cb-grid-2' },
4772
- React.createElement('label', { className: 'cb-check-label' },
4773
- React.createElement('input', {
4935
+ h('div', { className: 'cb-grid-2' },
4936
+ h('label', { className: 'cb-check-label' },
4937
+ h('input', {
4774
4938
  type: 'checkbox',
4775
4939
  checked: draft ? draft.liveInterimPreview !== false : true,
4776
4940
  disabled: !writable,
@@ -4778,8 +4942,8 @@ function MacrosSection(props) {
4778
4942
  }),
4779
4943
  t('liveInterimPreview'),
4780
4944
  ),
4781
- React.createElement('label', { className: 'cb-check-label' },
4782
- React.createElement('input', {
4945
+ h('label', { className: 'cb-check-label' },
4946
+ h('input', {
4783
4947
  type: 'checkbox',
4784
4948
  checked: draft ? draft.autoSendVisualRing !== false : true,
4785
4949
  disabled: !writable,
@@ -4787,8 +4951,8 @@ function MacrosSection(props) {
4787
4951
  }),
4788
4952
  t('autoSendVisualRing'),
4789
4953
  ),
4790
- React.createElement('label', { className: 'cb-check-label' },
4791
- React.createElement('input', {
4954
+ h('label', { className: 'cb-check-label' },
4955
+ h('input', {
4792
4956
  type: 'checkbox',
4793
4957
  checked: draft ? draft.voiceActions !== false : true,
4794
4958
  disabled: !writable,
@@ -4796,8 +4960,8 @@ function MacrosSection(props) {
4796
4960
  }),
4797
4961
  t('voiceActionsLabel'),
4798
4962
  ),
4799
- React.createElement('label', { className: 'cb-check-label' },
4800
- React.createElement('input', {
4963
+ h('label', { className: 'cb-check-label' },
4964
+ h('input', {
4801
4965
  type: 'checkbox',
4802
4966
  checked: draft ? draft.structuredPromptVoice !== false : true,
4803
4967
  disabled: !writable,
@@ -4808,20 +4972,20 @@ function MacrosSection(props) {
4808
4972
  ),
4809
4973
  ),
4810
4974
  // Dictation Modes & Prompts (#264)
4811
- React.createElement(DictationModesSection, {
4975
+ h(DictationModesSection, {
4812
4976
  t: t,
4813
4977
  draft: draft,
4814
4978
  setTop: setTop,
4815
4979
  writable: writable,
4816
4980
  }),
4817
4981
  // Voice Action Macros (#167)
4818
- React.createElement(MacrosSection, {
4982
+ h(MacrosSection, {
4819
4983
  t: t,
4820
4984
  draft: draft,
4821
4985
  setTop: setTop,
4822
4986
  writable: writable,
4823
4987
  }),
4824
- React.createElement(JargonEditor, {
4988
+ h(JargonEditor, {
4825
4989
  t: t,
4826
4990
  draft: draft,
4827
4991
  setTop: setTop,
@@ -4829,13 +4993,13 @@ function MacrosSection(props) {
4829
4993
  jargonInput: jargonInput,
4830
4994
  setJargonInput: setJargonInput,
4831
4995
  }),
4832
- React.createElement(ProviderDashboard, {
4996
+ h(ProviderDashboard, {
4833
4997
  t: t,
4834
4998
  statsData: statsData,
4835
4999
  recentDiagnostics: recentDiagnostics,
4836
5000
  onRefresh: fetchStatus,
4837
5001
  }),
4838
- React.createElement(UpdaterCard, {
5002
+ h(UpdaterCard, {
4839
5003
  t: t,
4840
5004
  updaterStatus: updaterStatus,
4841
5005
  updaterLoading: updaterLoading,
@@ -4845,10 +5009,10 @@ function MacrosSection(props) {
4845
5009
  onCheck: checkUpdater,
4846
5010
  onUpdateNow: onUpdateNow,
4847
5011
  }),
4848
- React.createElement('div', { className: 'cb-row' },
4849
- React.createElement('button', { type: 'button', className: 'cb-btn cb-btn-primary', disabled: !writable, onClick: save }, t('save')),
4850
- saved ? React.createElement('span', { className: 'dvs-ok' }, t('saved')) : null,
4851
- err ? React.createElement('span', { className: 'dvs-bad' }, err) : null,
5012
+ h('div', { className: 'cb-row' },
5013
+ h('button', { type: 'button', className: 'cb-btn cb-btn-primary', disabled: !writable, onClick: save }, t('save')),
5014
+ saved ? h('span', { className: 'dvs-ok' }, t('saved')) : null,
5015
+ err ? h('span', { className: 'dvs-bad' }, err) : null,
4852
5016
  ),
4853
5017
  )
4854
5018
  }
@@ -5084,39 +5248,39 @@ function MacrosSection(props) {
5084
5248
  nextStepHint = t('wizardHintProvider')
5085
5249
  }
5086
5250
 
5087
- return React.createElement('div', { className: 'cb-section-card', style: { borderColor: 'var(--dsw-alias-state-brand-primary)' } },
5088
- React.createElement('div', { className: 'cb-section-title' },
5089
- React.createElement('span', null, '✨ ' + t('wizardTitle')),
5090
- React.createElement('div', { style: { display: 'flex', gap: '8px' } },
5091
- testResult ? React.createElement('span', { className: 'cb-badge cb-badge-ok' }, '✓ ' + t('wizardStatusCompleted')) : null,
5092
- onClose ? React.createElement('button', {
5251
+ return h('div', { className: 'cb-section-card', style: { borderColor: 'var(--dsw-alias-state-brand-primary)' } },
5252
+ h('div', { className: 'cb-section-title' },
5253
+ h('span', null, '✨ ' + t('wizardTitle')),
5254
+ h('div', { style: { display: 'flex', gap: '8px' } },
5255
+ testResult ? h('span', { className: 'cb-badge cb-badge-ok' }, '✓ ' + t('wizardStatusCompleted')) : null,
5256
+ onClose ? h('button', {
5093
5257
  type: 'button', className: 'cb-btn cb-btn-mini', onClick: onClose, title: t('wizardClose'),
5094
5258
  }, '✕') : null,
5095
5259
  ),
5096
5260
  ),
5097
- React.createElement('div', { className: 'cb-section-desc' }, t('wizardDesc')),
5261
+ h('div', { className: 'cb-section-desc' }, t('wizardDesc')),
5098
5262
 
5099
5263
  // Step 1: Environment & Permissions
5100
- React.createElement('div', { className: 'dvs-card' },
5101
- React.createElement('div', { className: 'dvs-h' }, t('wizardStep1')),
5102
- React.createElement('div', { className: 'cb-row' },
5103
- React.createElement('span', { className: 'cb-badge ' + (isSecure ? 'cb-badge-ok' : 'cb-badge-err') },
5264
+ h('div', { className: 'dvs-card' },
5265
+ h('div', { className: 'dvs-h' }, t('wizardStep1')),
5266
+ h('div', { className: 'cb-row' },
5267
+ h('span', { className: 'cb-badge ' + (isSecure ? 'cb-badge-ok' : 'cb-badge-err') },
5104
5268
  isSecure ? ('✓ ' + t('wizardSecureContextOk')) : ('✗ ' + t('wizardSecureContextBad'))
5105
5269
  ),
5106
- React.createElement('span', { className: 'cb-badge ' + (hasMediaDevices ? 'cb-badge-ok' : 'cb-badge-err') },
5270
+ h('span', { className: 'cb-badge ' + (hasMediaDevices ? 'cb-badge-ok' : 'cb-badge-err') },
5107
5271
  hasMediaDevices ? ('✓ ' + t('wizardMediaDevicesOk')) : ('✗ ' + t('wizardMediaDevicesBad'))
5108
5272
  ),
5109
- React.createElement('span', { className: 'cb-badge ' + (hasMediaRecorder ? 'cb-badge-ok' : 'cb-badge-err') },
5273
+ h('span', { className: 'cb-badge ' + (hasMediaRecorder ? 'cb-badge-ok' : 'cb-badge-err') },
5110
5274
  hasMediaRecorder ? ('✓ ' + t('wizardMediaRecorderOk')) : ('✗ ' + t('wizardMediaRecorderBad'))
5111
5275
  ),
5112
- React.createElement('span', { className: 'cb-badge ' + (micPermission === 'granted' ? 'cb-badge-ok' : (micPermission === 'denied' ? 'cb-badge-err' : 'cb-badge-warn')) },
5276
+ h('span', { className: 'cb-badge ' + (micPermission === 'granted' ? 'cb-badge-ok' : (micPermission === 'denied' ? 'cb-badge-err' : 'cb-badge-warn')) },
5113
5277
  micPermission === 'granted' ? ('✓ ' + t('wizardMicPermissionOk')) : (micPermission === 'denied' ? ('✗ ' + t('wizardMicPermissionBad')) : ('· ' + t('wizardMicPermissionPrompt')))
5114
5278
  ),
5115
5279
  ),
5116
- React.createElement('div', { className: 'cb-field', style: { marginTop: '8px' } },
5117
- React.createElement('label', null, t('wizardMicLabel')),
5118
- React.createElement('div', { className: 'cb-row' },
5119
- React.createElement('select', {
5280
+ h('div', { className: 'cb-field', style: { marginTop: '8px' } },
5281
+ h('label', null, t('wizardMicLabel')),
5282
+ h('div', { className: 'cb-row' },
5283
+ h('select', {
5120
5284
  value: selectedDev,
5121
5285
  style: { flex: 1, minWidth: '240px' },
5122
5286
  onChange: (e) => {
@@ -5125,10 +5289,10 @@ function MacrosSection(props) {
5125
5289
  stopSignalTest()
5126
5290
  },
5127
5291
  },
5128
- React.createElement('option', { value: '' }, 'Default System Microphone'),
5129
- devices.map((d) => React.createElement('option', { key: d.deviceId, value: d.deviceId }, d.label || `Microphone ${d.deviceId.slice(0, 8)}…`))
5292
+ h('option', { value: '' }, 'Default System Microphone'),
5293
+ devices.map((d) => h('option', { key: d.deviceId, value: d.deviceId }, d.label || `Microphone ${d.deviceId.slice(0, 8)}…`))
5130
5294
  ),
5131
- React.createElement('button', {
5295
+ h('button', {
5132
5296
  type: 'button',
5133
5297
  className: 'cb-btn',
5134
5298
  onClick: requestPermissionAndTestSignal,
@@ -5138,18 +5302,18 @@ function MacrosSection(props) {
5138
5302
  ),
5139
5303
 
5140
5304
  // Step 2: Live Audio Signal Test
5141
- React.createElement('div', { className: 'dvs-card' },
5142
- React.createElement('div', { className: 'dvs-h' }, t('wizardStep2')),
5143
- React.createElement('div', { className: 'cb-row', style: { alignItems: 'center' } },
5144
- React.createElement('button', {
5305
+ h('div', { className: 'dvs-card' },
5306
+ h('div', { className: 'dvs-h' }, t('wizardStep2')),
5307
+ h('div', { className: 'cb-row', style: { alignItems: 'center' } },
5308
+ h('button', {
5145
5309
  type: 'button',
5146
5310
  className: 'cb-btn ' + (signalTesting ? 'cb-btn-danger' : 'cb-btn-primary'),
5147
5311
  onClick: signalTesting ? stopSignalTest : requestPermissionAndTestSignal,
5148
5312
  }, signalTesting ? t('wizardStopSignalCheck') : t('wizardStartSignalCheck')),
5149
- signalTesting ? React.createElement('span', { className: 'dvs-sub' }, t('wizardSignalTesting')) : null,
5313
+ signalTesting ? h('span', { className: 'dvs-sub' }, t('wizardSignalTesting')) : null,
5150
5314
  ),
5151
- signalTesting ? React.createElement('div', { style: { marginTop: '8px', display: 'flex', flexDirection: 'column', gap: '6px' } },
5152
- React.createElement('div', {
5315
+ signalTesting ? h('div', { style: { marginTop: '8px', display: 'flex', flexDirection: 'column', gap: '6px' } },
5316
+ h('div', {
5153
5317
  style: {
5154
5318
  height: '14px',
5155
5319
  borderRadius: '7px',
@@ -5159,7 +5323,7 @@ function MacrosSection(props) {
5159
5323
  position: 'relative',
5160
5324
  },
5161
5325
  },
5162
- React.createElement('div', {
5326
+ h('div', {
5163
5327
  style: {
5164
5328
  width: `${currentRms}%`,
5165
5329
  height: '100%',
@@ -5168,7 +5332,7 @@ function MacrosSection(props) {
5168
5332
  },
5169
5333
  }),
5170
5334
  ),
5171
- React.createElement('span', { className: currentRms > 5 ? 'dvs-ok' : 'dvs-sub' },
5335
+ h('span', { className: currentRms > 5 ? 'dvs-ok' : 'dvs-sub' },
5172
5336
  currentRms > 5
5173
5337
  ? t('wizardSignalOk').replace('{level}', currentRms)
5174
5338
  : t('wizardSignalZero')
@@ -5177,14 +5341,14 @@ function MacrosSection(props) {
5177
5341
  ),
5178
5342
 
5179
5343
  // Step 3: Transcription Round-Trip Test
5180
- React.createElement('div', { className: 'dvs-card' },
5181
- React.createElement('div', { className: 'dvs-h' }, t('wizardStep3')),
5182
- React.createElement('div', { className: 'cb-row' },
5183
- React.createElement('span', { className: 'dvs-sub' }, `${t('wizardProviderLabel')} ${activeProvider}`),
5184
- React.createElement('span', { className: 'cb-badge' }, `${t('wizardProcessingArea')} ${areaLabel}`),
5344
+ h('div', { className: 'dvs-card' },
5345
+ h('div', { className: 'dvs-h' }, t('wizardStep3')),
5346
+ h('div', { className: 'cb-row' },
5347
+ h('span', { className: 'dvs-sub' }, `${t('wizardProviderLabel')} ${activeProvider}`),
5348
+ h('span', { className: 'cb-badge' }, `${t('wizardProcessingArea')} ${areaLabel}`),
5185
5349
  ),
5186
- React.createElement('div', { className: 'cb-row', style: { marginTop: '8px' } },
5187
- React.createElement('button', {
5350
+ h('div', { className: 'cb-row', style: { marginTop: '8px' } },
5351
+ h('button', {
5188
5352
  type: 'button',
5189
5353
  className: 'cb-btn cb-btn-primary',
5190
5354
  disabled: testingStt || micPermission === 'denied',
@@ -5194,11 +5358,11 @@ function MacrosSection(props) {
5194
5358
  : t('wizardStartTestRecord')
5195
5359
  ),
5196
5360
  ),
5197
- testResult ? React.createElement('div', { style: { marginTop: '8px', display: 'flex', flexDirection: 'column', gap: '4px' } },
5198
- React.createElement('div', { className: 'dvs-ok' },
5361
+ testResult ? h('div', { style: { marginTop: '8px', display: 'flex', flexDirection: 'column', gap: '4px' } },
5362
+ h('div', { className: 'dvs-ok' },
5199
5363
  '✓ ' + t('wizardTestSuccess').replace('{latency}', testLatency || 0)
5200
5364
  ),
5201
- React.createElement('blockquote', {
5365
+ h('blockquote', {
5202
5366
  style: {
5203
5367
  margin: '4px 0 0 0',
5204
5368
  padding: '8px 12px',
@@ -5210,19 +5374,19 @@ function MacrosSection(props) {
5210
5374
  },
5211
5375
  }, `“${testResult}”`),
5212
5376
  ) : null,
5213
- testError ? React.createElement('div', { className: 'cb-alert-err', style: { marginTop: '8px' } },
5377
+ testError ? h('div', { className: 'cb-alert-err', style: { marginTop: '8px' } },
5214
5378
  t('wizardTestFailed').replace('{error}', testError)
5215
5379
  ) : null,
5216
5380
  ),
5217
5381
 
5218
5382
  // Next Step Hint / Feedback
5219
- nextStepHint ? React.createElement('div', { className: 'dvs-wait', style: { fontStyle: 'italic' } },
5383
+ nextStepHint ? h('div', { className: 'dvs-wait', style: { fontStyle: 'italic' } },
5220
5384
  t('wizardNextStepHint').replace('{hint}', nextStepHint)
5221
5385
  ) : null,
5222
5386
 
5223
5387
  // Wizard Bottom Actions
5224
- React.createElement('div', { className: 'cb-row', style: { justifyContent: 'space-between', marginTop: '6px' } },
5225
- React.createElement('button', {
5388
+ h('div', { className: 'cb-row', style: { justifyContent: 'space-between', marginTop: '6px' } },
5389
+ h('button', {
5226
5390
  type: 'button',
5227
5391
  className: 'cb-btn',
5228
5392
  onClick: () => {
@@ -5230,7 +5394,7 @@ function MacrosSection(props) {
5230
5394
  if (onClose) onClose()
5231
5395
  },
5232
5396
  }, t('wizardSkip')),
5233
- testResult ? React.createElement('button', {
5397
+ testResult ? h('button', {
5234
5398
  type: 'button',
5235
5399
  className: 'cb-btn cb-btn-primary',
5236
5400
  onClick: () => {
@@ -5250,12 +5414,12 @@ function MacrosSection(props) {
5250
5414
  // Row seat (plugins.row.config): the host page draws title/icon/crumb and the
5251
5415
  // padding, so the summary is a one-liner and the page drops our card chrome.
5252
5416
  if (props && props.view === 'summary') {
5253
- return React.createElement('span', { className: 'dvo-pdesc' }, tt('cardHint'))
5417
+ return h('span', { className: 'dvo-pdesc' }, tt('cardHint'))
5254
5418
  }
5255
- return React.createElement(ErrorBoundary, null,
5256
- React.createElement(page ? 'div' : 'li',
5419
+ return h(ErrorBoundary, null,
5420
+ h(page ? 'div' : 'li',
5257
5421
  { className: page ? 'dvo-ppage' : (open ? 'dvo-pcard dvo-pcardOpen' : 'dvo-pcard') },
5258
- React.createElement('button',
5422
+ h('button',
5259
5423
  {
5260
5424
  type: 'button', className: 'dvo-phead',
5261
5425
  style: page ? { display: 'none' } : undefined,
@@ -5263,13 +5427,13 @@ function MacrosSection(props) {
5263
5427
  'aria-label': (open ? tt('collapse') : tt('expand')) + ': ' + title,
5264
5428
  onClick: () => setOpen((v) => !v),
5265
5429
  },
5266
- React.createElement('span', { className: 'dvo-pheadtext' },
5267
- React.createElement('span', { className: 'dvo-ptitle' }, title),
5268
- React.createElement('span', { className: 'dvo-pdesc' }, tt('cardHint')),
5430
+ h('span', { className: 'dvo-pheadtext' },
5431
+ h('span', { className: 'dvo-ptitle' }, title),
5432
+ h('span', { className: 'dvo-pdesc' }, tt('cardHint')),
5269
5433
  ),
5270
- React.createElement('span', { className: 'dvo-pchev' }, chevronIcon()),
5434
+ h('span', { className: 'dvo-pchev' }, chevronIcon()),
5271
5435
  ),
5272
- (page || open) ? React.createElement('div', { className: 'dvo-pbody dvo-settings-root' }, React.createElement(VoiceSection, props)) : null,
5436
+ (page || open) ? h('div', { className: 'dvo-pbody dvo-settings-root' }, h(VoiceSection, props)) : null,
5273
5437
  ),
5274
5438
  )
5275
5439
  }