@corti/dictation-web 0.1.5 → 0.1.7

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/README.md CHANGED
@@ -1,101 +1,108 @@
1
- # Corti Dictation SDK
2
-
3
- ## Overview
4
-
5
- The **Corti Dictation SDK** is a web component that enables real-time speech-to-text dictation using Corti's Dictation API. It provides a simple interface for capturing audio, streaming it to the API, and handling transcripts.
6
-
7
- > **Note:** OAuth 2.0 authentication is not handled by this SDK. The client must provide an API key or authorization token before using the component.
8
-
9
- ## Installation
10
-
11
- Include the SDK in your project by importing the JavaScript module:
12
-
13
- ```html
14
- npm i @corti/dictation-web
15
- ```
16
-
17
- ```html
18
- <script
19
- src="https://cdn.jsdelivr.net/npm/@corti/dictation-web/dist/bundle.min.js"
20
- preload
21
- type="module"
22
- ></script>
23
- ```
24
-
25
- ## Usage
26
-
27
- ### Demo
28
-
29
- 🚀 [Hosted Demo](https://codepen.io/hccullen/pen/OPJmxQR)
30
-
31
- ### Basic Example
32
-
33
- ```html
34
- <!DOCTYPE html>
35
- <html lang="en">
36
- <body>
37
- <script
38
- src="https://cdn.jsdelivr.net/npm/@corti/dictation-web/dist/bundle.min.js"
39
- preload
40
- type="module"
41
- ></script>
42
- <corti-dictation authToken="xyz"></corti-dictation>
43
- <textarea
44
- id="transcript"
45
- placeholder="Transcript will appear here..."
46
- ></textarea>
47
-
48
- <script>
49
- const dictationEl = document.getElementById('transcript');
50
- // Listen for events
51
- dictationEl.addEventListener('transcript', e => {
52
- document.getElementById('transcript').value += e.detail.data.text + ' ';
53
- });
54
- </script>
55
- </body>
56
- </html>
57
- ```
58
-
59
- ## API Reference
60
-
61
- ### Properties
62
-
63
- | Property | Type | Description |
64
- | ----------------- | ------ | ---------------------------------------------------- |
65
- | `devices` | Array | List of available recording devices. |
66
- | `recordingState` | String | Current state of recording (`stopped`, `recording`). |
67
- | `dictationConfig` | Object | Configuration settings for dictation. |
68
- | `authToken` | String | Authentication token from OAuth server |
69
-
70
- ### Methods
71
-
72
- | Method | Description |
73
- | ------------------- | -------------------------- |
74
- | `toggleRecording()` | Starts or stops recording. |
75
-
76
- ### Events
77
-
78
- | Event | Description |
79
- | -------------------------- | --------------------------------------------------------------------------------------------- |
80
- | `recording-state-changed` | Fired when the recording state changes. `detail.state` contains the new state. |
81
- | `recording-device-changed` | Fired when the user switches recording devices. `detail.deviceId` contains the new device ID. |
82
- | `transcript` | Fired when a new transcript is received. `detail.data.text` contains the transcribed text. |
83
- | `audio-level-changed` | Fired when the input audio level changes. `detail.audioLevel` contains the new level. |
84
- | `error` | Fired on error. `detail` contains the full error. |
85
-
86
- ## Authentication
87
-
88
- This SDK does not handle OAuth 2.0 authentication. The client must provide an API key or access token as a string in `authToken`.
89
-
90
- ## Notes
91
-
92
- - Works in modern browsers that support Web Components and MediaRecorder API.
93
- - Supports dark and light mode based on browser preference.
94
-
95
- ## License
96
-
97
- This SDK is not licensed.
98
-
99
- ## Support
100
-
101
- For issues or questions, contact **Corti Support** at [support@corti.ai](mailto:support@corti.ai).
1
+ # Corti Dictation SDK
2
+
3
+ ## Overview
4
+
5
+ The **Corti Dictation SDK** is a web component that enables real-time speech-to-text dictation using Corti's Dictation API. It provides a simple interface for capturing audio, streaming it to the API, and handling transcripts.
6
+
7
+ > **Note:** OAuth 2.0 authentication is not handled by this SDK. The client must provide an API key or authorization token before using the component.
8
+
9
+ ## Installation
10
+
11
+ Include the SDK in your project by importing the JavaScript module:
12
+
13
+ ```html
14
+ npm i @corti/dictation-web
15
+ ```
16
+
17
+ Alternatively, use a CDN to start quickly (not recommended).
18
+
19
+ ```html
20
+ <script
21
+ src="https://cdn.jsdelivr.net/npm/@corti/dictation-web/dist/bundle.min.js"
22
+ preload
23
+ type="module"
24
+ ></script>
25
+ ```
26
+
27
+ ## Usage
28
+
29
+ ### Demo
30
+
31
+ 🚀 [Hosted Demo](https://codepen.io/hccullen/pen/OPJmxQR)
32
+
33
+ ### Basic Example
34
+
35
+ ```html
36
+ <!DOCTYPE html>
37
+ <html lang="en">
38
+ <body>
39
+ <script
40
+ src="https://cdn.jsdelivr.net/npm/@corti/dictation-web/dist/bundle.min.js"
41
+ preload
42
+ type="module"
43
+ ></script>
44
+ <corti-dictation></corti-dictation>
45
+ <textarea
46
+ id="transcript"
47
+ placeholder="Transcript will appear here..."
48
+ ></textarea>
49
+
50
+ <script>
51
+ const dictation = document.getElementById('transcript');
52
+ dictation.authToken = "YOUR_AUTH_TOKEN" // Note: Never hardcode tokens
53
+ // Listen for events
54
+ dictationEl.addEventListener('transcript', e => {
55
+ document.getElementById('transcript').value += e.detail.data.text + ' ';
56
+ });
57
+ </script>
58
+ </body>
59
+ </html>
60
+ ```
61
+
62
+ ## API Reference
63
+
64
+ ### Properties
65
+
66
+ | Property | Type | Description |
67
+ | ----------------- | ------ | ---------------------------------------------------- |
68
+ | `devices` | Array | List of available recording devices. |
69
+ | `selectedDevice` | Object | The selected device used for recording (MediaDeviceInfo). |
70
+ | `recordingState` | String | Current state of recording (`stopped`, `recording`, `initializing` and `stopping`, ). |
71
+ | `dictationConfig` | Object | Configuration settings for dictation. |
72
+ | `authToken` | String | Authentication token from OAuth server |
73
+ | `debug_displayAudio` | Boolean | Overrides any device selection and instead uses getDisplayMedia to stream system audio. SHould only be used for debugging |
74
+
75
+ ### Methods
76
+
77
+ | Method | Description |
78
+ | ------------------- | -------------------------- |
79
+ | `toggleRecording()` | Starts or stops recording. |
80
+
81
+ ### Events
82
+
83
+ | Event | Description |
84
+ | -------------------------- | --------------------------------------------------------------------------------------------- |
85
+ | `ready` | Fired once the component is ready. |
86
+ | `recording-state-changed` | Fired when the recording state changes. `detail.state` contains the new state. |
87
+ | `recording-devices-changed` | Fired when the user switches recording devices or the list of recording devices changes. `detail.devices` contains the full devices list. `detail.selectedDevice` contains the current selected device. |
88
+ | `transcript` | Fired when a new transcript is received. `detail.data.text` contains the transcribed text. |
89
+ | `command` | Fired whenever a new command is detected. |
90
+ | `audio-level-changed` | Fired when the input audio level changes. `detail.audioLevel` contains the new level. |
91
+ | `error` | Fired on error. `detail` contains the full error. |
92
+
93
+ ## Authentication
94
+
95
+ This SDK does not handle OAuth 2.0 authentication. The client must provide an API key or access token as a string in `authToken`.
96
+
97
+ ## Notes
98
+
99
+ - Works in modern browsers that support Web Components and MediaRecorder API.
100
+ - Supports dark and light mode based on browser preference.
101
+
102
+ ## License
103
+
104
+ This SDK is not licensed.
105
+
106
+ ## Support
107
+
108
+ For issues or questions, contact **Corti Support** at [support@corti.ai](mailto:support@corti.ai).
@@ -2,19 +2,25 @@ import { LitElement } from 'lit';
2
2
  import './components/settings-menu.js';
3
3
  import './components/audio-visualiser.js';
4
4
  import './icons/icons.js';
5
- import type { DictationConfig } from './types.js';
5
+ import type { DictationConfig, RecordingState } from './types.js';
6
6
  export declare class CortiDictation extends LitElement {
7
7
  static styles: import("lit").CSSResult[];
8
- devices: MediaDeviceInfo[];
9
- recordingState: string;
10
8
  dictationConfig: DictationConfig;
11
9
  authToken: string | undefined;
10
+ debug_displayAudio: boolean;
12
11
  private _audioLevel;
12
+ private _recordingState;
13
+ private _selectedDevice;
14
+ private _devices;
13
15
  private recorderManager;
14
16
  connectedCallback(): Promise<void>;
15
17
  toggleRecording(): void;
18
+ get selectedDevice(): MediaDeviceInfo | null;
19
+ get recordingState(): RecordingState;
20
+ get devices(): MediaDeviceInfo[];
21
+ setRecordingDevice(device: MediaDeviceInfo): Promise<void>;
16
22
  _toggleRecording(): void;
17
- _onRecordingDeviceChanged(event: Event): Promise<void>;
23
+ _onRecordingDevicesChanged(event: Event): Promise<void>;
18
24
  render(): import("lit-html").TemplateResult<1>;
19
25
  }
20
26
  export default CortiDictation;
@@ -19,20 +19,29 @@ import CalloutStyles from './styles/callout.js';
19
19
  export class CortiDictation extends LitElement {
20
20
  constructor() {
21
21
  super(...arguments);
22
- this.devices = [];
23
- this.recordingState = 'stopped';
24
22
  this.dictationConfig = DEFAULT_DICTATION_CONFIG;
23
+ this.debug_displayAudio = false;
25
24
  this._audioLevel = 0;
25
+ this._recordingState = 'stopped';
26
+ this._devices = [];
26
27
  this.recorderManager = new RecorderManager();
27
28
  }
28
29
  async connectedCallback() {
29
30
  super.connectedCallback();
30
- await this.recorderManager.initialize();
31
- this.devices = this.recorderManager.devices;
31
+ const devices = await this.recorderManager.initialize();
32
+ if (devices.selectedDevice) {
33
+ this._selectedDevice = this.recorderManager.selectedDevice;
34
+ this._devices = this.recorderManager.devices;
35
+ this.dispatchEvent(new CustomEvent('ready'));
36
+ }
32
37
  // Map event names to any extra handling logic
33
38
  const eventHandlers = {
34
39
  'recording-state-changed': e => {
35
- this.recordingState = e.detail.state;
40
+ this._recordingState = e.detail.state;
41
+ },
42
+ 'devices-changed': () => {
43
+ this._devices = [...this.recorderManager.devices];
44
+ this.requestUpdate();
36
45
  },
37
46
  'audio-level-changed': e => {
38
47
  this._audioLevel = e.detail.audioLevel;
@@ -41,9 +50,12 @@ export class CortiDictation extends LitElement {
41
50
  };
42
51
  const eventsToRelay = [
43
52
  'recording-state-changed',
53
+ 'recording-devices-changed',
44
54
  'audio-level-changed',
45
55
  'error',
46
56
  'transcript',
57
+ 'command',
58
+ 'ready',
47
59
  ];
48
60
  eventsToRelay.forEach(eventName => {
49
61
  this.recorderManager.addEventListener(eventName, (e) => {
@@ -64,89 +76,106 @@ export class CortiDictation extends LitElement {
64
76
  toggleRecording() {
65
77
  this._toggleRecording();
66
78
  }
67
- _toggleRecording() {
79
+ get selectedDevice() {
80
+ return this.recorderManager.selectedDevice || null;
81
+ }
82
+ get recordingState() {
83
+ return this._recordingState;
84
+ }
85
+ get devices() {
86
+ return this._devices;
87
+ }
88
+ async setRecordingDevice(device) {
89
+ this.recorderManager.selectedDevice = device;
90
+ this._selectedDevice = device;
68
91
  if (!this.authToken)
69
92
  return;
70
- if (this.recordingState === 'recording') {
71
- this.recorderManager.stopRecording();
72
- }
73
- else if (this.recordingState === 'stopped') {
74
- this.recorderManager.startRecording({
93
+ if (this._recordingState === 'recording') {
94
+ await this.recorderManager.stopRecording();
95
+ await this.recorderManager.startRecording({
75
96
  dictationConfig: this.dictationConfig,
76
97
  authToken: this.authToken,
77
98
  });
78
99
  }
79
100
  }
80
- // Handle device change events if needed
81
- async _onRecordingDeviceChanged(event) {
82
- const customEvent = event;
101
+ _toggleRecording() {
83
102
  if (!this.authToken)
84
103
  return;
85
- this.recorderManager.selectedDevice = customEvent.detail.deviceId;
86
- if (this.recordingState === 'recording') {
87
- await this.recorderManager.stopRecording();
88
- await this.recorderManager.startRecording({
104
+ if (this._recordingState === 'recording') {
105
+ this.recorderManager.stopRecording();
106
+ }
107
+ else if (this._recordingState === 'stopped') {
108
+ this.recorderManager.startRecording({
89
109
  dictationConfig: this.dictationConfig,
90
110
  authToken: this.authToken,
111
+ debug_displayAudio: this.debug_displayAudio,
91
112
  });
92
113
  }
93
114
  }
115
+ // Handle device change events if needed
116
+ async _onRecordingDevicesChanged(event) {
117
+ const customEvent = event;
118
+ this.setRecordingDevice(customEvent.detail.selectedDevice);
119
+ }
94
120
  render() {
95
121
  const isConfigured = this.authToken;
96
122
  if (!isConfigured) {
97
- return html `
98
- <div class="wrapper">
99
- <div class="callout red tiny">
100
- Please configure the server settings in the parent component.
101
- </div>
102
- </div>
123
+ return html `
124
+ <div class="wrapper">
125
+ <div class="callout red small">No Auth Token</div>
126
+ </div>
103
127
  `;
104
128
  }
105
- const isLoading = this.recordingState === 'initializing' ||
106
- this.recordingState === 'stopping';
107
- const isRecording = this.recordingState === 'recording';
108
- return html `
109
- <div class="wrapper">
110
- <button
111
- @click=${this._toggleRecording}
112
- class=${isRecording ? 'red' : 'accent'}
113
- >
129
+ const isLoading = this._recordingState === 'initializing' ||
130
+ this._recordingState === 'stopping';
131
+ const isRecording = this._recordingState === 'recording';
132
+ return html `
133
+ <div class="wrapper">
134
+ <button
135
+ @click=${this._toggleRecording}
136
+ class=${isRecording ? 'red' : 'accent'}
137
+ >
114
138
  ${isLoading
115
139
  ? html `<icon-loading-spinner></icon-loading-spinner>`
116
140
  : isRecording
117
141
  ? html `<icon-recording></icon-recording>`
118
- : html `<icon-mic-on></icon-mic-on>`}
119
- <audio-visualiser
120
- .level=${this._audioLevel}
121
- .active=${isRecording}
122
- ></audio-visualiser>
123
- </button>
124
-
125
- <settings-menu
126
- .devices=${this.devices}
127
- .selectedDevice=${this.recorderManager.selectedDevice}
128
- ?settingsDisabled=${this.recordingState !== 'stopped'}
129
- @recording-device-changed=${this._onRecordingDeviceChanged}
130
- ></settings-menu>
131
- </div>
142
+ : html `<icon-mic-on></icon-mic-on>`}
143
+ <audio-visualiser
144
+ .level=${this._audioLevel}
145
+ .active=${isRecording}
146
+ ></audio-visualiser>
147
+ </button>
148
+
149
+ <settings-menu
150
+ .selectedDevice=${this._selectedDevice}
151
+ ?settingsDisabled=${this._recordingState !== 'stopped'}
152
+ @recording-devices-changed=${this._onRecordingDevicesChanged}
153
+ ></settings-menu>
154
+ </div>
132
155
  `;
133
156
  }
134
157
  }
135
158
  CortiDictation.styles = [ButtonStyles, ThemeStyles, ComponentStyles, CalloutStyles];
136
- __decorate([
137
- property({ type: Array })
138
- ], CortiDictation.prototype, "devices", void 0);
139
- __decorate([
140
- property({ type: String, reflect: true })
141
- ], CortiDictation.prototype, "recordingState", void 0);
142
159
  __decorate([
143
160
  property({ type: Object })
144
161
  ], CortiDictation.prototype, "dictationConfig", void 0);
145
162
  __decorate([
146
163
  property({ type: String })
147
164
  ], CortiDictation.prototype, "authToken", void 0);
165
+ __decorate([
166
+ property({ type: Boolean })
167
+ ], CortiDictation.prototype, "debug_displayAudio", void 0);
148
168
  __decorate([
149
169
  state()
150
170
  ], CortiDictation.prototype, "_audioLevel", void 0);
171
+ __decorate([
172
+ state()
173
+ ], CortiDictation.prototype, "_recordingState", void 0);
174
+ __decorate([
175
+ state()
176
+ ], CortiDictation.prototype, "_selectedDevice", void 0);
177
+ __decorate([
178
+ state()
179
+ ], CortiDictation.prototype, "_devices", void 0);
151
180
  export default CortiDictation;
152
181
  //# sourceMappingURL=CortiDictation.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"CortiDictation.js","sourceRoot":"","sources":["../src/CortiDictation.ts"],"names":[],"mappings":"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","sourcesContent":["// corti-dictation.ts\nimport { html, LitElement } from 'lit';\nimport { property, state } from 'lit/decorators.js';\nimport { RecorderManager } from './RecorderManager.js';\nimport './components/settings-menu.js';\nimport './components/audio-visualiser.js';\nimport './icons/icons.js';\nimport ThemeStyles from './styles/theme.js';\nimport ButtonStyles from './styles/buttons.js';\nimport ComponentStyles from './styles/ComponentStyles.js';\n\nimport type { DictationConfig } from './types.js';\nimport { DEFAULT_DICTATION_CONFIG } from './constants.js';\nimport CalloutStyles from './styles/callout.js';\n\nexport class CortiDictation extends LitElement {\n static styles = [ButtonStyles, ThemeStyles, ComponentStyles, CalloutStyles];\n\n @property({ type: Array })\n devices: MediaDeviceInfo[] = [];\n\n @property({ type: String, reflect: true })\n recordingState = 'stopped';\n\n @property({ type: Object })\n dictationConfig: DictationConfig = DEFAULT_DICTATION_CONFIG;\n\n @property({ type: String })\n authToken: string | undefined;\n\n @state()\n private _audioLevel = 0;\n\n private recorderManager = new RecorderManager();\n\n async connectedCallback() {\n super.connectedCallback();\n await this.recorderManager.initialize();\n this.devices = this.recorderManager.devices;\n\n // Map event names to any extra handling logic\n const eventHandlers: Record<string, (e: CustomEvent) => void> = {\n 'recording-state-changed': e => {\n this.recordingState = e.detail.state;\n },\n 'audio-level-changed': e => {\n this._audioLevel = e.detail.audioLevel;\n this.requestUpdate();\n },\n };\n\n const eventsToRelay = [\n 'recording-state-changed',\n 'audio-level-changed',\n 'error',\n 'transcript',\n ];\n\n eventsToRelay.forEach(eventName => {\n this.recorderManager.addEventListener(eventName, (e: Event) => {\n const customEvent = e as CustomEvent;\n // Perform any additional handling if defined\n if (eventHandlers[eventName]) {\n eventHandlers[eventName](customEvent);\n }\n // Re-dispatch the event from the component\n this.dispatchEvent(\n new CustomEvent(eventName, {\n detail: customEvent.detail,\n bubbles: true,\n composed: true,\n }),\n );\n });\n });\n }\n\n public toggleRecording() {\n this._toggleRecording();\n }\n\n _toggleRecording() {\n if (!this.authToken) return;\n if (this.recordingState === 'recording') {\n this.recorderManager.stopRecording();\n } else if (this.recordingState === 'stopped') {\n this.recorderManager.startRecording({\n dictationConfig: this.dictationConfig,\n authToken: this.authToken,\n });\n }\n }\n\n // Handle device change events if needed\n async _onRecordingDeviceChanged(event: Event) {\n const customEvent = event as CustomEvent;\n if (!this.authToken) return;\n this.recorderManager.selectedDevice = customEvent.detail.deviceId;\n if (this.recordingState === 'recording') {\n await this.recorderManager.stopRecording();\n await this.recorderManager.startRecording({\n dictationConfig: this.dictationConfig,\n authToken: this.authToken,\n });\n }\n }\n\n render() {\n const isConfigured = this.authToken;\n if (!isConfigured) {\n return html`\n <div class=\"wrapper\">\n <div class=\"callout red tiny\">\n Please configure the server settings in the parent component.\n </div>\n </div>\n `;\n }\n\n const isLoading =\n this.recordingState === 'initializing' ||\n this.recordingState === 'stopping';\n const isRecording = this.recordingState === 'recording';\n return html`\n <div class=\"wrapper\">\n <button\n @click=${this._toggleRecording}\n class=${isRecording ? 'red' : 'accent'}\n >\n ${isLoading\n ? html`<icon-loading-spinner></icon-loading-spinner>`\n : isRecording\n ? html`<icon-recording></icon-recording>`\n : html`<icon-mic-on></icon-mic-on>`}\n <audio-visualiser\n .level=${this._audioLevel}\n .active=${isRecording}\n ></audio-visualiser>\n </button>\n\n <settings-menu\n .devices=${this.devices}\n .selectedDevice=${this.recorderManager.selectedDevice}\n ?settingsDisabled=${this.recordingState !== 'stopped'}\n @recording-device-changed=${this._onRecordingDeviceChanged}\n ></settings-menu>\n </div>\n `;\n }\n}\n\nexport default CortiDictation;\n"]}
1
+ {"version":3,"file":"CortiDictation.js","sourceRoot":"","sources":["../src/CortiDictation.ts"],"names":[],"mappings":"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","sourcesContent":["// corti-dictation.ts\r\nimport { html, LitElement } from 'lit';\r\nimport { property, state } from 'lit/decorators.js';\r\nimport { RecorderManager } from './RecorderManager.js';\r\nimport './components/settings-menu.js';\r\nimport './components/audio-visualiser.js';\r\nimport './icons/icons.js';\r\nimport ThemeStyles from './styles/theme.js';\r\nimport ButtonStyles from './styles/buttons.js';\r\nimport ComponentStyles from './styles/ComponentStyles.js';\r\n\r\nimport type { DictationConfig, RecordingState } from './types.js';\r\nimport { DEFAULT_DICTATION_CONFIG } from './constants.js';\r\nimport CalloutStyles from './styles/callout.js';\r\n\r\nexport class CortiDictation extends LitElement {\r\n static styles = [ButtonStyles, ThemeStyles, ComponentStyles, CalloutStyles];\r\n\r\n @property({ type: Object })\r\n dictationConfig: DictationConfig = DEFAULT_DICTATION_CONFIG;\r\n\r\n @property({ type: String })\r\n authToken: string | undefined;\r\n\r\n @property({ type: Boolean })\r\n debug_displayAudio: boolean = false;\r\n\r\n @state()\r\n private _audioLevel: number = 0;\r\n\r\n @state()\r\n private _recordingState: RecordingState = 'stopped';\r\n\r\n @state()\r\n private _selectedDevice: MediaDeviceInfo | undefined;\r\n\r\n @state()\r\n private _devices: MediaDeviceInfo[] = [];\r\n\r\n private recorderManager = new RecorderManager();\r\n\r\n async connectedCallback() {\r\n super.connectedCallback();\r\n const devices = await this.recorderManager.initialize();\r\n if (devices.selectedDevice) {\r\n this._selectedDevice = this.recorderManager.selectedDevice;\r\n this._devices = this.recorderManager.devices;\r\n this.dispatchEvent(new CustomEvent('ready'));\r\n }\r\n\r\n // Map event names to any extra handling logic\r\n const eventHandlers: Record<string, (e: CustomEvent) => void> = {\r\n 'recording-state-changed': e => {\r\n this._recordingState = e.detail.state;\r\n },\r\n 'devices-changed': () => {\r\n this._devices = [...this.recorderManager.devices];\r\n this.requestUpdate();\r\n },\r\n 'audio-level-changed': e => {\r\n this._audioLevel = e.detail.audioLevel;\r\n this.requestUpdate();\r\n },\r\n };\r\n\r\n const eventsToRelay = [\r\n 'recording-state-changed',\r\n 'recording-devices-changed',\r\n 'audio-level-changed',\r\n 'error',\r\n 'transcript',\r\n 'command',\r\n 'ready',\r\n ];\r\n\r\n eventsToRelay.forEach(eventName => {\r\n this.recorderManager.addEventListener(eventName, (e: Event) => {\r\n const customEvent = e as CustomEvent;\r\n // Perform any additional handling if defined\r\n if (eventHandlers[eventName]) {\r\n eventHandlers[eventName](customEvent);\r\n }\r\n // Re-dispatch the event from the component\r\n this.dispatchEvent(\r\n new CustomEvent(eventName, {\r\n detail: customEvent.detail,\r\n bubbles: true,\r\n composed: true,\r\n }),\r\n );\r\n });\r\n });\r\n }\r\n\r\n public toggleRecording() {\r\n this._toggleRecording();\r\n }\r\n\r\n public get selectedDevice(): MediaDeviceInfo | null {\r\n return this.recorderManager.selectedDevice || null;\r\n }\r\n\r\n public get recordingState(): RecordingState {\r\n return this._recordingState;\r\n }\r\n\r\n public get devices(): MediaDeviceInfo[] {\r\n return this._devices;\r\n }\r\n\r\n public async setRecordingDevice(device: MediaDeviceInfo) {\r\n this.recorderManager.selectedDevice = device;\r\n this._selectedDevice = device;\r\n if (!this.authToken) return;\r\n if (this._recordingState === 'recording') {\r\n await this.recorderManager.stopRecording();\r\n await this.recorderManager.startRecording({\r\n dictationConfig: this.dictationConfig,\r\n authToken: this.authToken,\r\n });\r\n }\r\n }\r\n\r\n _toggleRecording() {\r\n if (!this.authToken) return;\r\n if (this._recordingState === 'recording') {\r\n this.recorderManager.stopRecording();\r\n } else if (this._recordingState === 'stopped') {\r\n this.recorderManager.startRecording({\r\n dictationConfig: this.dictationConfig,\r\n authToken: this.authToken,\r\n debug_displayAudio: this.debug_displayAudio,\r\n });\r\n }\r\n }\r\n\r\n // Handle device change events if needed\r\n async _onRecordingDevicesChanged(event: Event) {\r\n const customEvent = event as CustomEvent;\r\n this.setRecordingDevice(customEvent.detail.selectedDevice);\r\n }\r\n\r\n render() {\r\n const isConfigured = this.authToken;\r\n if (!isConfigured) {\r\n return html`\r\n <div class=\"wrapper\">\r\n <div class=\"callout red small\">No Auth Token</div>\r\n </div>\r\n `;\r\n }\r\n\r\n const isLoading =\r\n this._recordingState === 'initializing' ||\r\n this._recordingState === 'stopping';\r\n const isRecording = this._recordingState === 'recording';\r\n return html`\r\n <div class=\"wrapper\">\r\n <button\r\n @click=${this._toggleRecording}\r\n class=${isRecording ? 'red' : 'accent'}\r\n >\r\n ${isLoading\r\n ? html`<icon-loading-spinner></icon-loading-spinner>`\r\n : isRecording\r\n ? html`<icon-recording></icon-recording>`\r\n : html`<icon-mic-on></icon-mic-on>`}\r\n <audio-visualiser\r\n .level=${this._audioLevel}\r\n .active=${isRecording}\r\n ></audio-visualiser>\r\n </button>\r\n\r\n <settings-menu\r\n .selectedDevice=${this._selectedDevice}\r\n ?settingsDisabled=${this._recordingState !== 'stopped'}\r\n @recording-devices-changed=${this._onRecordingDevicesChanged}\r\n ></settings-menu>\r\n </div>\r\n `;\r\n }\r\n}\r\n\r\nexport default CortiDictation;\r\n"]}
@@ -4,7 +4,6 @@ export declare class DictationService extends EventTarget {
4
4
  private webSocket;
5
5
  private authToken;
6
6
  private dictationConfig;
7
- private serverConfig;
8
7
  constructor(mediaStream: MediaStream, { dictationConfig, authToken, }: {
9
8
  dictationConfig: DictationConfig;
10
9
  authToken: string;
@@ -10,7 +10,6 @@ export class DictationService extends EventTarget {
10
10
  if (!config) {
11
11
  throw new Error('Invalid token');
12
12
  }
13
- this.serverConfig = config;
14
13
  this.mediaRecorder.ondataavailable = event => {
15
14
  if (this.webSocket?.readyState === WebSocket.OPEN) {
16
15
  this.webSocket.send(event.data);
@@ -25,7 +24,16 @@ export class DictationService extends EventTarget {
25
24
  }));
26
25
  }
27
26
  startRecording() {
28
- const url = `wss://api.${this.serverConfig.environment}.corti.app/audio-bridge/v2/transcribe?tenant-name=${this.serverConfig.tenant}&token=Bearer%20${this.authToken}`;
27
+ const serverConfig = decodeToken(this.authToken);
28
+ if (!serverConfig) {
29
+ this.dispatchEvent(new CustomEvent('error', {
30
+ detail: 'Invalid token',
31
+ bubbles: true,
32
+ composed: true,
33
+ }));
34
+ return;
35
+ }
36
+ const url = `wss://api.${serverConfig.environment}.corti.app/audio-bridge/v2/transcribe?tenant-name=${serverConfig.tenant}&token=Bearer%20${this.authToken}`;
29
37
  this.webSocket = new WebSocket(url);
30
38
  this.webSocket.onopen = () => {
31
39
  this.webSocket.send(JSON.stringify({
@@ -35,11 +43,22 @@ export class DictationService extends EventTarget {
35
43
  };
36
44
  this.webSocket.onmessage = event => {
37
45
  const message = JSON.parse(event.data);
38
- if (message.type === 'config') {
39
- this.mediaRecorder.start(250);
40
- }
41
- else if (message.type === 'transcript') {
42
- this.dispatchCustomEvent('transcript', message);
46
+ switch (message.type) {
47
+ case 'CONFIG_ACCEPTED':
48
+ this.mediaRecorder.start(250);
49
+ break;
50
+ case 'CONFIG_DENIED':
51
+ this.dispatchCustomEvent('error', message);
52
+ return this.stopRecording();
53
+ case 'transcript':
54
+ this.dispatchCustomEvent('transcript', message);
55
+ break;
56
+ case 'command':
57
+ this.dispatchCustomEvent('command', message);
58
+ break;
59
+ default:
60
+ console.warn(`Unhandled message type: ${message.type}`);
61
+ break;
43
62
  }
44
63
  };
45
64
  this.webSocket.onerror = event => {
@@ -50,7 +69,7 @@ export class DictationService extends EventTarget {
50
69
  };
51
70
  }
52
71
  async stopRecording() {
53
- this.mediaRecorder.stop();
72
+ this.mediaRecorder?.stop();
54
73
  if (this.webSocket?.readyState === WebSocket.OPEN) {
55
74
  this.webSocket.send(JSON.stringify({ type: 'end' }));
56
75
  }
@@ -1 +1 @@
1
- {"version":3,"file":"DictationService.js","sourceRoot":"","sources":["../src/DictationService.ts"],"names":[],"mappings":"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","sourcesContent":["import type { DictationConfig, ServerConfig } from './types.js';\nimport { decodeToken } from './utils.js';\n\nexport class DictationService extends EventTarget {\n private mediaRecorder: MediaRecorder;\n private webSocket!: WebSocket;\n private authToken: string;\n private dictationConfig: DictationConfig;\n private serverConfig: ServerConfig;\n\n constructor(\n mediaStream: MediaStream,\n {\n dictationConfig,\n authToken,\n }: { dictationConfig: DictationConfig; authToken: string },\n ) {\n super();\n this.mediaRecorder = new MediaRecorder(mediaStream);\n this.authToken = authToken;\n this.dictationConfig = dictationConfig;\n\n // Decode token during construction.\n const config = decodeToken(this.authToken);\n if (!config) {\n throw new Error('Invalid token');\n }\n this.serverConfig = config;\n\n this.mediaRecorder.ondataavailable = event => {\n if (this.webSocket?.readyState === WebSocket.OPEN) {\n this.webSocket.send(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 startRecording(): void {\n const url = `wss://api.${this.serverConfig.environment}.corti.app/audio-bridge/v2/transcribe?tenant-name=${this.serverConfig.tenant}&token=Bearer%20${this.authToken}`;\n this.webSocket = new WebSocket(url);\n\n this.webSocket.onopen = () => {\n this.webSocket.send(\n JSON.stringify({\n type: 'config',\n configuration: this.dictationConfig,\n }),\n );\n };\n\n this.webSocket.onmessage = event => {\n const message = JSON.parse(event.data);\n if (message.type === 'config') {\n this.mediaRecorder.start(250);\n } else if (message.type === 'transcript') {\n this.dispatchCustomEvent('transcript', message);\n }\n };\n\n this.webSocket.onerror = event => {\n this.dispatchCustomEvent('error', event);\n };\n\n this.webSocket.onclose = 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.send(JSON.stringify({ type: 'end' }));\n }\n\n const timeout = setTimeout(() => {\n if (this.webSocket?.readyState === WebSocket.OPEN) {\n this.webSocket.close();\n }\n }, 10000);\n\n this.webSocket.onclose = () => {\n this.webSocket?.close();\n clearTimeout(timeout);\n };\n }\n}\n"]}
1
+ {"version":3,"file":"DictationService.js","sourceRoot":"","sources":["../src/DictationService.ts"],"names":[],"mappings":"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","sourcesContent":["import type { DictationConfig, ServerConfig } from './types.js';\r\nimport { decodeToken } from './utils.js';\r\n\r\nexport class DictationService extends EventTarget {\r\n private mediaRecorder: MediaRecorder;\r\n private webSocket!: WebSocket;\r\n private authToken: string;\r\n private dictationConfig: DictationConfig;\r\n\r\n constructor(\r\n mediaStream: MediaStream,\r\n {\r\n dictationConfig,\r\n authToken,\r\n }: { dictationConfig: DictationConfig; authToken: string },\r\n ) {\r\n super();\r\n this.mediaRecorder = new MediaRecorder(mediaStream);\r\n this.authToken = authToken;\r\n this.dictationConfig = dictationConfig;\r\n\r\n // Decode token during construction.\r\n const config = decodeToken(this.authToken);\r\n if (!config) {\r\n throw new Error('Invalid token');\r\n }\r\n\r\n this.mediaRecorder.ondataavailable = event => {\r\n if (this.webSocket?.readyState === WebSocket.OPEN) {\r\n this.webSocket.send(event.data);\r\n }\r\n };\r\n }\r\n\r\n private dispatchCustomEvent(eventName: string, detail?: unknown): void {\r\n this.dispatchEvent(\r\n new CustomEvent(eventName, {\r\n detail,\r\n bubbles: true,\r\n composed: true,\r\n }),\r\n );\r\n }\r\n\r\n public startRecording() {\r\n const serverConfig: ServerConfig | undefined = decodeToken(this.authToken);\r\n if (!serverConfig) {\r\n this.dispatchEvent(\r\n new CustomEvent('error', {\r\n detail: 'Invalid token',\r\n bubbles: true,\r\n composed: true,\r\n }),\r\n );\r\n return;\r\n }\r\n\r\n const url = `wss://api.${serverConfig.environment}.corti.app/audio-bridge/v2/transcribe?tenant-name=${serverConfig.tenant}&token=Bearer%20${this.authToken}`;\r\n this.webSocket = new WebSocket(url);\r\n\r\n this.webSocket.onopen = () => {\r\n this.webSocket.send(\r\n JSON.stringify({\r\n type: 'config',\r\n configuration: this.dictationConfig,\r\n }),\r\n );\r\n };\r\n\r\n this.webSocket.onmessage = event => {\r\n const message = JSON.parse(event.data);\r\n switch (message.type) {\r\n case 'CONFIG_ACCEPTED':\r\n this.mediaRecorder.start(250);\r\n break;\r\n case 'CONFIG_DENIED':\r\n this.dispatchCustomEvent('error', message);\r\n return this.stopRecording();\r\n case 'transcript':\r\n this.dispatchCustomEvent('transcript', message);\r\n break;\r\n case 'command':\r\n this.dispatchCustomEvent('command', message);\r\n break;\r\n default:\r\n console.warn(`Unhandled message type: ${message.type}`);\r\n break;\r\n }\r\n };\r\n\r\n this.webSocket.onerror = event => {\r\n this.dispatchCustomEvent('error', event);\r\n };\r\n\r\n this.webSocket.onclose = event => {\r\n this.dispatchCustomEvent('stream-closed', event);\r\n };\r\n }\r\n\r\n public async stopRecording(): Promise<void> {\r\n this.mediaRecorder?.stop();\r\n\r\n if (this.webSocket?.readyState === WebSocket.OPEN) {\r\n this.webSocket.send(JSON.stringify({ type: 'end' }));\r\n }\r\n\r\n const timeout: NodeJS.Timeout = setTimeout(() => {\r\n if (this.webSocket?.readyState === WebSocket.OPEN) {\r\n this.webSocket.close();\r\n }\r\n }, 10000);\r\n\r\n this.webSocket.onclose = () => {\r\n this.webSocket?.close();\r\n clearTimeout(timeout);\r\n };\r\n }\r\n}\r\n"]}
@@ -1,20 +1,23 @@
1
1
  import type { DictationConfig, RecordingState } from './types.js';
2
2
  export declare class RecorderManager extends EventTarget {
3
3
  devices: MediaDeviceInfo[];
4
- selectedDevice: string;
4
+ selectedDevice: MediaDeviceInfo | undefined;
5
5
  recordingState: RecordingState;
6
6
  private _mediaStream;
7
7
  private _audioService;
8
8
  private _dictationService;
9
9
  private _visualiserInterval?;
10
+ constructor();
10
11
  initialize(): Promise<{
11
12
  devices: MediaDeviceInfo[];
12
- defaultDeviceId?: string;
13
+ selectedDevice: MediaDeviceInfo | undefined;
13
14
  }>;
14
15
  private dispatchCustomEvent;
16
+ private handleDevicesChange;
15
17
  startRecording(params: {
16
18
  dictationConfig: DictationConfig;
17
19
  authToken: string;
20
+ debug_displayAudio?: boolean;
18
21
  }): Promise<void>;
19
22
  stopRecording(): Promise<void>;
20
23
  private _updateRecordingState;