@corti/dictation-web 0.1.4 → 0.1.6
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 +54 -39
- package/dist/CortiDictation.d.ts +9 -4
- package/dist/CortiDictation.js +58 -33
- package/dist/CortiDictation.js.map +1 -1
- package/dist/DictationService.js +1 -1
- package/dist/DictationService.js.map +1 -1
- package/dist/RecorderManager.d.ts +4 -2
- package/dist/RecorderManager.js +31 -7
- package/dist/RecorderManager.js.map +1 -1
- package/dist/bundle.js +124 -54
- package/dist/components/settings-menu.d.ts +5 -2
- package/dist/components/settings-menu.js +30 -14
- package/dist/components/settings-menu.js.map +1 -1
- package/dist/constants.js +1 -1
- package/dist/constants.js.map +1 -1
- package/dist/styles/ComponentStyles.js +1 -0
- package/dist/styles/ComponentStyles.js.map +1 -1
- package/dist/styles/callout.js +7 -0
- package/dist/styles/callout.js.map +1 -1
- package/dist/utils.d.ts +1 -1
- package/dist/utils.js +2 -2
- package/dist/utils.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# Corti Dictation SDK
|
|
2
2
|
|
|
3
3
|
## Overview
|
|
4
|
+
|
|
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.
|
|
5
6
|
|
|
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.
|
|
7
8
|
|
|
8
|
-
|
|
9
9
|
## Installation
|
|
10
10
|
|
|
11
11
|
Include the SDK in your project by importing the JavaScript module:
|
|
@@ -14,6 +14,15 @@ Include the SDK in your project by importing the JavaScript module:
|
|
|
14
14
|
npm i @corti/dictation-web
|
|
15
15
|
```
|
|
16
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
|
+
```
|
|
17
26
|
|
|
18
27
|
## Usage
|
|
19
28
|
|
|
@@ -21,74 +30,80 @@ npm i @corti/dictation-web
|
|
|
21
30
|
|
|
22
31
|
🚀 [Hosted Demo](https://codepen.io/hccullen/pen/OPJmxQR)
|
|
23
32
|
|
|
24
|
-
|
|
25
33
|
### Basic Example
|
|
26
34
|
|
|
27
35
|
```html
|
|
28
36
|
<!DOCTYPE html>
|
|
29
37
|
<html lang="en">
|
|
30
|
-
<
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
document.getElementById('transcript')
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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 dictationEl = document.getElementById('transcript');
|
|
52
|
+
|
|
53
|
+
// Set properties
|
|
54
|
+
dictationEl.authToken = "abc"
|
|
55
|
+
|
|
56
|
+
// Listen for events
|
|
57
|
+
dictationEl.addEventListener('transcript', e => {
|
|
58
|
+
document.getElementById('transcript').value += e.detail.data.text + ' ';
|
|
59
|
+
});
|
|
60
|
+
</script>
|
|
61
|
+
</body>
|
|
47
62
|
</html>
|
|
48
63
|
```
|
|
49
64
|
|
|
50
|
-
|
|
51
65
|
## API Reference
|
|
52
66
|
|
|
53
67
|
### Properties
|
|
54
68
|
|
|
55
|
-
| Property
|
|
56
|
-
|
|
57
|
-
| `devices`
|
|
58
|
-
| `
|
|
59
|
-
| `
|
|
60
|
-
| `
|
|
69
|
+
| Property | Type | Description |
|
|
70
|
+
| ----------------- | ------ | ---------------------------------------------------- |
|
|
71
|
+
| `devices` | Array | List of available recording devices. |
|
|
72
|
+
| `selectedDevice` | Object | The selected device used for recording (MediaDeviceInfo). |
|
|
73
|
+
| `recordingState` | String | Current state of recording (`stopped`, `recording`, `initializing` and `stopping`, ). |
|
|
74
|
+
| `dictationConfig` | Object | Configuration settings for dictation. |
|
|
75
|
+
| `authToken` | String | Authentication token from OAuth server |
|
|
61
76
|
|
|
62
77
|
### Methods
|
|
63
78
|
|
|
64
|
-
| Method | Description
|
|
65
|
-
|
|
79
|
+
| Method | Description |
|
|
80
|
+
| ------------------- | -------------------------- |
|
|
66
81
|
| `toggleRecording()` | Starts or stops recording. |
|
|
67
82
|
|
|
68
83
|
### Events
|
|
69
84
|
|
|
70
|
-
| Event | Description
|
|
71
|
-
|
|
72
|
-
| `
|
|
73
|
-
| `recording-
|
|
74
|
-
| `
|
|
75
|
-
| `
|
|
76
|
-
|
|
85
|
+
| Event | Description |
|
|
86
|
+
| -------------------------- | --------------------------------------------------------------------------------------------- |
|
|
87
|
+
| `ready` | Fired once the component is ready. |
|
|
88
|
+
| `recording-state-changed` | Fired when the recording state changes. `detail.state` contains the new state. |
|
|
89
|
+
| `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. |
|
|
90
|
+
| `transcript` | Fired when a new transcript is received. `detail.data.text` contains the transcribed text. |
|
|
91
|
+
| `audio-level-changed` | Fired when the input audio level changes. `detail.audioLevel` contains the new level. |
|
|
92
|
+
| `error` | Fired on error. `detail` contains the full error. |
|
|
77
93
|
|
|
78
94
|
## Authentication
|
|
79
95
|
|
|
80
96
|
This SDK does not handle OAuth 2.0 authentication. The client must provide an API key or access token as a string in `authToken`.
|
|
81
97
|
|
|
82
|
-
|
|
83
98
|
## Notes
|
|
99
|
+
|
|
84
100
|
- Works in modern browsers that support Web Components and MediaRecorder API.
|
|
85
101
|
- Supports dark and light mode based on browser preference.
|
|
86
102
|
|
|
87
|
-
|
|
88
103
|
## License
|
|
89
|
-
This SDK is not licensed.
|
|
90
104
|
|
|
105
|
+
This SDK is not licensed.
|
|
91
106
|
|
|
92
107
|
## Support
|
|
93
|
-
For issues or questions, contact **Corti Support** at [support@corti.ai](mailto:support@corti.ai).
|
|
94
108
|
|
|
109
|
+
For issues or questions, contact **Corti Support** at [support@corti.ai](mailto:support@corti.ai).
|
package/dist/CortiDictation.d.ts
CHANGED
|
@@ -2,19 +2,24 @@ 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;
|
|
12
10
|
private _audioLevel;
|
|
11
|
+
private _recordingState;
|
|
12
|
+
private _selectedDevice;
|
|
13
|
+
private _devices;
|
|
13
14
|
private recorderManager;
|
|
14
15
|
connectedCallback(): Promise<void>;
|
|
15
16
|
toggleRecording(): void;
|
|
17
|
+
get selectedDevice(): MediaDeviceInfo | null;
|
|
18
|
+
get recordingState(): RecordingState;
|
|
19
|
+
get devices(): MediaDeviceInfo[];
|
|
20
|
+
setRecordingDevice(device: MediaDeviceInfo): Promise<void>;
|
|
16
21
|
_toggleRecording(): void;
|
|
17
|
-
|
|
22
|
+
_onRecordingDevicesChanged(event: Event): Promise<void>;
|
|
18
23
|
render(): import("lit-html").TemplateResult<1>;
|
|
19
24
|
}
|
|
20
25
|
export default CortiDictation;
|
package/dist/CortiDictation.js
CHANGED
|
@@ -19,20 +19,28 @@ 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;
|
|
25
23
|
this._audioLevel = 0;
|
|
24
|
+
this._recordingState = 'stopped';
|
|
25
|
+
this._devices = [];
|
|
26
26
|
this.recorderManager = new RecorderManager();
|
|
27
27
|
}
|
|
28
28
|
async connectedCallback() {
|
|
29
29
|
super.connectedCallback();
|
|
30
|
-
await this.recorderManager.initialize();
|
|
31
|
-
|
|
30
|
+
const devices = await this.recorderManager.initialize();
|
|
31
|
+
if (devices.selectedDevice) {
|
|
32
|
+
this._selectedDevice = this.recorderManager.selectedDevice;
|
|
33
|
+
this._devices = this.recorderManager.devices;
|
|
34
|
+
this.dispatchEvent(new CustomEvent('ready'));
|
|
35
|
+
}
|
|
32
36
|
// Map event names to any extra handling logic
|
|
33
37
|
const eventHandlers = {
|
|
34
38
|
'recording-state-changed': e => {
|
|
35
|
-
this.
|
|
39
|
+
this._recordingState = e.detail.state;
|
|
40
|
+
},
|
|
41
|
+
'devices-changed': () => {
|
|
42
|
+
this._devices = [...this.recorderManager.devices];
|
|
43
|
+
this.requestUpdate();
|
|
36
44
|
},
|
|
37
45
|
'audio-level-changed': e => {
|
|
38
46
|
this._audioLevel = e.detail.audioLevel;
|
|
@@ -41,9 +49,11 @@ export class CortiDictation extends LitElement {
|
|
|
41
49
|
};
|
|
42
50
|
const eventsToRelay = [
|
|
43
51
|
'recording-state-changed',
|
|
52
|
+
'recording-devices-changed',
|
|
44
53
|
'audio-level-changed',
|
|
45
54
|
'error',
|
|
46
55
|
'transcript',
|
|
56
|
+
'ready',
|
|
47
57
|
];
|
|
48
58
|
eventsToRelay.forEach(eventName => {
|
|
49
59
|
this.recorderManager.addEventListener(eventName, (e) => {
|
|
@@ -64,47 +74,60 @@ export class CortiDictation extends LitElement {
|
|
|
64
74
|
toggleRecording() {
|
|
65
75
|
this._toggleRecording();
|
|
66
76
|
}
|
|
67
|
-
|
|
77
|
+
get selectedDevice() {
|
|
78
|
+
return this.recorderManager.selectedDevice || null;
|
|
79
|
+
}
|
|
80
|
+
get recordingState() {
|
|
81
|
+
return this._recordingState;
|
|
82
|
+
}
|
|
83
|
+
get devices() {
|
|
84
|
+
return this._devices;
|
|
85
|
+
}
|
|
86
|
+
async setRecordingDevice(device) {
|
|
87
|
+
this.recorderManager.selectedDevice = device;
|
|
88
|
+
this._selectedDevice = device;
|
|
68
89
|
if (!this.authToken)
|
|
69
90
|
return;
|
|
70
|
-
if (this.
|
|
71
|
-
this.recorderManager.stopRecording();
|
|
72
|
-
|
|
73
|
-
else if (this.recordingState === 'stopped') {
|
|
74
|
-
this.recorderManager.startRecording({
|
|
91
|
+
if (this._recordingState === 'recording') {
|
|
92
|
+
await this.recorderManager.stopRecording();
|
|
93
|
+
await this.recorderManager.startRecording({
|
|
75
94
|
dictationConfig: this.dictationConfig,
|
|
76
95
|
authToken: this.authToken,
|
|
77
96
|
});
|
|
78
97
|
}
|
|
79
98
|
}
|
|
80
|
-
|
|
81
|
-
async _onRecordingDeviceChanged(event) {
|
|
82
|
-
const customEvent = event;
|
|
99
|
+
_toggleRecording() {
|
|
83
100
|
if (!this.authToken)
|
|
84
101
|
return;
|
|
85
|
-
this.
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
102
|
+
if (this._recordingState === 'recording') {
|
|
103
|
+
this.recorderManager.stopRecording();
|
|
104
|
+
}
|
|
105
|
+
else if (this._recordingState === 'stopped') {
|
|
106
|
+
this.recorderManager.startRecording({
|
|
89
107
|
dictationConfig: this.dictationConfig,
|
|
90
108
|
authToken: this.authToken,
|
|
91
109
|
});
|
|
92
110
|
}
|
|
93
111
|
}
|
|
112
|
+
// Handle device change events if needed
|
|
113
|
+
async _onRecordingDevicesChanged(event) {
|
|
114
|
+
const customEvent = event;
|
|
115
|
+
this.setRecordingDevice(customEvent.detail.selectedDevice);
|
|
116
|
+
}
|
|
94
117
|
render() {
|
|
95
118
|
const isConfigured = this.authToken;
|
|
96
119
|
if (!isConfigured) {
|
|
97
120
|
return html `
|
|
98
121
|
<div class="wrapper">
|
|
99
|
-
<div class="callout red
|
|
100
|
-
|
|
122
|
+
<div class="callout red small">
|
|
123
|
+
No Auth Token
|
|
101
124
|
</div>
|
|
102
125
|
</div>
|
|
103
126
|
`;
|
|
104
127
|
}
|
|
105
|
-
const isLoading = this.
|
|
106
|
-
this.
|
|
107
|
-
const isRecording = this.
|
|
128
|
+
const isLoading = this._recordingState === 'initializing' ||
|
|
129
|
+
this._recordingState === 'stopping';
|
|
130
|
+
const isRecording = this._recordingState === 'recording';
|
|
108
131
|
return html `
|
|
109
132
|
<div class="wrapper">
|
|
110
133
|
<button
|
|
@@ -123,22 +146,15 @@ export class CortiDictation extends LitElement {
|
|
|
123
146
|
</button>
|
|
124
147
|
|
|
125
148
|
<settings-menu
|
|
126
|
-
.
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
@recording-device-changed=${this._onRecordingDeviceChanged}
|
|
149
|
+
.selectedDevice=${this._selectedDevice}
|
|
150
|
+
?settingsDisabled=${this._recordingState !== 'stopped'}
|
|
151
|
+
@recording-devices-changed=${this._onRecordingDevicesChanged}
|
|
130
152
|
></settings-menu>
|
|
131
153
|
</div>
|
|
132
154
|
`;
|
|
133
155
|
}
|
|
134
156
|
}
|
|
135
157
|
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
158
|
__decorate([
|
|
143
159
|
property({ type: Object })
|
|
144
160
|
], CortiDictation.prototype, "dictationConfig", void 0);
|
|
@@ -148,5 +164,14 @@ __decorate([
|
|
|
148
164
|
__decorate([
|
|
149
165
|
state()
|
|
150
166
|
], CortiDictation.prototype, "_audioLevel", void 0);
|
|
167
|
+
__decorate([
|
|
168
|
+
state()
|
|
169
|
+
], CortiDictation.prototype, "_recordingState", void 0);
|
|
170
|
+
__decorate([
|
|
171
|
+
state()
|
|
172
|
+
], CortiDictation.prototype, "_selectedDevice", void 0);
|
|
173
|
+
__decorate([
|
|
174
|
+
state()
|
|
175
|
+
], CortiDictation.prototype, "_devices", void 0);
|
|
151
176
|
export default CortiDictation;
|
|
152
177
|
//# sourceMappingURL=CortiDictation.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CortiDictation.js","sourceRoot":"","sources":["../src/CortiDictation.ts"],"names":[],"mappings":";;;;;;AAAA,qBAAqB;AACrB,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,KAAK,CAAC;AACvC,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,+BAA+B,CAAC;AACvC,OAAO,kCAAkC,CAAC;AAC1C,OAAO,kBAAkB,CAAC;AAC1B,OAAO,WAAW,MAAM,mBAAmB,CAAC;AAC5C,OAAO,YAAY,MAAM,qBAAqB,CAAC;AAC/C,OAAO,eAAe,MAAM,6BAA6B,CAAC;AAG1D,OAAO,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAC;AAC1D,OAAO,aAAa,MAAM,qBAAqB,CAAC;AAEhD,MAAM,OAAO,cAAe,SAAQ,UAAU;IAA9C;;QAIE,
|
|
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, RecordingState } 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: Object })\n dictationConfig: DictationConfig = DEFAULT_DICTATION_CONFIG;\n \n @property({ type: String })\n authToken: string | undefined;\n \n @state()\n private _audioLevel: number = 0;\n \n @state()\n private _recordingState: RecordingState = 'stopped';\n\n @state()\n private _selectedDevice: MediaDeviceInfo | undefined;\n\n @state()\n private _devices: MediaDeviceInfo[] = [];\n\n\n private recorderManager = new RecorderManager();\n\n async connectedCallback() {\n super.connectedCallback();\n const devices = await this.recorderManager.initialize();\n if(devices.selectedDevice) {\n this._selectedDevice = this.recorderManager.selectedDevice;\n this._devices = this.recorderManager.devices;\n this.dispatchEvent(new CustomEvent('ready'));\n }\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 'devices-changed':() => {\n this._devices = [...this.recorderManager.devices];\n this.requestUpdate();\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 'recording-devices-changed',\n 'audio-level-changed',\n 'error',\n 'transcript',\n 'ready',\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 public get selectedDevice(): MediaDeviceInfo | null {\n return this.recorderManager.selectedDevice || null;\n }\n\n public get recordingState(): RecordingState {\n return this._recordingState;\n }\n\n public get devices(): MediaDeviceInfo[] {\n return this._devices;\n }\n\n public async setRecordingDevice(device: MediaDeviceInfo) {\n this.recorderManager.selectedDevice = device;\n this._selectedDevice = device;\n if (!this.authToken) return;\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 _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 _onRecordingDevicesChanged(event: Event) {\n const customEvent = event as CustomEvent;\n this.setRecordingDevice(customEvent.detail.selectedDevice);\n }\n\n render() {\n const isConfigured = this.authToken;\n if (!isConfigured) {\n return html`\n <div class=\"wrapper\">\n <div class=\"callout red small\">\n No Auth Token\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 .selectedDevice=${this._selectedDevice}\n ?settingsDisabled=${this._recordingState !== 'stopped'}\n @recording-devices-changed=${this._onRecordingDevicesChanged}\n ></settings-menu>\n </div>\n `;\n }\n}\n\nexport default CortiDictation;\n"]}
|
package/dist/DictationService.js
CHANGED
|
@@ -35,7 +35,7 @@ export class DictationService extends EventTarget {
|
|
|
35
35
|
};
|
|
36
36
|
this.webSocket.onmessage = event => {
|
|
37
37
|
const message = JSON.parse(event.data);
|
|
38
|
-
if (message.type === '
|
|
38
|
+
if (message.type === 'CONFIG_ACCEPTED') {
|
|
39
39
|
this.mediaRecorder.start(250);
|
|
40
40
|
}
|
|
41
41
|
else if (message.type === 'transcript') {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DictationService.js","sourceRoot":"","sources":["../src/DictationService.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAEzC,MAAM,OAAO,gBAAiB,SAAQ,WAAW;IAO/C,YACE,WAAwB,EACxB,EACE,eAAe,EACf,SAAS,GAC+C;QAE1D,KAAK,EAAE,CAAC;QACR,IAAI,CAAC,aAAa,GAAG,IAAI,aAAa,CAAC,WAAW,CAAC,CAAC;QACpD,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,IAAI,CAAC,eAAe,GAAG,eAAe,CAAC;QAEvC,oCAAoC;QACpC,MAAM,MAAM,GAAG,WAAW,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAC3C,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,IAAI,KAAK,CAAC,eAAe,CAAC,CAAC;QACnC,CAAC;QACD,IAAI,CAAC,YAAY,GAAG,MAAM,CAAC;QAE3B,IAAI,CAAC,aAAa,CAAC,eAAe,GAAG,KAAK,CAAC,EAAE;YAC3C,IAAI,IAAI,CAAC,SAAS,EAAE,UAAU,KAAK,SAAS,CAAC,IAAI,EAAE,CAAC;gBAClD,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAClC,CAAC;QACH,CAAC,CAAC;IACJ,CAAC;IAEO,mBAAmB,CAAC,SAAiB,EAAE,MAAe;QAC5D,IAAI,CAAC,aAAa,CAChB,IAAI,WAAW,CAAC,SAAS,EAAE;YACzB,MAAM;YACN,OAAO,EAAE,IAAI;YACb,QAAQ,EAAE,IAAI;SACf,CAAC,CACH,CAAC;IACJ,CAAC;IAEM,cAAc;QACnB,MAAM,GAAG,GAAG,aAAa,IAAI,CAAC,YAAY,CAAC,WAAW,qDAAqD,IAAI,CAAC,YAAY,CAAC,MAAM,mBAAmB,IAAI,CAAC,SAAS,EAAE,CAAC;QACvK,IAAI,CAAC,SAAS,GAAG,IAAI,SAAS,CAAC,GAAG,CAAC,CAAC;QAEpC,IAAI,CAAC,SAAS,CAAC,MAAM,GAAG,GAAG,EAAE;YAC3B,IAAI,CAAC,SAAS,CAAC,IAAI,CACjB,IAAI,CAAC,SAAS,CAAC;gBACb,IAAI,EAAE,QAAQ;gBACd,aAAa,EAAE,IAAI,CAAC,eAAe;aACpC,CAAC,CACH,CAAC;QACJ,CAAC,CAAC;QAEF,IAAI,CAAC,SAAS,CAAC,SAAS,GAAG,KAAK,CAAC,EAAE;YACjC,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YACvC,IAAI,OAAO,CAAC,IAAI,KAAK,
|
|
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_ACCEPTED') {\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,17 +1,19 @@
|
|
|
1
1
|
import type { DictationConfig, RecordingState } from './types.js';
|
|
2
2
|
export declare class RecorderManager extends EventTarget {
|
|
3
3
|
devices: MediaDeviceInfo[];
|
|
4
|
-
selectedDevice:
|
|
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
|
-
|
|
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;
|
package/dist/RecorderManager.js
CHANGED
|
@@ -3,19 +3,19 @@ import { AudioService } from './audioService.js';
|
|
|
3
3
|
import { DictationService } from './DictationService.js';
|
|
4
4
|
export class RecorderManager extends EventTarget {
|
|
5
5
|
constructor() {
|
|
6
|
-
super(
|
|
6
|
+
super();
|
|
7
7
|
this.devices = [];
|
|
8
|
-
this.selectedDevice = '';
|
|
9
8
|
this.recordingState = 'stopped';
|
|
10
9
|
this._mediaStream = null;
|
|
11
10
|
this._audioService = null;
|
|
12
11
|
this._dictationService = null;
|
|
12
|
+
navigator.mediaDevices.addEventListener('devicechange', this.handleDevicesChange.bind(this));
|
|
13
13
|
}
|
|
14
14
|
async initialize() {
|
|
15
15
|
const deviceResponse = await getAudioDevices();
|
|
16
16
|
this.devices = deviceResponse.devices;
|
|
17
|
-
this.selectedDevice = deviceResponse.
|
|
18
|
-
return
|
|
17
|
+
this.selectedDevice = deviceResponse.defaultDevice;
|
|
18
|
+
return { devices: this.devices, selectedDevice: this.selectedDevice };
|
|
19
19
|
}
|
|
20
20
|
dispatchCustomEvent(eventName, detail) {
|
|
21
21
|
this.dispatchEvent(new CustomEvent(eventName, {
|
|
@@ -24,12 +24,35 @@ export class RecorderManager extends EventTarget {
|
|
|
24
24
|
composed: true,
|
|
25
25
|
}));
|
|
26
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
|
+
}
|
|
27
38
|
async startRecording(params) {
|
|
28
39
|
this._updateRecordingState('initializing');
|
|
29
|
-
//
|
|
40
|
+
// Choose constraints based on whether the selected device is "default".
|
|
41
|
+
const constraints = this.selectedDevice?.deviceId === 'default'
|
|
42
|
+
? { audio: true }
|
|
43
|
+
: { audio: { deviceId: this.selectedDevice?.deviceId } };
|
|
30
44
|
try {
|
|
31
|
-
this._mediaStream =
|
|
32
|
-
|
|
45
|
+
this._mediaStream =
|
|
46
|
+
await navigator.mediaDevices.getUserMedia(constraints);
|
|
47
|
+
this._mediaStream.getTracks().forEach((track) => {
|
|
48
|
+
track.addEventListener('ended', () => {
|
|
49
|
+
if (this.recordingState === 'recording') {
|
|
50
|
+
this.dispatchCustomEvent('error', {
|
|
51
|
+
message: 'Microphone access was lost.',
|
|
52
|
+
});
|
|
53
|
+
this.stopRecording();
|
|
54
|
+
}
|
|
55
|
+
});
|
|
33
56
|
});
|
|
34
57
|
this._audioService = new AudioService(this._mediaStream);
|
|
35
58
|
}
|
|
@@ -71,6 +94,7 @@ export class RecorderManager extends EventTarget {
|
|
|
71
94
|
this._mediaStream.getTracks().forEach(track => track.stop());
|
|
72
95
|
this._mediaStream = null;
|
|
73
96
|
}
|
|
97
|
+
this.dispatchCustomEvent('audio-level-changed', { audioLevel: 0 });
|
|
74
98
|
await this._dictationService?.stopRecording();
|
|
75
99
|
this._updateRecordingState('stopped');
|
|
76
100
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RecorderManager.js","sourceRoot":"","sources":["../src/RecorderManager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAGzD,MAAM,OAAO,eAAgB,SAAQ,WAAW;
|
|
1
|
+
{"version":3,"file":"RecorderManager.js","sourceRoot":"","sources":["../src/RecorderManager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAGzD,MAAM,OAAO,eAAgB,SAAQ,WAAW;IAe9C;QACE,KAAK,EAAE,CAAC;QAfH,YAAO,GAAsB,EAAE,CAAC;QAIhC,mBAAc,GAAmB,SAAS,CAAC;QAE1C,iBAAY,GAAuB,IAAI,CAAC;QAExC,kBAAa,GAAwB,IAAI,CAAC;QAE1C,sBAAiB,GAA4B,IAAI,CAAC;QAMxD,SAAS,CAAC,YAAY,CAAC,gBAAgB,CACrC,cAAc,EACd,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,CACpC,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,UAAU;QACd,MAAM,cAAc,GAAG,MAAM,eAAe,EAAE,CAAC;QAC/C,IAAI,CAAC,OAAO,GAAG,cAAc,CAAC,OAAO,CAAC;QACtC,IAAI,CAAC,cAAc,GAAG,cAAc,CAAC,aAAa,CAAC;QACnD,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,cAAc,EAAE,IAAI,CAAC,cAAc,EAAE,CAAC;IACxE,CAAC;IAEO,mBAAmB,CAAC,SAAiB,EAAE,MAAgB;QAC7D,IAAI,CAAC,aAAa,CAChB,IAAI,WAAW,CAAC,SAAS,EAAE;YACzB,MAAM;YACN,OAAO,EAAE,IAAI;YACb,QAAQ,EAAE,IAAI;SACf,CAAC,CACH,CAAC;IACJ,CAAC;IAEO,KAAK,CAAC,mBAAmB;QAC/B,MAAM,cAAc,GAAG,MAAM,eAAe,EAAE,CAAC;QAC/C,IAAI,CAAC,OAAO,GAAG,cAAc,CAAC,OAAO,CAAC;QACtC,IACE,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAChB,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,QAAQ,KAAK,IAAI,CAAC,cAAc,EAAE,QAAQ,CAC5D,EACD,CAAC;YACD,IAAI,CAAC,cAAc,GAAG,cAAc,CAAC,aAAa,CAAC;QACrD,CAAC;QACD,IAAI,CAAC,mBAAmB,CAAC,2BAA2B,EAAE;YACpD,OAAO,EAAE,cAAc,CAAC,OAAO;YAC/B,cAAc,EAAE,IAAI,CAAC,cAAc,IAAI,cAAc,CAAC,aAAa;SACpE,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,cAAc,CAAC,MAGpB;QACC,IAAI,CAAC,qBAAqB,CAAC,cAAc,CAAC,CAAC;QAE3C,wEAAwE;QACxE,MAAM,WAAW,GACf,IAAI,CAAC,cAAc,EAAE,QAAQ,KAAK,SAAS;YACzC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE;YACjB,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,QAAQ,EAAE,IAAI,CAAC,cAAc,EAAE,QAAQ,EAAE,EAAE,CAAC;QAE7D,IAAI,CAAC;YACH,IAAI,CAAC,YAAY;gBACf,MAAM,SAAS,CAAC,YAAY,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC;YACzD,IAAI,CAAC,YAAY,CAAC,SAAS,EAAE,CAAC,OAAO,CAAC,CAAC,KAAuB,EAAE,EAAE;gBAChE,KAAK,CAAC,gBAAgB,CAAC,OAAO,EAAE,GAAG,EAAE;oBACnC,IAAI,IAAI,CAAC,cAAc,KAAK,WAAW,EAAE,CAAC;wBACxC,IAAI,CAAC,mBAAmB,CAAC,OAAO,EAAE;4BAChC,OAAO,EAAE,6BAA6B;yBACvC,CAAC,CAAC;wBACH,IAAI,CAAC,aAAa,EAAE,CAAC;oBACvB,CAAC;gBACH,CAAC,CAAC,CAAC;YACL,CAAC,CAAC,CAAC;YACH,IAAI,CAAC,aAAa,GAAG,IAAI,YAAY,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QAC3D,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,mBAAmB,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;YACzC,IAAI,CAAC,qBAAqB,CAAC,SAAS,CAAC,CAAC;YACtC,OAAO;QACT,CAAC;QAED,gCAAgC;QAChC,IAAI,CAAC;YACH,IAAI,CAAC,iBAAiB,GAAG,IAAI,gBAAgB,CAAC,IAAI,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;QAC3E,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,mBAAmB,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;YACzC,IAAI,CAAC,aAAa,EAAE,CAAC;YACrB,OAAO;QACT,CAAC;QAED,oCAAoC;QACpC,IAAI,CAAC,iBAAiB,CAAC,gBAAgB,CAAC,OAAO,EAAE,CAAC,CAAC,EAAE,CACnD,IAAI,CAAC,mBAAmB,CAAC,OAAO,EAAG,CAAiB,CAAC,MAAM,CAAC,CAC7D,CAAC;QACF,IAAI,CAAC,iBAAiB,CAAC,gBAAgB,CAAC,eAAe,EAAE,GAAG,EAAE,CAC5D,IAAI,CAAC,aAAa,EAAE,CACrB,CAAC;QACF,IAAI,CAAC,iBAAiB,CAAC,gBAAgB,CAAC,YAAY,EAAE,CAAC,CAAC,EAAE,CACxD,IAAI,CAAC,mBAAmB,CAAC,YAAY,EAAG,CAAiB,CAAC,MAAM,CAAC,CAClE,CAAC;QAEF,IAAI,CAAC,iBAAiB,CAAC,cAAc,EAAE,CAAC;QACxC,IAAI,CAAC,qBAAqB,CAAC,WAAW,CAAC,CAAC;QAExC,oCAAoC;QACpC,IAAI,CAAC,mBAAmB,GAAG,MAAM,CAAC,WAAW,CAAC,GAAG,EAAE;YACjD,MAAM,KAAK,GAAG,IAAI,CAAC,aAAa;gBAC9B,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,aAAa,EAAE,GAAG,CAAC;gBACxC,CAAC,CAAC,CAAC,CAAC;YACN,IAAI,CAAC,mBAAmB,CAAC,qBAAqB,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,CAAC;QACzE,CAAC,EAAE,GAAG,CAAC,CAAC;IACV,CAAC;IAED,KAAK,CAAC,aAAa;QACjB,IAAI,CAAC,qBAAqB,CAAC,UAAU,CAAC,CAAC;QACvC,IAAI,IAAI,CAAC,mBAAmB,EAAE,CAAC;YAC7B,aAAa,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;YACxC,IAAI,CAAC,mBAAmB,GAAG,SAAS,CAAC;QACvC,CAAC;QACD,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;YACtB,IAAI,CAAC,YAAY,CAAC,SAAS,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;YAC7D,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC;QAC3B,CAAC;QACD,IAAI,CAAC,mBAAmB,CAAC,qBAAqB,EAAE,EAAE,UAAU,EAAE,CAAC,EAAE,CAAC,CAAC;QACnE,MAAM,IAAI,CAAC,iBAAiB,EAAE,aAAa,EAAE,CAAC;QAC9C,IAAI,CAAC,qBAAqB,CAAC,SAAS,CAAC,CAAC;IACxC,CAAC;IAEO,qBAAqB,CAAC,KAAqB;QACjD,IAAI,CAAC,cAAc,GAAG,KAAK,CAAC;QAC5B,IAAI,CAAC,aAAa,CAChB,IAAI,WAAW,CAAC,yBAAyB,EAAE;YACzC,MAAM,EAAE,EAAE,KAAK,EAAE;YACjB,OAAO,EAAE,IAAI;YACb,QAAQ,EAAE,IAAI;SACf,CAAC,CACH,CAAC;IACJ,CAAC;CACF","sourcesContent":["import { getAudioDevices } from './utils.js';\nimport { AudioService } from './audioService.js';\nimport { DictationService } from './DictationService.js';\nimport type { DictationConfig, RecordingState } 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: DictationConfig;\n authToken: string;\n }): Promise<void> {\n this._updateRecordingState('initializing');\n\n // Choose constraints based on whether the selected device is \"default\".\n const constraints =\n this.selectedDevice?.deviceId === 'default'\n ? { audio: true }\n : { audio: { deviceId: this.selectedDevice?.deviceId } };\n\n try {\n this._mediaStream =\n await navigator.mediaDevices.getUserMedia(constraints);\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 this._audioService = new AudioService(this._mediaStream);\n } catch (error) {\n this.dispatchCustomEvent('error', error);\n this._updateRecordingState('stopped');\n return;\n }\n\n // Initialize dictation service.\n try {\n this._dictationService = new DictationService(this._mediaStream, params);\n } catch (error) {\n this.dispatchCustomEvent('error', error);\n this.stopRecording();\n return;\n }\n\n // Forward dictation service events.\n this._dictationService.addEventListener('error', e =>\n this.dispatchCustomEvent('error', (e as CustomEvent).detail),\n );\n this._dictationService.addEventListener('stream-closed', () =>\n this.stopRecording(),\n );\n this._dictationService.addEventListener('transcript', e =>\n this.dispatchCustomEvent('transcript', (e as CustomEvent).detail),\n );\n\n this._dictationService.startRecording();\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 this._mediaStream = null;\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"]}
|
package/dist/bundle.js
CHANGED
|
@@ -623,8 +623,8 @@ async function getAudioDevices() {
|
|
|
623
623
|
try {
|
|
624
624
|
const devices = await navigator.mediaDevices.enumerateDevices();
|
|
625
625
|
const audioDevices = devices.filter((device) => device.kind === "audioinput");
|
|
626
|
-
const
|
|
627
|
-
return { devices: audioDevices,
|
|
626
|
+
const defaultDevice = audioDevices.length > 0 ? audioDevices[0] : void 0;
|
|
627
|
+
return { devices: audioDevices, defaultDevice };
|
|
628
628
|
} catch (error) {
|
|
629
629
|
console.error("Error enumerating devices:", error);
|
|
630
630
|
return { devices: [] };
|
|
@@ -722,7 +722,7 @@ var DictationService = class extends EventTarget {
|
|
|
722
722
|
};
|
|
723
723
|
this.webSocket.onmessage = (event) => {
|
|
724
724
|
const message = JSON.parse(event.data);
|
|
725
|
-
if (message.type === "
|
|
725
|
+
if (message.type === "CONFIG_ACCEPTED") {
|
|
726
726
|
this.mediaRecorder.start(250);
|
|
727
727
|
} else if (message.type === "transcript") {
|
|
728
728
|
this.dispatchCustomEvent("transcript", message);
|
|
@@ -755,19 +755,19 @@ var DictationService = class extends EventTarget {
|
|
|
755
755
|
// dist/RecorderManager.js
|
|
756
756
|
var RecorderManager = class extends EventTarget {
|
|
757
757
|
constructor() {
|
|
758
|
-
super(
|
|
758
|
+
super();
|
|
759
759
|
this.devices = [];
|
|
760
|
-
this.selectedDevice = "";
|
|
761
760
|
this.recordingState = "stopped";
|
|
762
761
|
this._mediaStream = null;
|
|
763
762
|
this._audioService = null;
|
|
764
763
|
this._dictationService = null;
|
|
764
|
+
navigator.mediaDevices.addEventListener("devicechange", this.handleDevicesChange.bind(this));
|
|
765
765
|
}
|
|
766
766
|
async initialize() {
|
|
767
767
|
const deviceResponse = await getAudioDevices();
|
|
768
768
|
this.devices = deviceResponse.devices;
|
|
769
|
-
this.selectedDevice = deviceResponse.
|
|
770
|
-
return
|
|
769
|
+
this.selectedDevice = deviceResponse.defaultDevice;
|
|
770
|
+
return { devices: this.devices, selectedDevice: this.selectedDevice };
|
|
771
771
|
}
|
|
772
772
|
dispatchCustomEvent(eventName, detail) {
|
|
773
773
|
this.dispatchEvent(new CustomEvent(eventName, {
|
|
@@ -776,11 +776,31 @@ var RecorderManager = class extends EventTarget {
|
|
|
776
776
|
composed: true
|
|
777
777
|
}));
|
|
778
778
|
}
|
|
779
|
+
async handleDevicesChange() {
|
|
780
|
+
const deviceResponse = await getAudioDevices();
|
|
781
|
+
this.devices = deviceResponse.devices;
|
|
782
|
+
if (!this.devices.find((device) => device.deviceId === this.selectedDevice?.deviceId)) {
|
|
783
|
+
this.selectedDevice = deviceResponse.defaultDevice;
|
|
784
|
+
}
|
|
785
|
+
this.dispatchCustomEvent("recording-devices-changed", {
|
|
786
|
+
devices: deviceResponse.devices,
|
|
787
|
+
selectedDevice: this.selectedDevice || deviceResponse.defaultDevice
|
|
788
|
+
});
|
|
789
|
+
}
|
|
779
790
|
async startRecording(params) {
|
|
780
791
|
this._updateRecordingState("initializing");
|
|
792
|
+
const constraints = this.selectedDevice?.deviceId === "default" ? { audio: true } : { audio: { deviceId: this.selectedDevice?.deviceId } };
|
|
781
793
|
try {
|
|
782
|
-
this._mediaStream = await navigator.mediaDevices.getUserMedia(
|
|
783
|
-
|
|
794
|
+
this._mediaStream = await navigator.mediaDevices.getUserMedia(constraints);
|
|
795
|
+
this._mediaStream.getTracks().forEach((track) => {
|
|
796
|
+
track.addEventListener("ended", () => {
|
|
797
|
+
if (this.recordingState === "recording") {
|
|
798
|
+
this.dispatchCustomEvent("error", {
|
|
799
|
+
message: "Microphone access was lost."
|
|
800
|
+
});
|
|
801
|
+
this.stopRecording();
|
|
802
|
+
}
|
|
803
|
+
});
|
|
784
804
|
});
|
|
785
805
|
this._audioService = new AudioService(this._mediaStream);
|
|
786
806
|
} catch (error) {
|
|
@@ -815,6 +835,7 @@ var RecorderManager = class extends EventTarget {
|
|
|
815
835
|
this._mediaStream.getTracks().forEach((track) => track.stop());
|
|
816
836
|
this._mediaStream = null;
|
|
817
837
|
}
|
|
838
|
+
this.dispatchCustomEvent("audio-level-changed", { audioLevel: 0 });
|
|
818
839
|
await this._dictationService?.stopRecording();
|
|
819
840
|
this._updateRecordingState("stopped");
|
|
820
841
|
}
|
|
@@ -928,7 +949,7 @@ var DEFAULT_DICTATION_CONFIG = {
|
|
|
928
949
|
primaryLanguage: "en",
|
|
929
950
|
interimResults: true,
|
|
930
951
|
spokenPunctuation: true,
|
|
931
|
-
automaticPunctuation:
|
|
952
|
+
automaticPunctuation: false,
|
|
932
953
|
model: "others"
|
|
933
954
|
};
|
|
934
955
|
|
|
@@ -944,6 +965,8 @@ var CalloutStyles = i`
|
|
|
944
965
|
font-size: 0.9rem;
|
|
945
966
|
gap: 8px;
|
|
946
967
|
align-items: flex-start;
|
|
968
|
+
max-width: 100%;
|
|
969
|
+
height: fit-content;
|
|
947
970
|
&.red {
|
|
948
971
|
background: var(--callout-red-background);
|
|
949
972
|
border: 1px solid var(--callout-red-border);
|
|
@@ -954,6 +977,11 @@ var CalloutStyles = i`
|
|
|
954
977
|
border: 1px solid var(--callout-orange-border);
|
|
955
978
|
color: var(--callout-orange-text);
|
|
956
979
|
}
|
|
980
|
+
&.small {
|
|
981
|
+
width: 100%;
|
|
982
|
+
padding: 6px;
|
|
983
|
+
font-size: 0.7rem;
|
|
984
|
+
}
|
|
957
985
|
}
|
|
958
986
|
`;
|
|
959
987
|
var callout_default = CalloutStyles;
|
|
@@ -967,17 +995,33 @@ var __decorate = function(decorators, target, key, desc) {
|
|
|
967
995
|
};
|
|
968
996
|
var SettingsMenu = class SettingsMenu2 extends r4 {
|
|
969
997
|
constructor() {
|
|
970
|
-
super(
|
|
971
|
-
this.devices = [];
|
|
972
|
-
this.selectedDevice = "";
|
|
998
|
+
super();
|
|
973
999
|
this.selectedLanguage = "";
|
|
974
1000
|
this.settingsDisabled = false;
|
|
1001
|
+
this._devices = [];
|
|
1002
|
+
navigator.mediaDevices.addEventListener("devicechange", this.handleDevicesChange.bind(this));
|
|
1003
|
+
}
|
|
1004
|
+
// on load, get the available devices
|
|
1005
|
+
async connectedCallback() {
|
|
1006
|
+
super.connectedCallback();
|
|
1007
|
+
const deviceResponse = await getAudioDevices();
|
|
1008
|
+
this._devices = deviceResponse.devices;
|
|
1009
|
+
}
|
|
1010
|
+
async handleDevicesChange() {
|
|
1011
|
+
const deviceResponse = await getAudioDevices();
|
|
1012
|
+
this._devices = deviceResponse.devices;
|
|
975
1013
|
}
|
|
976
1014
|
_selectDevice(deviceId) {
|
|
977
|
-
this.
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
1015
|
+
const device = this._devices.find((d3) => d3.deviceId === deviceId);
|
|
1016
|
+
if (!device) {
|
|
1017
|
+
return;
|
|
1018
|
+
}
|
|
1019
|
+
this.selectedDevice = device;
|
|
1020
|
+
this.dispatchEvent(new CustomEvent("recording-devices-changed", {
|
|
1021
|
+
detail: {
|
|
1022
|
+
devices: this._devices,
|
|
1023
|
+
selectedDevice: device
|
|
1024
|
+
},
|
|
981
1025
|
bubbles: true,
|
|
982
1026
|
composed: true
|
|
983
1027
|
}));
|
|
@@ -1007,10 +1051,10 @@ var SettingsMenu = class SettingsMenu2 extends r4 {
|
|
|
1007
1051
|
}}
|
|
1008
1052
|
?disabled=${this.settingsDisabled}
|
|
1009
1053
|
>
|
|
1010
|
-
${this.
|
|
1054
|
+
${this._devices.map((device) => x`
|
|
1011
1055
|
<option
|
|
1012
1056
|
value=${device.deviceId}
|
|
1013
|
-
?selected=${this.selectedDevice === device
|
|
1057
|
+
?selected=${this.selectedDevice === device}
|
|
1014
1058
|
>
|
|
1015
1059
|
${device.label || "Unknown Device"}
|
|
1016
1060
|
</option>
|
|
@@ -1084,9 +1128,6 @@ SettingsMenu.styles = [
|
|
|
1084
1128
|
select_default,
|
|
1085
1129
|
callout_default
|
|
1086
1130
|
];
|
|
1087
|
-
__decorate([
|
|
1088
|
-
n4({ type: Array })
|
|
1089
|
-
], SettingsMenu.prototype, "devices", void 0);
|
|
1090
1131
|
__decorate([
|
|
1091
1132
|
n4({ type: String })
|
|
1092
1133
|
], SettingsMenu.prototype, "selectedDevice", void 0);
|
|
@@ -1096,6 +1137,9 @@ __decorate([
|
|
|
1096
1137
|
__decorate([
|
|
1097
1138
|
n4({ type: Boolean })
|
|
1098
1139
|
], SettingsMenu.prototype, "settingsDisabled", void 0);
|
|
1140
|
+
__decorate([
|
|
1141
|
+
r6()
|
|
1142
|
+
], SettingsMenu.prototype, "_devices", void 0);
|
|
1099
1143
|
SettingsMenu = __decorate([
|
|
1100
1144
|
t3("settings-menu")
|
|
1101
1145
|
], SettingsMenu);
|
|
@@ -1399,6 +1443,7 @@ var ComponentStyles = i`
|
|
|
1399
1443
|
width: min-content;
|
|
1400
1444
|
gap: 4px;
|
|
1401
1445
|
height: 46px;
|
|
1446
|
+
width: 98px;
|
|
1402
1447
|
box-sizing: border-box;
|
|
1403
1448
|
overflow: hidden;
|
|
1404
1449
|
}
|
|
@@ -1449,19 +1494,27 @@ var __decorate4 = function(decorators, target, key, desc) {
|
|
|
1449
1494
|
var CortiDictation = class extends r4 {
|
|
1450
1495
|
constructor() {
|
|
1451
1496
|
super(...arguments);
|
|
1452
|
-
this.devices = [];
|
|
1453
|
-
this.recordingState = "stopped";
|
|
1454
1497
|
this.dictationConfig = DEFAULT_DICTATION_CONFIG;
|
|
1455
1498
|
this._audioLevel = 0;
|
|
1499
|
+
this._recordingState = "stopped";
|
|
1500
|
+
this._devices = [];
|
|
1456
1501
|
this.recorderManager = new RecorderManager();
|
|
1457
1502
|
}
|
|
1458
1503
|
async connectedCallback() {
|
|
1459
1504
|
super.connectedCallback();
|
|
1460
|
-
await this.recorderManager.initialize();
|
|
1461
|
-
|
|
1505
|
+
const devices = await this.recorderManager.initialize();
|
|
1506
|
+
if (devices.selectedDevice) {
|
|
1507
|
+
this._selectedDevice = this.recorderManager.selectedDevice;
|
|
1508
|
+
this._devices = this.recorderManager.devices;
|
|
1509
|
+
this.dispatchEvent(new CustomEvent("ready"));
|
|
1510
|
+
}
|
|
1462
1511
|
const eventHandlers = {
|
|
1463
1512
|
"recording-state-changed": (e5) => {
|
|
1464
|
-
this.
|
|
1513
|
+
this._recordingState = e5.detail.state;
|
|
1514
|
+
},
|
|
1515
|
+
"devices-changed": () => {
|
|
1516
|
+
this._devices = [...this.recorderManager.devices];
|
|
1517
|
+
this.requestUpdate();
|
|
1465
1518
|
},
|
|
1466
1519
|
"audio-level-changed": (e5) => {
|
|
1467
1520
|
this._audioLevel = e5.detail.audioLevel;
|
|
@@ -1470,9 +1523,11 @@ var CortiDictation = class extends r4 {
|
|
|
1470
1523
|
};
|
|
1471
1524
|
const eventsToRelay = [
|
|
1472
1525
|
"recording-state-changed",
|
|
1526
|
+
"recording-devices-changed",
|
|
1473
1527
|
"audio-level-changed",
|
|
1474
1528
|
"error",
|
|
1475
|
-
"transcript"
|
|
1529
|
+
"transcript",
|
|
1530
|
+
"ready"
|
|
1476
1531
|
];
|
|
1477
1532
|
eventsToRelay.forEach((eventName) => {
|
|
1478
1533
|
this.recorderManager.addEventListener(eventName, (e5) => {
|
|
@@ -1491,45 +1546,58 @@ var CortiDictation = class extends r4 {
|
|
|
1491
1546
|
toggleRecording() {
|
|
1492
1547
|
this._toggleRecording();
|
|
1493
1548
|
}
|
|
1494
|
-
|
|
1549
|
+
get selectedDevice() {
|
|
1550
|
+
return this.recorderManager.selectedDevice || null;
|
|
1551
|
+
}
|
|
1552
|
+
get recordingState() {
|
|
1553
|
+
return this._recordingState;
|
|
1554
|
+
}
|
|
1555
|
+
get devices() {
|
|
1556
|
+
return this._devices;
|
|
1557
|
+
}
|
|
1558
|
+
async setRecordingDevice(device) {
|
|
1559
|
+
this.recorderManager.selectedDevice = device;
|
|
1560
|
+
this._selectedDevice = device;
|
|
1495
1561
|
if (!this.authToken)
|
|
1496
1562
|
return;
|
|
1497
|
-
if (this.
|
|
1498
|
-
this.recorderManager.stopRecording();
|
|
1499
|
-
|
|
1500
|
-
this.recorderManager.startRecording({
|
|
1563
|
+
if (this._recordingState === "recording") {
|
|
1564
|
+
await this.recorderManager.stopRecording();
|
|
1565
|
+
await this.recorderManager.startRecording({
|
|
1501
1566
|
dictationConfig: this.dictationConfig,
|
|
1502
1567
|
authToken: this.authToken
|
|
1503
1568
|
});
|
|
1504
1569
|
}
|
|
1505
1570
|
}
|
|
1506
|
-
|
|
1507
|
-
async _onRecordingDeviceChanged(event) {
|
|
1508
|
-
const customEvent = event;
|
|
1571
|
+
_toggleRecording() {
|
|
1509
1572
|
if (!this.authToken)
|
|
1510
1573
|
return;
|
|
1511
|
-
this.
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1574
|
+
if (this._recordingState === "recording") {
|
|
1575
|
+
this.recorderManager.stopRecording();
|
|
1576
|
+
} else if (this._recordingState === "stopped") {
|
|
1577
|
+
this.recorderManager.startRecording({
|
|
1515
1578
|
dictationConfig: this.dictationConfig,
|
|
1516
1579
|
authToken: this.authToken
|
|
1517
1580
|
});
|
|
1518
1581
|
}
|
|
1519
1582
|
}
|
|
1583
|
+
// Handle device change events if needed
|
|
1584
|
+
async _onRecordingDevicesChanged(event) {
|
|
1585
|
+
const customEvent = event;
|
|
1586
|
+
this.setRecordingDevice(customEvent.detail.selectedDevice);
|
|
1587
|
+
}
|
|
1520
1588
|
render() {
|
|
1521
1589
|
const isConfigured = this.authToken;
|
|
1522
1590
|
if (!isConfigured) {
|
|
1523
1591
|
return x`
|
|
1524
1592
|
<div class="wrapper">
|
|
1525
|
-
<div class="callout red
|
|
1526
|
-
|
|
1593
|
+
<div class="callout red small">
|
|
1594
|
+
No Auth Token
|
|
1527
1595
|
</div>
|
|
1528
1596
|
</div>
|
|
1529
1597
|
`;
|
|
1530
1598
|
}
|
|
1531
|
-
const isLoading = this.
|
|
1532
|
-
const isRecording = this.
|
|
1599
|
+
const isLoading = this._recordingState === "initializing" || this._recordingState === "stopping";
|
|
1600
|
+
const isRecording = this._recordingState === "recording";
|
|
1533
1601
|
return x`
|
|
1534
1602
|
<div class="wrapper">
|
|
1535
1603
|
<button
|
|
@@ -1544,22 +1612,15 @@ var CortiDictation = class extends r4 {
|
|
|
1544
1612
|
</button>
|
|
1545
1613
|
|
|
1546
1614
|
<settings-menu
|
|
1547
|
-
.
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
@recording-device-changed=${this._onRecordingDeviceChanged}
|
|
1615
|
+
.selectedDevice=${this._selectedDevice}
|
|
1616
|
+
?settingsDisabled=${this._recordingState !== "stopped"}
|
|
1617
|
+
@recording-devices-changed=${this._onRecordingDevicesChanged}
|
|
1551
1618
|
></settings-menu>
|
|
1552
1619
|
</div>
|
|
1553
1620
|
`;
|
|
1554
1621
|
}
|
|
1555
1622
|
};
|
|
1556
1623
|
CortiDictation.styles = [buttons_default, theme_default, ComponentStyles_default, callout_default];
|
|
1557
|
-
__decorate4([
|
|
1558
|
-
n4({ type: Array })
|
|
1559
|
-
], CortiDictation.prototype, "devices", void 0);
|
|
1560
|
-
__decorate4([
|
|
1561
|
-
n4({ type: String, reflect: true })
|
|
1562
|
-
], CortiDictation.prototype, "recordingState", void 0);
|
|
1563
1624
|
__decorate4([
|
|
1564
1625
|
n4({ type: Object })
|
|
1565
1626
|
], CortiDictation.prototype, "dictationConfig", void 0);
|
|
@@ -1569,6 +1630,15 @@ __decorate4([
|
|
|
1569
1630
|
__decorate4([
|
|
1570
1631
|
r6()
|
|
1571
1632
|
], CortiDictation.prototype, "_audioLevel", void 0);
|
|
1633
|
+
__decorate4([
|
|
1634
|
+
r6()
|
|
1635
|
+
], CortiDictation.prototype, "_recordingState", void 0);
|
|
1636
|
+
__decorate4([
|
|
1637
|
+
r6()
|
|
1638
|
+
], CortiDictation.prototype, "_selectedDevice", void 0);
|
|
1639
|
+
__decorate4([
|
|
1640
|
+
r6()
|
|
1641
|
+
], CortiDictation.prototype, "_devices", void 0);
|
|
1572
1642
|
var CortiDictation_default = CortiDictation;
|
|
1573
1643
|
|
|
1574
1644
|
// dist/index.js
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { LitElement, TemplateResult, CSSResultGroup } from 'lit';
|
|
2
2
|
export declare class SettingsMenu extends LitElement {
|
|
3
|
-
|
|
4
|
-
selectedDevice: string;
|
|
3
|
+
selectedDevice: MediaDeviceInfo | undefined;
|
|
5
4
|
selectedLanguage: string;
|
|
6
5
|
settingsDisabled: boolean;
|
|
6
|
+
private _devices;
|
|
7
|
+
constructor();
|
|
8
|
+
connectedCallback(): Promise<void>;
|
|
9
|
+
private handleDevicesChange;
|
|
7
10
|
static styles: CSSResultGroup;
|
|
8
11
|
private _selectDevice;
|
|
9
12
|
render(): TemplateResult;
|
|
@@ -6,26 +6,42 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
6
6
|
};
|
|
7
7
|
// mic-selector.ts
|
|
8
8
|
import { LitElement, html, css } from 'lit';
|
|
9
|
-
import { customElement, property } from 'lit/decorators.js';
|
|
9
|
+
import { customElement, property, state } from 'lit/decorators.js';
|
|
10
10
|
import ButtonStyles from '../styles/buttons.js';
|
|
11
11
|
import SelectStyles from '../styles/select.js';
|
|
12
12
|
import { LANGUAGES_SUPPORTED } from '../constants.js';
|
|
13
|
-
import { getLanguageName } from '../utils.js';
|
|
13
|
+
import { getAudioDevices, getLanguageName } from '../utils.js';
|
|
14
14
|
import CalloutStyles from '../styles/callout.js';
|
|
15
15
|
let SettingsMenu = class SettingsMenu extends LitElement {
|
|
16
16
|
constructor() {
|
|
17
|
-
super(
|
|
18
|
-
this.devices = [];
|
|
19
|
-
this.selectedDevice = '';
|
|
17
|
+
super();
|
|
20
18
|
this.selectedLanguage = '';
|
|
21
19
|
this.settingsDisabled = false;
|
|
20
|
+
this._devices = [];
|
|
21
|
+
navigator.mediaDevices.addEventListener('devicechange', this.handleDevicesChange.bind(this));
|
|
22
|
+
}
|
|
23
|
+
// on load, get the available devices
|
|
24
|
+
async connectedCallback() {
|
|
25
|
+
super.connectedCallback();
|
|
26
|
+
const deviceResponse = await getAudioDevices();
|
|
27
|
+
this._devices = deviceResponse.devices;
|
|
28
|
+
}
|
|
29
|
+
async handleDevicesChange() {
|
|
30
|
+
const deviceResponse = await getAudioDevices();
|
|
31
|
+
this._devices = deviceResponse.devices;
|
|
22
32
|
}
|
|
23
33
|
_selectDevice(deviceId) {
|
|
24
|
-
this.selectedDevice = deviceId;
|
|
25
34
|
// Find the device object
|
|
26
|
-
const device = this.
|
|
27
|
-
|
|
28
|
-
|
|
35
|
+
const device = this._devices.find(d => d.deviceId === deviceId);
|
|
36
|
+
if (!device) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
this.selectedDevice = device;
|
|
40
|
+
this.dispatchEvent(new CustomEvent('recording-devices-changed', {
|
|
41
|
+
detail: {
|
|
42
|
+
devices: this._devices,
|
|
43
|
+
selectedDevice: device,
|
|
44
|
+
},
|
|
29
45
|
bubbles: true,
|
|
30
46
|
composed: true,
|
|
31
47
|
}));
|
|
@@ -57,10 +73,10 @@ let SettingsMenu = class SettingsMenu extends LitElement {
|
|
|
57
73
|
}}
|
|
58
74
|
?disabled=${this.settingsDisabled}
|
|
59
75
|
>
|
|
60
|
-
${this.
|
|
76
|
+
${this._devices.map(device => html `
|
|
61
77
|
<option
|
|
62
78
|
value=${device.deviceId}
|
|
63
|
-
?selected=${this.selectedDevice === device
|
|
79
|
+
?selected=${this.selectedDevice === device}
|
|
64
80
|
>
|
|
65
81
|
${device.label || 'Unknown Device'}
|
|
66
82
|
</option>
|
|
@@ -134,9 +150,6 @@ SettingsMenu.styles = [
|
|
|
134
150
|
SelectStyles,
|
|
135
151
|
CalloutStyles,
|
|
136
152
|
];
|
|
137
|
-
__decorate([
|
|
138
|
-
property({ type: Array })
|
|
139
|
-
], SettingsMenu.prototype, "devices", void 0);
|
|
140
153
|
__decorate([
|
|
141
154
|
property({ type: String })
|
|
142
155
|
], SettingsMenu.prototype, "selectedDevice", void 0);
|
|
@@ -146,6 +159,9 @@ __decorate([
|
|
|
146
159
|
__decorate([
|
|
147
160
|
property({ type: Boolean })
|
|
148
161
|
], SettingsMenu.prototype, "settingsDisabled", void 0);
|
|
162
|
+
__decorate([
|
|
163
|
+
state()
|
|
164
|
+
], SettingsMenu.prototype, "_devices", void 0);
|
|
149
165
|
SettingsMenu = __decorate([
|
|
150
166
|
customElement('settings-menu')
|
|
151
167
|
], SettingsMenu);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"settings-menu.js","sourceRoot":"","sources":["../../src/components/settings-menu.ts"],"names":[],"mappings":";;;;;;AAAA,kBAAkB;AAClB,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,GAAG,EAAkC,MAAM,KAAK,CAAC;AAC5E,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"settings-menu.js","sourceRoot":"","sources":["../../src/components/settings-menu.ts"],"names":[],"mappings":"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","sourcesContent":["// mic-selector.ts\nimport { LitElement, html, css, TemplateResult, CSSResultGroup } from 'lit';\nimport { customElement, property, state } from 'lit/decorators.js';\n\nimport ButtonStyles from '../styles/buttons.js';\nimport SelectStyles from '../styles/select.js';\nimport { LANGUAGES_SUPPORTED } from '../constants.js';\nimport { getAudioDevices, getLanguageName } from '../utils.js';\nimport CalloutStyles from '../styles/callout.js';\n\n@customElement('settings-menu')\nexport class SettingsMenu extends LitElement {\n @property({ type: String })\n selectedDevice: MediaDeviceInfo | undefined;\n\n @property({ type: String })\n selectedLanguage: string = '';\n\n @property({ type: Boolean })\n settingsDisabled: boolean = false;\n\n @state()\n private _devices: MediaDeviceInfo[] = [];\n\n constructor() {\n super();\n navigator.mediaDevices.addEventListener(\n 'devicechange',\n this.handleDevicesChange.bind(this),\n );\n }\n\n // on load, get the available devices\n async connectedCallback(): Promise<void> {\n super.connectedCallback();\n const deviceResponse = await getAudioDevices();\n this._devices = deviceResponse.devices;\n }\n\n private async handleDevicesChange() {\n const deviceResponse = await getAudioDevices();\n this._devices = deviceResponse.devices;\n }\n\n static styles: CSSResultGroup = [\n css`\n :host {\n display: block;\n font-family: var(--component-font-family);\n }\n /* Retain the anchor-name styling for this component */\n #settings-popover-button {\n anchor-name: --settings_popover_btn;\n }\n [popover] {\n margin: 0;\n padding: 16px;\n border: 0;\n background: var(--card-background);\n border: 1px solid var(--card-border-color);\n border-radius: var(--card-border-radius);\n box-shadow: var(--card-box-shadow);\n z-index: 1000;\n max-width: 260px;\n width: 100%;\n min-width: 200px;\n position-anchor: --settings_popover_btn;\n position-area: bottom;\n position-visibility: always;\n /* inset: unset; */\n transform: translateX(40%);\n overflow-x: hidden;\n }\n .settings-wrapper {\n display: flex;\n flex-direction: column;\n gap: 20px;\n }\n `,\n ButtonStyles,\n SelectStyles,\n CalloutStyles,\n ];\n\n private _selectDevice(deviceId: string): void {\n // Find the device object\n const device = this._devices.find(d => d.deviceId === deviceId);\n if(!device) {\n return;\n }\n this.selectedDevice = device;\n this.dispatchEvent(\n new CustomEvent('recording-devices-changed', {\n detail: {\n devices: this._devices,\n selectedDevice: device,\n },\n bubbles: true,\n composed: true,\n }),\n );\n }\n\n render(): TemplateResult {\n return html`\n <div class=\"mic-selector\">\n <button id=\"settings-popover-button\" popovertarget=\"settings-popover\">\n <icon-settings></icon-settings>\n </button>\n <div id=\"settings-popover\" popover>\n <div class=\"settings-wrapper\">\n ${this.settingsDisabled\n ? html`\n <div class=\"callout orange\">\n Recording is in progress. Stop recording to change settings.\n </div>\n `\n : ''}\n <div class=\"form-group\">\n <label id=\"device-select-label\" for=\"device-select\">\n Recording Device\n </label>\n <select\n id=\"device-select\"\n aria-labelledby=\"device-select-label\"\n @change=${(e: Event) => {\n this._selectDevice((e.target as HTMLSelectElement).value);\n }}\n ?disabled=${this.settingsDisabled}\n >\n ${this._devices.map(\n device => html`\n <option\n value=${device.deviceId}\n ?selected=${this.selectedDevice === device}\n >\n ${device.label || 'Unknown Device'}\n </option>\n `,\n )}\n </select>\n </div>\n <div class=\"form-group\">\n <label id=\"language-select-label\" for=\"language-select\">\n Dictation Language\n </label>\n <select\n id=\"language-select\"\n aria-labelledby=\"language-select-label\"\n @change=${(e: Event) => {\n this._selectDevice((e.target as HTMLSelectElement).value);\n }}\n ?disabled=${this.settingsDisabled}\n >\n ${LANGUAGES_SUPPORTED.map(\n language => html`\n <option\n value=${language}\n ?selected=${this.selectedLanguage === language}\n >\n ${getLanguageName(language)}\n </option>\n `,\n )}\n </select>\n </div>\n </div>\n </div>\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'settings-menu': SettingsMenu;\n }\n}\n"]}
|
package/dist/constants.js
CHANGED
package/dist/constants.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;AAChD,MAAM,CAAC,MAAM,wBAAwB,GAAoB;IACvD,eAAe,EAAE,IAAI;IACrB,cAAc,EAAE,IAAI;IACpB,iBAAiB,EAAE,IAAI;IACvB,oBAAoB,EAAE,
|
|
1
|
+
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;AAChD,MAAM,CAAC,MAAM,wBAAwB,GAAoB;IACvD,eAAe,EAAE,IAAI;IACrB,cAAc,EAAE,IAAI;IACpB,iBAAiB,EAAE,IAAI;IACvB,oBAAoB,EAAE,KAAK;IAC3B,KAAK,EAAE,QAAQ;CAChB,CAAC","sourcesContent":["import { DictationConfig } from './types.js';\n\nexport const LANGUAGES_SUPPORTED = ['en', 'da'];\nexport const DEFAULT_DICTATION_CONFIG: DictationConfig = {\n primaryLanguage: 'en',\n interimResults: true,\n spokenPunctuation: true,\n automaticPunctuation: false,\n model: 'others',\n};\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ComponentStyles.js","sourceRoot":"","sources":["../../src/styles/ComponentStyles.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAE1B,MAAM,eAAe,GAAG,GAAG,CAAA
|
|
1
|
+
{"version":3,"file":"ComponentStyles.js","sourceRoot":"","sources":["../../src/styles/ComponentStyles.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAE1B,MAAM,eAAe,GAAG,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiD1B,CAAC;AAEF,eAAe,eAAe,CAAC","sourcesContent":["import { css } from 'lit';\n\nconst ComponentStyles = css`\n .wrapper {\n background-color: var(--card-background);\n border: 1px solid var(--card-border-color);\n border-radius: var(--card-border-radius);\n box-shadow: var(--card-box-shadow);\n padding: var(--card-padding);\n display: flex;\n width: min-content;\n gap: 4px;\n height: 46px;\n width: 98px;\n box-sizing: border-box;\n overflow: hidden;\n }\n h2 {\n margin: 0 0 10px;\n font-size: 1rem;\n font-weight: 500;\n }\n label {\n font-size: 0.9rem;\n margin-right: 8px;\n }\n select {\n padding: 4px 6px;\n font-size: 0.9rem;\n border: 1px solid var(--card-border-color);\n border-radius: 4px;\n background-color: var(--card-background);\n color: inherit;\n }\n\n .visualiser {\n width: 16px;\n height: 100%;\n background: var(--visualiser-background);\n margin-top: 8px;\n border-radius: 5px;\n overflow: hidden;\n display: flex;\n align-items: flex-end;\n }\n .level {\n height: 100%;\n width: 100%;\n background: var(--visualiser-level-color);\n transition: width 0.1s ease-in-out;\n }\n`;\n\nexport default ComponentStyles;\n"]}
|
package/dist/styles/callout.js
CHANGED
|
@@ -10,6 +10,8 @@ const CalloutStyles = css `
|
|
|
10
10
|
font-size: 0.9rem;
|
|
11
11
|
gap: 8px;
|
|
12
12
|
align-items: flex-start;
|
|
13
|
+
max-width: 100%;
|
|
14
|
+
height: fit-content;
|
|
13
15
|
&.red {
|
|
14
16
|
background: var(--callout-red-background);
|
|
15
17
|
border: 1px solid var(--callout-red-border);
|
|
@@ -20,6 +22,11 @@ const CalloutStyles = css `
|
|
|
20
22
|
border: 1px solid var(--callout-orange-border);
|
|
21
23
|
color: var(--callout-orange-text);
|
|
22
24
|
}
|
|
25
|
+
&.small {
|
|
26
|
+
width: 100%;
|
|
27
|
+
padding: 6px;
|
|
28
|
+
font-size: 0.7rem;
|
|
29
|
+
}
|
|
23
30
|
}
|
|
24
31
|
`;
|
|
25
32
|
export default CalloutStyles;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"callout.js","sourceRoot":"","sources":["../../src/styles/callout.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAE1B,MAAM,aAAa,GAAG,GAAG,CAAA
|
|
1
|
+
{"version":3,"file":"callout.js","sourceRoot":"","sources":["../../src/styles/callout.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAE1B,MAAM,aAAa,GAAG,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6BxB,CAAC;AAEF,eAAe,aAAa,CAAC","sourcesContent":["import { css } from 'lit';\n\nconst CalloutStyles = css`\n .callout {\n background: var(--callout-accent-background);\n border: 1px solid var(--callout-accent-border);\n color: var(--callout-accent-text);\n padding: 8px;\n border-radius: var(--card-inner-border-radius);\n display: flex;\n font-size: 0.9rem;\n gap: 8px;\n align-items: flex-start;\n max-width: 100%;\n height: fit-content;\n &.red {\n background: var(--callout-red-background);\n border: 1px solid var(--callout-red-border);\n color: var(--callout-red-text);\n }\n &.orange {\n background: var(--callout-orange-background);\n border: 1px solid var(--callout-orange-border);\n color: var(--callout-orange-text);\n }\n &.small {\n width: 100%;\n padding: 6px;\n font-size: 0.7rem;\n }\n }\n`;\n\nexport default CalloutStyles;\n"]}
|
package/dist/utils.d.ts
CHANGED
|
@@ -27,7 +27,7 @@ export declare function requestMicAccess(): Promise<void>;
|
|
|
27
27
|
*/
|
|
28
28
|
export declare function getAudioDevices(): Promise<{
|
|
29
29
|
devices: MediaDeviceInfo[];
|
|
30
|
-
|
|
30
|
+
defaultDevice?: MediaDeviceInfo;
|
|
31
31
|
}>;
|
|
32
32
|
/**
|
|
33
33
|
* Decodes a JWT token and extracts environment and tenant details from its issuer URL.
|
package/dist/utils.js
CHANGED
|
@@ -66,8 +66,8 @@ export async function getAudioDevices() {
|
|
|
66
66
|
// Optionally: await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
67
67
|
const devices = await navigator.mediaDevices.enumerateDevices();
|
|
68
68
|
const audioDevices = devices.filter(device => device.kind === 'audioinput');
|
|
69
|
-
const
|
|
70
|
-
return { devices: audioDevices,
|
|
69
|
+
const defaultDevice = audioDevices.length > 0 ? audioDevices[0] : undefined;
|
|
70
|
+
return { devices: audioDevices, defaultDevice };
|
|
71
71
|
}
|
|
72
72
|
catch (error) {
|
|
73
73
|
console.error('Error enumerating devices:', error);
|
package/dist/utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAAC,YAAoB;IAClD,MAAM,UAAU,GAAG,SAAS,CAAC,QAAQ,IAAI,IAAI,CAAC;IAC9C,MAAM,YAAY,GAAG,IAAI,IAAI,CAAC,YAAY,CAAC,CAAC,UAAU,CAAC,EAAE;QACvD,IAAI,EAAE,UAAU;KACjB,CAAC,CAAC;IACH,MAAM,YAAY,GAAG,YAAY,CAAC,EAAE,CAAC,YAAY,CAAC,CAAC;IACnD,OAAO,YAAY,IAAI,YAAY,CAAC;AACtC,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB;IACpC,IAAI,CAAC;QACH,+CAA+C;QAC/C,IAAI,CAAC,SAAS,CAAC,WAAW,EAAE,CAAC;YAC3B,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,YAAY,CAAC,YAAY,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;YAC1E,MAAM,CAAC,SAAS,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;YAClD,OAAO;QACT,CAAC;QAED,MAAM,gBAAgB,GAAG,MAAM,SAAS,CAAC,WAAW,CAAC,KAAK,CAAC;YACzD,oCAAoC;YACpC,IAAI,EAAE,YAA8B;SACrC,CAAC,CAAC;QAEH,IAAI,gBAAgB,CAAC,KAAK,KAAK,QAAQ,EAAE,CAAC;YACxC,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,YAAY,CAAC,YAAY,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;YAC1E,MAAM,CAAC,SAAS,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;QACpD,CAAC;aAAM,IAAI,gBAAgB,CAAC,KAAK,KAAK,QAAQ,EAAE,CAAC;YAC/C,OAAO,CAAC,IAAI,CAAC,kCAAkC,CAAC,CAAC;QACnD,CAAC;IACH,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,kDAAkD,EAAE,KAAK,CAAC,CAAC;IAC3E,CAAC;AACH,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe;IAInC,IAAI,CAAC,SAAS,CAAC,YAAY,EAAE,gBAAgB,EAAE,CAAC;QAC9C,OAAO,CAAC,KAAK,CAAC,kCAAkC,CAAC,CAAC;QAClD,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IACzB,CAAC;IAED,MAAM,gBAAgB,EAAE,CAAC;IAEzB,IAAI,CAAC;QACH,0EAA0E;QAC1E,MAAM,OAAO,GAAG,MAAM,SAAS,CAAC,YAAY,CAAC,gBAAgB,EAAE,CAAC;QAChE,MAAM,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,YAAY,CAAC,CAAC;QAC5E,MAAM,
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"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","sourcesContent":["/* eslint-disable no-console */\n/**\n * Returns the localized name of a language given its BCP-47 code.\n *\n * @param languageCode - The BCP-47 language code (e.g. \"en\")\n * @returns The localized language name (e.g. \"English\") or the original code if unavailable.\n */\nexport function getLanguageName(languageCode: string): string {\n const userLocale = navigator.language || 'en';\n const displayNames = new Intl.DisplayNames([userLocale], {\n type: 'language',\n });\n const languageName = displayNames.of(languageCode);\n return languageName || languageCode;\n}\n\n/**\n * Requests access to the microphone.\n *\n * This function checks if the microphone permission is in \"prompt\" state, then requests\n * access and stops any active tracks immediately. It also logs if permission is already granted.\n *\n * @returns A promise that resolves when the permission request is complete.\n */\nexport async function requestMicAccess(): Promise<void> {\n try {\n // Fallback if Permissions API is not available\n if (!navigator.permissions) {\n const stream = await navigator.mediaDevices.getUserMedia({ audio: true });\n stream.getTracks().forEach(track => track.stop());\n return;\n }\n\n const permissionStatus = await navigator.permissions.query({\n // eslint-disable-next-line no-undef\n name: 'microphone' as PermissionName,\n });\n\n if (permissionStatus.state === 'prompt') {\n const stream = await navigator.mediaDevices.getUserMedia({ audio: true });\n stream.getTracks().forEach(track => track.stop());\n } else if (permissionStatus.state === 'denied') {\n console.warn('Microphone permission is denied.');\n }\n } catch (error) {\n console.error('Error checking/requesting microphone permission:', error);\n }\n}\n\n/**\n * Retrieves available audio input devices.\n *\n * This function uses the mediaDevices API to enumerate devices and filters out those\n * which are audio inputs. In some browsers, you may need to request user media before\n * device labels are populated.\n *\n * @returns A promise that resolves with an object containing:\n * - `devices`: an array of MediaDeviceInfo objects for audio inputs.\n * - `defaultDeviceId`: the deviceId of the first audio input, if available.\n */\nexport async function getAudioDevices(): Promise<{\n devices: MediaDeviceInfo[];\n defaultDevice?: MediaDeviceInfo;\n}> {\n if (!navigator.mediaDevices?.enumerateDevices) {\n console.error('Media devices API not supported.');\n return { devices: [] };\n }\n\n await requestMicAccess();\n\n try {\n // Optionally: await navigator.mediaDevices.getUserMedia({ audio: true });\n const devices = await navigator.mediaDevices.enumerateDevices();\n const audioDevices = devices.filter(device => device.kind === 'audioinput');\n const defaultDevice =\n audioDevices.length > 0 ? audioDevices[0]: undefined;\n return { devices: audioDevices, defaultDevice };\n } catch (error) {\n console.error('Error enumerating devices:', error);\n return { devices: [] };\n }\n}\n\n/**\n * Decodes a JWT token and extracts environment and tenant details from its issuer URL.\n *\n * This function assumes the JWT token follows the standard header.payload.signature format.\n * It decodes the payload from base64 URL format, parses it as JSON, and then uses a regex\n * to extract the `environment` and `tenant` from the issuer URL (iss field) if it matches the pattern:\n * https://keycloak.{environment}.corti.app/realms/{tenant}.\n *\n * @param token - A JSON Web Token (JWT) string.\n * @returns An object containing:\n * - `environment`: The extracted environment from the issuer URL.\n * - `tenant`: The extracted tenant from the issuer URL.\n * - `token`: The original token string.\n * If the issuer URL doesn't match the expected format, the function returns the full decoded token details.\n *\n * @throws Will throw an error if:\n * - The token format is invalid.\n * - The base64 decoding or URI decoding fails.\n * - The JSON payload is invalid.\n * - The token payload does not contain an issuer (iss) field.\n */\nexport function decodeToken(token: string) {\n // Validate the token structure (should contain at least header and payload parts)\n const parts = token.split('.');\n if (parts.length < 2) {\n throw new Error('Invalid token format: expected header.payload.signature');\n }\n\n // Retrieve the payload (second part) of the JWT token\n const base64Url = parts[1];\n\n // Replace URL-safe characters to match standard base64 encoding\n const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');\n\n // Decode the base64 string into a JSON string\n let jsonPayload: string;\n try {\n jsonPayload = decodeURIComponent(\n atob(base64)\n .split('')\n .map(c => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))\n .join(''),\n );\n } catch (error) {\n throw new Error('Failed to decode token payload');\n }\n\n // Parse the JSON string to obtain token details\n let tokenDetails: { iss: string; [key: string]: unknown };\n try {\n tokenDetails = JSON.parse(jsonPayload);\n } catch (error) {\n throw new Error('Invalid JSON payload in token');\n }\n\n // Extract the issuer URL from the token details\n const issuerUrl: string = tokenDetails.iss;\n if (!issuerUrl) {\n throw new Error('Token payload does not contain an issuer (iss) field');\n }\n\n // Regex to extract environment and tenant from issuer URL:\n // Expected format: https://keycloak.{environment}.corti.app/realms/{tenant}\n // Note: Unnecessary escapes in character classes have been removed.\n const regex =\n /^https:\\/\\/(keycloak|auth)\\.([^.]+)\\.corti\\.app\\/realms\\/([^/]+)/;\n const match = issuerUrl.match(regex);\n\n // If the issuer URL matches the expected pattern, return the extracted values along with the token\n if (match) {\n return {\n environment: match[2],\n tenant: match[3],\n token,\n };\n }\n}\n"]}
|