@goodandready/dsh-voice 0.9.1 → 0.9.3
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 +145 -28
- package/lib/index.js +7 -7
- package/lib/normalize.js +7 -7
- package/lib/sensevoice-installer.js +3 -1
- package/package.json +2 -1
package/lib/client.js
CHANGED
|
@@ -547,15 +547,15 @@ window.__ModuleLoader__.load({
|
|
|
547
547
|
const trimmed = String(text || '').trim().toLowerCase()
|
|
548
548
|
const { buttons, input } = promptInfo
|
|
549
549
|
if (buttons && buttons.length > 0) {
|
|
550
|
-
if (/^(1|первый|first)$/i.test(trimmed) && buttons[0]) { buttons[0].click(); return true }
|
|
551
|
-
if (/^(2|второй|second)$/i.test(trimmed) && buttons[1]) { buttons[1].click(); return true }
|
|
552
|
-
if (/^(3|третий|third)$/i.test(trimmed) && buttons[2]) { buttons[2].click(); return true }
|
|
553
|
-
if (/^(да|yes|ok|confirm
|
|
554
|
-
const yesBtn = buttons.find((b) => /да|yes|ok|confirm
|
|
550
|
+
if (/^(1|первый|first|第一)$/i.test(trimmed) && buttons[0]) { buttons[0].click(); return true }
|
|
551
|
+
if (/^(2|второй|second|第二)$/i.test(trimmed) && buttons[1]) { buttons[1].click(); return true }
|
|
552
|
+
if (/^(3|третий|third|第三)$/i.test(trimmed) && buttons[2]) { buttons[2].click(); return true }
|
|
553
|
+
if (/^(да|yes|ok|confirm|согласен|是|好|确认|同意)$/i.test(trimmed)) {
|
|
554
|
+
const yesBtn = buttons.find((b) => /да|yes|ok|confirm|是|好|确认|同意/i.test(b.textContent || ''))
|
|
555
555
|
if (yesBtn) { yesBtn.click(); return true }
|
|
556
556
|
}
|
|
557
|
-
if (/^(нет|no|отмена|cancel)$/i.test(trimmed)) {
|
|
558
|
-
const noBtn = buttons.find((b) => /нет|no|cancel
|
|
557
|
+
if (/^(нет|no|отмена|cancel|不|否|取消)$/i.test(trimmed)) {
|
|
558
|
+
const noBtn = buttons.find((b) => /нет|no|cancel|отмена|不|否|取消/i.test(b.textContent || ''))
|
|
559
559
|
if (noBtn) { noBtn.click(); return true }
|
|
560
560
|
}
|
|
561
561
|
}
|
|
@@ -697,8 +697,8 @@ window.__ModuleLoader__.load({
|
|
|
697
697
|
if (!res.ok || !parsed || !parsed.ok) {
|
|
698
698
|
throw new Error((parsed && parsed.error && parsed.error.message) || `HTTP ${res.status}`)
|
|
699
699
|
}
|
|
700
|
-
if (parsed.command) return { command: parsed.command, text: '' }
|
|
701
|
-
return { text: String(parsed.text || '').trim(), command: null }
|
|
700
|
+
if (parsed.command) return { command: parsed.command, text: '', action: null }
|
|
701
|
+
return { text: String(parsed.text || '').trim(), command: null, action: parsed.action || null }
|
|
702
702
|
}
|
|
703
703
|
|
|
704
704
|
function tidyPhrase(text) {
|
|
@@ -773,6 +773,9 @@ window.__ModuleLoader__.load({
|
|
|
773
773
|
|
|
774
774
|
function keyLabel(name) {
|
|
775
775
|
if (!name) return t('keyUnset')
|
|
776
|
+
if (typeof name === 'string' && name.includes('+')) {
|
|
777
|
+
return name.split('+').map((p) => keyLabel(p)).join(' + ')
|
|
778
|
+
}
|
|
776
779
|
if (name === 'Space') return t('keySpace')
|
|
777
780
|
if (KEY_LABELS[name]) return KEY_LABELS[name]
|
|
778
781
|
// Drop the Key/Digit prefix from codes like KeyR and Digit5.
|
|
@@ -781,10 +784,23 @@ window.__ModuleLoader__.load({
|
|
|
781
784
|
|
|
782
785
|
// What to store on key press. Pure modifiers are remembered by name:
|
|
783
786
|
// left and right have different codes but the user means "either".
|
|
787
|
+
// Supports combos like Control+Space or Alt+KeyV.
|
|
784
788
|
function keyFromEvent(event) {
|
|
785
|
-
if (
|
|
786
|
-
|
|
787
|
-
|
|
789
|
+
if (!event) return ''
|
|
790
|
+
const modifiers = []
|
|
791
|
+
if (event.ctrlKey && event.key !== 'Control') modifiers.push('Control')
|
|
792
|
+
if (event.altKey && event.key !== 'Alt') modifiers.push('Alt')
|
|
793
|
+
if (event.shiftKey && event.key !== 'Shift') modifiers.push('Shift')
|
|
794
|
+
if (event.metaKey && event.key !== 'Meta') modifiers.push('Meta')
|
|
795
|
+
|
|
796
|
+
const main = ['Control', 'Alt', 'Shift', 'Meta'].includes(event.key)
|
|
797
|
+
? event.key
|
|
798
|
+
: (event.code || event.key || '')
|
|
799
|
+
|
|
800
|
+
if (modifiers.length > 0 && !['Control', 'Alt', 'Shift', 'Meta'].includes(main)) {
|
|
801
|
+
return modifiers.concat(main).join('+')
|
|
802
|
+
}
|
|
803
|
+
return main
|
|
788
804
|
}
|
|
789
805
|
|
|
790
806
|
// Announce that the user started speaking.
|
|
@@ -893,7 +909,10 @@ window.__ModuleLoader__.load({
|
|
|
893
909
|
chainsPromise = fetch('/dsh-voice/status', { cache: 'no-store' })
|
|
894
910
|
.then((res) => res.json())
|
|
895
911
|
.then((data) => (data && data.modes) || {})
|
|
896
|
-
.catch(() =>
|
|
912
|
+
.catch(() => {
|
|
913
|
+
chainsPromise = null
|
|
914
|
+
return {}
|
|
915
|
+
})
|
|
897
916
|
}
|
|
898
917
|
return chainsPromise.then((modes) => {
|
|
899
918
|
const row = modes[mode] || {}
|
|
@@ -1212,18 +1231,56 @@ window.__ModuleLoader__.load({
|
|
|
1212
1231
|
// Hotkey: holding a key is easier than aiming the mouse. While the key is
|
|
1213
1232
|
// down we record; Escape cancels.
|
|
1214
1233
|
function hotkeyMatches(event, name) {
|
|
1234
|
+
if (!event || !name) return false
|
|
1235
|
+
if (typeof name === 'string' && name.includes('+')) {
|
|
1236
|
+
const parts = name.split('+')
|
|
1237
|
+
const main = parts[parts.length - 1]
|
|
1238
|
+
const needCtrl = parts.includes('Control')
|
|
1239
|
+
const needAlt = parts.includes('Alt')
|
|
1240
|
+
const needShift = parts.includes('Shift')
|
|
1241
|
+
const needMeta = parts.includes('Meta')
|
|
1242
|
+
if (needCtrl && !event.ctrlKey) return false
|
|
1243
|
+
if (needAlt && !event.altKey) return false
|
|
1244
|
+
if (needShift && !event.shiftKey) return false
|
|
1245
|
+
if (needMeta && !event.metaKey) return false
|
|
1246
|
+
return event.code === main || event.key === main
|
|
1247
|
+
}
|
|
1215
1248
|
if (name === 'Control') return event.key === 'Control'
|
|
1216
1249
|
if (name === 'Alt') return event.key === 'Alt'
|
|
1217
1250
|
if (name === 'Shift') return event.key === 'Shift'
|
|
1251
|
+
if (name === 'Meta') return event.key === 'Meta'
|
|
1218
1252
|
return event.code === name || event.key === name
|
|
1219
1253
|
}
|
|
1220
1254
|
|
|
1255
|
+
function isAllowedInEditable(name) {
|
|
1256
|
+
if (!name) return false
|
|
1257
|
+
if (['Control', 'Alt', 'Shift', 'Meta'].includes(name)) return true
|
|
1258
|
+
if (/^F\d{1,2}$/.test(name)) return true
|
|
1259
|
+
if (typeof name === 'string' && name.includes('+')) return true
|
|
1260
|
+
if (['Tab', 'Pause', 'ScrollLock', 'Insert'].includes(name)) return true
|
|
1261
|
+
return false
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
let activeHotkeyCleanup = null
|
|
1265
|
+
|
|
1266
|
+
function clearGlobalHotkey() {
|
|
1267
|
+
if (typeof activeHotkeyCleanup === 'function') {
|
|
1268
|
+
try { activeHotkeyCleanup() } catch (_) { /* ignore */ }
|
|
1269
|
+
activeHotkeyCleanup = null
|
|
1270
|
+
}
|
|
1271
|
+
if (typeof window !== 'undefined' && typeof window.__dsh_voice_hotkey_cleanup === 'function') {
|
|
1272
|
+
try { window.__dsh_voice_hotkey_cleanup() } catch (_) { /* ignore */ }
|
|
1273
|
+
window.__dsh_voice_hotkey_cleanup = null
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1221
1277
|
function installHotkey(ctx, keyName, mode) {
|
|
1278
|
+
clearGlobalHotkey()
|
|
1222
1279
|
if (typeof document === 'undefined' || !keyName) return () => {}
|
|
1223
1280
|
const down = (event) => {
|
|
1224
1281
|
if (event.repeat) return
|
|
1225
|
-
// A modifier
|
|
1226
|
-
// type. A plain letter key must not be hijacked while typing.
|
|
1282
|
+
// A modifier, function key or combo does not fight the text field:
|
|
1283
|
+
// modifiers/combos do not type plain text. A plain letter key must not be hijacked while typing.
|
|
1227
1284
|
const target = event.target
|
|
1228
1285
|
const isEditable = target && (
|
|
1229
1286
|
target.tagName === 'INPUT' ||
|
|
@@ -1231,23 +1288,41 @@ window.__ModuleLoader__.load({
|
|
|
1231
1288
|
target.isContentEditable ||
|
|
1232
1289
|
(typeof target.getAttribute === 'function' && target.getAttribute('contenteditable') === 'true')
|
|
1233
1290
|
)
|
|
1234
|
-
if (isEditable && !
|
|
1291
|
+
if (isEditable && !isAllowedInEditable(keyName)) return
|
|
1235
1292
|
|
|
1236
|
-
if (hotkeyMatches(event, keyName))
|
|
1293
|
+
if (hotkeyMatches(event, keyName)) {
|
|
1294
|
+
event.preventDefault()
|
|
1295
|
+
event.stopPropagation()
|
|
1296
|
+
beginHold(mode)
|
|
1297
|
+
}
|
|
1237
1298
|
}
|
|
1238
1299
|
const up = (event) => {
|
|
1239
|
-
if (hotkeyMatches(event, keyName))
|
|
1240
|
-
|
|
1300
|
+
if (hotkeyMatches(event, keyName)) {
|
|
1301
|
+
event.preventDefault()
|
|
1302
|
+
endHold(false)
|
|
1303
|
+
} else if (event.key === 'Escape' && hold.armed) {
|
|
1304
|
+
event.preventDefault()
|
|
1305
|
+
endHold(true)
|
|
1306
|
+
}
|
|
1241
1307
|
}
|
|
1242
1308
|
const blur = () => { if (hold.armed) endHold(true) }
|
|
1243
1309
|
document.addEventListener('keydown', down, true)
|
|
1244
1310
|
document.addEventListener('keyup', up, true)
|
|
1245
1311
|
window.addEventListener('blur', blur)
|
|
1246
|
-
|
|
1312
|
+
const cleanup = () => {
|
|
1247
1313
|
document.removeEventListener('keydown', down, true)
|
|
1248
1314
|
document.removeEventListener('keyup', up, true)
|
|
1249
1315
|
window.removeEventListener('blur', blur)
|
|
1316
|
+
if (activeHotkeyCleanup === cleanup) activeHotkeyCleanup = null
|
|
1317
|
+
if (typeof window !== 'undefined' && window.__dsh_voice_hotkey_cleanup === cleanup) {
|
|
1318
|
+
window.__dsh_voice_hotkey_cleanup = null
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
activeHotkeyCleanup = cleanup
|
|
1322
|
+
if (typeof window !== 'undefined') {
|
|
1323
|
+
window.__dsh_voice_hotkey_cleanup = cleanup
|
|
1250
1324
|
}
|
|
1325
|
+
return cleanup
|
|
1251
1326
|
}
|
|
1252
1327
|
|
|
1253
1328
|
function cancelCurrent() {
|
|
@@ -1938,6 +2013,7 @@ window.__ModuleLoader__.load({
|
|
|
1938
2013
|
alive = false
|
|
1939
2014
|
window.removeEventListener('dsh-voice:settings-saved', reload)
|
|
1940
2015
|
dispose()
|
|
2016
|
+
clearGlobalHotkey()
|
|
1941
2017
|
}
|
|
1942
2018
|
}, 'dsh-voice: hold hotkey')
|
|
1943
2019
|
|
|
@@ -2187,6 +2263,7 @@ window.__ModuleLoader__.load({
|
|
|
2187
2263
|
const [err, setErr] = React.useState('')
|
|
2188
2264
|
const [serverConfig, setServerConfig] = React.useState(null)
|
|
2189
2265
|
const [configLoading, setConfigLoading] = React.useState(true)
|
|
2266
|
+
const [jargonInput, setJargonInput] = React.useState(null)
|
|
2190
2267
|
|
|
2191
2268
|
React.useEffect(() => {
|
|
2192
2269
|
let alive = true
|
|
@@ -2215,11 +2292,17 @@ window.__ModuleLoader__.load({
|
|
|
2215
2292
|
if (event.key === 'Escape') { setCatching(false); return }
|
|
2216
2293
|
const chosen = keyFromEvent(event)
|
|
2217
2294
|
setDraft((d) => Object.assign({}, d || {}, { hotkey: chosen }))
|
|
2295
|
+
voice.hotkey = chosen || ''
|
|
2296
|
+
if (chosen) {
|
|
2297
|
+
installHotkey(ctx, chosen, 'message')
|
|
2298
|
+
} else {
|
|
2299
|
+
clearGlobalHotkey()
|
|
2300
|
+
}
|
|
2218
2301
|
setCatching(false)
|
|
2219
2302
|
}
|
|
2220
2303
|
document.addEventListener('keydown', onKey, true)
|
|
2221
2304
|
return () => document.removeEventListener('keydown', onKey, true)
|
|
2222
|
-
}, [catching])
|
|
2305
|
+
}, [catching, ctx])
|
|
2223
2306
|
|
|
2224
2307
|
React.useEffect(() => {
|
|
2225
2308
|
let alive = true
|
|
@@ -2505,6 +2588,21 @@ window.__ModuleLoader__.load({
|
|
|
2505
2588
|
const save = async () => {
|
|
2506
2589
|
setErr(''); setSaved(false)
|
|
2507
2590
|
if (!draft) return
|
|
2591
|
+
if (jargonInput !== null) {
|
|
2592
|
+
const lines = jargonInput.split('\n')
|
|
2593
|
+
const parsed = lines
|
|
2594
|
+
.map((line) => {
|
|
2595
|
+
const parts = line.split(/->|=|:/)
|
|
2596
|
+
if (parts.length >= 2) {
|
|
2597
|
+
const from = parts[0].trim()
|
|
2598
|
+
const to = parts.slice(1).join(':').trim()
|
|
2599
|
+
if (from && to) return { from, to }
|
|
2600
|
+
}
|
|
2601
|
+
return null
|
|
2602
|
+
})
|
|
2603
|
+
.filter(Boolean)
|
|
2604
|
+
draft.jargonDictionary = parsed
|
|
2605
|
+
}
|
|
2508
2606
|
|
|
2509
2607
|
let httpOk = false
|
|
2510
2608
|
let httpError = null
|
|
@@ -2543,7 +2641,14 @@ window.__ModuleLoader__.load({
|
|
|
2543
2641
|
}
|
|
2544
2642
|
|
|
2545
2643
|
const d = draft || {}
|
|
2644
|
+
voice.hotkey = d.hotkey || ''
|
|
2645
|
+
if (voice.hotkey) {
|
|
2646
|
+
installHotkey(ctx, voice.hotkey, 'message')
|
|
2647
|
+
} else {
|
|
2648
|
+
clearGlobalHotkey()
|
|
2649
|
+
}
|
|
2546
2650
|
Object.assign(voice.settings, {
|
|
2651
|
+
hotkey: d.hotkey || '',
|
|
2547
2652
|
vadSilenceMs: Number(d.dictation && d.dictation.vadSilenceMs) || 700,
|
|
2548
2653
|
autoSendMs: Number(d.message && d.message.autoSendMs) || 4000,
|
|
2549
2654
|
beep: !!d.beep,
|
|
@@ -2592,7 +2697,11 @@ window.__ModuleLoader__.load({
|
|
|
2592
2697
|
}, catching ? t('pressKey') : keyLabel(draft && draft.hotkey)),
|
|
2593
2698
|
React.createElement('button', {
|
|
2594
2699
|
type: 'button', className: 'cb-btn cb-btn-mini', title: t('clearKey'),
|
|
2595
|
-
disabled: !writable, onClick: () =>
|
|
2700
|
+
disabled: !writable, onClick: () => {
|
|
2701
|
+
setDraft((d) => Object.assign({}, d || {}, { hotkey: '' }))
|
|
2702
|
+
voice.hotkey = ''
|
|
2703
|
+
clearGlobalHotkey()
|
|
2704
|
+
},
|
|
2596
2705
|
}, '×'),
|
|
2597
2706
|
),
|
|
2598
2707
|
React.createElement('span', { className: 'dvs-sub' }, t('hotkeyHint1') + t('hotkeyHint2')),
|
|
@@ -3028,16 +3137,24 @@ window.__ModuleLoader__.load({
|
|
|
3028
3137
|
rows: 3,
|
|
3029
3138
|
disabled: !writable,
|
|
3030
3139
|
placeholder: t('jargonPlaceholder'),
|
|
3031
|
-
value:
|
|
3032
|
-
?
|
|
3033
|
-
:
|
|
3140
|
+
value: jargonInput !== null
|
|
3141
|
+
? jargonInput
|
|
3142
|
+
: (Array.isArray(draft && draft.jargonDictionary)
|
|
3143
|
+
? draft.jargonDictionary.map((item) => item && item.from ? `${item.from} -> ${item.to}` : '').filter(Boolean).join('\n')
|
|
3144
|
+
: ''),
|
|
3034
3145
|
onChange: (e) => {
|
|
3035
|
-
|
|
3146
|
+
setJargonInput(e.target.value)
|
|
3147
|
+
},
|
|
3148
|
+
onBlur: () => {
|
|
3149
|
+
if (jargonInput === null) return
|
|
3150
|
+
const lines = jargonInput.split('\n')
|
|
3036
3151
|
const parsed = lines
|
|
3037
3152
|
.map((line) => {
|
|
3038
3153
|
const parts = line.split(/->|=|:/)
|
|
3039
3154
|
if (parts.length >= 2) {
|
|
3040
|
-
|
|
3155
|
+
const from = parts[0].trim()
|
|
3156
|
+
const to = parts.slice(1).join(':').trim()
|
|
3157
|
+
if (from && to) return { from, to }
|
|
3041
3158
|
}
|
|
3042
3159
|
return null
|
|
3043
3160
|
})
|
|
@@ -3110,7 +3227,7 @@ window.__ModuleLoader__.load({
|
|
|
3110
3227
|
React.createElement('div', { className: 'cb-section-desc' }, t('versionHint')),
|
|
3111
3228
|
React.createElement('div', { className: 'cb-row', style: { alignItems: 'center', gap: '12px' } },
|
|
3112
3229
|
React.createElement('span', { className: 'cb-badge cb-badge-ok' },
|
|
3113
|
-
'v' + ((updaterStatus && updaterStatus.currentVersion) || '0.9.
|
|
3230
|
+
'v' + ((updaterStatus && updaterStatus.currentVersion) || '0.9.3')
|
|
3114
3231
|
),
|
|
3115
3232
|
updaterStatus && updaterStatus.updateAvailable
|
|
3116
3233
|
? React.createElement('span', { className: 'cb-badge cb-badge-warn' },
|
package/lib/index.js
CHANGED
|
@@ -27,14 +27,11 @@ import { createPolishText } from './polish.js'
|
|
|
27
27
|
import { createUserMessage } from '@deepseek-ai/dsh-llm'
|
|
28
28
|
import { buildProviderOrder, sessionCommand } from './transcribe-core.js'
|
|
29
29
|
import { registerPluginUpdater } from './updater.js'
|
|
30
|
-
import { registerSensevoiceInstaller } from './sensevoice-installer.js'
|
|
30
|
+
import { registerSensevoiceInstaller, findTokensFile } from './sensevoice-installer.js'
|
|
31
31
|
import { createConfigReader } from './config-compat.js'
|
|
32
32
|
import { registerConfigRoutes } from './config-routes.js'
|
|
33
33
|
import { getAllowedAudioRoots, validateAudioPath, validateAudioContent } from './audio-guard.js'
|
|
34
|
-
|
|
35
|
-
function isAutoLang(lang) {
|
|
36
|
-
return !lang || lang === 'auto' || String(lang).includes(',')
|
|
37
|
-
}
|
|
34
|
+
import { isAutoLang } from './provider-http.js'
|
|
38
35
|
|
|
39
36
|
export const name = '@goodandready/dsh-voice'
|
|
40
37
|
export const NS = 'dsh-voice'
|
|
@@ -324,9 +321,12 @@ export function apply(ctx, baseConfig) {
|
|
|
324
321
|
try {
|
|
325
322
|
let port = '6006'
|
|
326
323
|
try { port = new URL(cfg.sensevoiceUrl).port || '6006' } catch { /* invalid URL */ }
|
|
324
|
+
const tokensPath = findTokensFile(path.dirname(cfg.sensevoiceModel))
|
|
325
|
+
const tokensArg = tokensPath ? ` --tokens=${JSON.stringify(tokensPath)}` : ''
|
|
327
326
|
const spec = ctx.shell.resolve({
|
|
328
327
|
command: `${JSON.stringify(cfg.sensevoiceBin)}`
|
|
329
328
|
+ ` --sense-voice-model=${JSON.stringify(cfg.sensevoiceModel)}`
|
|
329
|
+
+ tokensArg
|
|
330
330
|
+ ` --port=${port} --num-threads=4`,
|
|
331
331
|
timeoutMs: 0,
|
|
332
332
|
stdoutMaxBytes: 4 * 1024 * 1024,
|
|
@@ -595,7 +595,7 @@ export function apply(ctx, baseConfig) {
|
|
|
595
595
|
},
|
|
596
596
|
}), 'dsh-voice: /transcribe route')
|
|
597
597
|
|
|
598
|
-
ctx.tools.register(
|
|
598
|
+
ctx.effect(() => ctx.tools.register(
|
|
599
599
|
defineTool({
|
|
600
600
|
name: 'transcribe_audio',
|
|
601
601
|
description:
|
|
@@ -649,7 +649,7 @@ export function apply(ctx, baseConfig) {
|
|
|
649
649
|
return out
|
|
650
650
|
},
|
|
651
651
|
}),
|
|
652
|
-
)
|
|
652
|
+
), 'dsh-voice: transcribe_audio tool')
|
|
653
653
|
|
|
654
654
|
ctx.effect(() => () => {
|
|
655
655
|
if (child) { try { child.kill && child.kill() } catch { /* already dead */ } }
|
package/lib/normalize.js
CHANGED
|
@@ -128,28 +128,28 @@ export function extractVoiceActions(text) {
|
|
|
128
128
|
if (!raw) return { text: '', action: null }
|
|
129
129
|
|
|
130
130
|
const lower = raw.toLowerCase().replace(/[.!?…]+$/, '').trim()
|
|
131
|
-
if (/^(отправь|отправить|send|send it|send message)$/i.test(lower)) {
|
|
131
|
+
if (/^(отправь|отправить|send|send it|send message|发送|发出)$/i.test(lower)) {
|
|
132
132
|
return { text: '', action: 'send' }
|
|
133
133
|
}
|
|
134
|
-
if (/^(отмена|cancel|discard)$/i.test(lower)) {
|
|
134
|
+
if (/^(отмена|cancel|discard|取消|算了)$/i.test(lower)) {
|
|
135
135
|
return { text: '', action: 'cancel' }
|
|
136
136
|
}
|
|
137
|
-
if (/^(очисти|очистить|clear|clear composer)$/i.test(lower)) {
|
|
137
|
+
if (/^(очисти|очистить|clear|clear composer|清空|清除)$/i.test(lower)) {
|
|
138
138
|
return { text: '', action: 'clear' }
|
|
139
139
|
}
|
|
140
|
-
if (/^(новая строка|с новой строки|new line|newline)$/i.test(lower)) {
|
|
140
|
+
if (/^(новая строка|с новой строки|new line|newline|换行|新行)$/i.test(lower)) {
|
|
141
141
|
return { text: '', action: 'newline' }
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
-
const trailingSend = /[\s,]+(отправь|отправить|send|send it)[.!?…]*$/i
|
|
144
|
+
const trailingSend = /[\s,]+(отправь|отправить|send|send it|发送|发出)[.!?…]*$/i
|
|
145
145
|
if (trailingSend.test(raw)) {
|
|
146
146
|
return { text: raw.replace(trailingSend, '').trim(), action: 'send' }
|
|
147
147
|
}
|
|
148
|
-
const trailingCancel = /[\s,]+(отмена|cancel)[.!?…]*$/i
|
|
148
|
+
const trailingCancel = /[\s,]+(отмена|cancel|取消|算了)[.!?…]*$/i
|
|
149
149
|
if (trailingCancel.test(raw)) {
|
|
150
150
|
return { text: raw.replace(trailingCancel, '').trim(), action: 'cancel' }
|
|
151
151
|
}
|
|
152
|
-
const trailingNewline = /[\s,]+(новая строка|с новой строки|new line)[.!?…]*$/i
|
|
152
|
+
const trailingNewline = /[\s,]+(новая строка|с новой строки|new line|换行|新行)[.!?…]*$/i
|
|
153
153
|
if (trailingNewline.test(raw)) {
|
|
154
154
|
return { text: raw.replace(trailingNewline, '').trim() + '\n', action: 'newline' }
|
|
155
155
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Downloads pre-quantized ONNX model and configures autostart on the host.
|
|
3
3
|
|
|
4
4
|
import { createWriteStream, existsSync, readdirSync } from 'node:fs'
|
|
5
|
-
import { mkdir, stat } from 'node:fs/promises'
|
|
5
|
+
import { mkdir, stat, unlink } from 'node:fs/promises'
|
|
6
6
|
import { homedir } from 'node:os'
|
|
7
7
|
import path from 'node:path'
|
|
8
8
|
import { spawn } from 'node:child_process'
|
|
@@ -161,6 +161,7 @@ export async function startSensevoiceInstall(ctx, cfg, updateConfigFn, startSens
|
|
|
161
161
|
installerState.step = 'extracting'
|
|
162
162
|
installerState.progress = 95
|
|
163
163
|
await extractTarBz2(archivePath, modelDir)
|
|
164
|
+
await unlink(archivePath).catch(() => {})
|
|
164
165
|
|
|
165
166
|
const modelFile = findModelFile(modelDir)
|
|
166
167
|
if (!modelFile) throw new Error('model.int8.onnx not found after extraction')
|
|
@@ -183,6 +184,7 @@ export async function startSensevoiceInstall(ctx, cfg, updateConfigFn, startSens
|
|
|
183
184
|
|
|
184
185
|
return { ok: true, modelPath: modelFile }
|
|
185
186
|
} catch (err) {
|
|
187
|
+
await unlink(archivePath).catch(() => {})
|
|
186
188
|
installerState.installing = false
|
|
187
189
|
installerState.error = String(err?.message || err)
|
|
188
190
|
installerState.step = 'failed'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-voice",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.3",
|
|
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",
|
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
"scripts": {
|
|
44
44
|
"test": "node --test test/*.test.mjs",
|
|
45
45
|
"build:client": "node scripts/build-client.mjs",
|
|
46
|
+
"build:check": "node scripts/build-client.mjs --check",
|
|
46
47
|
"pretest": "node scripts/build-client.mjs",
|
|
47
48
|
"lint": "node --check lib/*.js scripts/*.mjs",
|
|
48
49
|
"pack:check": "node scripts/pack-check.mjs"
|