@corti/dictation-web 0.0.0-rc.359

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.
Files changed (55) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +156 -0
  3. package/dist/CortiDictation.d.ts +51 -0
  4. package/dist/CortiDictation.js +281 -0
  5. package/dist/CortiDictation.js.map +1 -0
  6. package/dist/DictationService.d.ts +16 -0
  7. package/dist/DictationService.js +88 -0
  8. package/dist/DictationService.js.map +1 -0
  9. package/dist/RecorderManager.d.ts +25 -0
  10. package/dist/RecorderManager.js +145 -0
  11. package/dist/RecorderManager.js.map +1 -0
  12. package/dist/audioService.d.ts +6 -0
  13. package/dist/audioService.js +21 -0
  14. package/dist/audioService.js.map +1 -0
  15. package/dist/bundle.js +11064 -0
  16. package/dist/components/audio-visualiser.d.ts +12 -0
  17. package/dist/components/audio-visualiser.js +65 -0
  18. package/dist/components/audio-visualiser.js.map +1 -0
  19. package/dist/components/settings-menu.d.ts +20 -0
  20. package/dist/components/settings-menu.js +185 -0
  21. package/dist/components/settings-menu.js.map +1 -0
  22. package/dist/constants.d.ts +3 -0
  23. package/dist/constants.js +16 -0
  24. package/dist/constants.js.map +1 -0
  25. package/dist/icons/icons.d.ts +17 -0
  26. package/dist/icons/icons.js +158 -0
  27. package/dist/icons/icons.js.map +1 -0
  28. package/dist/icons/index.d.ts +1 -0
  29. package/dist/icons/index.js +2 -0
  30. package/dist/icons/index.js.map +1 -0
  31. package/dist/index.d.ts +2 -0
  32. package/dist/index.js +6 -0
  33. package/dist/index.js.map +1 -0
  34. package/dist/styles/ComponentStyles.d.ts +2 -0
  35. package/dist/styles/ComponentStyles.js +53 -0
  36. package/dist/styles/ComponentStyles.js.map +1 -0
  37. package/dist/styles/buttons.d.ts +2 -0
  38. package/dist/styles/buttons.js +58 -0
  39. package/dist/styles/buttons.js.map +1 -0
  40. package/dist/styles/callout.d.ts +2 -0
  41. package/dist/styles/callout.js +33 -0
  42. package/dist/styles/callout.js.map +1 -0
  43. package/dist/styles/select.d.ts +2 -0
  44. package/dist/styles/select.js +36 -0
  45. package/dist/styles/select.js.map +1 -0
  46. package/dist/styles/theme.d.ts +2 -0
  47. package/dist/styles/theme.js +56 -0
  48. package/dist/styles/theme.js.map +1 -0
  49. package/dist/types.d.ts +8 -0
  50. package/dist/types.js +2 -0
  51. package/dist/types.js.map +1 -0
  52. package/dist/utils.d.ts +59 -0
  53. package/dist/utils.js +179 -0
  54. package/dist/utils.js.map +1 -0
  55. package/package.json +120 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DictationService.js","sourceRoot":"","sources":["../src/DictationService.ts"],"names":[],"mappings":"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","sourcesContent":["import { CortiClient, Corti } from '@corti/sdk';\n\nimport type { ServerConfig } from './types.js';\nimport { getErrorMessage } from './utils.js';\n\ntype TranscribeSocket = Awaited<\n ReturnType<CortiClient['transcribe']['connect']>\n>;\n\nexport class DictationService extends EventTarget {\n private mediaRecorder: MediaRecorder;\n private webSocket!: TranscribeSocket;\n private serverConfig: ServerConfig;\n private dictationConfig: Corti.TranscribeConfig;\n private cortiClient: CortiClient;\n\n constructor(\n mediaStream: MediaStream,\n {\n dictationConfig,\n serverConfig,\n }: { dictationConfig: Corti.TranscribeConfig; serverConfig: ServerConfig },\n ) {\n super();\n this.mediaRecorder = new MediaRecorder(mediaStream);\n this.serverConfig = serverConfig;\n this.dictationConfig = dictationConfig;\n\n // We're forced to remove from expiresIn/refreshExpiresIn here,\n // because we recreate connection every time we connect\n // => it makes expiresIn (we receive from API) out of date\n const {\n expiresIn, // eslint-disable-line @typescript-eslint/no-unused-vars\n refreshExpiresIn, // eslint-disable-line @typescript-eslint/no-unused-vars\n ...authConfig\n } = serverConfig;\n\n this.cortiClient = new CortiClient({\n auth: authConfig,\n });\n\n this.mediaRecorder.ondataavailable = event => {\n if (this.webSocket?.readyState === WebSocket.OPEN) {\n this.webSocket.sendAudio(event.data);\n }\n };\n }\n\n private dispatchCustomEvent(eventName: string, detail?: unknown): void {\n this.dispatchEvent(\n new CustomEvent(eventName, {\n detail,\n bubbles: true,\n composed: true,\n }),\n );\n }\n\n public async startRecording() {\n if (!this.serverConfig) {\n this.dispatchEvent(\n new CustomEvent('error', {\n detail: 'Invalid token',\n bubbles: true,\n composed: true,\n }),\n );\n return;\n }\n\n this.webSocket = await this.cortiClient.transcribe.connect({\n configuration: this.dictationConfig,\n });\n\n this.webSocket.on('message', message => {\n switch (message.type) {\n case 'CONFIG_ACCEPTED':\n this.mediaRecorder.start(250);\n break;\n case 'transcript':\n this.dispatchCustomEvent('transcript', message);\n break;\n case 'command':\n this.dispatchCustomEvent('command', message);\n break;\n case 'usage':\n this.dispatchCustomEvent('usage', message);\n break;\n default:\n console.warn(`Unhandled message type: ${message.type}`);\n break;\n }\n });\n\n this.webSocket.on('error', event => {\n this.stopRecording();\n\n this.dispatchCustomEvent('error', getErrorMessage(event));\n });\n\n this.webSocket.on('close', event => {\n this.dispatchCustomEvent('stream-closed', event);\n });\n }\n\n public async stopRecording(): Promise<void> {\n this.mediaRecorder?.stop();\n\n if (this.webSocket.readyState === WebSocket.OPEN) {\n this.webSocket.sendEnd({\n type: 'end',\n });\n }\n\n const timeout: NodeJS.Timeout = setTimeout(() => {\n if (this.webSocket?.readyState === WebSocket.OPEN) {\n this.webSocket.close();\n }\n }, 10000);\n\n this.webSocket.on('close', (event) => {\n clearTimeout(timeout);\n this.dispatchCustomEvent('stream-closed', event);\n });\n }\n}\n"]}
@@ -0,0 +1,25 @@
1
+ import { Corti } from '@corti/sdk';
2
+ import type { RecordingState, ServerConfig } from './types.js';
3
+ export declare class RecorderManager extends EventTarget {
4
+ devices: MediaDeviceInfo[];
5
+ selectedDevice: MediaDeviceInfo | undefined;
6
+ recordingState: RecordingState;
7
+ private _mediaStream;
8
+ private _audioService;
9
+ private _dictationService;
10
+ private _visualiserInterval?;
11
+ constructor();
12
+ initialize(): Promise<{
13
+ devices: MediaDeviceInfo[];
14
+ selectedDevice: MediaDeviceInfo | undefined;
15
+ }>;
16
+ private dispatchCustomEvent;
17
+ private handleDevicesChange;
18
+ startRecording(params: {
19
+ dictationConfig: Corti.TranscribeConfig;
20
+ serverConfig: ServerConfig;
21
+ debug_displayAudio?: boolean;
22
+ }): Promise<void>;
23
+ stopRecording(): Promise<void>;
24
+ private _updateRecordingState;
25
+ }
@@ -0,0 +1,145 @@
1
+ import { getAudioDevices, getMediaStream } from './utils.js';
2
+ import { AudioService } from './audioService.js';
3
+ import { DictationService } from './DictationService.js';
4
+ export class RecorderManager extends EventTarget {
5
+ constructor() {
6
+ super();
7
+ this.devices = [];
8
+ this.recordingState = 'stopped';
9
+ this._mediaStream = null;
10
+ this._audioService = null;
11
+ this._dictationService = null;
12
+ navigator.mediaDevices.addEventListener('devicechange', this.handleDevicesChange.bind(this));
13
+ }
14
+ async initialize() {
15
+ const deviceResponse = await getAudioDevices();
16
+ this.devices = deviceResponse.devices;
17
+ this.selectedDevice = deviceResponse.defaultDevice;
18
+ return { devices: this.devices, selectedDevice: this.selectedDevice };
19
+ }
20
+ dispatchCustomEvent(eventName, detail) {
21
+ this.dispatchEvent(new CustomEvent(eventName, {
22
+ detail,
23
+ bubbles: true,
24
+ composed: true,
25
+ }));
26
+ }
27
+ async handleDevicesChange() {
28
+ const deviceResponse = await getAudioDevices();
29
+ this.devices = deviceResponse.devices;
30
+ if (!this.devices.find(device => device.deviceId === this.selectedDevice?.deviceId)) {
31
+ this.selectedDevice = deviceResponse.defaultDevice;
32
+ }
33
+ this.dispatchCustomEvent('recording-devices-changed', {
34
+ devices: deviceResponse.devices,
35
+ selectedDevice: this.selectedDevice || deviceResponse.defaultDevice,
36
+ });
37
+ }
38
+ async startRecording(params) {
39
+ this._updateRecordingState('initializing');
40
+ try {
41
+ this._mediaStream = await getMediaStream(params.debug_displayAudio
42
+ ? 'display_audio'
43
+ : this.selectedDevice?.deviceId);
44
+ this._mediaStream.getTracks().forEach((track) => {
45
+ track.addEventListener('ended', () => {
46
+ if (this.recordingState === 'recording') {
47
+ this.dispatchCustomEvent('error', {
48
+ message: 'Microphone access was lost.',
49
+ });
50
+ this.stopRecording();
51
+ }
52
+ });
53
+ });
54
+ this._audioService = new AudioService(this._mediaStream);
55
+ }
56
+ catch (error) {
57
+ this.dispatchCustomEvent('error', error);
58
+ this.stopRecording();
59
+ return;
60
+ }
61
+ // Initialize dictation service.
62
+ try {
63
+ this._dictationService = new DictationService(this._mediaStream, params);
64
+ // Forward custom events from dictation service
65
+ this._dictationService.addEventListener('error', e => {
66
+ this.dispatchEvent(new CustomEvent('error', {
67
+ detail: e.detail,
68
+ bubbles: true,
69
+ composed: true,
70
+ }));
71
+ this.stopRecording();
72
+ });
73
+ this._dictationService.addEventListener('stream-closed', (e) => {
74
+ this.dispatchEvent(new CustomEvent('stream-closed', {
75
+ detail: e.detail,
76
+ bubbles: true,
77
+ composed: true,
78
+ }));
79
+ this.stopRecording();
80
+ });
81
+ this._dictationService.addEventListener('transcript', e => this.dispatchEvent(new CustomEvent('transcript', {
82
+ detail: e.detail,
83
+ bubbles: true,
84
+ composed: true,
85
+ })));
86
+ this._dictationService.addEventListener('command', e => this.dispatchEvent(new CustomEvent('command', {
87
+ detail: e.detail,
88
+ bubbles: true,
89
+ composed: true,
90
+ })));
91
+ this._dictationService.addEventListener('usage', e => this.dispatchEvent(new CustomEvent('usage', {
92
+ detail: e.detail,
93
+ bubbles: true,
94
+ composed: true,
95
+ })));
96
+ }
97
+ catch (error) {
98
+ this.dispatchCustomEvent('error', error);
99
+ this.stopRecording();
100
+ return;
101
+ }
102
+ try {
103
+ this._dictationService?.startRecording();
104
+ }
105
+ catch (error) {
106
+ this.dispatchEvent(new CustomEvent('error', {
107
+ detail: error,
108
+ bubbles: true,
109
+ composed: true,
110
+ }));
111
+ this.stopRecording();
112
+ return;
113
+ }
114
+ this._updateRecordingState('recording');
115
+ // Update audio level visualization.
116
+ this._visualiserInterval = window.setInterval(() => {
117
+ const level = this._audioService
118
+ ? this._audioService.getAudioLevel() * 3
119
+ : 0;
120
+ this.dispatchCustomEvent('audio-level-changed', { audioLevel: level });
121
+ }, 150);
122
+ }
123
+ async stopRecording() {
124
+ this._updateRecordingState('stopping');
125
+ if (this._visualiserInterval) {
126
+ clearInterval(this._visualiserInterval);
127
+ this._visualiserInterval = undefined;
128
+ }
129
+ if (this._mediaStream) {
130
+ this._mediaStream.getTracks().forEach(track => track.stop());
131
+ }
132
+ this.dispatchCustomEvent('audio-level-changed', { audioLevel: 0 });
133
+ await this._dictationService?.stopRecording();
134
+ this._updateRecordingState('stopped');
135
+ }
136
+ _updateRecordingState(state) {
137
+ this.recordingState = state;
138
+ this.dispatchEvent(new CustomEvent('recording-state-changed', {
139
+ detail: { state },
140
+ bubbles: true,
141
+ composed: true,
142
+ }));
143
+ }
144
+ }
145
+ //# sourceMappingURL=RecorderManager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RecorderManager.js","sourceRoot":"","sources":["../src/RecorderManager.ts"],"names":[],"mappings":"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","sourcesContent":["import { Corti } from '@corti/sdk';\n\nimport { getAudioDevices, getMediaStream } from './utils.js';\nimport { AudioService } from './audioService.js';\nimport { DictationService } from './DictationService.js';\nimport type { RecordingState, ServerConfig } from './types.js';\n\nexport class RecorderManager extends EventTarget {\n public devices: MediaDeviceInfo[] = [];\n\n public selectedDevice: MediaDeviceInfo | undefined;\n\n public recordingState: RecordingState = 'stopped';\n\n private _mediaStream: MediaStream | null = null;\n\n private _audioService: AudioService | null = null;\n\n private _dictationService: DictationService | null = null;\n\n private _visualiserInterval?: number;\n\n constructor() {\n super();\n navigator.mediaDevices.addEventListener(\n 'devicechange',\n this.handleDevicesChange.bind(this),\n );\n }\n\n async initialize() {\n const deviceResponse = await getAudioDevices();\n this.devices = deviceResponse.devices;\n this.selectedDevice = deviceResponse.defaultDevice;\n return { devices: this.devices, selectedDevice: this.selectedDevice };\n }\n\n private dispatchCustomEvent(eventName: string, detail?: unknown): void {\n this.dispatchEvent(\n new CustomEvent(eventName, {\n detail,\n bubbles: true,\n composed: true,\n }),\n );\n }\n\n private async handleDevicesChange() {\n const deviceResponse = await getAudioDevices();\n this.devices = deviceResponse.devices;\n if (\n !this.devices.find(\n device => device.deviceId === this.selectedDevice?.deviceId,\n )\n ) {\n this.selectedDevice = deviceResponse.defaultDevice;\n }\n this.dispatchCustomEvent('recording-devices-changed', {\n devices: deviceResponse.devices,\n selectedDevice: this.selectedDevice || deviceResponse.defaultDevice,\n });\n }\n\n async startRecording(params: {\n dictationConfig: Corti.TranscribeConfig;\n serverConfig: ServerConfig;\n debug_displayAudio?: boolean;\n }): Promise<void> {\n this._updateRecordingState('initializing');\n\n try {\n this._mediaStream = await getMediaStream(\n params.debug_displayAudio\n ? 'display_audio'\n : this.selectedDevice?.deviceId,\n );\n\n this._mediaStream.getTracks().forEach((track: MediaStreamTrack) => {\n track.addEventListener('ended', () => {\n if (this.recordingState === 'recording') {\n this.dispatchCustomEvent('error', {\n message: 'Microphone access was lost.',\n });\n this.stopRecording();\n }\n });\n });\n\n this._audioService = new AudioService(this._mediaStream);\n } catch (error) {\n this.dispatchCustomEvent('error', error);\n this.stopRecording();\n return;\n }\n\n // Initialize dictation service.\n try {\n this._dictationService = new DictationService(this._mediaStream, params);\n\n // Forward custom events from dictation service\n this._dictationService.addEventListener('error', e => {\n this.dispatchEvent(\n new CustomEvent('error', {\n detail: (e as CustomEvent).detail,\n bubbles: true,\n composed: true,\n }),\n );\n this.stopRecording();\n });\n this._dictationService.addEventListener('stream-closed', (e) => {\n this.dispatchEvent(\n new CustomEvent('stream-closed', {\n detail: (e as CustomEvent).detail,\n bubbles: true,\n composed: true,\n }),\n );\n this.stopRecording();\n });\n this._dictationService.addEventListener('transcript', e =>\n this.dispatchEvent(\n new CustomEvent('transcript', {\n detail: (e as CustomEvent).detail,\n bubbles: true,\n composed: true,\n }),\n ),\n );\n this._dictationService.addEventListener('command', e =>\n this.dispatchEvent(\n new CustomEvent('command', {\n detail: (e as CustomEvent).detail,\n bubbles: true,\n composed: true,\n }),\n ),\n );\n this._dictationService.addEventListener('usage', e =>\n this.dispatchEvent(\n new CustomEvent('usage', {\n detail: (e as CustomEvent).detail,\n bubbles: true,\n composed: true,\n }),\n ),\n );\n } catch (error) {\n this.dispatchCustomEvent('error', error);\n this.stopRecording();\n return;\n }\n\n try {\n this._dictationService?.startRecording();\n } catch (error) {\n this.dispatchEvent(\n new CustomEvent('error', {\n detail: error,\n bubbles: true,\n composed: true,\n }),\n );\n this.stopRecording();\n return;\n }\n this._updateRecordingState('recording');\n\n // Update audio level visualization.\n this._visualiserInterval = window.setInterval(() => {\n const level = this._audioService\n ? this._audioService.getAudioLevel() * 3\n : 0;\n this.dispatchCustomEvent('audio-level-changed', { audioLevel: level });\n }, 150);\n }\n\n async stopRecording() {\n this._updateRecordingState('stopping');\n if (this._visualiserInterval) {\n clearInterval(this._visualiserInterval);\n this._visualiserInterval = undefined;\n }\n if (this._mediaStream) {\n this._mediaStream.getTracks().forEach(track => track.stop());\n }\n this.dispatchCustomEvent('audio-level-changed', { audioLevel: 0 });\n await this._dictationService?.stopRecording();\n this._updateRecordingState('stopped');\n }\n\n private _updateRecordingState(state: RecordingState) {\n this.recordingState = state;\n this.dispatchEvent(\n new CustomEvent('recording-state-changed', {\n detail: { state },\n bubbles: true,\n composed: true,\n }),\n );\n }\n}\n"]}
@@ -0,0 +1,6 @@
1
+ export declare class AudioService {
2
+ private audioContext;
3
+ private analyser;
4
+ constructor(mediaStream: MediaStream);
5
+ getAudioLevel(): number;
6
+ }
@@ -0,0 +1,21 @@
1
+ export class AudioService {
2
+ constructor(mediaStream) {
3
+ this.audioContext = new AudioContext();
4
+ const source = this.audioContext.createMediaStreamSource(mediaStream);
5
+ this.analyser = this.audioContext.createAnalyser();
6
+ this.analyser.fftSize = 8192;
7
+ source.connect(this.analyser);
8
+ }
9
+ getAudioLevel() {
10
+ const bufferLength = this.analyser.fftSize;
11
+ const dataArray = new Uint8Array(bufferLength);
12
+ this.analyser.getByteTimeDomainData(dataArray);
13
+ let sum = 0;
14
+ for (let i = 0; i < bufferLength; i += 1) {
15
+ const normalized = (dataArray[i] - 128) / 128;
16
+ sum += normalized * normalized;
17
+ }
18
+ return Math.sqrt(sum / bufferLength);
19
+ }
20
+ }
21
+ //# sourceMappingURL=audioService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audioService.js","sourceRoot":"","sources":["../src/audioService.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,YAAY;IAKvB,YAAY,WAAwB;QAClC,IAAI,CAAC,YAAY,GAAG,IAAI,YAAY,EAAE,CAAC;QACvC,MAAM,MAAM,GAAG,IAAI,CAAC,YAAY,CAAC,uBAAuB,CAAC,WAAW,CAAC,CAAC;QACtE,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,YAAY,CAAC,cAAc,EAAE,CAAC;QACnD,IAAI,CAAC,QAAQ,CAAC,OAAO,GAAG,IAAI,CAAC;QAC7B,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAChC,CAAC;IAEM,aAAa;QAClB,MAAM,YAAY,GAAG,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC;QAC3C,MAAM,SAAS,GAAG,IAAI,UAAU,CAAC,YAAY,CAAC,CAAC;QAC/C,IAAI,CAAC,QAAQ,CAAC,qBAAqB,CAAC,SAAS,CAAC,CAAC;QAC/C,IAAI,GAAG,GAAG,CAAC,CAAC;QACZ,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,YAAY,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;YACzC,MAAM,UAAU,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC;YAC9C,GAAG,IAAI,UAAU,GAAG,UAAU,CAAC;QACjC,CAAC;QACD,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,GAAG,YAAY,CAAC,CAAC;IACvC,CAAC;CACF","sourcesContent":["export class AudioService {\n private audioContext: AudioContext;\n\n private analyser: AnalyserNode;\n\n constructor(mediaStream: MediaStream) {\n this.audioContext = new AudioContext();\n const source = this.audioContext.createMediaStreamSource(mediaStream);\n this.analyser = this.audioContext.createAnalyser();\n this.analyser.fftSize = 8192;\n source.connect(this.analyser);\n }\n\n public getAudioLevel(): number {\n const bufferLength = this.analyser.fftSize;\n const dataArray = new Uint8Array(bufferLength);\n this.analyser.getByteTimeDomainData(dataArray);\n let sum = 0;\n for (let i = 0; i < bufferLength; i += 1) {\n const normalized = (dataArray[i] - 128) / 128;\n sum += normalized * normalized;\n }\n return Math.sqrt(sum / bufferLength);\n }\n}\n"]}