@goodandready/dsh-voice 0.9.38 → 0.9.39
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 +621 -457
- package/lib/host-routes.js +93 -0
- package/lib/schema.js +4 -0
- package/lib/ws-framer.js +160 -0
- package/package.json +1 -1
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
|
|
1895
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
?
|
|
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
|
|
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
|
-
|
|
2277
|
-
|
|
2474
|
+
h('div', { className: 'dvo-preview-header' },
|
|
2475
|
+
h('div', { style: { display: 'flex', alignItems: 'center', gap: '8px' } },
|
|
2278
2476
|
hasPolish
|
|
2279
|
-
?
|
|
2280
|
-
|
|
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
|
-
|
|
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
|
-
:
|
|
2497
|
+
: h('span', { className: 'dvo-count' }, t('rawText')),
|
|
2300
2498
|
p.audioUrl
|
|
2301
|
-
?
|
|
2302
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2517
|
+
h('span', { className: 'dvo-audio-time' }, t('listenBack')),
|
|
2320
2518
|
)
|
|
2321
2519
|
: null,
|
|
2322
2520
|
),
|
|
2323
|
-
|
|
2521
|
+
h('div', { style: { display: 'flex', alignItems: 'center', gap: '8px' } },
|
|
2324
2522
|
(!p.paused && !p.isEditing && v.settings.autoSendVisualRing !== false)
|
|
2325
|
-
?
|
|
2326
|
-
|
|
2327
|
-
|
|
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
|
-
|
|
2532
|
+
h('span', { className: 'dvo-count' },
|
|
2335
2533
|
(p.paused || p.isEditing) ? '' : (left + t('secondsShort'))
|
|
2336
2534
|
),
|
|
2337
|
-
|
|
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
|
-
|
|
2543
|
+
h('div', { className: 'dvo-preview-body' },
|
|
2346
2544
|
p.isEditing
|
|
2347
|
-
?
|
|
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
|
-
:
|
|
2561
|
+
: h('span', { className: 'dvo-preview-text' }, p.activeText),
|
|
2364
2562
|
!p.isEditing
|
|
2365
|
-
?
|
|
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
|
-
|
|
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
|
-
?
|
|
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
|
-
|
|
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
|
|
3271
|
+
return h(
|
|
3118
3272
|
'div',
|
|
3119
3273
|
{ className: 'cb-alert-err', style: { margin: '12px 0', padding: '14px', borderRadius: '8px' } },
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
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
|
|
3160
|
-
rows.map((row, i) =>
|
|
3161
|
-
|
|
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) =>
|
|
3166
|
-
|
|
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
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
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
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
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) =>
|
|
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
|
|
3196
|
-
rows.map((row, i) =>
|
|
3197
|
-
|
|
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
|
-
|
|
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) =>
|
|
3203
|
-
|
|
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
|
-
|
|
3362
|
+
h('div', { className: 'dvs-row' },
|
|
3209
3363
|
field(i, row, 'baseURL', 'https://openrouter.ai/api/v1', true),
|
|
3210
3364
|
),
|
|
3211
|
-
|
|
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
|
-
?
|
|
3370
|
+
? h('div', { className: 'dvs-row' },
|
|
3217
3371
|
field(i, row, 'prompt', t('customModelHint'), true))
|
|
3218
3372
|
: null,
|
|
3219
3373
|
)),
|
|
3220
|
-
|
|
3221
|
-
|
|
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
|
-
|
|
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
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
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
|
-
|
|
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 ?
|
|
3435
|
+
exportMsg ? h('div', {
|
|
3282
3436
|
className: exportMsg.startsWith('✓') ? 'dvs-ok' : 'cb-alert-err',
|
|
3283
3437
|
style: { fontSize: '12px' },
|
|
3284
3438
|
}, exportMsg) : null,
|
|
3285
|
-
|
|
3286
|
-
|
|
3439
|
+
h('div', { className: 'dvo-dash' },
|
|
3440
|
+
h('div', { className: 'dvo-dash-grid' },
|
|
3287
3441
|
Object.keys(statsData).length === 0
|
|
3288
|
-
?
|
|
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
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
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
|
-
|
|
3317
|
-
|
|
3318
|
-
|
|
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
|
-
|
|
3326
|
-
|
|
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
|
-
?
|
|
3329
|
-
:
|
|
3330
|
-
recentDiagnostics.slice(-5).reverse().map((d) =>
|
|
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
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
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
|
-
|
|
3341
|
-
d.audioBytes ?
|
|
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
|
-
|
|
3497
|
+
h('span', { style: { fontWeight: '600' } },
|
|
3344
3498
|
t('diagTotal').replace('{ms}', d.totalTookMs)
|
|
3345
3499
|
),
|
|
3346
3500
|
),
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
d.breakdown && d.breakdown.polishMs ?
|
|
3351
|
-
typeof d.wordCount === 'number' ?
|
|
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
|
-
|
|
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
|
|
3379
|
-
|
|
3380
|
-
|
|
3532
|
+
return h('div', { className: 'cb-section-card' },
|
|
3533
|
+
h('div', { className: 'cb-section-title' },
|
|
3534
|
+
h('span', null, '🎙️ ' + t('dictationModeTitle')),
|
|
3381
3535
|
),
|
|
3382
|
-
|
|
3383
|
-
|
|
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
|
-
|
|
3386
|
-
|
|
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
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
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
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
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
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
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
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
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
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
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) =>
|
|
3494
|
-
|
|
3495
|
-
|
|
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
|
-
|
|
3642
|
+
h('span', { className: 'dvs-sub' }, hint),
|
|
3500
3643
|
)
|
|
3501
3644
|
|
|
3502
|
-
return
|
|
3503
|
-
|
|
3504
|
-
|
|
3645
|
+
return h('div', { className: 'cb-section-card' },
|
|
3646
|
+
h('div', { className: 'cb-section-title' },
|
|
3647
|
+
h('span', null, '⚙️ ' + t('hardwareTitle')),
|
|
3505
3648
|
),
|
|
3506
|
-
|
|
3649
|
+
h('div', { className: 'cb-section-desc' }, t('hardwareDesc')),
|
|
3507
3650
|
// Hotkey picker
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3663
|
+
h('span', { className: 'dvs-sub' }, t('hotkeyHint1') + t('hotkeyHint2')),
|
|
3521
3664
|
),
|
|
3522
3665
|
// Mic & noise gate
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
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
|
-
|
|
3532
|
-
devices.map((d) =>
|
|
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
|
-
|
|
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
|
-
|
|
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 ?
|
|
3693
|
+
calibrateMsg ? h('div', {
|
|
3551
3694
|
style: { fontSize: '11px', color: 'var(--dsw-alias-state-success-primary)', marginTop: '4px' },
|
|
3552
3695
|
}, calibrateMsg) : null,
|
|
3553
|
-
testingMic ?
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
gateThresholdPercent > 0 ?
|
|
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
|
-
|
|
3702
|
+
h('span', { style: { fontSize: '11px', fontVariantNumeric: 'tabular-nums', width: '32px', textAlign: 'right' } }, `${testLevel}%`),
|
|
3560
3703
|
) : null,
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
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
|
-
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
!['-999', '-50', '-45', '-35', '-25'].includes(String(gateDbVal)) ?
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3593
|
-
|
|
3594
|
-
|
|
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
|
-
|
|
3601
|
-
|
|
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
|
-
|
|
3608
|
-
|
|
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
|
-
|
|
3615
|
-
|
|
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
|
-
|
|
3622
|
-
|
|
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
|
-
|
|
3629
|
-
|
|
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
|
-
|
|
3636
|
-
|
|
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
|
-
|
|
3643
|
-
|
|
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
|
-
|
|
3650
|
-
|
|
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
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
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
|
-
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
|
|
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
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
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
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3861
|
+
h('span', { className: 'dvs-sub' }, t('npuProviderHint')),
|
|
3698
3862
|
),
|
|
3699
3863
|
// Hardware Benchmark
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
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
|
-
?
|
|
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 ?
|
|
3715
|
-
|
|
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
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
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
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
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
|
|
4022
|
-
|
|
4023
|
-
|
|
4185
|
+
return h('div', { className: 'cb-section-card' },
|
|
4186
|
+
h('div', { className: 'cb-section-title' },
|
|
4187
|
+
h('span', null, '📖 ' + t('jargonTitle')),
|
|
4024
4188
|
),
|
|
4025
|
-
|
|
4026
|
-
|
|
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
|
-
|
|
4035
|
-
|
|
4036
|
-
|
|
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
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
|
|
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
|
-
|
|
4263
|
+
h('div', { className: 'cb-field-desc' }, t('voiceMacrosDesc')),
|
|
4100
4264
|
macros.length === 0
|
|
4101
|
-
?
|
|
4265
|
+
? h('div', { style: { fontStyle: 'italic', color: 'var(--dsw-alias-label-tertiary)', fontSize: '12px', marginTop: '6px' } },
|
|
4102
4266
|
'No macros configured.',
|
|
4103
4267
|
)
|
|
4104
|
-
:
|
|
4268
|
+
: h('div', { style: { display: 'flex', flexDirection: 'column', gap: '8px', marginTop: '6px' } },
|
|
4105
4269
|
macros.map((m, idx) =>
|
|
4106
|
-
|
|
4107
|
-
|
|
4108
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
4154
|
-
|
|
4155
|
-
|
|
4156
|
-
|
|
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
|
-
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
|
|
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
|
-
|
|
4168
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
?
|
|
4185
|
-
|
|
4186
|
-
|
|
4348
|
+
? h('div', { style: { marginTop: '8px' } },
|
|
4349
|
+
h('div', { className: 'dvo-progress-bar' },
|
|
4350
|
+
h('div', { className: 'dvo-progress-bar-fill' })
|
|
4187
4351
|
),
|
|
4188
|
-
|
|
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
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
|
|
4210
|
-
onOpenWizard ?
|
|
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
|
-
|
|
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
|
-
|
|
4221
|
-
|
|
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
|
-
|
|
4388
|
+
h('span', { className: 'cb-badge ' + (whisperRunning ? 'cb-badge-ok' : 'cb-badge-warn') },
|
|
4225
4389
|
whisperRunning ? (`✓ ${t('whisperReady')}`) : (`· ${t('whisperStopped')}`)
|
|
4226
4390
|
),
|
|
4227
|
-
|
|
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
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
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
|
-
|
|
4251
|
-
|
|
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
|
-
?
|
|
4417
|
+
? h('span', { className: 'cb-badge cb-badge-ok' }, 'v' + updaterStatus.currentVersion)
|
|
4254
4418
|
: null,
|
|
4255
4419
|
updaterLoading && (!updaterStatus || !updaterStatus.currentVersion)
|
|
4256
|
-
?
|
|
4420
|
+
? h('span', { className: 'cb-badge' }, '⏳ ' + t('checkingUpdates'))
|
|
4257
4421
|
: !updaterStatus
|
|
4258
|
-
?
|
|
4422
|
+
? h('span', { className: 'cb-badge cb-badge-warn' }, '❓ ' + t('updateStatusUnknown'))
|
|
4259
4423
|
: updaterStatus.updateAvailable
|
|
4260
|
-
?
|
|
4424
|
+
? h('span', { className: 'cb-badge cb-badge-warn' },
|
|
4261
4425
|
t('updateAvailable') + ': v' + updaterStatus.latestVersion
|
|
4262
4426
|
)
|
|
4263
4427
|
: updaterStatus.latestCheckFailed
|
|
4264
|
-
?
|
|
4428
|
+
? h('span', { className: 'cb-badge cb-badge-warn' },
|
|
4265
4429
|
'⚠️ ' + t('updateCheckFailed')
|
|
4266
4430
|
)
|
|
4267
|
-
:
|
|
4431
|
+
: h('span', { className: 'cb-badge cb-badge-ok' },
|
|
4268
4432
|
t('upToDate')
|
|
4269
4433
|
),
|
|
4270
4434
|
updaterStatus && updaterStatus.updateAvailable
|
|
4271
|
-
?
|
|
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
|
-
?
|
|
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) =>
|
|
4401
|
-
|
|
4402
|
-
|
|
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 ?
|
|
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
|
|
4581
|
-
|
|
4582
|
-
|
|
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) =>
|
|
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) =>
|
|
4591
|
-
|
|
4592
|
-
|
|
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
|
-
|
|
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
|
|
4604
|
-
|
|
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 ?
|
|
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
|
-
|
|
4621
|
-
|
|
4622
|
-
|
|
4784
|
+
h('div', { className: 'cb-section-card' },
|
|
4785
|
+
h('div', { className: 'cb-section-title' },
|
|
4786
|
+
h('span', null, '🎙️ ' + t('dictationBtn')),
|
|
4623
4787
|
),
|
|
4624
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4637
|
-
|
|
4638
|
-
|
|
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
|
-
|
|
4645
|
-
|
|
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
|
-
|
|
4653
|
-
|
|
4654
|
-
|
|
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
|
-
|
|
4823
|
+
h('span', { className: 'dvs-sub' }, t('vadAdaptHint')),
|
|
4660
4824
|
),
|
|
4661
4825
|
textField('wakeWord', t('wakeWord'), t('wakeWordHint')),
|
|
4662
4826
|
),
|
|
4663
|
-
|
|
4664
|
-
|
|
4665
|
-
|
|
4827
|
+
h('div', { className: 'cb-section-card' },
|
|
4828
|
+
h('div', { className: 'cb-section-title' },
|
|
4829
|
+
h('span', null, '💬 ' + t('messageTitle')),
|
|
4666
4830
|
),
|
|
4667
|
-
|
|
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
|
-
|
|
4837
|
+
h('div', { className: 'cb-grid-2' },
|
|
4674
4838
|
langField('message'),
|
|
4675
4839
|
numField('message', 'autoSendMs', t('undoMs'), t('undoHint')),
|
|
4676
4840
|
),
|
|
4677
|
-
|
|
4678
|
-
|
|
4679
|
-
|
|
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
|
-
|
|
4686
|
-
|
|
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
|
-
|
|
4693
|
-
|
|
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
|
-
|
|
4702
|
-
|
|
4703
|
-
|
|
4865
|
+
h('div', { className: 'cb-section-card' },
|
|
4866
|
+
h('div', { className: 'cb-section-title' },
|
|
4867
|
+
h('span', null, '⚙️ ' + t('customTitle')),
|
|
4704
4868
|
),
|
|
4705
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4742
|
-
|
|
4743
|
-
|
|
4905
|
+
h('div', { className: 'cb-section-card' },
|
|
4906
|
+
h('div', { className: 'cb-section-title' },
|
|
4907
|
+
h('span', null, '🎙️ ' + t('turnTakingTitle')),
|
|
4744
4908
|
),
|
|
4745
|
-
|
|
4746
|
-
|
|
4747
|
-
|
|
4748
|
-
|
|
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
|
-
|
|
4757
|
-
|
|
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
|
-
|
|
4768
|
-
|
|
4769
|
-
|
|
4931
|
+
h('div', { className: 'cb-section-card' },
|
|
4932
|
+
h('div', { className: 'cb-section-title' },
|
|
4933
|
+
h('span', null, '✨ ' + t('smartActionsTitle')),
|
|
4770
4934
|
),
|
|
4771
|
-
|
|
4772
|
-
|
|
4773
|
-
|
|
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
|
-
|
|
4782
|
-
|
|
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
|
-
|
|
4791
|
-
|
|
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
|
-
|
|
4800
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4982
|
+
h(MacrosSection, {
|
|
4819
4983
|
t: t,
|
|
4820
4984
|
draft: draft,
|
|
4821
4985
|
setTop: setTop,
|
|
4822
4986
|
writable: writable,
|
|
4823
4987
|
}),
|
|
4824
|
-
|
|
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
|
-
|
|
4996
|
+
h(ProviderDashboard, {
|
|
4833
4997
|
t: t,
|
|
4834
4998
|
statsData: statsData,
|
|
4835
4999
|
recentDiagnostics: recentDiagnostics,
|
|
4836
5000
|
onRefresh: fetchStatus,
|
|
4837
5001
|
}),
|
|
4838
|
-
|
|
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
|
-
|
|
4849
|
-
|
|
4850
|
-
saved ?
|
|
4851
|
-
err ?
|
|
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
|
|
5088
|
-
|
|
5089
|
-
|
|
5090
|
-
|
|
5091
|
-
testResult ?
|
|
5092
|
-
onClose ?
|
|
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
|
-
|
|
5261
|
+
h('div', { className: 'cb-section-desc' }, t('wizardDesc')),
|
|
5098
5262
|
|
|
5099
5263
|
// Step 1: Environment & Permissions
|
|
5100
|
-
|
|
5101
|
-
|
|
5102
|
-
|
|
5103
|
-
|
|
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
|
-
|
|
5270
|
+
h('span', { className: 'cb-badge ' + (hasMediaDevices ? 'cb-badge-ok' : 'cb-badge-err') },
|
|
5107
5271
|
hasMediaDevices ? ('✓ ' + t('wizardMediaDevicesOk')) : ('✗ ' + t('wizardMediaDevicesBad'))
|
|
5108
5272
|
),
|
|
5109
|
-
|
|
5273
|
+
h('span', { className: 'cb-badge ' + (hasMediaRecorder ? 'cb-badge-ok' : 'cb-badge-err') },
|
|
5110
5274
|
hasMediaRecorder ? ('✓ ' + t('wizardMediaRecorderOk')) : ('✗ ' + t('wizardMediaRecorderBad'))
|
|
5111
5275
|
),
|
|
5112
|
-
|
|
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
|
-
|
|
5117
|
-
|
|
5118
|
-
|
|
5119
|
-
|
|
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
|
-
|
|
5129
|
-
devices.map((d) =>
|
|
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
|
-
|
|
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
|
-
|
|
5142
|
-
|
|
5143
|
-
|
|
5144
|
-
|
|
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 ?
|
|
5313
|
+
signalTesting ? h('span', { className: 'dvs-sub' }, t('wizardSignalTesting')) : null,
|
|
5150
5314
|
),
|
|
5151
|
-
signalTesting ?
|
|
5152
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
5181
|
-
|
|
5182
|
-
|
|
5183
|
-
|
|
5184
|
-
|
|
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
|
-
|
|
5187
|
-
|
|
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 ?
|
|
5198
|
-
|
|
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
|
-
|
|
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 ?
|
|
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 ?
|
|
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
|
-
|
|
5225
|
-
|
|
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 ?
|
|
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
|
|
5417
|
+
return h('span', { className: 'dvo-pdesc' }, tt('cardHint'))
|
|
5254
5418
|
}
|
|
5255
|
-
return
|
|
5256
|
-
|
|
5419
|
+
return h(ErrorBoundary, null,
|
|
5420
|
+
h(page ? 'div' : 'li',
|
|
5257
5421
|
{ className: page ? 'dvo-ppage' : (open ? 'dvo-pcard dvo-pcardOpen' : 'dvo-pcard') },
|
|
5258
|
-
|
|
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
|
-
|
|
5267
|
-
|
|
5268
|
-
|
|
5430
|
+
h('span', { className: 'dvo-pheadtext' },
|
|
5431
|
+
h('span', { className: 'dvo-ptitle' }, title),
|
|
5432
|
+
h('span', { className: 'dvo-pdesc' }, tt('cardHint')),
|
|
5269
5433
|
),
|
|
5270
|
-
|
|
5434
|
+
h('span', { className: 'dvo-pchev' }, chevronIcon()),
|
|
5271
5435
|
),
|
|
5272
|
-
(page || open) ?
|
|
5436
|
+
(page || open) ? h('div', { className: 'dvo-pbody dvo-settings-root' }, h(VoiceSection, props)) : null,
|
|
5273
5437
|
),
|
|
5274
5438
|
)
|
|
5275
5439
|
}
|