@goodandready/dsh-voice 0.8.19 → 0.8.21
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-src/10-locale.js +14 -0
- package/lib/client-src/40-recording.js +3 -0
- package/lib/client-src/70-settings.js +347 -222
- package/lib/client.js +364 -222
- package/lib/index.js +8 -4
- package/lib/providers.js +16 -5
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -163,6 +163,20 @@ window.__ModuleLoader__.load({
|
|
|
163
163
|
'visDynamicOrb': 'Dynamic Orb',
|
|
164
164
|
'visBars': 'Classic Bars',
|
|
165
165
|
'visOff': 'Off',
|
|
166
|
+
'statusTitle': 'Connection & Engine Status',
|
|
167
|
+
'statusDesc': 'Current runtime connectivity to voice backends and recognition services.',
|
|
168
|
+
'badgeHostOnline': 'Host online ({ms} ms)',
|
|
169
|
+
'badgeHostOffline': 'Host unreachable',
|
|
170
|
+
'badgeWhisperActive': 'Whisper active',
|
|
171
|
+
'badgeWhisperInactive': 'Whisper offline',
|
|
172
|
+
'badgeSenseVoiceActive': 'SenseVoice active',
|
|
173
|
+
'badgeSenseVoiceInactive': 'SenseVoice offline',
|
|
174
|
+
'badgeProviders': '{count} providers ready',
|
|
175
|
+
'refreshStats': 'Refresh Stats',
|
|
176
|
+
'hardwareTitle': 'Hardware & Local Engines',
|
|
177
|
+
'hardwareDesc': 'Microphone device, local whisper.cpp and SenseVoice runtime settings.',
|
|
178
|
+
'uiError': 'Voice UI Error:',
|
|
179
|
+
'retry': 'Retry',
|
|
166
180
|
}
|
|
167
181
|
|
|
168
182
|
// Strings are also needed outside components — in recording handlers and
|
|
@@ -495,6 +509,9 @@ window.__ModuleLoader__.load({
|
|
|
495
509
|
// Fetch the mode chain from the host once. Only needed to decide whether
|
|
496
510
|
// to use browser recognition or record a file.
|
|
497
511
|
let chainsPromise = null
|
|
512
|
+
if (typeof window !== 'undefined') {
|
|
513
|
+
window.addEventListener('dsh-voice:settings-saved', () => { chainsPromise = null })
|
|
514
|
+
}
|
|
498
515
|
function modeChain(mode) {
|
|
499
516
|
if (!chainsPromise) {
|
|
500
517
|
chainsPromise = fetch('/dsh-voice/status', { cache: 'no-store' })
|
|
@@ -1345,19 +1362,40 @@ window.__ModuleLoader__.load({
|
|
|
1345
1362
|
const LANGS = ['auto', 'ru', 'en', 'uk', 'de']
|
|
1346
1363
|
|
|
1347
1364
|
const SET_CSS =
|
|
1348
|
-
'.
|
|
1365
|
+
'.cb-page{display:flex;flex-direction:column;gap:18px;padding:4px 0 24px;max-width:960px}' +
|
|
1366
|
+
'.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}' +
|
|
1367
|
+
'.cb-section-title{font-size:15px;font-weight:600;color:var(--dsw-alias-label-primary);display:flex;align-items:center;justify-content:space-between}' +
|
|
1368
|
+
'.cb-section-desc{font-size:13px;color:var(--dsw-alias-label-secondary);margin-top:-4px;line-height:1.4}' +
|
|
1369
|
+
'.cb-row{display:flex;flex-wrap:wrap;gap:10px;align-items:center}' +
|
|
1370
|
+
'.cb-grid-2{display:grid;grid-template-columns:repeat(auto-fit, minmax(260px, 1fr));gap:12px}' +
|
|
1371
|
+
'.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}' +
|
|
1372
|
+
'.cb-badge-ok{border-color:var(--dsw-alias-state-success-primary);color:var(--dsw-alias-state-success-primary);background:rgba(16,185,129,0.08)}' +
|
|
1373
|
+
'.cb-badge-warn{border-color:var(--dsw-alias-state-warning-primary);color:var(--dsw-alias-state-warning-primary);background:rgba(245,158,11,0.08)}' +
|
|
1374
|
+
'.cb-badge-bad{border-color:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary);background:rgba(239,68,68,0.08)}' +
|
|
1375
|
+
'.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}' +
|
|
1376
|
+
'.cb-input:focus{outline:none;border-color:var(--dsw-alias-state-brand-primary)}' +
|
|
1377
|
+
'.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}' +
|
|
1378
|
+
'.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))}' +
|
|
1379
|
+
'.cb-btn-primary{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3);border-color:transparent}' +
|
|
1380
|
+
'.cb-btn-primary:hover:not(:disabled){background:var(--dsw-alias-label-primary) !important;color:var(--dsw-alias-bg-layer-3) !important;opacity:0.88}' +
|
|
1381
|
+
'.cb-btn-danger{color:var(--dsw-alias-state-error-primary);border-color:rgba(239,68,68,0.3)}' +
|
|
1382
|
+
'.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}' +
|
|
1383
|
+
'.cb-btn-mini:hover:not(:disabled){background:var(--dsw-alias-bg-layer-2)}' +
|
|
1384
|
+
'.cb-field{display:flex;flex-direction:column;gap:6px;padding:6px 0;font-size:13px;color:var(--dsw-alias-label-primary)}' +
|
|
1385
|
+
'.cb-field select,.cb-field input[type="text"],.cb-field input[type="number"],.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}' +
|
|
1386
|
+
'.cb-field textarea{height:auto;padding:8px 10px;font-family:inherit}' +
|
|
1387
|
+
'.cb-check-label{display:flex;align-items:center;gap:8px;cursor:pointer;font-size:13px;color:var(--dsw-alias-label-primary);user-select:none}' +
|
|
1388
|
+
'.cb-check-label input[type="checkbox"]{width:16px;height:16px;cursor:pointer}' +
|
|
1389
|
+
'.cb-alert-err{padding:10px 14px;border-radius:8px;background:rgba(239,68,68,0.1);color:var(--dsw-alias-state-error-primary);font-size:13px}' +
|
|
1390
|
+
'.dvs-wrap{display:flex;flex-direction:column;gap:18px;padding:4px 0;max-width:960px}' +
|
|
1349
1391
|
'.dvs-block{display:flex;flex-direction:column;gap:10px}' +
|
|
1350
1392
|
'.dvs-h{font-size:14px;font-weight:600;color:var(--dsw-alias-label-primary)}' +
|
|
1351
1393
|
'.dvs-sub{font-size:12px;color:var(--dsw-alias-label-secondary)}' +
|
|
1352
1394
|
'.dvs-row{display:flex;gap:8px;align-items:center}' +
|
|
1353
|
-
'.dvs-row select,.dvs-row input{background:var(--dsw-alias-bg-layer-
|
|
1395
|
+
'.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}' +
|
|
1354
1396
|
'.dvs-row .dvs-model{flex:1}' +
|
|
1355
|
-
'.dvs-
|
|
1356
|
-
'.dvs-
|
|
1357
|
-
'.dvs-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}' +
|
|
1358
|
-
'.dvs-save{appearance:none;font:inherit;cursor:pointer;border:1px solid transparent;border-radius:8px;padding:5px 14px;font-size:13px;background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3)}' +
|
|
1359
|
-
'.dvs-card{display:flex;flex-direction:column;gap:6px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:10px}' +
|
|
1360
|
-
'.dvs-card input,.dvs-card select{background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l1);color:var(--dsw-alias-label-primary);border-radius:6px;padding:6px 8px;font-size:13px}' +
|
|
1397
|
+
'.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}' +
|
|
1398
|
+
'.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}' +
|
|
1361
1399
|
'.dvs-wait{font-size:13px;color:var(--dsw-alias-label-secondary);line-height:1.5;max-width:520px}' +
|
|
1362
1400
|
'.dvs-ok{font-size:12px;color:var(--dsw-alias-state-success-primary)}' +
|
|
1363
1401
|
'.dvs-bad{font-size:12px;color:var(--dsw-alias-state-error-primary)}' +
|
|
@@ -1368,7 +1406,7 @@ window.__ModuleLoader__.load({
|
|
|
1368
1406
|
'.dvo-pdesc{color:var(--dsw-alias-label-secondary);font-size:13px}' +
|
|
1369
1407
|
'.dvo-pchev{flex:none;display:flex;color:var(--dsw-alias-label-secondary);transition:transform .15s ease}' +
|
|
1370
1408
|
'.dvo-pcardOpen .dvo-pchev{transform:rotate(180deg)}' +
|
|
1371
|
-
'.dvo-pbody{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding-bottom:
|
|
1409
|
+
'.dvo-pbody{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding-bottom:12px}'
|
|
1372
1410
|
const setCssId = 'dsh-voice/settings.module.css'
|
|
1373
1411
|
if (typeof document !== 'undefined' && !document.querySelector('style[data-dsh-plugin="dsh-voice"][data-plugin-css="' + setCssId + '"]')) {
|
|
1374
1412
|
const tag = document.createElement('style')
|
|
@@ -1467,6 +1505,46 @@ window.__ModuleLoader__.load({
|
|
|
1467
1505
|
)
|
|
1468
1506
|
}
|
|
1469
1507
|
|
|
1508
|
+
function createErrorBoundary() {
|
|
1509
|
+
if (!React || typeof React.Component !== 'function') {
|
|
1510
|
+
return function NoopBoundary(props) { return props?.children || null }
|
|
1511
|
+
}
|
|
1512
|
+
return class ErrorBoundary extends React.Component {
|
|
1513
|
+
constructor(props) {
|
|
1514
|
+
super(props)
|
|
1515
|
+
this.state = { hasError: false, error: null }
|
|
1516
|
+
}
|
|
1517
|
+
static getDerivedStateFromError(error) {
|
|
1518
|
+
return { hasError: true, error }
|
|
1519
|
+
}
|
|
1520
|
+
componentDidCatch(error, errorInfo) {
|
|
1521
|
+
console.error('[dsh-voice] React Error:', error, errorInfo)
|
|
1522
|
+
}
|
|
1523
|
+
render() {
|
|
1524
|
+
if (this.state.hasError) {
|
|
1525
|
+
return React.createElement(
|
|
1526
|
+
'div',
|
|
1527
|
+
{ className: 'cb-alert-err', style: { margin: '12px 0', padding: '14px', borderRadius: '8px' } },
|
|
1528
|
+
React.createElement('div', { style: { fontWeight: 600, marginBottom: '6px' } }, '⚠️ ' + t('uiError')),
|
|
1529
|
+
React.createElement('div', { style: { fontSize: '12px', wordBreak: 'break-all' } }, String(this.state.error?.message || this.state.error)),
|
|
1530
|
+
React.createElement(
|
|
1531
|
+
'button',
|
|
1532
|
+
{
|
|
1533
|
+
type: 'button',
|
|
1534
|
+
className: 'cb-btn',
|
|
1535
|
+
style: { marginTop: '10px', fontSize: '12px', padding: '4px 10px' },
|
|
1536
|
+
onClick: () => this.setState({ hasError: false, error: null }),
|
|
1537
|
+
},
|
|
1538
|
+
t('retry'),
|
|
1539
|
+
),
|
|
1540
|
+
)
|
|
1541
|
+
}
|
|
1542
|
+
return this.props?.children || null
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1546
|
+
const ErrorBoundary = createErrorBoundary()
|
|
1547
|
+
|
|
1470
1548
|
function VoiceSection(props) {
|
|
1471
1549
|
const t = (props && props.t) || moduleT
|
|
1472
1550
|
const ctx = props.ctx
|
|
@@ -1481,12 +1559,7 @@ window.__ModuleLoader__.load({
|
|
|
1481
1559
|
const [saved, setSaved] = React.useState(false)
|
|
1482
1560
|
const [err, setErr] = React.useState('')
|
|
1483
1561
|
|
|
1484
|
-
// Hotkey picker: not a name field — press the key you want.
|
|
1485
|
-
// not need to know codes like KeyR.
|
|
1486
|
-
//
|
|
1487
|
-
// Hooks are declared here, above every return: declaring them later
|
|
1488
|
-
// would leave fewer hooks on the not-ready branch and React would
|
|
1489
|
-
// unmount the section with an error.
|
|
1562
|
+
// Hotkey picker: not a name field — press the key you want.
|
|
1490
1563
|
const [catching, setCatching] = React.useState(false)
|
|
1491
1564
|
React.useEffect(() => {
|
|
1492
1565
|
if (!catching) return undefined
|
|
@@ -1518,22 +1591,10 @@ window.__ModuleLoader__.load({
|
|
|
1518
1591
|
.catch(() => {})
|
|
1519
1592
|
}, [])
|
|
1520
1593
|
|
|
1521
|
-
// The snapshot carries a status, and status matters more than value.
|
|
1522
|
-
// loading — no host answer yet;
|
|
1523
|
-
// unavailable — host answered but does not know this namespace yet
|
|
1524
|
-
// (page opened in the first seconds while plugins register);
|
|
1525
|
-
// ready — values are present.
|
|
1526
|
-
// At unavailable, writable comes from the document and stays true, so
|
|
1527
|
-
// without a status check the card draws an empty but seemingly working
|
|
1528
|
-
// form — that is exactly how this bug looked.
|
|
1529
1594
|
const ready = !!snap && snap.status === 'ready'
|
|
1530
1595
|
const value = ready && snap.value ? snap.value : {}
|
|
1531
1596
|
const writable = ready && snap.writable !== false
|
|
1532
1597
|
|
|
1533
|
-
// The browser settings mirror reloads only on value writes and
|
|
1534
|
-
// reconnect; a new namespace is not such a signal. It will not fix
|
|
1535
|
-
// itself — ask it to reload until ready. The mirror is shared, so this
|
|
1536
|
-
// also repairs other settings sections.
|
|
1537
1598
|
React.useEffect(() => {
|
|
1538
1599
|
if (ready) return undefined
|
|
1539
1600
|
let tries = 0
|
|
@@ -1545,11 +1606,8 @@ window.__ModuleLoader__.load({
|
|
|
1545
1606
|
return () => clearInterval(timer)
|
|
1546
1607
|
}, [ready])
|
|
1547
1608
|
|
|
1548
|
-
// Seed the draft only from a ready snapshot. Previously it was filled
|
|
1549
|
-
// from the first snapshot and an empty draft froze forever: later
|
|
1550
|
-
// snapshots did not reseed it and the card stayed empty until reload.
|
|
1551
1609
|
React.useEffect(() => { if (ready && draft === null) setDraft(JSON.parse(JSON.stringify(value))) }, [ready, draft, value])
|
|
1552
|
-
|
|
1610
|
+
|
|
1553
1611
|
React.useEffect(() => {
|
|
1554
1612
|
if (!ready) return
|
|
1555
1613
|
voice.settings = Object.assign({}, voice.settings, {
|
|
@@ -1573,23 +1631,34 @@ window.__ModuleLoader__.load({
|
|
|
1573
1631
|
})
|
|
1574
1632
|
}, [ready, value, snap])
|
|
1575
1633
|
|
|
1576
|
-
const [
|
|
1577
|
-
React.
|
|
1578
|
-
|
|
1579
|
-
|
|
1634
|
+
const [statusData, setStatusData] = React.useState(null)
|
|
1635
|
+
const [statusLatency, setStatusLatency] = React.useState(null)
|
|
1636
|
+
const fetchStatus = React.useCallback(() => {
|
|
1637
|
+
const start = performance.now()
|
|
1638
|
+
const ctrl = new AbortController()
|
|
1639
|
+
const tId = setTimeout(() => ctrl.abort(), 5000)
|
|
1640
|
+
fetch('/dsh-voice/status', { cache: 'no-store', signal: ctrl.signal })
|
|
1580
1641
|
.then((r) => r.json())
|
|
1581
1642
|
.then((data) => {
|
|
1582
|
-
|
|
1583
|
-
|
|
1643
|
+
clearTimeout(tId)
|
|
1644
|
+
setStatusLatency(Math.round(performance.now() - start))
|
|
1645
|
+
if (data && data.ok) {
|
|
1646
|
+
setStatusData(data)
|
|
1584
1647
|
}
|
|
1585
1648
|
})
|
|
1586
|
-
.catch(() => {
|
|
1587
|
-
|
|
1649
|
+
.catch(() => {
|
|
1650
|
+
clearTimeout(tId)
|
|
1651
|
+
setStatusLatency(null)
|
|
1652
|
+
})
|
|
1588
1653
|
}, [])
|
|
1589
1654
|
|
|
1655
|
+
React.useEffect(() => {
|
|
1656
|
+
fetchStatus()
|
|
1657
|
+
}, [fetchStatus])
|
|
1658
|
+
|
|
1590
1659
|
if (!ready) {
|
|
1591
1660
|
const waiting = !snap || snap.status === 'loading'
|
|
1592
|
-
return React.createElement('div', { className: '
|
|
1661
|
+
return React.createElement('div', { className: 'cb-page' },
|
|
1593
1662
|
React.createElement('div', { className: 'dvs-wait' }, waiting
|
|
1594
1663
|
? t('loadingSettings')
|
|
1595
1664
|
: t('notReady1')
|
|
@@ -1609,10 +1678,6 @@ window.__ModuleLoader__.load({
|
|
|
1609
1678
|
setErr(''); setSaved(false)
|
|
1610
1679
|
if (!draft) return
|
|
1611
1680
|
|
|
1612
|
-
// Fields are written one by one. Previously the first failure broke
|
|
1613
|
-
// the loop: later fields were never saved, the composer never got the
|
|
1614
|
-
// hotkey reload signal, and the button looked like "nothing happens".
|
|
1615
|
-
// Now every field is attempted and failures are collected by name.
|
|
1616
1681
|
const failed = []
|
|
1617
1682
|
for (const k of Object.keys(draft)) {
|
|
1618
1683
|
try {
|
|
@@ -1637,12 +1702,11 @@ window.__ModuleLoader__.load({
|
|
|
1637
1702
|
contextGlossary: draft.contextGlossary !== false,
|
|
1638
1703
|
visualizerStyle: draft.visualizerStyle || 'liquid-wave',
|
|
1639
1704
|
})
|
|
1640
|
-
// The composer owns the hotkey handler — tell it to reload, otherwise
|
|
1641
|
-
// a new key only works after a full page reload.
|
|
1642
1705
|
try { window.dispatchEvent(new CustomEvent('dsh-voice:settings-saved')) } catch (noEvents) { /* nobody */ }
|
|
1643
1706
|
|
|
1644
1707
|
if (failed.length) { setErr(t('saveFailed') + ' ' + failed.join('; ')); return }
|
|
1645
1708
|
setSaved(true); setTimeout(() => setSaved(false), 2000)
|
|
1709
|
+
fetchStatus()
|
|
1646
1710
|
}
|
|
1647
1711
|
|
|
1648
1712
|
const modeVal = (mode, key, fallback) => {
|
|
@@ -1650,174 +1714,270 @@ window.__ModuleLoader__.load({
|
|
|
1650
1714
|
return m && m[key] !== undefined ? m[key] : fallback
|
|
1651
1715
|
}
|
|
1652
1716
|
|
|
1653
|
-
// Custom provider names come from the draft so a just-added entry
|
|
1654
|
-
// appears in chain selects immediately.
|
|
1655
1717
|
const chainOptions = BUILTIN.concat(
|
|
1656
1718
|
(draft && Array.isArray(draft.customProviders) ? draft.customProviders : [])
|
|
1657
1719
|
.map((c) => String(c && c.key || '').trim())
|
|
1658
1720
|
.filter((k) => k && BUILTIN.indexOf(k) < 0),
|
|
1659
1721
|
)
|
|
1660
1722
|
|
|
1661
|
-
const hotkeyField = () => React.createElement('
|
|
1662
|
-
t('hotkey'),
|
|
1663
|
-
React.createElement('div', { className: '
|
|
1723
|
+
const hotkeyField = () => React.createElement('div', { className: 'cb-field' },
|
|
1724
|
+
React.createElement('label', null, t('hotkey')),
|
|
1725
|
+
React.createElement('div', { className: 'cb-row' },
|
|
1664
1726
|
React.createElement('button', {
|
|
1665
|
-
type: 'button', className: '
|
|
1727
|
+
type: 'button', className: 'cb-btn cb-btn-primary', disabled: !writable,
|
|
1666
1728
|
onClick: () => setCatching(true),
|
|
1667
1729
|
}, catching ? t('pressKey') : keyLabel(draft && draft.hotkey)),
|
|
1668
1730
|
React.createElement('button', {
|
|
1669
|
-
type: 'button', className: '
|
|
1731
|
+
type: 'button', className: 'cb-btn cb-btn-mini', title: t('clearKey'),
|
|
1670
1732
|
disabled: !writable, onClick: () => setDraft((d) => Object.assign({}, d || {}, { hotkey: '' })),
|
|
1671
1733
|
}, '×'),
|
|
1672
1734
|
),
|
|
1673
|
-
React.createElement('span', { className: 'dvs-sub' },
|
|
1674
|
-
|
|
1675
|
-
+ t('hotkeyHint2')))
|
|
1735
|
+
React.createElement('span', { className: 'dvs-sub' }, t('hotkeyHint1') + t('hotkeyHint2')),
|
|
1736
|
+
)
|
|
1676
1737
|
|
|
1677
|
-
const micField = () => React.createElement('
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1738
|
+
const micField = () => React.createElement('div', { className: 'cb-field' },
|
|
1739
|
+
React.createElement('label', null, t('mic')),
|
|
1740
|
+
React.createElement('select', {
|
|
1741
|
+
value: String((snap && snap.micDeviceId) || ''), disabled: !writable,
|
|
1742
|
+
onChange: (e) => setTop('micDeviceId', e.target.value),
|
|
1743
|
+
},
|
|
1744
|
+
React.createElement('option', { value: '' }, t('micDefault')),
|
|
1745
|
+
devices.map((d) => React.createElement('option', { key: d.deviceId, value: d.deviceId },
|
|
1746
|
+
d.label || d.deviceId.slice(0, 12))),
|
|
1747
|
+
),
|
|
1748
|
+
)
|
|
1749
|
+
|
|
1750
|
+
const langField = (mode) => React.createElement('div', { className: 'cb-field' },
|
|
1751
|
+
React.createElement('label', null, t('language')),
|
|
1686
1752
|
React.createElement('select', {
|
|
1687
1753
|
value: modeVal(mode, 'language', 'ru'), disabled: !writable,
|
|
1688
1754
|
onChange: (e) => setIn(mode, 'language', e.target.value),
|
|
1689
|
-
}, LANGS.map((o) => React.createElement('option', { key: o, value: o }, o)))
|
|
1755
|
+
}, LANGS.map((o) => React.createElement('option', { key: o, value: o }, o))),
|
|
1756
|
+
)
|
|
1690
1757
|
|
|
1691
|
-
const numField = (mode, key, label, hint) => React.createElement('
|
|
1758
|
+
const numField = (mode, key, label, hint) => React.createElement('div', { className: 'cb-field' },
|
|
1759
|
+
React.createElement('label', null, label),
|
|
1692
1760
|
React.createElement('input', {
|
|
1693
1761
|
type: 'number', value: modeVal(mode, key, ''), disabled: !writable,
|
|
1694
1762
|
onChange: (e) => setIn(mode, key, Number(e.target.value)),
|
|
1695
1763
|
}),
|
|
1696
|
-
React.createElement('span', { className: 'dvs-sub' }, hint)
|
|
1764
|
+
React.createElement('span', { className: 'dvs-sub' }, hint),
|
|
1765
|
+
)
|
|
1697
1766
|
|
|
1698
|
-
const textField = (key, label, hint) => React.createElement('
|
|
1767
|
+
const textField = (key, label, hint) => React.createElement('div', { className: 'cb-field' },
|
|
1768
|
+
React.createElement('label', null, label),
|
|
1699
1769
|
React.createElement('input', {
|
|
1700
|
-
value: draft && draft[key] !== undefined ? draft[key] : '', disabled: !writable,
|
|
1770
|
+
type: 'text', value: draft && draft[key] !== undefined ? draft[key] : '', disabled: !writable,
|
|
1701
1771
|
onChange: (e) => setTop(key, e.target.value),
|
|
1702
1772
|
}),
|
|
1703
|
-
React.createElement('span', { className: 'dvs-sub' }, hint)
|
|
1773
|
+
React.createElement('span', { className: 'dvs-sub' }, hint),
|
|
1774
|
+
)
|
|
1775
|
+
|
|
1776
|
+
const statsData = (statusData && statusData.providerStats) || {}
|
|
1777
|
+
const hostOnline = statusLatency !== null
|
|
1778
|
+
const whisperRunning = !!(statusData && statusData.whisperRunning)
|
|
1779
|
+
const providerCount = (statusData && Array.isArray(statusData.providers)) ? statusData.providers.length : chainOptions.length
|
|
1780
|
+
|
|
1781
|
+
return React.createElement('div', { className: 'cb-page' },
|
|
1782
|
+
// 1. Connection & Engine Status Card
|
|
1783
|
+
React.createElement('div', { className: 'cb-section-card' },
|
|
1784
|
+
React.createElement('div', { className: 'cb-section-title' },
|
|
1785
|
+
React.createElement('span', null, t('statusTitle')),
|
|
1786
|
+
React.createElement('button', {
|
|
1787
|
+
type: 'button', className: 'cb-btn', style: { fontSize: '12px', padding: '4px 8px' },
|
|
1788
|
+
onClick: fetchStatus,
|
|
1789
|
+
}, '🔄 ' + t('refreshStats')),
|
|
1790
|
+
),
|
|
1791
|
+
React.createElement('div', { className: 'cb-section-desc' }, t('statusDesc')),
|
|
1792
|
+
React.createElement('div', { className: 'cb-row' },
|
|
1793
|
+
hostOnline
|
|
1794
|
+
? React.createElement('span', { className: 'cb-badge cb-badge-ok' }, '● ' + t('badgeHostOnline').replace('{ms}', statusLatency))
|
|
1795
|
+
: React.createElement('span', { className: 'cb-badge cb-badge-bad' }, '● ' + t('badgeHostOffline')),
|
|
1796
|
+
whisperRunning
|
|
1797
|
+
? React.createElement('span', { className: 'cb-badge cb-badge-ok' }, '● ' + t('badgeWhisperActive'))
|
|
1798
|
+
: React.createElement('span', { className: 'cb-badge cb-badge-warn' }, '○ ' + t('badgeWhisperInactive')),
|
|
1799
|
+
React.createElement('span', { className: 'cb-badge cb-badge-ok' }, '✓ ' + t('badgeProviders').replace('{count}', providerCount)),
|
|
1800
|
+
),
|
|
1801
|
+
),
|
|
1704
1802
|
|
|
1705
|
-
|
|
1706
|
-
React.createElement('div', { className: '
|
|
1707
|
-
React.createElement('div', { className: '
|
|
1708
|
-
|
|
1803
|
+
// 2. Dictation Mode Card
|
|
1804
|
+
React.createElement('div', { className: 'cb-section-card' },
|
|
1805
|
+
React.createElement('div', { className: 'cb-section-title' },
|
|
1806
|
+
React.createElement('span', null, '🎙️ ' + t('dictationBtn')),
|
|
1807
|
+
),
|
|
1808
|
+
React.createElement('div', { className: 'cb-section-desc' }, t('dictationHint')),
|
|
1709
1809
|
React.createElement(ChainEditor, {
|
|
1710
1810
|
value: draft && draft.dictation ? draft.dictation.chain : [], writable: writable,
|
|
1711
1811
|
options: chainOptions,
|
|
1712
1812
|
onChange: (v) => setIn('dictation', 'chain', v),
|
|
1713
1813
|
}),
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
}
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1814
|
+
React.createElement('div', { className: 'cb-grid-2' },
|
|
1815
|
+
langField('dictation'),
|
|
1816
|
+
numField('dictation', 'vadSilenceMs', t('pauseMs'), t('pauseHint')),
|
|
1817
|
+
numField('dictation', 'sendDelayMs', t('sendDelay'), t('sendDelayHint')),
|
|
1818
|
+
numField('dictation', 'streamChunkMs', t('streamChunkMs'), t('streamHint')),
|
|
1819
|
+
),
|
|
1820
|
+
React.createElement('div', { className: 'cb-row' },
|
|
1821
|
+
React.createElement('label', { className: 'cb-check-label' },
|
|
1822
|
+
React.createElement('input', {
|
|
1823
|
+
type: 'checkbox', checked: !!(draft && draft.dictation && draft.dictation.polish), disabled: !writable,
|
|
1824
|
+
onChange: (e) => setIn('dictation', 'polish', e.target.checked),
|
|
1825
|
+
}),
|
|
1826
|
+
t('polish'),
|
|
1827
|
+
),
|
|
1828
|
+
React.createElement('label', { className: 'cb-check-label' },
|
|
1829
|
+
React.createElement('input', {
|
|
1830
|
+
type: 'checkbox', checked: !!(draft && draft.dictation && draft.dictation.stream), disabled: !writable,
|
|
1831
|
+
onChange: (e) => setIn('dictation', 'stream', e.target.checked),
|
|
1832
|
+
}),
|
|
1833
|
+
t('stream'),
|
|
1834
|
+
),
|
|
1835
|
+
),
|
|
1836
|
+
React.createElement('div', { className: 'cb-field' },
|
|
1837
|
+
React.createElement('label', null, t('vadAdapt')),
|
|
1735
1838
|
React.createElement('input', {
|
|
1736
1839
|
type: 'range', min: 0, max: 1, step: 0.1,
|
|
1737
1840
|
value: Number(draft && draft.dictation && draft.dictation.vadAdapt) || 0, disabled: !writable,
|
|
1738
1841
|
onChange: (e) => setIn('dictation', 'vadAdapt', Number(e.target.value)),
|
|
1739
|
-
})
|
|
1842
|
+
}),
|
|
1843
|
+
React.createElement('span', { className: 'dvs-sub' }, t('vadAdaptHint')),
|
|
1844
|
+
),
|
|
1845
|
+
textField('wakeWord', t('wakeWord'), t('wakeWordHint')),
|
|
1740
1846
|
),
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1847
|
+
|
|
1848
|
+
// 3. Voice Message Card
|
|
1849
|
+
React.createElement('div', { className: 'cb-section-card' },
|
|
1850
|
+
React.createElement('div', { className: 'cb-section-title' },
|
|
1851
|
+
React.createElement('span', null, '💬 ' + t('messageTitle')),
|
|
1852
|
+
),
|
|
1853
|
+
React.createElement('div', { className: 'cb-section-desc' }, t('messageHint')),
|
|
1744
1854
|
React.createElement(ChainEditor, {
|
|
1745
1855
|
value: draft && draft.message ? draft.message.chain : [], writable: writable,
|
|
1746
1856
|
options: chainOptions,
|
|
1747
1857
|
onChange: (v) => setIn('message', 'chain', v),
|
|
1748
1858
|
}),
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1859
|
+
React.createElement('div', { className: 'cb-grid-2' },
|
|
1860
|
+
langField('message'),
|
|
1861
|
+
numField('message', 'autoSendMs', t('undoMs'), t('undoHint')),
|
|
1862
|
+
),
|
|
1863
|
+
React.createElement('div', { className: 'cb-row' },
|
|
1864
|
+
React.createElement('label', { className: 'cb-check-label' },
|
|
1865
|
+
React.createElement('input', {
|
|
1866
|
+
type: 'checkbox', checked: !!(draft && draft.message && draft.message.polish), disabled: !writable,
|
|
1867
|
+
onChange: (e) => setIn('message', 'polish', e.target.checked),
|
|
1868
|
+
}),
|
|
1869
|
+
t('polish'),
|
|
1870
|
+
),
|
|
1871
|
+
React.createElement('label', { className: 'cb-check-label' },
|
|
1872
|
+
React.createElement('input', {
|
|
1873
|
+
type: 'checkbox', checked: !!(draft && draft.message && draft.message.polishSend), disabled: !writable,
|
|
1874
|
+
onChange: (e) => setIn('message', 'polishSend', e.target.checked),
|
|
1875
|
+
}),
|
|
1876
|
+
t('polishSend'),
|
|
1877
|
+
),
|
|
1878
|
+
React.createElement('label', { className: 'cb-check-label' },
|
|
1879
|
+
React.createElement('input', {
|
|
1880
|
+
type: 'checkbox', checked: !!(draft && draft.message && draft.message.sessionCommands), disabled: !writable,
|
|
1881
|
+
onChange: (e) => setIn('message', 'sessionCommands', e.target.checked),
|
|
1882
|
+
}),
|
|
1883
|
+
t('sessionCommands'),
|
|
1884
|
+
),
|
|
1885
|
+
),
|
|
1766
1886
|
),
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1887
|
+
|
|
1888
|
+
// 4. Custom Providers Card
|
|
1889
|
+
React.createElement('div', { className: 'cb-section-card' },
|
|
1890
|
+
React.createElement('div', { className: 'cb-section-title' },
|
|
1891
|
+
React.createElement('span', null, '⚙️ ' + t('customTitle')),
|
|
1892
|
+
),
|
|
1893
|
+
React.createElement('div', { className: 'cb-section-desc' }, t('customHint')),
|
|
1771
1894
|
React.createElement(CustomEditor, {
|
|
1772
1895
|
value: draft && draft.customProviders ? draft.customProviders : [], writable: writable,
|
|
1896
|
+
options: chainOptions,
|
|
1773
1897
|
onChange: (v) => setTop('customProviders', v),
|
|
1774
1898
|
}),
|
|
1775
1899
|
),
|
|
1776
|
-
|
|
1777
|
-
|
|
1900
|
+
|
|
1901
|
+
// 5. Hardware & Local Engines Card
|
|
1902
|
+
React.createElement('div', { className: 'cb-section-card' },
|
|
1903
|
+
React.createElement('div', { className: 'cb-section-title' },
|
|
1904
|
+
React.createElement('span', null, '🔧 ' + t('hardwareTitle')),
|
|
1905
|
+
),
|
|
1906
|
+
React.createElement('div', { className: 'cb-section-desc' }, t('hardwareDesc')),
|
|
1778
1907
|
hotkeyField(),
|
|
1779
|
-
textField('whisperUrl', t('whisperEndpoint'), t('whisperEndpointHint')),
|
|
1780
|
-
textField('whisperBin', t('whisperBin'), t('whisperBinHint')),
|
|
1781
|
-
textField('whisperModel', t('whisperModel'), t('whisperModelHint')),
|
|
1782
|
-
textField('deepgramBaseUrl', t('deepgramEndpoint'), t('deepgramEndpointHint')),
|
|
1783
|
-
React.createElement('label', { className: 'dvs-field' }, t('whisperAutostart'),
|
|
1784
|
-
React.createElement('input', {
|
|
1785
|
-
type: 'checkbox', checked: !!(draft && draft.autoStart), disabled: !writable,
|
|
1786
|
-
onChange: (e) => setTop('autoStart', e.target.checked),
|
|
1787
|
-
})),
|
|
1788
|
-
React.createElement('label', { className: 'dvs-field', title: t('normalizeTranscriptHint') },
|
|
1789
|
-
t('normalizeTranscript'),
|
|
1790
|
-
React.createElement('input', {
|
|
1791
|
-
type: 'checkbox', checked: !!(draft && draft.normalizeTranscript), disabled: !writable,
|
|
1792
|
-
onChange: (e) => setTop('normalizeTranscript', e.target.checked),
|
|
1793
|
-
})),
|
|
1794
|
-
React.createElement('label', { className: 'dvs-field' }, t('beep'),
|
|
1795
|
-
React.createElement('input', {
|
|
1796
|
-
type: 'checkbox', checked: !!(draft && draft.beep), disabled: !writable,
|
|
1797
|
-
onChange: (e) => setTop('beep', e.target.checked),
|
|
1798
|
-
})),
|
|
1799
|
-
React.createElement('label', { className: 'dvs-field', title: t('localOnlyHint') }, t('localOnly'),
|
|
1800
|
-
React.createElement('input', {
|
|
1801
|
-
type: 'checkbox', checked: !!(draft && draft.localOnly), disabled: !writable,
|
|
1802
|
-
onChange: (e) => setTop('localOnly', e.target.checked),
|
|
1803
|
-
})),
|
|
1804
|
-
React.createElement('label', { className: 'dvs-field' }, t('voiceCommandsLabel'),
|
|
1805
|
-
React.createElement('input', {
|
|
1806
|
-
type: 'checkbox', checked: !!(draft && draft.voiceCommands), disabled: !writable,
|
|
1807
|
-
onChange: (e) => setTop('voiceCommands', e.target.checked),
|
|
1808
|
-
})),
|
|
1809
1908
|
micField(),
|
|
1810
|
-
React.createElement('
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1909
|
+
React.createElement('div', { className: 'cb-grid-2' },
|
|
1910
|
+
textField('whisperUrl', t('whisperEndpoint'), t('whisperEndpointHint')),
|
|
1911
|
+
textField('whisperBin', t('whisperBin'), t('whisperBinHint')),
|
|
1912
|
+
textField('whisperModel', t('whisperModel'), t('whisperModelHint')),
|
|
1913
|
+
textField('deepgramBaseUrl', t('deepgramEndpoint'), t('deepgramEndpointHint')),
|
|
1914
|
+
textField('sensevoiceUrl', t('sensevoiceEndpoint'), t('sensevoiceEndpointHint')),
|
|
1915
|
+
textField('sensevoiceBin', t('sensevoiceBin'), t('sensevoiceBinHint')),
|
|
1916
|
+
textField('sensevoiceModel', t('sensevoiceModel'), t('sensevoiceModelHint')),
|
|
1917
|
+
textField('polishBaseUrl', t('polishBaseUrl'), t('polishBaseUrlHint')),
|
|
1918
|
+
textField('polishModel', t('polishModel'), ''),
|
|
1919
|
+
textField('polishKeyEnv', t('polishKeyEnv'), ''),
|
|
1920
|
+
),
|
|
1921
|
+
React.createElement('div', { className: 'cb-row' },
|
|
1922
|
+
React.createElement('label', { className: 'cb-check-label' },
|
|
1923
|
+
React.createElement('input', {
|
|
1924
|
+
type: 'checkbox', checked: !!(draft && draft.autoStart), disabled: !writable,
|
|
1925
|
+
onChange: (e) => setTop('autoStart', e.target.checked),
|
|
1926
|
+
}),
|
|
1927
|
+
t('whisperAutostart'),
|
|
1928
|
+
),
|
|
1929
|
+
React.createElement('label', { className: 'cb-check-label' },
|
|
1930
|
+
React.createElement('input', {
|
|
1931
|
+
type: 'checkbox', checked: !!(draft && draft.sensevoiceAutostart), disabled: !writable,
|
|
1932
|
+
onChange: (e) => setTop('sensevoiceAutostart', e.target.checked),
|
|
1933
|
+
}),
|
|
1934
|
+
t('sensevoiceAutostart'),
|
|
1935
|
+
),
|
|
1936
|
+
React.createElement('label', { className: 'cb-check-label' },
|
|
1937
|
+
React.createElement('input', {
|
|
1938
|
+
type: 'checkbox', checked: !!(draft && draft.localOnly), disabled: !writable,
|
|
1939
|
+
onChange: (e) => setTop('localOnly', e.target.checked),
|
|
1940
|
+
}),
|
|
1941
|
+
t('localOnly'),
|
|
1942
|
+
),
|
|
1943
|
+
React.createElement('label', { className: 'cb-check-label' },
|
|
1944
|
+
React.createElement('input', {
|
|
1945
|
+
type: 'checkbox', checked: !!(draft && draft.beep), disabled: !writable,
|
|
1946
|
+
onChange: (e) => setTop('beep', e.target.checked),
|
|
1947
|
+
}),
|
|
1948
|
+
t('beep'),
|
|
1949
|
+
),
|
|
1950
|
+
React.createElement('label', { className: 'cb-check-label' },
|
|
1951
|
+
React.createElement('input', {
|
|
1952
|
+
type: 'checkbox', checked: draft ? draft.noiseSuppression !== false : true, disabled: !writable,
|
|
1953
|
+
onChange: (e) => setTop('noiseSuppression', e.target.checked),
|
|
1954
|
+
}),
|
|
1955
|
+
t('noiseSuppression'),
|
|
1956
|
+
),
|
|
1957
|
+
React.createElement('label', { className: 'cb-check-label' },
|
|
1958
|
+
React.createElement('input', {
|
|
1959
|
+
type: 'checkbox', checked: draft ? draft.contextGlossary !== false : true, disabled: !writable,
|
|
1960
|
+
onChange: (e) => setTop('contextGlossary', e.target.checked),
|
|
1961
|
+
}),
|
|
1962
|
+
t('contextGlossary'),
|
|
1963
|
+
),
|
|
1964
|
+
React.createElement('label', { className: 'cb-check-label' },
|
|
1965
|
+
React.createElement('input', {
|
|
1966
|
+
type: 'checkbox', checked: !!(draft && draft.voiceCommands), disabled: !writable,
|
|
1967
|
+
onChange: (e) => setTop('voiceCommands', e.target.checked),
|
|
1968
|
+
}),
|
|
1969
|
+
t('voiceCommandsLabel'),
|
|
1970
|
+
),
|
|
1971
|
+
React.createElement('label', { className: 'cb-check-label' },
|
|
1972
|
+
React.createElement('input', {
|
|
1973
|
+
type: 'checkbox', checked: !!(draft && draft.normalizeTranscript), disabled: !writable,
|
|
1974
|
+
onChange: (e) => setTop('normalizeTranscript', e.target.checked),
|
|
1975
|
+
}),
|
|
1976
|
+
t('normalizeTranscript'),
|
|
1977
|
+
),
|
|
1978
|
+
),
|
|
1979
|
+
React.createElement('div', { className: 'cb-field' },
|
|
1980
|
+
React.createElement('label', null, t('visualizerStyle')),
|
|
1821
1981
|
React.createElement('select', {
|
|
1822
1982
|
value: String((draft && draft.visualizerStyle) || 'liquid-wave'), disabled: !writable,
|
|
1823
1983
|
onChange: (e) => setTop('visualizerStyle', e.target.value),
|
|
@@ -1826,43 +1986,27 @@ window.__ModuleLoader__.load({
|
|
|
1826
1986
|
React.createElement('option', { value: 'dynamic-orb' }, t('visDynamicOrb')),
|
|
1827
1987
|
React.createElement('option', { value: 'bars' }, t('visBars')),
|
|
1828
1988
|
React.createElement('option', { value: 'off' }, t('visOff')),
|
|
1829
|
-
)
|
|
1830
|
-
|
|
1989
|
+
),
|
|
1990
|
+
),
|
|
1991
|
+
React.createElement('div', { className: 'cb-field' },
|
|
1992
|
+
React.createElement('label', null, t('vocabulary')),
|
|
1831
1993
|
React.createElement('textarea', {
|
|
1832
1994
|
rows: 3, disabled: !writable,
|
|
1833
1995
|
value: Array.isArray(draft && draft.vocabulary) ? draft.vocabulary.join('\n') : '',
|
|
1834
1996
|
onChange: (e) => setTop('vocabulary', e.target.value.split('\n').map((x) => x.trim()).filter(Boolean)),
|
|
1835
|
-
})
|
|
1836
|
-
|
|
1837
|
-
React.createElement('input', {
|
|
1838
|
-
type: 'text', value: String((snap && snap.polishBaseUrl) || ''), disabled: !writable,
|
|
1839
|
-
onChange: (e) => setTop('polishBaseUrl', e.target.value),
|
|
1840
|
-
})),
|
|
1841
|
-
React.createElement('label', { className: 'dvs-field' }, t('polishModel'),
|
|
1842
|
-
React.createElement('input', {
|
|
1843
|
-
type: 'text', value: String((draft && draft.polishModel) || ''), disabled: !writable,
|
|
1844
|
-
onChange: (e) => setTop('polishModel', e.target.value),
|
|
1845
|
-
})),
|
|
1846
|
-
React.createElement('label', { className: 'dvs-field' }, t('polishKeyEnv'),
|
|
1847
|
-
React.createElement('input', {
|
|
1848
|
-
type: 'text', value: String((draft && draft.polishKeyEnv) || ''), disabled: !writable,
|
|
1849
|
-
onChange: (e) => setTop('polishKeyEnv', e.target.value),
|
|
1850
|
-
})),
|
|
1851
|
-
),
|
|
1852
|
-
React.createElement('div', { className: 'dvs-block' },
|
|
1853
|
-
React.createElement('div', { className: 'dvs-h' }, 'SenseVoice-ONNX / Sherpa-ONNX'),
|
|
1854
|
-
React.createElement('div', { className: 'dvs-sub' }, t('sensevoiceHint')),
|
|
1855
|
-
textField('sensevoiceUrl', t('sensevoiceEndpoint'), t('sensevoiceEndpointHint')),
|
|
1856
|
-
textField('sensevoiceBin', t('sensevoiceBin'), t('sensevoiceBinHint')),
|
|
1857
|
-
textField('sensevoiceModel', t('sensevoiceModel'), t('sensevoiceModelHint')),
|
|
1858
|
-
React.createElement('label', { className: 'dvs-field' }, t('sensevoiceAutostart'),
|
|
1859
|
-
React.createElement('input', {
|
|
1860
|
-
type: 'checkbox', checked: !!(draft && draft.sensevoiceAutostart), disabled: !writable,
|
|
1861
|
-
onChange: (e) => setTop('sensevoiceAutostart', e.target.checked),
|
|
1862
|
-
})),
|
|
1997
|
+
}),
|
|
1998
|
+
),
|
|
1863
1999
|
),
|
|
1864
|
-
|
|
1865
|
-
|
|
2000
|
+
|
|
2001
|
+
// 6. Provider Telemetry & Latency Dashboard
|
|
2002
|
+
React.createElement('div', { className: 'cb-section-card' },
|
|
2003
|
+
React.createElement('div', { className: 'cb-section-title' },
|
|
2004
|
+
React.createElement('span', null, '📊 ' + t('providerDashboard')),
|
|
2005
|
+
React.createElement('button', {
|
|
2006
|
+
type: 'button', className: 'cb-btn', style: { fontSize: '12px', padding: '4px 8px' },
|
|
2007
|
+
onClick: fetchStatus,
|
|
2008
|
+
}, '🔄 ' + t('refreshStats')),
|
|
2009
|
+
),
|
|
1866
2010
|
React.createElement('div', { className: 'dvo-dash' },
|
|
1867
2011
|
React.createElement('div', { className: 'dvo-dash-grid' },
|
|
1868
2012
|
Object.keys(statsData).length === 0
|
|
@@ -1899,53 +2043,51 @@ window.__ModuleLoader__.load({
|
|
|
1899
2043
|
React.createElement('span', null, t('successRate')),
|
|
1900
2044
|
),
|
|
1901
2045
|
)
|
|
1902
|
-
})
|
|
1903
|
-
)
|
|
1904
|
-
)
|
|
2046
|
+
}),
|
|
2047
|
+
),
|
|
2048
|
+
),
|
|
1905
2049
|
),
|
|
1906
|
-
|
|
1907
|
-
|
|
2050
|
+
|
|
2051
|
+
// Action bar (Save)
|
|
2052
|
+
React.createElement('div', { className: 'cb-row' },
|
|
2053
|
+
React.createElement('button', { type: 'button', className: 'cb-btn cb-btn-primary', disabled: !writable, onClick: save }, t('save')),
|
|
1908
2054
|
saved ? React.createElement('span', { className: 'dvs-ok' }, t('saved')) : null,
|
|
1909
2055
|
err ? React.createElement('span', { className: 'dvs-bad' }, err) : null,
|
|
1910
2056
|
),
|
|
1911
2057
|
)
|
|
1912
2058
|
}
|
|
1913
2059
|
|
|
1914
|
-
// Card in Settings → Plugins → Plugin settings (#18)
|
|
1915
|
-
// draws the list shell, so title, hint and collapse are ours.
|
|
1916
|
-
// Body mounts on first expand; the settings snapshot arrives then.
|
|
2060
|
+
// Card in Settings → Plugins → Plugin settings (#18)
|
|
1917
2061
|
function PluginCard(props) {
|
|
1918
2062
|
const [open, setOpen] = React.useState(false)
|
|
1919
2063
|
const tt = (props && props.t) || t
|
|
1920
2064
|
const title = tt('title')
|
|
1921
|
-
return React.createElement(
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
React.createElement('span', { className: 'dvo-
|
|
1932
|
-
|
|
2065
|
+
return React.createElement(ErrorBoundary, null,
|
|
2066
|
+
React.createElement('li',
|
|
2067
|
+
{ className: open ? 'dvo-pcard dvo-pcardOpen' : 'dvo-pcard' },
|
|
2068
|
+
React.createElement('button',
|
|
2069
|
+
{
|
|
2070
|
+
type: 'button', className: 'dvo-phead',
|
|
2071
|
+
'aria-expanded': open ? 'true' : 'false',
|
|
2072
|
+
'aria-label': (open ? tt('collapse') : tt('expand')) + ': ' + title,
|
|
2073
|
+
onClick: () => setOpen((v) => !v),
|
|
2074
|
+
},
|
|
2075
|
+
React.createElement('span', { className: 'dvo-pheadtext' },
|
|
2076
|
+
React.createElement('span', { className: 'dvo-ptitle' }, title),
|
|
2077
|
+
React.createElement('span', { className: 'dvo-pdesc' }, tt('cardHint')),
|
|
2078
|
+
),
|
|
2079
|
+
React.createElement('span', { className: 'dvo-pchev' }, chevronIcon()),
|
|
1933
2080
|
),
|
|
1934
|
-
React.createElement('
|
|
2081
|
+
open ? React.createElement('div', { className: 'dvo-pbody' }, React.createElement(VoiceSection, props)) : null,
|
|
1935
2082
|
),
|
|
1936
|
-
open ? React.createElement('div', { className: 'dvo-pbody' }, React.createElement(VoiceSection, props)) : null,
|
|
1937
2083
|
)
|
|
1938
2084
|
}
|
|
1939
2085
|
|
|
1940
2086
|
function registerSettings(ctx) {
|
|
1941
|
-
// Settings card under Settings → Plugins → Plugin settings (#18).
|
|
1942
|
-
// The registration key must equal NS (the settings namespace), otherwise
|
|
1943
|
-
// the tab silently skips the slot.
|
|
1944
2087
|
ctx.slots.inject('settings.plugin.item', () => ctx.slots.register(
|
|
1945
2088
|
{
|
|
1946
2089
|
name: 'settings.plugin.item',
|
|
1947
2090
|
key: NS,
|
|
1948
|
-
// locale on the slot record is what makes the component receive props.t.
|
|
1949
2091
|
locale: NS,
|
|
1950
2092
|
inject: () => ({ ctx: ctx }),
|
|
1951
2093
|
},
|