@goodandready/dsh-voice 0.8.36 → 0.8.37
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 +16 -6
- package/lib/config-compat.js +43 -0
- package/lib/index.js +33 -10
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -2166,7 +2166,10 @@ window.__ModuleLoader__.load({
|
|
|
2166
2166
|
// writes are dropped. The server does not share that restriction, and
|
|
2167
2167
|
// dsh-lanmode rebuilds the same mechanics on the same calls. Prefer its
|
|
2168
2168
|
// service when installed, otherwise the kernel one (fine on loopback).
|
|
2169
|
-
const
|
|
2169
|
+
const settingsService = (ctx.get && ctx.get('lanSettings')) || ctx.configForms || ctx.settingsScope
|
|
2170
|
+
const scope = settingsService && typeof settingsService.get === 'function'
|
|
2171
|
+
? settingsService.get(NS)
|
|
2172
|
+
: (settingsService && typeof settingsService.bind === 'function' ? settingsService.bind({ namespace: NS }) : null)
|
|
2170
2173
|
const [snap, setSnap] = React.useState(null)
|
|
2171
2174
|
const [draft, setDraft] = React.useState(null)
|
|
2172
2175
|
const [saved, setSaved] = React.useState(false)
|
|
@@ -2190,11 +2193,11 @@ window.__ModuleLoader__.load({
|
|
|
2190
2193
|
|
|
2191
2194
|
React.useEffect(() => {
|
|
2192
2195
|
let alive = true
|
|
2193
|
-
const render = () => { if (alive) setSnap(scope.getSnapshot()) }
|
|
2196
|
+
const render = () => { if (alive && scope && typeof scope.getSnapshot === 'function') setSnap(scope.getSnapshot()) }
|
|
2194
2197
|
render()
|
|
2195
|
-
const off = scope.subscribe(render)
|
|
2198
|
+
const off = (scope && typeof scope.subscribe === 'function') ? scope.subscribe(render) : () => {}
|
|
2196
2199
|
return () => { alive = false; off() }
|
|
2197
|
-
}, [])
|
|
2200
|
+
}, [scope])
|
|
2198
2201
|
|
|
2199
2202
|
const [devices, setDevices] = React.useState([])
|
|
2200
2203
|
React.useEffect(() => {
|
|
@@ -2214,7 +2217,13 @@ window.__ModuleLoader__.load({
|
|
|
2214
2217
|
const timer = setInterval(() => {
|
|
2215
2218
|
if (tries >= 15) { clearInterval(timer); return }
|
|
2216
2219
|
tries += 1
|
|
2217
|
-
try {
|
|
2220
|
+
try {
|
|
2221
|
+
if (ctx.configForms && typeof ctx.configForms.describe === 'function') {
|
|
2222
|
+
ctx.configForms.describe().load()
|
|
2223
|
+
} else if (ctx.settingsScope && typeof ctx.settingsScope.describe === 'function') {
|
|
2224
|
+
ctx.settingsScope.describe().load()
|
|
2225
|
+
}
|
|
2226
|
+
} catch (e) { /* service not up yet */ }
|
|
2218
2227
|
}, 1000)
|
|
2219
2228
|
return () => clearInterval(timer)
|
|
2220
2229
|
}, [ready])
|
|
@@ -3125,7 +3134,7 @@ window.__ModuleLoader__.load({
|
|
|
3125
3134
|
},
|
|
3126
3135
|
PluginCard,
|
|
3127
3136
|
))
|
|
3128
|
-
// Row seat
|
|
3137
|
+
// Row seat (plugins.row.config): rendered in the plugin row in Plugins table.
|
|
3129
3138
|
ctx.slots.inject('plugins.row.config', () => ctx.slots.register(
|
|
3130
3139
|
{
|
|
3131
3140
|
name: 'plugins.row.config',
|
|
@@ -3135,6 +3144,7 @@ window.__ModuleLoader__.load({
|
|
|
3135
3144
|
},
|
|
3136
3145
|
PluginCard,
|
|
3137
3146
|
))
|
|
3147
|
+
// Legacy seat fallback
|
|
3138
3148
|
ctx.slots.inject('settings.plugin.item', () => ctx.slots.register(
|
|
3139
3149
|
{
|
|
3140
3150
|
name: 'settings.plugin.item',
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// Decode modern volatile Config references while retaining stable identities for
|
|
2
|
+
// the request hot path. Legacy cores pass ordinary resolved configuration.
|
|
3
|
+
// If ctx is provided, invalidation listeners cache the unwrapped tree so the hot
|
|
4
|
+
// path never recursively walks volatile nodes on unchanged configuration.
|
|
5
|
+
export function createConfigReader(config, ctx) {
|
|
6
|
+
let previous
|
|
7
|
+
let dirty = true
|
|
8
|
+
if (ctx && typeof ctx.on === 'function') {
|
|
9
|
+
const markDirty = () => { dirty = true }
|
|
10
|
+
if (typeof ctx.effect === 'function') {
|
|
11
|
+
ctx.effect(() => ctx.on('settings/document-updated', markDirty))
|
|
12
|
+
ctx.effect(() => ctx.on('loader/volatile-update', markDirty))
|
|
13
|
+
ctx.effect(() => ctx.on('config', markDirty))
|
|
14
|
+
} else {
|
|
15
|
+
ctx.on('settings/document-updated', markDirty)
|
|
16
|
+
ctx.on('loader/volatile-update', markDirty)
|
|
17
|
+
ctx.on('config', markDirty)
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const read = (value, held) => {
|
|
22
|
+
if (value && typeof value.get === 'function') value = value.get()
|
|
23
|
+
if (!value || typeof value !== 'object') return value
|
|
24
|
+
const keys = Object.keys(value)
|
|
25
|
+
const next = Array.isArray(value) ? [] : {}
|
|
26
|
+
let same = held && typeof held === 'object' && Array.isArray(value) === Array.isArray(held)
|
|
27
|
+
&& keys.length === Object.keys(held).length
|
|
28
|
+
for (const key of keys) {
|
|
29
|
+
Object.defineProperty(next, key, { value: read(value[key], held?.[key]), writable: true, enumerable: true, configurable: true })
|
|
30
|
+
if (!held || !Object.hasOwn(held, key) || !Object.is(next[key], held[key])) same = false
|
|
31
|
+
}
|
|
32
|
+
return same ? held : next
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return () => {
|
|
36
|
+
if (ctx && !dirty && previous !== undefined) {
|
|
37
|
+
return previous
|
|
38
|
+
}
|
|
39
|
+
previous = read(config, previous)
|
|
40
|
+
dirty = false
|
|
41
|
+
return previous
|
|
42
|
+
}
|
|
43
|
+
}
|
package/lib/index.js
CHANGED
|
@@ -28,6 +28,7 @@ import { createUserMessage } from '@deepseek-ai/dsh-llm'
|
|
|
28
28
|
import { buildProviderOrder, sessionCommand } from './transcribe-core.js'
|
|
29
29
|
import { registerPluginUpdater } from './updater.js'
|
|
30
30
|
import { registerSensevoiceInstaller } from './sensevoice-installer.js'
|
|
31
|
+
import { createConfigReader } from './config-compat.js'
|
|
31
32
|
|
|
32
33
|
function isAutoLang(lang) {
|
|
33
34
|
return !lang || lang === 'auto' || String(lang).includes(',')
|
|
@@ -65,7 +66,21 @@ const CustomProvider = z.object({
|
|
|
65
66
|
+ 'Empty means the built-in one.'),
|
|
66
67
|
})
|
|
67
68
|
|
|
68
|
-
|
|
69
|
+
function makeVolatile(schema) {
|
|
70
|
+
if (!schema || typeof schema.volatile !== 'function') return schema
|
|
71
|
+
if (schema.type === 'object' && schema.dict) {
|
|
72
|
+
const dict = {}
|
|
73
|
+
for (const [k, v] of Object.entries(schema.dict)) {
|
|
74
|
+
dict[k] = makeVolatile(v)
|
|
75
|
+
}
|
|
76
|
+
const res = z.object(dict)
|
|
77
|
+
if (schema.meta) Object.assign(res.meta, schema.meta)
|
|
78
|
+
return res
|
|
79
|
+
}
|
|
80
|
+
return schema.volatile()
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const BaseConfig = z.object({
|
|
69
84
|
dictation: z.object({
|
|
70
85
|
chain: z.array(ChainEntry)
|
|
71
86
|
.default([{ provider: 'deepgram', model: '' }, { provider: 'groq', model: '' }, { provider: 'local-whisper', model: '' }])
|
|
@@ -181,6 +196,10 @@ export const Config = z.object({
|
|
|
181
196
|
.description('Target voice dictation and numeric choices to active DSH structured prompt modals.'),
|
|
182
197
|
})
|
|
183
198
|
|
|
199
|
+
export const Config = typeof z.number().volatile === 'function'
|
|
200
|
+
? makeVolatile(BaseConfig)
|
|
201
|
+
: BaseConfig
|
|
202
|
+
|
|
184
203
|
const MIME_BY_EXT = {
|
|
185
204
|
'.wav': 'audio/wav', '.mp3': 'audio/mpeg', '.m4a': 'audio/mp4', '.mp4': 'audio/mp4',
|
|
186
205
|
'.ogg': 'audio/ogg', '.oga': 'audio/ogg', '.flac': 'audio/flac', '.webm': 'audio/webm', '.aac': 'audio/aac',
|
|
@@ -190,18 +209,22 @@ const MIME_BY_EXT = {
|
|
|
190
209
|
export function apply(ctx, baseConfig) {
|
|
191
210
|
let child = null
|
|
192
211
|
|
|
193
|
-
// The settings card edits a namespace named after the plugin.
|
|
194
|
-
//
|
|
195
|
-
//
|
|
196
|
-
|
|
212
|
+
// The settings card edits a namespace named after the plugin.
|
|
213
|
+
// Using createConfigReader decodes volatile nodes on modern DSH cores
|
|
214
|
+
// while caching unwrapped values across requests.
|
|
215
|
+
const readEntryConfig = createConfigReader(baseConfig, ctx)
|
|
197
216
|
let scope = null
|
|
198
|
-
let getConfig =
|
|
199
|
-
const live = () =>
|
|
217
|
+
let getConfig = readEntryConfig
|
|
218
|
+
const live = () => BaseConfig(structuredClone(getConfig() ?? {})) ?? readEntryConfig()
|
|
200
219
|
|
|
201
220
|
ctx.inject(['settings'], (sctx) => {
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
221
|
+
try {
|
|
222
|
+
scope = sctx.settings.register(NS, Config, { base: baseConfig })
|
|
223
|
+
if (scope && typeof scope.get === 'function') {
|
|
224
|
+
getConfig = () => scope.get() ?? readEntryConfig()
|
|
225
|
+
}
|
|
226
|
+
} catch { /* modern DSH handles settings via SettingsForms */ }
|
|
227
|
+
sctx.effect(() => () => { getConfig = readEntryConfig; scope = null })
|
|
205
228
|
})
|
|
206
229
|
|
|
207
230
|
async function resolveKey(ref) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-voice",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.37",
|
|
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",
|