@goodandready/dsh-voice 0.9.7 → 0.9.9
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 +19 -15
- package/lib/polish.js +10 -0
- package/lib/updater.js +32 -24
- package/package.json +5 -5
package/lib/client.js
CHANGED
|
@@ -922,7 +922,6 @@ window.__ModuleLoader__.load({
|
|
|
922
922
|
}
|
|
923
923
|
})
|
|
924
924
|
}
|
|
925
|
-
|
|
926
925
|
// ------------------------------------------------------------ recording
|
|
927
926
|
function teardown(rec) {
|
|
928
927
|
if (!rec) return
|
|
@@ -932,7 +931,6 @@ window.__ModuleLoader__.load({
|
|
|
932
931
|
if (rec.analyser) { try { rec.analyser.disconnect() } catch (e) { /* already disconnected */ } }
|
|
933
932
|
if (rec.audioCtx) { try { rec.audioCtx.close() } catch (e) { /* already closed */ } }
|
|
934
933
|
}
|
|
935
|
-
|
|
936
934
|
if (typeof window !== 'undefined') {
|
|
937
935
|
const onPageUnload = () => {
|
|
938
936
|
if (voice.rec) teardown(voice.rec)
|
|
@@ -943,12 +941,10 @@ window.__ModuleLoader__.load({
|
|
|
943
941
|
window.addEventListener('pagehide', onPageUnload)
|
|
944
942
|
window.addEventListener('beforeunload', onPageUnload)
|
|
945
943
|
}
|
|
946
|
-
|
|
947
944
|
function waitStop(recorder) {
|
|
948
945
|
if (!recorder || recorder.state === 'inactive') return Promise.resolve()
|
|
949
946
|
return new Promise((resolve) => recorder.addEventListener('stop', resolve, { once: true }))
|
|
950
947
|
}
|
|
951
|
-
|
|
952
948
|
async function openMic(mode) {
|
|
953
949
|
if (typeof navigator === 'undefined' || !navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
|
|
954
950
|
throw new Error(t('micUnavailable'))
|
|
@@ -982,7 +978,6 @@ window.__ModuleLoader__.load({
|
|
|
982
978
|
if (rec.audioCtx.state === 'suspended') { rec.audioCtx.resume().catch(() => {}) }
|
|
983
979
|
const src = rec.audioCtx.createMediaStreamSource(stream)
|
|
984
980
|
rec.srcNode = src
|
|
985
|
-
|
|
986
981
|
let lastNode = src
|
|
987
982
|
try {
|
|
988
983
|
const filter = rec.audioCtx.createBiquadFilter()
|
|
@@ -993,7 +988,6 @@ window.__ModuleLoader__.load({
|
|
|
993
988
|
lastNode = filter
|
|
994
989
|
rec.filterNode = filter
|
|
995
990
|
} catch (e) { /* audio filter fallback */ }
|
|
996
|
-
|
|
997
991
|
rec.analyser = rec.audioCtx.createAnalyser()
|
|
998
992
|
rec.analyser.fftSize = 128
|
|
999
993
|
lastNode.connect(rec.analyser)
|
|
@@ -1002,7 +996,6 @@ window.__ModuleLoader__.load({
|
|
|
1002
996
|
recorder.start()
|
|
1003
997
|
return rec
|
|
1004
998
|
}
|
|
1005
|
-
|
|
1006
999
|
function currentLevel(rec) {
|
|
1007
1000
|
if (!rec || !rec.analyser) return 0
|
|
1008
1001
|
const data = new Uint8Array(rec.analyser.frequencyBinCount)
|
|
@@ -1010,7 +1003,6 @@ window.__ModuleLoader__.load({
|
|
|
1010
1003
|
let sum = 0
|
|
1011
1004
|
for (let i = 0; i < data.length; i++) sum += data[i]
|
|
1012
1005
|
const raw = Math.min(1, (sum / data.length / 255) * 2.2)
|
|
1013
|
-
|
|
1014
1006
|
const gateDb = Number(voice.settings && voice.settings.noiseGateDb !== undefined ? voice.settings.noiseGateDb : -45)
|
|
1015
1007
|
if (gateDb > -90) {
|
|
1016
1008
|
const gateAmp = Math.pow(10, gateDb / 20) * 2.2
|
|
@@ -1018,11 +1010,9 @@ window.__ModuleLoader__.load({
|
|
|
1018
1010
|
}
|
|
1019
1011
|
return raw
|
|
1020
1012
|
}
|
|
1021
|
-
|
|
1022
1013
|
// Cut the current phrase: stop the recorder, send the finished file and
|
|
1023
1014
|
// immediately start a new recording with the same recorder.
|
|
1024
1015
|
let dictationQueue = Promise.resolve()
|
|
1025
|
-
|
|
1026
1016
|
function cutPhrase() {
|
|
1027
1017
|
const rec = voice.rec
|
|
1028
1018
|
if (!rec || rec.cutting || rec.closing) return
|
|
@@ -1037,6 +1027,7 @@ window.__ModuleLoader__.load({
|
|
|
1037
1027
|
return
|
|
1038
1028
|
}
|
|
1039
1029
|
stopped.then(async () => {
|
|
1030
|
+
if (rec.cancelled || voice.activeOpId !== opId) return
|
|
1040
1031
|
const blob = new Blob(rec.chunks, { type: rec.mime })
|
|
1041
1032
|
rec.chunks = []
|
|
1042
1033
|
rec.silenceMs = 0
|
|
@@ -1107,6 +1098,7 @@ window.__ModuleLoader__.load({
|
|
|
1107
1098
|
// Browser recognition instead of file recording. Returns false when the
|
|
1108
1099
|
// browser cannot do it — then fall back to the normal path.
|
|
1109
1100
|
function startBrowserLeg(mode, language) {
|
|
1101
|
+
if (voice.settings && voice.settings.localOnly) return false
|
|
1110
1102
|
if (!browserRecognitionAvailable()) return false
|
|
1111
1103
|
const finals = []
|
|
1112
1104
|
voice.caption = ''
|
|
@@ -1163,6 +1155,7 @@ window.__ModuleLoader__.load({
|
|
|
1163
1155
|
|
|
1164
1156
|
function startRecording(mode) {
|
|
1165
1157
|
if (voice.phase !== 'idle' && voice.phase !== 'error') return
|
|
1158
|
+
voice.activeOpId = (voice.activeOpId || 0) + 1
|
|
1166
1159
|
if (voice.isTtsSpeaking) {
|
|
1167
1160
|
if (voice.settings.bargeIn) {
|
|
1168
1161
|
triggerBargeIn()
|
|
@@ -1326,6 +1319,7 @@ window.__ModuleLoader__.load({
|
|
|
1326
1319
|
}
|
|
1327
1320
|
|
|
1328
1321
|
function cancelCurrent() {
|
|
1322
|
+
voice.activeOpId = (voice.activeOpId || 0) + 1
|
|
1329
1323
|
announceVoice('end')
|
|
1330
1324
|
focusComposer()
|
|
1331
1325
|
if (voice.browser) {
|
|
@@ -1372,6 +1366,8 @@ window.__ModuleLoader__.load({
|
|
|
1372
1366
|
const rec = voice.rec
|
|
1373
1367
|
if (!rec || rec.closing) return
|
|
1374
1368
|
rec.closing = true
|
|
1369
|
+
voice.activeOpId = (voice.activeOpId || 0) + 1
|
|
1370
|
+
const opId = voice.activeOpId
|
|
1375
1371
|
const mode = rec.mode
|
|
1376
1372
|
const stopped = waitStop(rec.recorder)
|
|
1377
1373
|
try {
|
|
@@ -1391,6 +1387,7 @@ window.__ModuleLoader__.load({
|
|
|
1391
1387
|
voice.set({ phase: 'processing' })
|
|
1392
1388
|
try {
|
|
1393
1389
|
const out = await sendAudio(blob, rec.mime, mode)
|
|
1390
|
+
if (rec.cancelled || voice.activeOpId !== opId || voice.phase === 'idle') return
|
|
1394
1391
|
if (out.command) { runSessionCommand(out.command); return }
|
|
1395
1392
|
const text = out.text || ''
|
|
1396
1393
|
const action = out.action || null
|
|
@@ -1429,6 +1426,7 @@ window.__ModuleLoader__.load({
|
|
|
1429
1426
|
voice.set({ phase: 'idle' })
|
|
1430
1427
|
}
|
|
1431
1428
|
} catch (e) {
|
|
1429
|
+
if (rec.cancelled || voice.activeOpId !== opId || voice.phase === 'idle') return
|
|
1432
1430
|
voice.set({ phase: 'error', error: String(e && e.message ? e.message : e) })
|
|
1433
1431
|
}
|
|
1434
1432
|
})
|
|
@@ -1980,6 +1978,7 @@ window.__ModuleLoader__.load({
|
|
|
1980
1978
|
// The composer needs fresh settings without opening the card.
|
|
1981
1979
|
Object.assign(voice.settings, {
|
|
1982
1980
|
beep: !!(data && data.beep),
|
|
1981
|
+
localOnly: !!(data && data.localOnly),
|
|
1983
1982
|
micDeviceId: String((data && data.micDeviceId) || ''),
|
|
1984
1983
|
historyLimit: Number(data && data.historyLimit),
|
|
1985
1984
|
voiceCommands: !!(data && data.voiceCommands),
|
|
@@ -2673,7 +2672,8 @@ window.__ModuleLoader__.load({
|
|
|
2673
2672
|
const draft = props.draft
|
|
2674
2673
|
const setTop = props.setTop
|
|
2675
2674
|
const writable = props.writable
|
|
2676
|
-
const
|
|
2675
|
+
const jargonInput = props.jargonInput
|
|
2676
|
+
const setJargonInput = props.setJargonInput
|
|
2677
2677
|
|
|
2678
2678
|
return React.createElement('div', { className: 'cb-section-card' },
|
|
2679
2679
|
React.createElement('div', { className: 'cb-section-title' },
|
|
@@ -2694,14 +2694,14 @@ window.__ModuleLoader__.load({
|
|
|
2694
2694
|
rows: 3,
|
|
2695
2695
|
disabled: !writable,
|
|
2696
2696
|
placeholder: t('jargonPlaceholder'),
|
|
2697
|
-
value: jargonInput !== null
|
|
2697
|
+
value: jargonInput !== null && jargonInput !== undefined
|
|
2698
2698
|
? jargonInput
|
|
2699
2699
|
: (Array.isArray(draft && draft.jargonDictionary)
|
|
2700
2700
|
? draft.jargonDictionary.map((item) => item && item.from ? `${item.from} -> ${item.to}` : '').filter(Boolean).join('\n')
|
|
2701
2701
|
: ''),
|
|
2702
|
-
onChange: (e) => setJargonInput(e.target.value),
|
|
2702
|
+
onChange: (e) => setJargonInput && setJargonInput(e.target.value),
|
|
2703
2703
|
onBlur: () => {
|
|
2704
|
-
if (jargonInput === null) return
|
|
2704
|
+
if (jargonInput === null || jargonInput === undefined) return
|
|
2705
2705
|
const lines = jargonInput.split('\n')
|
|
2706
2706
|
const parsed = lines
|
|
2707
2707
|
.map((line) => {
|
|
@@ -2883,6 +2883,7 @@ window.__ModuleLoader__.load({
|
|
|
2883
2883
|
|
|
2884
2884
|
// Hotkey picker: not a name field — press the key you want.
|
|
2885
2885
|
const [catching, setCatching] = React.useState(false)
|
|
2886
|
+
const [jargonInput, setJargonInput] = React.useState(null)
|
|
2886
2887
|
React.useEffect(() => {
|
|
2887
2888
|
if (!catching) return undefined
|
|
2888
2889
|
const onKey = (event) => {
|
|
@@ -2956,6 +2957,7 @@ window.__ModuleLoader__.load({
|
|
|
2956
2957
|
vadSilenceMs: Number(val.dictation && val.dictation.vadSilenceMs) || 700,
|
|
2957
2958
|
autoSendMs: Number(val.message && val.message.autoSendMs) || 4000,
|
|
2958
2959
|
beep: !!val.beep,
|
|
2960
|
+
localOnly: !!val.localOnly,
|
|
2959
2961
|
micDeviceId: String(val.micDeviceId || ''),
|
|
2960
2962
|
historyLimit: Number(val.historyLimit) || 20,
|
|
2961
2963
|
voiceCommands: !!val.voiceCommands,
|
|
@@ -3099,7 +3101,7 @@ window.__ModuleLoader__.load({
|
|
|
3099
3101
|
})
|
|
3100
3102
|
try { window.dispatchEvent(new CustomEvent('dsh-voice:settings-saved')) } catch (noEvents) { /* nobody */ }
|
|
3101
3103
|
|
|
3102
|
-
setSaved(true)
|
|
3104
|
+
setSaved(true); setJargonInput(null)
|
|
3103
3105
|
setTimeout(() => setSaved(false), 2000)
|
|
3104
3106
|
fetchStatus()
|
|
3105
3107
|
}
|
|
@@ -3356,6 +3358,8 @@ window.__ModuleLoader__.load({
|
|
|
3356
3358
|
draft: draft,
|
|
3357
3359
|
setTop: setTop,
|
|
3358
3360
|
writable: writable,
|
|
3361
|
+
jargonInput: jargonInput,
|
|
3362
|
+
setJargonInput: setJargonInput,
|
|
3359
3363
|
}),
|
|
3360
3364
|
|
|
3361
3365
|
// 10. Provider Telemetry & Latency Dashboard
|
package/lib/polish.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isLoopback } from './http-util.js'
|
|
1
2
|
// Transcript polish. Pure factory: inject fetch, key resolver, LLM, config.
|
|
2
3
|
// On any failure return the original text — polish must never block input.
|
|
3
4
|
|
|
@@ -14,6 +15,15 @@ export function createPolishText({ resolveKey, fetchImpl, getConfig, llm, getAge
|
|
|
14
15
|
const enable = modeCfg && (modeCfg.polish === true || modeCfg.polishSend === true)
|
|
15
16
|
if (!enable || !text) return text
|
|
16
17
|
const cfg = getConfig() || {}
|
|
18
|
+
if (cfg.localOnly) {
|
|
19
|
+
if (!cfg.polishBaseUrl) return text
|
|
20
|
+
try {
|
|
21
|
+
const u = new URL(cfg.polishBaseUrl)
|
|
22
|
+
if (!isLoopback(u.hostname)) return text
|
|
23
|
+
} catch {
|
|
24
|
+
return text
|
|
25
|
+
}
|
|
26
|
+
}
|
|
17
27
|
const ask =
|
|
18
28
|
'Fix the punctuation and spelling of this dictated text and split it into '
|
|
19
29
|
+ 'paragraphs where the speaker changes topic. Remove filler words ("um", "uh", '
|
package/lib/updater.js
CHANGED
|
@@ -198,23 +198,25 @@ export function checkProfileLock(profileDir) {
|
|
|
198
198
|
const lockPath = resolve(profileDir, 'package.json.lock');
|
|
199
199
|
if (!existsSync(lockPath)) return { locked: false };
|
|
200
200
|
const pid = readLockPid(lockPath);
|
|
201
|
-
if (pid
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
201
|
+
if (pid !== null) {
|
|
202
|
+
if (isProcessAlive(pid)) {
|
|
203
|
+
return { locked: true, pid };
|
|
204
|
+
}
|
|
205
|
+
try {
|
|
206
|
+
unlinkSync(lockPath);
|
|
207
|
+
return { locked: false, cleanedStale: true };
|
|
208
|
+
} catch {
|
|
209
|
+
return { locked: false };
|
|
210
|
+
}
|
|
210
211
|
}
|
|
212
|
+
return { locked: true, pid: null };
|
|
211
213
|
}
|
|
212
214
|
|
|
213
215
|
async function installExact(target, packageSpec, options) {
|
|
214
216
|
if (target.cliEntry === undefined) throw new Error('Automatic update is unavailable in this runtime.');
|
|
215
217
|
const lockCheck = checkProfileLock(target.profileDir);
|
|
216
218
|
if (lockCheck.locked) {
|
|
217
|
-
throw new Error(`Another package installation is currently in progress (PID ${lockCheck.pid}).`);
|
|
219
|
+
throw new Error(`Another package installation is currently in progress${lockCheck.pid ? ` (PID ${lockCheck.pid})` : ''}.`);
|
|
218
220
|
}
|
|
219
221
|
await new Promise((resolvePromise, reject) => {
|
|
220
222
|
const child = spawn(process.execPath, [
|
|
@@ -228,28 +230,34 @@ async function installExact(target, packageSpec, options) {
|
|
|
228
230
|
env: { ...process.env, NO_COLOR: '1' },
|
|
229
231
|
});
|
|
230
232
|
let detail = '';
|
|
233
|
+
let timedOut = false;
|
|
234
|
+
let killTimer = null;
|
|
231
235
|
child.stdout?.on('data', chunk => { detail = (detail + String(chunk)).slice(-4_000); });
|
|
232
236
|
child.stderr?.on('data', chunk => { detail = (detail + String(chunk)).slice(-4_000); });
|
|
233
|
-
const
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
checkProfileLock(target.profileDir);
|
|
240
|
-
}, 1000).unref?.();
|
|
241
|
-
reject(new Error('Update timed out; use the normal DSH update flow.'));
|
|
237
|
+
const timeoutTimer = setTimeout(() => {
|
|
238
|
+
timedOut = true;
|
|
239
|
+
try { child.kill('SIGTERM'); } catch { /* ignore */ }
|
|
240
|
+
killTimer = setTimeout(() => {
|
|
241
|
+
try { child.kill('SIGKILL'); } catch { /* ignore */ }
|
|
242
|
+
}, 1000);
|
|
242
243
|
}, UPDATE_TIMEOUT_MS);
|
|
243
244
|
child.once('error', error => {
|
|
244
|
-
clearTimeout(
|
|
245
|
+
clearTimeout(timeoutTimer);
|
|
246
|
+
if (killTimer) clearTimeout(killTimer);
|
|
245
247
|
checkProfileLock(target.profileDir);
|
|
246
248
|
reject(error);
|
|
247
249
|
});
|
|
248
250
|
child.once('exit', code => {
|
|
249
|
-
clearTimeout(
|
|
251
|
+
clearTimeout(timeoutTimer);
|
|
252
|
+
if (killTimer) clearTimeout(killTimer);
|
|
250
253
|
checkProfileLock(target.profileDir);
|
|
251
|
-
if (
|
|
252
|
-
|
|
254
|
+
if (timedOut) {
|
|
255
|
+
reject(new Error('Update timed out; use the normal DSH update flow.'));
|
|
256
|
+
} else if (code === 0) {
|
|
257
|
+
resolvePromise();
|
|
258
|
+
} else {
|
|
259
|
+
reject(new Error(detail.trim() || `Update exited with code ${String(code)}.`));
|
|
260
|
+
}
|
|
253
261
|
});
|
|
254
262
|
});
|
|
255
263
|
}
|
|
@@ -293,7 +301,7 @@ export function registerPluginUpdater(ctx, options) {
|
|
|
293
301
|
if (installing || lockStatus.locked) {
|
|
294
302
|
json(response, 409, {
|
|
295
303
|
error: lockStatus.locked
|
|
296
|
-
? `Another package installation is currently in progress (PID ${lockStatus.pid}).`
|
|
304
|
+
? `Another package installation is currently in progress${lockStatus.pid ? ` (PID ${lockStatus.pid})` : ''}.`
|
|
297
305
|
: 'This plugin is already updating.',
|
|
298
306
|
});
|
|
299
307
|
return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-voice",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.9",
|
|
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",
|
|
@@ -63,10 +63,10 @@
|
|
|
63
63
|
},
|
|
64
64
|
"peerDependencies": {
|
|
65
65
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
66
|
-
"@deepseek-ai/dsh-tools": "^0.1.0-rc.
|
|
67
|
-
"@deepseek-ai/dsh-credentials": "^0.1.0-rc.
|
|
68
|
-
"@deepseek-ai/dsh-host-webserver": "^0.1.0-rc.
|
|
69
|
-
"@deepseek-ai/dsh-llm": "^0.1.0-rc.
|
|
66
|
+
"@deepseek-ai/dsh-tools": "^0.1.7-rc.2 || ^0.2.0-rc.1",
|
|
67
|
+
"@deepseek-ai/dsh-credentials": "^0.1.7-rc.2 || ^0.2.0-rc.1",
|
|
68
|
+
"@deepseek-ai/dsh-host-webserver": "^0.1.7-rc.2 || ^0.2.0-rc.1",
|
|
69
|
+
"@deepseek-ai/dsh-llm": "^0.1.7-rc.2 || ^0.2.0-rc.1",
|
|
70
70
|
"@deepseek-ai/schemastery": "^3.18.1"
|
|
71
71
|
}
|
|
72
72
|
}
|