@goodandready/dsh-voice 0.9.44 → 0.9.45
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 +81 -77
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -1,13 +1,6 @@
|
|
|
1
1
|
// dsh-voice — client half (browser).
|
|
2
2
|
//
|
|
3
|
-
//
|
|
4
|
-
// mic — dictation: speech is cut on pauses, each chunk is recognized and
|
|
5
|
-
// appended to the composer; send stays with the user;
|
|
6
|
-
// wave — voice message: one whole recording; after recognition the text is
|
|
7
|
-
// sent to the agent when the cancel window expires.
|
|
8
|
-
//
|
|
9
|
-
// The recording pill lives in conversation.input.dock; settings are a card
|
|
10
|
-
// in settings.plugin.item.
|
|
3
|
+
// Dictation (mic), voice message (wave), dock pill and settings card.
|
|
11
4
|
|
|
12
5
|
window.__ModuleLoader__.load({
|
|
13
6
|
id: '@goodandready/dsh-voice',
|
|
@@ -32,9 +25,7 @@ window.__ModuleLoader__.load({
|
|
|
32
25
|
const ROW_ID = 'dsh-voice'
|
|
33
26
|
const ROW_CONFIG_KEY = PKG + '#' + ROW_ID
|
|
34
27
|
|
|
35
|
-
// UI strings
|
|
36
|
-
// translate them without touching this plugin. English is the source
|
|
37
|
-
// language, the default, and the fallback.
|
|
28
|
+
// UI strings in locale registry; English is source/default.
|
|
38
29
|
const en = {
|
|
39
30
|
'saveFailed': 'Some fields were not saved —',
|
|
40
31
|
'cardHint': 'Dictation and voice messages: providers, chains, local whisper',
|
|
@@ -320,6 +311,7 @@ window.__ModuleLoader__.load({
|
|
|
320
311
|
'wizardTestFailed': 'STT test failed: {error}',
|
|
321
312
|
'wizardNextStepHint': 'Next step: {hint}',
|
|
322
313
|
'wizardHintPermission': 'Click "Request Microphone Access" and allow browser permission prompt.', 'wizardHintDevice': 'Check microphone connection or select a different input device.', 'wizardHintHttps': 'Open DeepSeek Harness via HTTPS or localhost address.', 'wizardHintProvider': 'Configure STT engine credentials or install SenseVoice model below.',
|
|
314
|
+
'wizardHintWinDesktop': 'System microphone blocked. Open Windows Settings → Privacy & security → Microphone, and turn ON "Let desktop apps access your microphone".', 'wizardHintWinBrowser': 'Microphone permission denied. Allow access in your browser address bar or Windows Settings → Privacy & security → Microphone.', 'wizardHintMacDesktop': 'System microphone blocked. Open macOS System Settings → Privacy & Security → Microphone, and enable DSH Desktop.', 'wizardOpenWinMicSettings': 'Open Windows Mic Settings',
|
|
323
315
|
'wizardFinishSuccess': 'Setup & Verification Complete', 'wizardSkip': 'Skip Wizard',
|
|
324
316
|
'wizardStatusCompleted': 'Microphone and STT verified',
|
|
325
317
|
'exportDiagnosticsBtn': 'Export Diagnostics Report', 'exportingDiagnostics': 'Exporting report…',
|
|
@@ -612,6 +604,7 @@ window.__ModuleLoader__.load({
|
|
|
612
604
|
'wizardTestFailed': 'STT 测试失败:{error}',
|
|
613
605
|
'wizardNextStepHint': '建议下一步:{hint}',
|
|
614
606
|
'wizardHintPermission': '点击“请求麦克风权限”并在浏览器弹窗中允许访问。', 'wizardHintDevice': '检查麦克风硬件连接或切换选择其他输入设备。', 'wizardHintHttps': '请通过 HTTPS 或 localhost 访问 DeepSeek Harness。', 'wizardHintProvider': '请在下方配置 STT 密钥凭据或一键安装 SenseVoice 本地模型。',
|
|
607
|
+
'wizardHintWinDesktop': '系统麦克风被阻止。请在 Windows【设置】→【隐私和安全性】→【麦克风】中开启最下方的【允许桌面应用访问你的麦克风】。', 'wizardHintWinBrowser': '麦克风权限被拒绝。请在浏览器地址栏允许麦克风权限,或在 Windows【设置】→【隐私和安全性】→【麦克风】中开启访问。', 'wizardHintMacDesktop': '系统麦克风被阻止。请在 macOS【系统设置】→【隐私与安全性】→【麦克风】中勾选允许 DSH Desktop。', 'wizardOpenWinMicSettings': '打开系统麦克风设置',
|
|
615
608
|
'wizardFinishSuccess': '设置与验证已完成', 'wizardSkip': '跳过向导',
|
|
616
609
|
'wizardStatusCompleted': '麦克风与 STT 诊断已就绪',
|
|
617
610
|
'exportDiagnosticsBtn': '导出诊断报告', 'exportingDiagnostics': '正在导出报告…',
|
|
@@ -1184,8 +1177,7 @@ window.__ModuleLoader__.load({
|
|
|
1184
1177
|
if (resolveStop) { resolveStop(); resolveStop = null }
|
|
1185
1178
|
options.onError(code || t('recognitionError'))
|
|
1186
1179
|
}
|
|
1187
|
-
//
|
|
1188
|
-
// until we stop it, otherwise dictation dies silently on the first pause.
|
|
1180
|
+
// Restart browser recognition until explicitly stopped
|
|
1189
1181
|
recognition.onend = () => {
|
|
1190
1182
|
if (stopped) {
|
|
1191
1183
|
if (resolveStop) { resolveStop(); resolveStop = null }
|
|
@@ -1219,8 +1211,7 @@ window.__ModuleLoader__.load({
|
|
|
1219
1211
|
// Fetch the mode chain from the host once. Only needed to decide whether
|
|
1220
1212
|
|
|
1221
1213
|
voice._core = { tidyPhrase, applyVoiceCommands, VOICE_COMMANDS, undoLastInsert, undoLastChunk: undoLastInsert, insertHistory, insertDraftText, appendDraft, resolveBrowserRecognitionLang }
|
|
1222
|
-
//
|
|
1223
|
-
// Detects WebGPU and WebAssembly availability in browser and provides offline transcription with graceful WASM fallback.
|
|
1214
|
+
// Client WebGPU/WASM Whisper offline recognition (#168)
|
|
1224
1215
|
|
|
1225
1216
|
function isWebGpuSupported() {
|
|
1226
1217
|
if (typeof navigator === 'undefined' || !navigator.gpu) return false
|
|
@@ -1326,8 +1317,7 @@ voice.webGpu = {
|
|
|
1326
1317
|
load: loadWebGpuWhisper,
|
|
1327
1318
|
clearCache: () => { _webGpuPipelines.clear() },
|
|
1328
1319
|
}
|
|
1329
|
-
//
|
|
1330
|
-
// Provides neural voice activity detection to filter out mechanical keyboard clicks, sighs and fan noise.
|
|
1320
|
+
// Neural Silero VAD client module (#279)
|
|
1331
1321
|
|
|
1332
1322
|
function isSileroVadSupported() {
|
|
1333
1323
|
return typeof Float32Array !== 'undefined'
|
|
@@ -1373,8 +1363,7 @@ voice.sileroVad = {
|
|
|
1373
1363
|
evaluate: evaluateSileroFrame,
|
|
1374
1364
|
reset: () => { _sileroState = { h: null, c: null } },
|
|
1375
1365
|
}
|
|
1376
|
-
//
|
|
1377
|
-
// Streams audio frames to local SenseVoice/Whisper servers with seamless HTTP fallback.
|
|
1366
|
+
// Streaming WebSocket client (#282)
|
|
1378
1367
|
|
|
1379
1368
|
function isWsStreamingSupported() {
|
|
1380
1369
|
return typeof WebSocket !== 'undefined'
|
|
@@ -2146,9 +2135,7 @@ voice._chainsPromise = null
|
|
|
2146
2135
|
return String(name).replace(/^Key/, '').replace(/^Digit/, '')
|
|
2147
2136
|
}
|
|
2148
2137
|
|
|
2149
|
-
//
|
|
2150
|
-
// left and right have different codes but the user means "either".
|
|
2151
|
-
// Supports combos like Control+Space or Alt+KeyV.
|
|
2138
|
+
// Hotkey combo normalization
|
|
2152
2139
|
function keyFromEvent(event) {
|
|
2153
2140
|
if (!event) return ''
|
|
2154
2141
|
const modifiers = []
|
|
@@ -2211,11 +2198,7 @@ voice._chainsPromise = null
|
|
|
2211
2198
|
return event.code === name || event.key === name
|
|
2212
2199
|
}
|
|
2213
2200
|
|
|
2214
|
-
//
|
|
2215
|
-
//
|
|
2216
|
-
// Playback should mute immediately: listening and talking at once is
|
|
2217
|
-
// impossible. There is no plugin-to-plugin API — this broadcasts a window
|
|
2218
|
-
// event that anyone may hear, so either side works alone.
|
|
2201
|
+
// Broadcast window event so active playback mutes when speaking.
|
|
2219
2202
|
|
|
2220
2203
|
// --------------------------------------------------------- hold gesture
|
|
2221
2204
|
const HOLD_THRESHOLD_MS = 350
|
|
@@ -2332,8 +2315,7 @@ voice._chainsPromise = null
|
|
|
2332
2315
|
}
|
|
2333
2316
|
return cleanup
|
|
2334
2317
|
}
|
|
2335
|
-
//
|
|
2336
|
-
// Editable preview state machine, session pinning & UI component (#170).
|
|
2318
|
+
// Editable preview state machine (#170)
|
|
2337
2319
|
|
|
2338
2320
|
function initPendingPreview(text, target, options = {}) {
|
|
2339
2321
|
const autoMs = Number(options.autoSendMs) || 4000
|
|
@@ -2787,16 +2769,13 @@ function cycleDictationMode() {
|
|
|
2787
2769
|
const adapt = Number(voice.settings.vadAdapt) || 0
|
|
2788
2770
|
let effectiveVad = Number(voice.settings.vadSilenceMs) || 700
|
|
2789
2771
|
if (adapt > 0 && rec.hadSpeech) {
|
|
2790
|
-
// Adaptive threshold (#41)
|
|
2791
|
-
// Dense speech -> lower threshold (cut more precisely);
|
|
2792
|
-
// pause-heavy -> threshold rises toward base (do not cut on breaths).
|
|
2772
|
+
// Adaptive threshold (#41)
|
|
2793
2773
|
const win = voice.levels.slice(-20)
|
|
2794
2774
|
const density = win.length ? win.filter((v) => v > 0.06).length / win.length : 0
|
|
2795
2775
|
const k = adapt * (density - 0.5) * 2
|
|
2796
2776
|
effectiveVad = Math.max(150, Math.round(Number(voice.settings.vadSilenceMs) * (1 - k)))
|
|
2797
2777
|
}
|
|
2798
|
-
// Continuous mode (#40)
|
|
2799
|
-
// without waiting for a long pause.
|
|
2778
|
+
// Continuous mode (#40)
|
|
2800
2779
|
const stream = !!voice.settings.stream && rec.mode === 'dictation'
|
|
2801
2780
|
if (stream && rec.hadSpeech && !rec.cutting) {
|
|
2802
2781
|
rec.streamMs += tick
|
|
@@ -2888,8 +2867,7 @@ function cycleDictationMode() {
|
|
|
2888
2867
|
const p = voice.pending
|
|
2889
2868
|
if (!p) return
|
|
2890
2869
|
if (p.undoOnly) {
|
|
2891
|
-
// Undo-only mode (#29-5)
|
|
2892
|
-
// panel; the text either stayed or was already undone.
|
|
2870
|
+
// Undo-only mode (#29-5)
|
|
2893
2871
|
p.leftMs -= tick
|
|
2894
2872
|
if (p.leftMs <= 0) voice.set({ phase: 'idle' })
|
|
2895
2873
|
else voice.notify()
|
|
@@ -2947,8 +2925,7 @@ function cycleDictationMode() {
|
|
|
2947
2925
|
const inBrowser = !!voice.browser
|
|
2948
2926
|
const rec = voice.rec
|
|
2949
2927
|
const hint = v.mode === 'dictation' ? t('dictationPill') : t('messagePill')
|
|
2950
|
-
// Live caption
|
|
2951
|
-
// final chunk the text is interim and changes on screen.
|
|
2928
|
+
// Live caption: interim preview
|
|
2952
2929
|
const caption = voice.caption || (inBrowser ? '' : null)
|
|
2953
2930
|
const activeSubMode = (voice._activeTarget && voice._activeTarget.dictationMode)
|
|
2954
2931
|
|| voice.selectedDictationMode
|
|
@@ -3112,8 +3089,7 @@ function cycleDictationMode() {
|
|
|
3112
3089
|
|
|
3113
3090
|
// --------------------------------------------------------------- slots
|
|
3114
3091
|
function registerComposer(ctx) {
|
|
3115
|
-
//
|
|
3116
|
-
// without restart: the settings card broadcasts and the composer reloads.
|
|
3092
|
+
// Hotkey listener with dynamic reload
|
|
3117
3093
|
ctx.effect(() => {
|
|
3118
3094
|
let dispose = () => {}
|
|
3119
3095
|
let alive = true
|
|
@@ -3176,9 +3152,7 @@ function cycleDictationMode() {
|
|
|
3176
3152
|
}
|
|
3177
3153
|
}, 'dsh-voice: hold hotkey')
|
|
3178
3154
|
|
|
3179
|
-
// locale: NS
|
|
3180
|
-
// and without it the label throws "cannot get property t without inject" while
|
|
3181
|
-
// the slot snapshot is built, breaking the whole client batch.
|
|
3155
|
+
// locale: NS required for core slot snapshot injection
|
|
3182
3156
|
ctx.effect(() => {
|
|
3183
3157
|
const offRight = ctx.slots.inject('conversation.input.right', () => ctx.slots.register(
|
|
3184
3158
|
{ name: 'conversation.input.right', id: '@goodandready/dsh-voice', order: 6, locale: NS, label: () => t('title') },
|
|
@@ -4458,11 +4432,7 @@ function MacrosSection(props) {
|
|
|
4458
4432
|
function VoiceSection(props) {
|
|
4459
4433
|
const t = (props && props.t) || moduleT
|
|
4460
4434
|
const ctx = (props && props.ctx) || {}
|
|
4461
|
-
//
|
|
4462
|
-
// shared document is not readable, every section gets "unavailable" and
|
|
4463
|
-
// writes are dropped. The server does not share that restriction, and
|
|
4464
|
-
// dsh-lanmode rebuilds the same mechanics on the same calls. Prefer its
|
|
4465
|
-
// service when installed, otherwise the kernel one (fine on loopback).
|
|
4435
|
+
// Settings adapter: prefer dsh-lanmode when remote, otherwise kernel.
|
|
4466
4436
|
const settingsService = (ctx.get && ctx.get('lanSettings')) || ctx.configForms || ctx.settingsScope
|
|
4467
4437
|
const scope = settingsService && typeof settingsService.get === 'function'
|
|
4468
4438
|
? settingsService.get(NS)
|
|
@@ -4778,10 +4748,7 @@ function MacrosSection(props) {
|
|
|
4778
4748
|
onOpenWizard: () => setShowWizard((v) => !v),
|
|
4779
4749
|
}),
|
|
4780
4750
|
showWizard ? h(FirstRunWizard, {
|
|
4781
|
-
t
|
|
4782
|
-
draft: draft,
|
|
4783
|
-
setTop: setTop,
|
|
4784
|
-
writable: writable,
|
|
4751
|
+
t, draft, setTop, writable,
|
|
4785
4752
|
onClose: () => setShowWizard(false),
|
|
4786
4753
|
}) : null,
|
|
4787
4754
|
h('div', { className: 'cb-section-card' },
|
|
@@ -4877,10 +4844,7 @@ function MacrosSection(props) {
|
|
|
4877
4844
|
}),
|
|
4878
4845
|
),
|
|
4879
4846
|
h(HardwareOptionsCard, {
|
|
4880
|
-
t
|
|
4881
|
-
draft: draft,
|
|
4882
|
-
setTop: setTop,
|
|
4883
|
-
writable: writable,
|
|
4847
|
+
t, draft, setTop, writable,
|
|
4884
4848
|
catching: catching,
|
|
4885
4849
|
setCatching: setCatching,
|
|
4886
4850
|
devices: devices,
|
|
@@ -4976,23 +4940,14 @@ function MacrosSection(props) {
|
|
|
4976
4940
|
),
|
|
4977
4941
|
// Dictation Modes & Prompts (#264)
|
|
4978
4942
|
h(DictationModesSection, {
|
|
4979
|
-
t
|
|
4980
|
-
draft: draft,
|
|
4981
|
-
setTop: setTop,
|
|
4982
|
-
writable: writable,
|
|
4943
|
+
t, draft, setTop, writable,
|
|
4983
4944
|
}),
|
|
4984
4945
|
// Voice Action Macros (#167)
|
|
4985
4946
|
h(MacrosSection, {
|
|
4986
|
-
t
|
|
4987
|
-
draft: draft,
|
|
4988
|
-
setTop: setTop,
|
|
4989
|
-
writable: writable,
|
|
4947
|
+
t, draft, setTop, writable,
|
|
4990
4948
|
}),
|
|
4991
4949
|
h(JargonEditor, {
|
|
4992
|
-
t
|
|
4993
|
-
draft: draft,
|
|
4994
|
-
setTop: setTop,
|
|
4995
|
-
writable: writable,
|
|
4950
|
+
t, draft, setTop, writable,
|
|
4996
4951
|
jargonInput: jargonInput,
|
|
4997
4952
|
setJargonInput: setJargonInput,
|
|
4998
4953
|
}),
|
|
@@ -5030,6 +4985,16 @@ function MacrosSection(props) {
|
|
|
5030
4985
|
const isSecure = typeof window !== 'undefined' ? (window.isSecureContext || window.location.protocol === 'https:' || window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') : true
|
|
5031
4986
|
const hasMediaDevices = typeof navigator !== 'undefined' && !!navigator.mediaDevices && typeof navigator.mediaDevices.getUserMedia === 'function'
|
|
5032
4987
|
const hasMediaRecorder = typeof MediaRecorder !== 'undefined'
|
|
4988
|
+
const isDesktop = typeof navigator !== 'undefined' && (
|
|
4989
|
+
/dsh-desktop|electron/i.test(navigator.userAgent || '') ||
|
|
4990
|
+
(typeof window !== 'undefined' && window.location.protocol === 'dsh-app:')
|
|
4991
|
+
)
|
|
4992
|
+
const isWindows = typeof navigator !== 'undefined' && (
|
|
4993
|
+
/win/i.test(navigator.platform || '') || /windows/i.test(navigator.userAgent || '')
|
|
4994
|
+
)
|
|
4995
|
+
const isMac = typeof navigator !== 'undefined' && (
|
|
4996
|
+
/mac/i.test(navigator.platform || '') || /macintosh|mac os x/i.test(navigator.userAgent || '')
|
|
4997
|
+
)
|
|
5033
4998
|
|
|
5034
4999
|
const [micPermission, setMicPermission] = React.useState('prompt')
|
|
5035
5000
|
const [devices, setDevices] = React.useState([])
|
|
@@ -5104,7 +5069,16 @@ function MacrosSection(props) {
|
|
|
5104
5069
|
const constraints = {
|
|
5105
5070
|
audio: selectedDev ? { deviceId: { exact: selectedDev } } : true,
|
|
5106
5071
|
}
|
|
5107
|
-
|
|
5072
|
+
let stream
|
|
5073
|
+
try {
|
|
5074
|
+
stream = await navigator.mediaDevices.getUserMedia(constraints)
|
|
5075
|
+
} catch (firstErr) {
|
|
5076
|
+
if (selectedDev) {
|
|
5077
|
+
stream = await navigator.mediaDevices.getUserMedia({ audio: true })
|
|
5078
|
+
} else {
|
|
5079
|
+
throw firstErr
|
|
5080
|
+
}
|
|
5081
|
+
}
|
|
5108
5082
|
signalStreamRef.current = stream
|
|
5109
5083
|
setMicPermission('granted')
|
|
5110
5084
|
refreshDevices()
|
|
@@ -5157,7 +5131,15 @@ function MacrosSection(props) {
|
|
|
5157
5131
|
const constraints = {
|
|
5158
5132
|
audio: selectedDev ? { deviceId: { exact: selectedDev } } : true,
|
|
5159
5133
|
}
|
|
5160
|
-
|
|
5134
|
+
try {
|
|
5135
|
+
testStream = await navigator.mediaDevices.getUserMedia(constraints)
|
|
5136
|
+
} catch (firstErr) {
|
|
5137
|
+
if (selectedDev) {
|
|
5138
|
+
testStream = await navigator.mediaDevices.getUserMedia({ audio: true })
|
|
5139
|
+
} else {
|
|
5140
|
+
throw firstErr
|
|
5141
|
+
}
|
|
5142
|
+
}
|
|
5161
5143
|
let mime = 'audio/webm;codecs=opus'
|
|
5162
5144
|
if (!MediaRecorder.isTypeSupported(mime)) mime = ''
|
|
5163
5145
|
recorder = mime ? new MediaRecorder(testStream, { mimeType: mime }) : new MediaRecorder(testStream)
|
|
@@ -5241,10 +5223,19 @@ function MacrosSection(props) {
|
|
|
5241
5223
|
}
|
|
5242
5224
|
|
|
5243
5225
|
let nextStepHint = ''
|
|
5226
|
+
const isPermDenied = micPermission === 'denied' || (testError && /permission|denied|notallowed/i.test(testError))
|
|
5244
5227
|
if (!isSecure) {
|
|
5245
5228
|
nextStepHint = t('wizardHintHttps')
|
|
5246
|
-
} else if (
|
|
5247
|
-
|
|
5229
|
+
} else if (isPermDenied) {
|
|
5230
|
+
if (isDesktop && isWindows) {
|
|
5231
|
+
nextStepHint = t('wizardHintWinDesktop')
|
|
5232
|
+
} else if (isDesktop && isMac) {
|
|
5233
|
+
nextStepHint = t('wizardHintMacDesktop')
|
|
5234
|
+
} else if (isWindows) {
|
|
5235
|
+
nextStepHint = t('wizardHintWinBrowser')
|
|
5236
|
+
} else {
|
|
5237
|
+
nextStepHint = t('wizardHintPermission')
|
|
5238
|
+
}
|
|
5248
5239
|
} else if (peakRms === 0 && signalTesting) {
|
|
5249
5240
|
nextStepHint = t('wizardHintDevice')
|
|
5250
5241
|
} else if (testError) {
|
|
@@ -5300,6 +5291,13 @@ function MacrosSection(props) {
|
|
|
5300
5291
|
className: 'cb-btn',
|
|
5301
5292
|
onClick: requestPermissionAndTestSignal,
|
|
5302
5293
|
}, t('wizardRequestMic')),
|
|
5294
|
+
(isWindows && isPermDenied) ? h('a', {
|
|
5295
|
+
href: 'ms-settings:privacy-microphone',
|
|
5296
|
+
target: '_blank',
|
|
5297
|
+
rel: 'noopener noreferrer',
|
|
5298
|
+
className: 'cb-btn cb-btn-mini',
|
|
5299
|
+
style: { marginLeft: '8px', textDecoration: 'none', display: 'inline-flex', alignItems: 'center' },
|
|
5300
|
+
}, '⚙ ' + t('wizardOpenWinMicSettings')) : null,
|
|
5303
5301
|
),
|
|
5304
5302
|
),
|
|
5305
5303
|
),
|
|
@@ -5383,8 +5381,15 @@ function MacrosSection(props) {
|
|
|
5383
5381
|
),
|
|
5384
5382
|
|
|
5385
5383
|
// Next Step Hint / Feedback
|
|
5386
|
-
nextStepHint ? h('div', { className: 'dvs-wait', style: { fontStyle: 'italic' } },
|
|
5387
|
-
t('wizardNextStepHint').replace('{hint}', nextStepHint)
|
|
5384
|
+
nextStepHint ? h('div', { className: 'dvs-wait', style: { fontStyle: 'italic', display: 'flex', alignItems: 'center', flexWrap: 'wrap', gap: '8px' } },
|
|
5385
|
+
h('span', null, t('wizardNextStepHint').replace('{hint}', nextStepHint)),
|
|
5386
|
+
(isWindows && isPermDenied) ? h('a', {
|
|
5387
|
+
href: 'ms-settings:privacy-microphone',
|
|
5388
|
+
target: '_blank',
|
|
5389
|
+
rel: 'noopener noreferrer',
|
|
5390
|
+
className: 'cb-btn cb-btn-mini',
|
|
5391
|
+
style: { textDecoration: 'none', fontStyle: 'normal' },
|
|
5392
|
+
}, '⚙ ' + t('wizardOpenWinMicSettings')) : null,
|
|
5388
5393
|
) : null,
|
|
5389
5394
|
|
|
5390
5395
|
// Wizard Bottom Actions
|
|
@@ -5484,9 +5489,7 @@ function MacrosSection(props) {
|
|
|
5484
5489
|
|
|
5485
5490
|
exports.inject = ['timer', 'slots', 'configForms', 'locale']
|
|
5486
5491
|
exports.apply = function apply(ctx) {
|
|
5487
|
-
// English
|
|
5488
|
-
// localized user dictionary. Other languages (like Russian) come from
|
|
5489
|
-
// the separate translation plugin at runtime.
|
|
5492
|
+
// English source/fallback, Chinese built-in dictionary.
|
|
5490
5493
|
const addLocale = (locale, dictionary) => {
|
|
5491
5494
|
try {
|
|
5492
5495
|
return ctx.locale.register(NS, locale, dictionary)
|
|
@@ -5518,6 +5521,7 @@ function MacrosSection(props) {
|
|
|
5518
5521
|
cycleDictationMode,
|
|
5519
5522
|
resolveBrowserRecognitionLang,
|
|
5520
5523
|
VoiceSection,
|
|
5524
|
+
FirstRunWizard,
|
|
5521
5525
|
cutPhrase,
|
|
5522
5526
|
sendAudio,
|
|
5523
5527
|
modeChain,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-voice",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.45",
|
|
4
4
|
"description": "Voice input for DeepSeek Harness: dictation chunked by pauses and voice messages, each with its own provider fallback chain (Deepgram, Groq, HuggingFace, local whisper.cpp, plus any OpenAI-compatible API of your own).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|