@deepseek-ai/dsh-experimental-client-ui-voice-input 0.2.2-alpha.3 → 0.2.2-rc.2
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 +1 -3710
- package/lib/client.js.map +1 -0
- package/lib/types/client/MicrophonePicker.d.ts.map +1 -0
- package/lib/types/client/MicrophonePicker.js.map +1 -0
- package/lib/types/client/PreparationCard.d.ts.map +1 -0
- package/lib/types/client/PreparationCard.js.map +1 -0
- package/lib/types/client/VoiceInput.d.ts.map +1 -0
- package/lib/types/client/VoiceInput.js.map +1 -0
- package/lib/types/client/VoiceSetupDialog.d.ts.map +1 -0
- package/lib/types/client/VoiceSetupDialog.js.map +1 -0
- package/lib/types/client/VoiceSetupPrompt.d.ts.map +1 -0
- package/lib/types/client/VoiceSetupPrompt.js.map +1 -0
- package/lib/types/client/Waveform.d.ts.map +1 -0
- package/lib/types/client/Waveform.js.map +1 -0
- package/lib/types/client/audio.d.ts.map +1 -0
- package/lib/types/client/audio.js.map +1 -0
- package/lib/types/client/failure-text.d.ts.map +1 -0
- package/lib/types/client/failure-text.js.map +1 -0
- package/lib/types/client/index.d.ts.map +1 -0
- package/lib/types/client/index.js.map +1 -0
- package/lib/types/client/locales.d.ts.map +1 -0
- package/lib/types/client/locales.js.map +1 -0
- package/lib/types/client/microphone-device.d.ts.map +1 -0
- package/lib/types/client/microphone-device.js.map +1 -0
- package/lib/types/client/mount.d.ts.map +1 -0
- package/lib/types/client/mount.js.map +1 -0
- package/lib/types/client/readiness.d.ts.map +1 -0
- package/lib/types/client/readiness.js.map +1 -0
- package/lib/types/index.d.ts.map +1 -0
- package/lib/types/index.js.map +1 -0
- package/package.json +23 -20
- package/src/client/MicrophonePicker.tsx +140 -0
- package/src/client/PreparationCard.tsx +165 -0
- package/src/client/VoiceInput.module.css +55 -0
- package/src/client/VoiceInput.tsx +182 -0
- package/src/client/VoiceSetupDialog.tsx +32 -0
- package/src/client/VoiceSetupPrompt.tsx +27 -0
- package/src/client/Waveform.tsx +33 -0
- package/src/client/audio.ts +188 -0
- package/src/client/failure-text.ts +15 -0
- package/src/client/index.ts +15 -0
- package/src/client/locales.ts +834 -0
- package/src/client/microphone-device.ts +20 -0
- package/src/client/mount.ts +77 -0
- package/src/client/readiness.ts +40 -0
- package/src/css-modules.d.ts +5 -0
- package/src/index.ts +3 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** Browser-local microphone preference shared by settings previews and dictation. */
|
|
2
|
+
import { createSnapshotStore, type SnapshotStore } from '@deepseek-ai/dsh-client-store'
|
|
3
|
+
|
|
4
|
+
/** An empty id follows the system input; labels store browser names without localized placeholders. */
|
|
5
|
+
export interface MicrophoneDevice {
|
|
6
|
+
readonly id: string
|
|
7
|
+
readonly label: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Restore the browser preference independently of Host recognition settings.
|
|
12
|
+
* @returns validated device selection, persisted through the shared Client store.
|
|
13
|
+
*/
|
|
14
|
+
export function createMicrophoneDeviceStore(): SnapshotStore<MicrophoneDevice> {
|
|
15
|
+
const store = createSnapshotStore<MicrophoneDevice>({ id: '', label: '' }, { persist: { name: 'dsh.voice-input.microphone' } })
|
|
16
|
+
const saved: unknown = store.getSnapshot()
|
|
17
|
+
if (!saved || typeof saved !== 'object' || !('id' in saved) || typeof saved.id !== 'string'
|
|
18
|
+
|| !('label' in saved) || typeof saved.label !== 'string') store.set({ id: '', label: '' })
|
|
19
|
+
return store
|
|
20
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/** Source-safe lifecycle for the optional speech Remote and browser UI. */
|
|
2
|
+
import type { Context } from '@deepseek-ai/cordis'
|
|
3
|
+
import type {} from '@deepseek-ai/dsh-api-remotes/client'
|
|
4
|
+
import type {} from '@deepseek-ai/dsh-experimental-api-speech-to-text/remote'
|
|
5
|
+
import type {} from '@deepseek-ai/dsh-client-locale/client'
|
|
6
|
+
import type {} from '@deepseek-ai/dsh-client-ui-renderer/client'
|
|
7
|
+
import type {} from '@deepseek-ai/dsh-client-ui-conversation/client'
|
|
8
|
+
import type { TypertRemoteContribution } from '@deepseek-ai/dsh-typert-protocol'
|
|
9
|
+
import { createSnapshotStore } from '@deepseek-ai/dsh-client-store'
|
|
10
|
+
import { VoiceInput, type VoiceInputInjected } from './VoiceInput.tsx'
|
|
11
|
+
import { Recording } from './audio.ts'
|
|
12
|
+
import { createMicrophoneDeviceStore } from './microphone-device.ts'
|
|
13
|
+
import { en, ja, es, fr, de, ru, pt, NS, zh } from './locales.ts'
|
|
14
|
+
import type {} from '@deepseek-ai/dsh-client-ui-plugin-manager/client'
|
|
15
|
+
import { observeReadiness } from './readiness.ts'
|
|
16
|
+
import { VoicePreparation } from './PreparationCard.tsx'
|
|
17
|
+
import { VoiceSetupPrompt } from './VoiceSetupPrompt.tsx'
|
|
18
|
+
|
|
19
|
+
export const inject = ['remote', 'slots', 'locale']
|
|
20
|
+
|
|
21
|
+
function registerUi(ctx: Context): void {
|
|
22
|
+
ctx.effect(() => ctx.locale.register(NS, { zh, en, ja, es, fr, de, ru, pt }))
|
|
23
|
+
const recordings = new Set<Recording>()
|
|
24
|
+
const microphone = createMicrophoneDeviceStore()
|
|
25
|
+
const readiness = observeReadiness(ctx)
|
|
26
|
+
ctx.effect(() => readiness.dispose)
|
|
27
|
+
ctx.effect(() => async () => { await Promise.all([...recordings].map(recording => recording.dispose())) })
|
|
28
|
+
// The Plugins page provides its navigation only while the current Peer may use it.
|
|
29
|
+
const pluginNavigation = createSnapshotStore(false)
|
|
30
|
+
ctx.inject(['pluginNavigation'], (scope: Context) => {
|
|
31
|
+
scope.effect(() => {
|
|
32
|
+
pluginNavigation.set(true)
|
|
33
|
+
return () => { pluginNavigation.set(false) }
|
|
34
|
+
}, 'voice-input: plugin navigation presence')
|
|
35
|
+
})
|
|
36
|
+
const actions: VoiceInputInjected = {
|
|
37
|
+
openSettings: () => { ctx.get('pluginNavigation')?.openBundle('@deepseek-ai/dsh-experimental-voice-input-bundle') },
|
|
38
|
+
hooks: { speechReadiness: readiness.state, microphoneDevice: microphone, pluginNavigation },
|
|
39
|
+
selectMicrophone: (device) => { microphone.set(device) },
|
|
40
|
+
createRecording: () => {
|
|
41
|
+
const recording = new Recording(() => { recordings.delete(recording) }, microphone.getSnapshot().id)
|
|
42
|
+
recordings.add(recording)
|
|
43
|
+
return recording
|
|
44
|
+
},
|
|
45
|
+
transcribe: async (request, signal) => await ctx.remote.speech.transcribe(request, signal),
|
|
46
|
+
configure: async (patch) => { const result = await ctx.remote.speech.configure(patch); if (!result.ok) throw result.error },
|
|
47
|
+
prepare: async (providerId, options) => {
|
|
48
|
+
const result = await ctx.remote.speech.prepare(providerId, options); if (!result.ok) throw result.error
|
|
49
|
+
},
|
|
50
|
+
cancelPreparation: async (providerId) => {
|
|
51
|
+
const result = await ctx.remote.speech.cancelPreparation(providerId); if (!result.ok) throw result.error
|
|
52
|
+
},
|
|
53
|
+
}
|
|
54
|
+
ctx.slots.inject('conversation.input.activity', () => ctx.slots.register({
|
|
55
|
+
name: 'conversation.input.activity', locale: NS, inject: () => actions,
|
|
56
|
+
}, VoiceInput))
|
|
57
|
+
ctx.slots.inject('plugins.bundle.config', () => ctx.slots.register({ name: 'plugins.bundle.config',
|
|
58
|
+
key: '@deepseek-ai/dsh-experimental-voice-input-bundle', locale: NS, inject: () => actions,
|
|
59
|
+
}, VoicePreparation))
|
|
60
|
+
ctx.slots.inject('plugins.bundle.activation', () => ctx.slots.register({ name: 'plugins.bundle.activation',
|
|
61
|
+
key: '@deepseek-ai/dsh-experimental-voice-input-bundle', locale: NS, inject: () => actions,
|
|
62
|
+
}, VoiceSetupPrompt))
|
|
63
|
+
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Mount this experimental namespace without adding it to stable API Remotes.
|
|
68
|
+
* @param ctx - Client runtime owning the Remote, dictionaries and slots.
|
|
69
|
+
* @param contribution - generated speech Remote definitions.
|
|
70
|
+
* @returns disposer joining UI and Remote withdrawal.
|
|
71
|
+
*/
|
|
72
|
+
export async function mountVoiceInput(ctx: Context, contribution: TypertRemoteContribution): Promise<() => Promise<void>> {
|
|
73
|
+
const disposeRemote = await ctx.remote.$mount(contribution)
|
|
74
|
+
const ui = ctx.inject(['remote.speech', 'slots', 'locale'], registerUi)
|
|
75
|
+
try { await ui } catch (error) { await ui.dispose(); await disposeRemote(); throw error }
|
|
76
|
+
return async () => { await ui.dispose(); await disposeRemote() }
|
|
77
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/** One reconnecting Host readiness mirror shared by every voice UI occurrence. */
|
|
2
|
+
import type { Context } from '@deepseek-ai/cordis'
|
|
3
|
+
import { RemoteStreamCarrierError } from '@deepseek-ai/dsh-api-gateway/client'
|
|
4
|
+
import { createSnapshotStore, type SnapshotStore } from '@deepseek-ai/dsh-client-store'
|
|
5
|
+
import type { SpeechCatalog } from '@deepseek-ai/dsh-experimental-api-speech-to-text/types'
|
|
6
|
+
import type {} from '@deepseek-ai/dsh-experimental-api-speech-to-text/remote'
|
|
7
|
+
|
|
8
|
+
/** Complete provider state, with a separate transport failure that never rewrites Host readiness. */
|
|
9
|
+
export interface SpeechReadiness {
|
|
10
|
+
readonly catalog: SpeechCatalog | null
|
|
11
|
+
readonly connected: boolean
|
|
12
|
+
readonly error: string | null
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Subscribe once per Client plugin, independent of rendered pages and Sessions.
|
|
17
|
+
* @param ctx - mounted speech Remote owner.
|
|
18
|
+
* @returns shared readiness snapshot and joined observation cleanup.
|
|
19
|
+
*/
|
|
20
|
+
export function observeReadiness(ctx: Context): { state: SnapshotStore<SpeechReadiness>; dispose: () => Promise<void> } {
|
|
21
|
+
const state = createSnapshotStore<SpeechReadiness>({ catalog: null, connected: false, error: null })
|
|
22
|
+
let disposed = false
|
|
23
|
+
const isDisposed = (): boolean => disposed
|
|
24
|
+
const fail = (error: unknown): void => {
|
|
25
|
+
state.set({ ...state.getSnapshot(), connected: false, error: error instanceof Error ? error.message : String(error) })
|
|
26
|
+
}
|
|
27
|
+
const stream = ctx.remote.$stream<SpeechCatalog>({
|
|
28
|
+
name: 'Speech readiness', open: signal => ctx.remote.speech.follow(signal),
|
|
29
|
+
ended: () => new RemoteStreamCarrierError('Speech readiness stream ended'), carrierFailed: fail,
|
|
30
|
+
})
|
|
31
|
+
const observing = (async () => {
|
|
32
|
+
try {
|
|
33
|
+
for await (const item of stream) {
|
|
34
|
+
state.set({ catalog: item.value, connected: true, error: null })
|
|
35
|
+
item.accept()
|
|
36
|
+
}
|
|
37
|
+
} catch (error) { if (!isDisposed()) fail(error) }
|
|
38
|
+
})()
|
|
39
|
+
return { state, dispose: async () => { disposed = true; await stream.dispose(); await observing } }
|
|
40
|
+
}
|
package/src/index.ts
ADDED