@corti/dictation-web 0.1.6 → 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 +108 -109
- package/dist/CortiDictation.d.ts +1 -0
- package/dist/CortiDictation.js +29 -25
- package/dist/CortiDictation.js.map +1 -1
- package/dist/DictationService.d.ts +0 -1
- package/dist/DictationService.js +27 -8
- package/dist/DictationService.js.map +1 -1
- package/dist/RecorderManager.d.ts +1 -0
- package/dist/RecorderManager.js +37 -14
- package/dist/RecorderManager.js.map +1 -1
- package/dist/bundle.js +89 -22
- package/dist/components/settings-menu.js.map +1 -1
- package/dist/constants.js +2 -2
- package/dist/constants.js.map +1 -1
- package/dist/styles/callout.js +29 -29
- package/dist/styles/callout.js.map +1 -1
- package/dist/types.d.ts +9 -3
- package/dist/types.js.map +1 -1
- package/dist/utils.d.ts +1 -0
- package/dist/utils.js +23 -1
- package/dist/utils.js.map +1 -1
- package/package.json +7 -5
package/README.md
CHANGED
|
@@ -1,109 +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
|
-
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
|
|
52
|
-
|
|
53
|
-
//
|
|
54
|
-
dictationEl.
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
|
70
|
-
|
|
|
71
|
-
| `
|
|
72
|
-
| `
|
|
73
|
-
| `
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
|
86
|
-
|
|
|
87
|
-
| `
|
|
88
|
-
| `
|
|
89
|
-
| `
|
|
90
|
-
| `
|
|
91
|
-
| `
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
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).
|
package/dist/CortiDictation.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ export declare class CortiDictation extends LitElement {
|
|
|
7
7
|
static styles: import("lit").CSSResult[];
|
|
8
8
|
dictationConfig: DictationConfig;
|
|
9
9
|
authToken: string | undefined;
|
|
10
|
+
debug_displayAudio: boolean;
|
|
10
11
|
private _audioLevel;
|
|
11
12
|
private _recordingState;
|
|
12
13
|
private _selectedDevice;
|
package/dist/CortiDictation.js
CHANGED
|
@@ -20,6 +20,7 @@ export class CortiDictation extends LitElement {
|
|
|
20
20
|
constructor() {
|
|
21
21
|
super(...arguments);
|
|
22
22
|
this.dictationConfig = DEFAULT_DICTATION_CONFIG;
|
|
23
|
+
this.debug_displayAudio = false;
|
|
23
24
|
this._audioLevel = 0;
|
|
24
25
|
this._recordingState = 'stopped';
|
|
25
26
|
this._devices = [];
|
|
@@ -53,6 +54,7 @@ export class CortiDictation extends LitElement {
|
|
|
53
54
|
'audio-level-changed',
|
|
54
55
|
'error',
|
|
55
56
|
'transcript',
|
|
57
|
+
'command',
|
|
56
58
|
'ready',
|
|
57
59
|
];
|
|
58
60
|
eventsToRelay.forEach(eventName => {
|
|
@@ -106,6 +108,7 @@ export class CortiDictation extends LitElement {
|
|
|
106
108
|
this.recorderManager.startRecording({
|
|
107
109
|
dictationConfig: this.dictationConfig,
|
|
108
110
|
authToken: this.authToken,
|
|
111
|
+
debug_displayAudio: this.debug_displayAudio,
|
|
109
112
|
});
|
|
110
113
|
}
|
|
111
114
|
}
|
|
@@ -117,40 +120,38 @@ export class CortiDictation extends LitElement {
|
|
|
117
120
|
render() {
|
|
118
121
|
const isConfigured = this.authToken;
|
|
119
122
|
if (!isConfigured) {
|
|
120
|
-
return html `
|
|
121
|
-
<div class="wrapper">
|
|
122
|
-
<div class="callout red small">
|
|
123
|
-
|
|
124
|
-
</div>
|
|
125
|
-
</div>
|
|
123
|
+
return html `
|
|
124
|
+
<div class="wrapper">
|
|
125
|
+
<div class="callout red small">No Auth Token</div>
|
|
126
|
+
</div>
|
|
126
127
|
`;
|
|
127
128
|
}
|
|
128
129
|
const isLoading = this._recordingState === 'initializing' ||
|
|
129
130
|
this._recordingState === 'stopping';
|
|
130
131
|
const isRecording = this._recordingState === 'recording';
|
|
131
|
-
return html `
|
|
132
|
-
<div class="wrapper">
|
|
133
|
-
<button
|
|
134
|
-
@click=${this._toggleRecording}
|
|
135
|
-
class=${isRecording ? 'red' : 'accent'}
|
|
136
|
-
>
|
|
132
|
+
return html `
|
|
133
|
+
<div class="wrapper">
|
|
134
|
+
<button
|
|
135
|
+
@click=${this._toggleRecording}
|
|
136
|
+
class=${isRecording ? 'red' : 'accent'}
|
|
137
|
+
>
|
|
137
138
|
${isLoading
|
|
138
139
|
? html `<icon-loading-spinner></icon-loading-spinner>`
|
|
139
140
|
: isRecording
|
|
140
141
|
? html `<icon-recording></icon-recording>`
|
|
141
|
-
: html `<icon-mic-on></icon-mic-on>`}
|
|
142
|
-
<audio-visualiser
|
|
143
|
-
.level=${this._audioLevel}
|
|
144
|
-
.active=${isRecording}
|
|
145
|
-
></audio-visualiser>
|
|
146
|
-
</button>
|
|
147
|
-
|
|
148
|
-
<settings-menu
|
|
149
|
-
.selectedDevice=${this._selectedDevice}
|
|
150
|
-
?settingsDisabled=${this._recordingState !== 'stopped'}
|
|
151
|
-
@recording-devices-changed=${this._onRecordingDevicesChanged}
|
|
152
|
-
></settings-menu>
|
|
153
|
-
</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>
|
|
154
155
|
`;
|
|
155
156
|
}
|
|
156
157
|
}
|
|
@@ -161,6 +162,9 @@ __decorate([
|
|
|
161
162
|
__decorate([
|
|
162
163
|
property({ type: String })
|
|
163
164
|
], CortiDictation.prototype, "authToken", void 0);
|
|
165
|
+
__decorate([
|
|
166
|
+
property({ type: Boolean })
|
|
167
|
+
], CortiDictation.prototype, "debug_displayAudio", void 0);
|
|
164
168
|
__decorate([
|
|
165
169
|
state()
|
|
166
170
|
], CortiDictation.prototype, "_audioLevel", void 0);
|
|
@@ -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, 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"]}
|
|
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;
|
package/dist/DictationService.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
|
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":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAEzC,MAAM,OAAO,gBAAiB,SAAQ,WAAW;
|
|
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"]}
|
|
@@ -17,6 +17,7 @@ export declare class RecorderManager extends EventTarget {
|
|
|
17
17
|
startRecording(params: {
|
|
18
18
|
dictationConfig: DictationConfig;
|
|
19
19
|
authToken: string;
|
|
20
|
+
debug_displayAudio?: boolean;
|
|
20
21
|
}): Promise<void>;
|
|
21
22
|
stopRecording(): Promise<void>;
|
|
22
23
|
private _updateRecordingState;
|
package/dist/RecorderManager.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getAudioDevices } from './utils.js';
|
|
1
|
+
import { getAudioDevices, getMediaStream } from './utils.js';
|
|
2
2
|
import { AudioService } from './audioService.js';
|
|
3
3
|
import { DictationService } from './DictationService.js';
|
|
4
4
|
export class RecorderManager extends EventTarget {
|
|
@@ -37,13 +37,10 @@ export class RecorderManager extends EventTarget {
|
|
|
37
37
|
}
|
|
38
38
|
async startRecording(params) {
|
|
39
39
|
this._updateRecordingState('initializing');
|
|
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 } };
|
|
44
40
|
try {
|
|
45
|
-
this._mediaStream =
|
|
46
|
-
|
|
41
|
+
this._mediaStream = await getMediaStream(params.debug_displayAudio
|
|
42
|
+
? 'display_audio'
|
|
43
|
+
: this.selectedDevice?.deviceId);
|
|
47
44
|
this._mediaStream.getTracks().forEach((track) => {
|
|
48
45
|
track.addEventListener('ended', () => {
|
|
49
46
|
if (this.recordingState === 'recording') {
|
|
@@ -58,23 +55,50 @@ export class RecorderManager extends EventTarget {
|
|
|
58
55
|
}
|
|
59
56
|
catch (error) {
|
|
60
57
|
this.dispatchCustomEvent('error', error);
|
|
61
|
-
this.
|
|
58
|
+
this.stopRecording();
|
|
62
59
|
return;
|
|
63
60
|
}
|
|
64
61
|
// Initialize dictation service.
|
|
65
62
|
try {
|
|
66
63
|
this._dictationService = new DictationService(this._mediaStream, params);
|
|
64
|
+
// Forward custom events from dictation service
|
|
65
|
+
this._dictationService.addEventListener('error', e => {
|
|
66
|
+
this.dispatchEvent(new CustomEvent('error', {
|
|
67
|
+
detail: e.detail,
|
|
68
|
+
bubbles: true,
|
|
69
|
+
composed: true,
|
|
70
|
+
}));
|
|
71
|
+
this.stopRecording();
|
|
72
|
+
});
|
|
73
|
+
this._dictationService.addEventListener('stream-closed', () => this.stopRecording());
|
|
74
|
+
this._dictationService.addEventListener('transcript', e => this.dispatchEvent(new CustomEvent('transcript', {
|
|
75
|
+
detail: e.detail,
|
|
76
|
+
bubbles: true,
|
|
77
|
+
composed: true,
|
|
78
|
+
})));
|
|
79
|
+
this._dictationService.addEventListener('command', e => this.dispatchEvent(new CustomEvent('command', {
|
|
80
|
+
detail: e.detail,
|
|
81
|
+
bubbles: true,
|
|
82
|
+
composed: true,
|
|
83
|
+
})));
|
|
67
84
|
}
|
|
68
85
|
catch (error) {
|
|
69
86
|
this.dispatchCustomEvent('error', error);
|
|
70
87
|
this.stopRecording();
|
|
71
88
|
return;
|
|
72
89
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
90
|
+
try {
|
|
91
|
+
this._dictationService?.startRecording();
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
this.dispatchEvent(new CustomEvent('error', {
|
|
95
|
+
detail: error,
|
|
96
|
+
bubbles: true,
|
|
97
|
+
composed: true,
|
|
98
|
+
}));
|
|
99
|
+
this.stopRecording();
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
78
102
|
this._updateRecordingState('recording');
|
|
79
103
|
// Update audio level visualization.
|
|
80
104
|
this._visualiserInterval = window.setInterval(() => {
|
|
@@ -92,7 +116,6 @@ export class RecorderManager extends EventTarget {
|
|
|
92
116
|
}
|
|
93
117
|
if (this._mediaStream) {
|
|
94
118
|
this._mediaStream.getTracks().forEach(track => track.stop());
|
|
95
|
-
this._mediaStream = null;
|
|
96
119
|
}
|
|
97
120
|
this.dispatchCustomEvent('audio-level-changed', { audioLevel: 0 });
|
|
98
121
|
await this._dictationService?.stopRecording();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RecorderManager.js","sourceRoot":"","sources":["../src/RecorderManager.ts"],"names":[],"mappings":"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","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"]}
|
|
1
|
+
{"version":3,"file":"RecorderManager.js","sourceRoot":"","sources":["../src/RecorderManager.ts"],"names":[],"mappings":"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","sourcesContent":["import { getAudioDevices, getMediaStream } from './utils.js';\r\nimport { AudioService } from './audioService.js';\r\nimport { DictationService } from './DictationService.js';\r\nimport type { DictationConfig, RecordingState } from './types.js';\r\n\r\nexport class RecorderManager extends EventTarget {\r\n public devices: MediaDeviceInfo[] = [];\r\n\r\n public selectedDevice: MediaDeviceInfo | undefined;\r\n\r\n public recordingState: RecordingState = 'stopped';\r\n\r\n private _mediaStream: MediaStream | null = null;\r\n\r\n private _audioService: AudioService | null = null;\r\n\r\n private _dictationService: DictationService | null = null;\r\n\r\n private _visualiserInterval?: number;\r\n\r\n constructor() {\r\n super();\r\n navigator.mediaDevices.addEventListener(\r\n 'devicechange',\r\n this.handleDevicesChange.bind(this),\r\n );\r\n }\r\n\r\n async initialize() {\r\n const deviceResponse = await getAudioDevices();\r\n this.devices = deviceResponse.devices;\r\n this.selectedDevice = deviceResponse.defaultDevice;\r\n return { devices: this.devices, selectedDevice: this.selectedDevice };\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 private async handleDevicesChange() {\r\n const deviceResponse = await getAudioDevices();\r\n this.devices = deviceResponse.devices;\r\n if (\r\n !this.devices.find(\r\n device => device.deviceId === this.selectedDevice?.deviceId,\r\n )\r\n ) {\r\n this.selectedDevice = deviceResponse.defaultDevice;\r\n }\r\n this.dispatchCustomEvent('recording-devices-changed', {\r\n devices: deviceResponse.devices,\r\n selectedDevice: this.selectedDevice || deviceResponse.defaultDevice,\r\n });\r\n }\r\n\r\n async startRecording(params: {\r\n dictationConfig: DictationConfig;\r\n authToken: string;\r\n debug_displayAudio?: boolean;\r\n }): Promise<void> {\r\n this._updateRecordingState('initializing');\r\n\r\n try {\r\n this._mediaStream = await getMediaStream(\r\n params.debug_displayAudio\r\n ? 'display_audio'\r\n : this.selectedDevice?.deviceId,\r\n );\r\n\r\n this._mediaStream.getTracks().forEach((track: MediaStreamTrack) => {\r\n track.addEventListener('ended', () => {\r\n if (this.recordingState === 'recording') {\r\n this.dispatchCustomEvent('error', {\r\n message: 'Microphone access was lost.',\r\n });\r\n this.stopRecording();\r\n }\r\n });\r\n });\r\n\r\n this._audioService = new AudioService(this._mediaStream);\r\n } catch (error) {\r\n this.dispatchCustomEvent('error', error);\r\n this.stopRecording();\r\n return;\r\n }\r\n\r\n // Initialize dictation service.\r\n try {\r\n this._dictationService = new DictationService(this._mediaStream, params);\r\n\r\n // Forward custom events from dictation service\r\n this._dictationService.addEventListener('error', e => {\r\n this.dispatchEvent(\r\n new CustomEvent('error', {\r\n detail: (e as CustomEvent).detail,\r\n bubbles: true,\r\n composed: true,\r\n }),\r\n );\r\n this.stopRecording();\r\n });\r\n this._dictationService.addEventListener('stream-closed', () =>\r\n this.stopRecording(),\r\n );\r\n this._dictationService.addEventListener('transcript', e =>\r\n this.dispatchEvent(\r\n new CustomEvent('transcript', {\r\n detail: (e as CustomEvent).detail,\r\n bubbles: true,\r\n composed: true,\r\n }),\r\n ),\r\n );\r\n this._dictationService.addEventListener('command', e =>\r\n this.dispatchEvent(\r\n new CustomEvent('command', {\r\n detail: (e as CustomEvent).detail,\r\n bubbles: true,\r\n composed: true,\r\n }),\r\n ),\r\n );\r\n } catch (error) {\r\n this.dispatchCustomEvent('error', error);\r\n this.stopRecording();\r\n return;\r\n }\r\n\r\n try {\r\n this._dictationService?.startRecording();\r\n } catch (error) {\r\n this.dispatchEvent(\r\n new CustomEvent('error', {\r\n detail: error,\r\n bubbles: true,\r\n composed: true,\r\n }),\r\n );\r\n this.stopRecording();\r\n return;\r\n }\r\n this._updateRecordingState('recording');\r\n\r\n // Update audio level visualization.\r\n this._visualiserInterval = window.setInterval(() => {\r\n const level = this._audioService\r\n ? this._audioService.getAudioLevel() * 3\r\n : 0;\r\n this.dispatchCustomEvent('audio-level-changed', { audioLevel: level });\r\n }, 150);\r\n }\r\n\r\n async stopRecording() {\r\n this._updateRecordingState('stopping');\r\n if (this._visualiserInterval) {\r\n clearInterval(this._visualiserInterval);\r\n this._visualiserInterval = undefined;\r\n }\r\n if (this._mediaStream) {\r\n this._mediaStream.getTracks().forEach(track => track.stop());\r\n }\r\n this.dispatchCustomEvent('audio-level-changed', { audioLevel: 0 });\r\n await this._dictationService?.stopRecording();\r\n this._updateRecordingState('stopped');\r\n }\r\n\r\n private _updateRecordingState(state: RecordingState) {\r\n this.recordingState = state;\r\n this.dispatchEvent(\r\n new CustomEvent('recording-state-changed', {\r\n detail: { state },\r\n bubbles: true,\r\n composed: true,\r\n }),\r\n );\r\n }\r\n}\r\n"]}
|
package/dist/bundle.js
CHANGED
|
@@ -633,7 +633,7 @@ async function getAudioDevices() {
|
|
|
633
633
|
function decodeToken(token) {
|
|
634
634
|
const parts = token.split(".");
|
|
635
635
|
if (parts.length < 2) {
|
|
636
|
-
throw new Error("Invalid token format
|
|
636
|
+
throw new Error("Invalid token format");
|
|
637
637
|
}
|
|
638
638
|
const base64Url = parts[1];
|
|
639
639
|
const base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/");
|
|
@@ -663,6 +663,25 @@ function decodeToken(token) {
|
|
|
663
663
|
};
|
|
664
664
|
}
|
|
665
665
|
}
|
|
666
|
+
async function getMediaStream(deviceId) {
|
|
667
|
+
if (!deviceId) {
|
|
668
|
+
throw new Error("No device ID provided");
|
|
669
|
+
}
|
|
670
|
+
if (deviceId === "display_audio") {
|
|
671
|
+
const stream = await navigator.mediaDevices.getDisplayMedia({
|
|
672
|
+
audio: true,
|
|
673
|
+
video: true
|
|
674
|
+
});
|
|
675
|
+
stream.getTracks().forEach((track) => {
|
|
676
|
+
if (track.kind === "video") {
|
|
677
|
+
stream.removeTrack(track);
|
|
678
|
+
}
|
|
679
|
+
});
|
|
680
|
+
return stream;
|
|
681
|
+
}
|
|
682
|
+
const constraints = deviceId !== "default" ? { audio: { deviceId: { exact: deviceId } } } : { audio: true };
|
|
683
|
+
return await navigator.mediaDevices.getUserMedia(constraints);
|
|
684
|
+
}
|
|
666
685
|
|
|
667
686
|
// dist/audioService.js
|
|
668
687
|
var AudioService = class {
|
|
@@ -697,7 +716,6 @@ var DictationService = class extends EventTarget {
|
|
|
697
716
|
if (!config) {
|
|
698
717
|
throw new Error("Invalid token");
|
|
699
718
|
}
|
|
700
|
-
this.serverConfig = config;
|
|
701
719
|
this.mediaRecorder.ondataavailable = (event) => {
|
|
702
720
|
if (this.webSocket?.readyState === WebSocket.OPEN) {
|
|
703
721
|
this.webSocket.send(event.data);
|
|
@@ -712,7 +730,16 @@ var DictationService = class extends EventTarget {
|
|
|
712
730
|
}));
|
|
713
731
|
}
|
|
714
732
|
startRecording() {
|
|
715
|
-
const
|
|
733
|
+
const serverConfig = decodeToken(this.authToken);
|
|
734
|
+
if (!serverConfig) {
|
|
735
|
+
this.dispatchEvent(new CustomEvent("error", {
|
|
736
|
+
detail: "Invalid token",
|
|
737
|
+
bubbles: true,
|
|
738
|
+
composed: true
|
|
739
|
+
}));
|
|
740
|
+
return;
|
|
741
|
+
}
|
|
742
|
+
const url = `wss://api.${serverConfig.environment}.corti.app/audio-bridge/v2/transcribe?tenant-name=${serverConfig.tenant}&token=Bearer%20${this.authToken}`;
|
|
716
743
|
this.webSocket = new WebSocket(url);
|
|
717
744
|
this.webSocket.onopen = () => {
|
|
718
745
|
this.webSocket.send(JSON.stringify({
|
|
@@ -722,10 +749,22 @@ var DictationService = class extends EventTarget {
|
|
|
722
749
|
};
|
|
723
750
|
this.webSocket.onmessage = (event) => {
|
|
724
751
|
const message = JSON.parse(event.data);
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
752
|
+
switch (message.type) {
|
|
753
|
+
case "CONFIG_ACCEPTED":
|
|
754
|
+
this.mediaRecorder.start(250);
|
|
755
|
+
break;
|
|
756
|
+
case "CONFIG_DENIED":
|
|
757
|
+
this.dispatchCustomEvent("error", message);
|
|
758
|
+
return this.stopRecording();
|
|
759
|
+
case "transcript":
|
|
760
|
+
this.dispatchCustomEvent("transcript", message);
|
|
761
|
+
break;
|
|
762
|
+
case "command":
|
|
763
|
+
this.dispatchCustomEvent("command", message);
|
|
764
|
+
break;
|
|
765
|
+
default:
|
|
766
|
+
console.warn(`Unhandled message type: ${message.type}`);
|
|
767
|
+
break;
|
|
729
768
|
}
|
|
730
769
|
};
|
|
731
770
|
this.webSocket.onerror = (event) => {
|
|
@@ -736,7 +775,7 @@ var DictationService = class extends EventTarget {
|
|
|
736
775
|
};
|
|
737
776
|
}
|
|
738
777
|
async stopRecording() {
|
|
739
|
-
this.mediaRecorder
|
|
778
|
+
this.mediaRecorder?.stop();
|
|
740
779
|
if (this.webSocket?.readyState === WebSocket.OPEN) {
|
|
741
780
|
this.webSocket.send(JSON.stringify({ type: "end" }));
|
|
742
781
|
}
|
|
@@ -789,9 +828,8 @@ var RecorderManager = class extends EventTarget {
|
|
|
789
828
|
}
|
|
790
829
|
async startRecording(params) {
|
|
791
830
|
this._updateRecordingState("initializing");
|
|
792
|
-
const constraints = this.selectedDevice?.deviceId === "default" ? { audio: true } : { audio: { deviceId: this.selectedDevice?.deviceId } };
|
|
793
831
|
try {
|
|
794
|
-
this._mediaStream = await
|
|
832
|
+
this._mediaStream = await getMediaStream(params.debug_displayAudio ? "display_audio" : this.selectedDevice?.deviceId);
|
|
795
833
|
this._mediaStream.getTracks().forEach((track) => {
|
|
796
834
|
track.addEventListener("ended", () => {
|
|
797
835
|
if (this.recordingState === "recording") {
|
|
@@ -805,20 +843,46 @@ var RecorderManager = class extends EventTarget {
|
|
|
805
843
|
this._audioService = new AudioService(this._mediaStream);
|
|
806
844
|
} catch (error) {
|
|
807
845
|
this.dispatchCustomEvent("error", error);
|
|
808
|
-
this.
|
|
846
|
+
this.stopRecording();
|
|
809
847
|
return;
|
|
810
848
|
}
|
|
811
849
|
try {
|
|
812
850
|
this._dictationService = new DictationService(this._mediaStream, params);
|
|
851
|
+
this._dictationService.addEventListener("error", (e5) => {
|
|
852
|
+
this.dispatchEvent(new CustomEvent("error", {
|
|
853
|
+
detail: e5.detail,
|
|
854
|
+
bubbles: true,
|
|
855
|
+
composed: true
|
|
856
|
+
}));
|
|
857
|
+
this.stopRecording();
|
|
858
|
+
});
|
|
859
|
+
this._dictationService.addEventListener("stream-closed", () => this.stopRecording());
|
|
860
|
+
this._dictationService.addEventListener("transcript", (e5) => this.dispatchEvent(new CustomEvent("transcript", {
|
|
861
|
+
detail: e5.detail,
|
|
862
|
+
bubbles: true,
|
|
863
|
+
composed: true
|
|
864
|
+
})));
|
|
865
|
+
this._dictationService.addEventListener("command", (e5) => this.dispatchEvent(new CustomEvent("command", {
|
|
866
|
+
detail: e5.detail,
|
|
867
|
+
bubbles: true,
|
|
868
|
+
composed: true
|
|
869
|
+
})));
|
|
813
870
|
} catch (error) {
|
|
814
871
|
this.dispatchCustomEvent("error", error);
|
|
815
872
|
this.stopRecording();
|
|
816
873
|
return;
|
|
817
874
|
}
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
875
|
+
try {
|
|
876
|
+
this._dictationService?.startRecording();
|
|
877
|
+
} catch (error) {
|
|
878
|
+
this.dispatchEvent(new CustomEvent("error", {
|
|
879
|
+
detail: error,
|
|
880
|
+
bubbles: true,
|
|
881
|
+
composed: true
|
|
882
|
+
}));
|
|
883
|
+
this.stopRecording();
|
|
884
|
+
return;
|
|
885
|
+
}
|
|
822
886
|
this._updateRecordingState("recording");
|
|
823
887
|
this._visualiserInterval = window.setInterval(() => {
|
|
824
888
|
const level = this._audioService ? this._audioService.getAudioLevel() * 3 : 0;
|
|
@@ -833,7 +897,6 @@ var RecorderManager = class extends EventTarget {
|
|
|
833
897
|
}
|
|
834
898
|
if (this._mediaStream) {
|
|
835
899
|
this._mediaStream.getTracks().forEach((track) => track.stop());
|
|
836
|
-
this._mediaStream = null;
|
|
837
900
|
}
|
|
838
901
|
this.dispatchCustomEvent("audio-level-changed", { audioLevel: 0 });
|
|
839
902
|
await this._dictationService?.stopRecording();
|
|
@@ -949,7 +1012,7 @@ var DEFAULT_DICTATION_CONFIG = {
|
|
|
949
1012
|
primaryLanguage: "en",
|
|
950
1013
|
interimResults: true,
|
|
951
1014
|
spokenPunctuation: true,
|
|
952
|
-
automaticPunctuation:
|
|
1015
|
+
automaticPunctuation: true,
|
|
953
1016
|
model: "others"
|
|
954
1017
|
};
|
|
955
1018
|
|
|
@@ -964,7 +1027,7 @@ var CalloutStyles = i`
|
|
|
964
1027
|
display: flex;
|
|
965
1028
|
font-size: 0.9rem;
|
|
966
1029
|
gap: 8px;
|
|
967
|
-
align-items:
|
|
1030
|
+
align-items: center;
|
|
968
1031
|
max-width: 100%;
|
|
969
1032
|
height: fit-content;
|
|
970
1033
|
&.red {
|
|
@@ -1495,6 +1558,7 @@ var CortiDictation = class extends r4 {
|
|
|
1495
1558
|
constructor() {
|
|
1496
1559
|
super(...arguments);
|
|
1497
1560
|
this.dictationConfig = DEFAULT_DICTATION_CONFIG;
|
|
1561
|
+
this.debug_displayAudio = false;
|
|
1498
1562
|
this._audioLevel = 0;
|
|
1499
1563
|
this._recordingState = "stopped";
|
|
1500
1564
|
this._devices = [];
|
|
@@ -1527,6 +1591,7 @@ var CortiDictation = class extends r4 {
|
|
|
1527
1591
|
"audio-level-changed",
|
|
1528
1592
|
"error",
|
|
1529
1593
|
"transcript",
|
|
1594
|
+
"command",
|
|
1530
1595
|
"ready"
|
|
1531
1596
|
];
|
|
1532
1597
|
eventsToRelay.forEach((eventName) => {
|
|
@@ -1576,7 +1641,8 @@ var CortiDictation = class extends r4 {
|
|
|
1576
1641
|
} else if (this._recordingState === "stopped") {
|
|
1577
1642
|
this.recorderManager.startRecording({
|
|
1578
1643
|
dictationConfig: this.dictationConfig,
|
|
1579
|
-
authToken: this.authToken
|
|
1644
|
+
authToken: this.authToken,
|
|
1645
|
+
debug_displayAudio: this.debug_displayAudio
|
|
1580
1646
|
});
|
|
1581
1647
|
}
|
|
1582
1648
|
}
|
|
@@ -1590,9 +1656,7 @@ var CortiDictation = class extends r4 {
|
|
|
1590
1656
|
if (!isConfigured) {
|
|
1591
1657
|
return x`
|
|
1592
1658
|
<div class="wrapper">
|
|
1593
|
-
<div class="callout red small">
|
|
1594
|
-
No Auth Token
|
|
1595
|
-
</div>
|
|
1659
|
+
<div class="callout red small">No Auth Token</div>
|
|
1596
1660
|
</div>
|
|
1597
1661
|
`;
|
|
1598
1662
|
}
|
|
@@ -1627,6 +1691,9 @@ __decorate4([
|
|
|
1627
1691
|
__decorate4([
|
|
1628
1692
|
n4({ type: String })
|
|
1629
1693
|
], CortiDictation.prototype, "authToken", void 0);
|
|
1694
|
+
__decorate4([
|
|
1695
|
+
n4({ type: Boolean })
|
|
1696
|
+
], CortiDictation.prototype, "debug_displayAudio", void 0);
|
|
1630
1697
|
__decorate4([
|
|
1631
1698
|
r6()
|
|
1632
1699
|
], CortiDictation.prototype, "_audioLevel", void 0);
|
|
@@ -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,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAEnE,OAAO,YAAY,MAAM,sBAAsB,CAAC;AAChD,OAAO,YAAY,MAAM,qBAAqB,CAAC;AAC/C,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC/D,OAAO,aAAa,MAAM,sBAAsB,CAAC;AAG1C,IAAM,YAAY,GAAlB,MAAM,YAAa,SAAQ,UAAU;IAa1C;QACE,KAAK,EAAE,CAAC;QATV,qBAAgB,GAAW,EAAE,CAAC;QAG9B,qBAAgB,GAAY,KAAK,CAAC;QAG1B,aAAQ,GAAsB,EAAE,CAAC;QAIvC,SAAS,CAAC,YAAY,CAAC,gBAAgB,CACrC,cAAc,EACd,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,CACpC,CAAC;IACJ,CAAC;IAED,qCAAqC;IACrC,KAAK,CAAC,iBAAiB;QACrB,KAAK,CAAC,iBAAiB,EAAE,CAAC;QAC1B,MAAM,cAAc,GAAG,MAAM,eAAe,EAAE,CAAC;QAC/C,IAAI,CAAC,QAAQ,GAAG,cAAc,CAAC,OAAO,CAAC;IACzC,CAAC;IAEO,KAAK,CAAC,mBAAmB;QAC/B,MAAM,cAAc,GAAG,MAAM,eAAe,EAAE,CAAC;QAC/C,IAAI,CAAC,QAAQ,GAAG,cAAc,CAAC,OAAO,CAAC;IACzC,CAAC;IA0CO,aAAa,CAAC,QAAgB;QACpC,yBAAyB;QACzB,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC;QAChE,
|
|
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,IAAI;IAC1B,KAAK,EAAE,QAAQ;CAChB,CAAC","sourcesContent":["import { DictationConfig } from './types.js';\r\n\r\nexport const LANGUAGES_SUPPORTED = ['en', 'da'];\r\nexport const DEFAULT_DICTATION_CONFIG: DictationConfig = {\r\n primaryLanguage: 'en',\r\n interimResults: true,\r\n spokenPunctuation: true,\r\n automaticPunctuation: true,\r\n model: 'others'\r\n};\r\n"]}
|
package/dist/styles/callout.js
CHANGED
|
@@ -1,33 +1,33 @@
|
|
|
1
1
|
import { css } from 'lit';
|
|
2
|
-
const CalloutStyles = css `
|
|
3
|
-
.callout {
|
|
4
|
-
background: var(--callout-accent-background);
|
|
5
|
-
border: 1px solid var(--callout-accent-border);
|
|
6
|
-
color: var(--callout-accent-text);
|
|
7
|
-
padding: 8px;
|
|
8
|
-
border-radius: var(--card-inner-border-radius);
|
|
9
|
-
display: flex;
|
|
10
|
-
font-size: 0.9rem;
|
|
11
|
-
gap: 8px;
|
|
12
|
-
align-items:
|
|
13
|
-
max-width: 100%;
|
|
14
|
-
height: fit-content;
|
|
15
|
-
&.red {
|
|
16
|
-
background: var(--callout-red-background);
|
|
17
|
-
border: 1px solid var(--callout-red-border);
|
|
18
|
-
color: var(--callout-red-text);
|
|
19
|
-
}
|
|
20
|
-
&.orange {
|
|
21
|
-
background: var(--callout-orange-background);
|
|
22
|
-
border: 1px solid var(--callout-orange-border);
|
|
23
|
-
color: var(--callout-orange-text);
|
|
24
|
-
}
|
|
25
|
-
&.small {
|
|
26
|
-
width: 100%;
|
|
27
|
-
padding: 6px;
|
|
28
|
-
font-size: 0.7rem;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
2
|
+
const CalloutStyles = css `
|
|
3
|
+
.callout {
|
|
4
|
+
background: var(--callout-accent-background);
|
|
5
|
+
border: 1px solid var(--callout-accent-border);
|
|
6
|
+
color: var(--callout-accent-text);
|
|
7
|
+
padding: 8px;
|
|
8
|
+
border-radius: var(--card-inner-border-radius);
|
|
9
|
+
display: flex;
|
|
10
|
+
font-size: 0.9rem;
|
|
11
|
+
gap: 8px;
|
|
12
|
+
align-items: center;
|
|
13
|
+
max-width: 100%;
|
|
14
|
+
height: fit-content;
|
|
15
|
+
&.red {
|
|
16
|
+
background: var(--callout-red-background);
|
|
17
|
+
border: 1px solid var(--callout-red-border);
|
|
18
|
+
color: var(--callout-red-text);
|
|
19
|
+
}
|
|
20
|
+
&.orange {
|
|
21
|
+
background: var(--callout-orange-background);
|
|
22
|
+
border: 1px solid var(--callout-orange-border);
|
|
23
|
+
color: var(--callout-orange-text);
|
|
24
|
+
}
|
|
25
|
+
&.small {
|
|
26
|
+
width: 100%;
|
|
27
|
+
padding: 6px;
|
|
28
|
+
font-size: 0.7rem;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
31
|
`;
|
|
32
32
|
export default CalloutStyles;
|
|
33
33
|
//# sourceMappingURL=callout.js.map
|
|
@@ -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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;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:
|
|
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';\r\n\r\nconst CalloutStyles = css`\r\n .callout {\r\n background: var(--callout-accent-background);\r\n border: 1px solid var(--callout-accent-border);\r\n color: var(--callout-accent-text);\r\n padding: 8px;\r\n border-radius: var(--card-inner-border-radius);\r\n display: flex;\r\n font-size: 0.9rem;\r\n gap: 8px;\r\n align-items: center;\r\n max-width: 100%;\r\n height: fit-content;\r\n &.red {\r\n background: var(--callout-red-background);\r\n border: 1px solid var(--callout-red-border);\r\n color: var(--callout-red-text);\r\n }\r\n &.orange {\r\n background: var(--callout-orange-background);\r\n border: 1px solid var(--callout-orange-border);\r\n color: var(--callout-orange-text);\r\n }\r\n &.small {\r\n width: 100%;\r\n padding: 6px;\r\n font-size: 0.7rem;\r\n }\r\n }\r\n`;\r\n\r\nexport default CalloutStyles;\r\n"]}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
export type RecordingState = 'initializing' | 'recording' | 'stopping' | 'stopped';
|
|
2
|
+
interface CommandVariable {
|
|
3
|
+
key: string;
|
|
4
|
+
type: 'enum' | 'string';
|
|
5
|
+
enum?: string[];
|
|
6
|
+
}
|
|
2
7
|
export interface Command {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
8
|
+
id: string;
|
|
9
|
+
phrases: string[];
|
|
10
|
+
variables?: CommandVariable[];
|
|
6
11
|
}
|
|
7
12
|
export interface DictationConfig {
|
|
8
13
|
primaryLanguage: string;
|
|
@@ -18,3 +23,4 @@ export interface ServerConfig {
|
|
|
18
23
|
tenant?: string;
|
|
19
24
|
token?: string;
|
|
20
25
|
}
|
|
26
|
+
export {};
|
package/dist/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"","sourcesContent":["export type RecordingState =\n | 'initializing'\n | 'recording'\n | 'stopping'\n | 'stopped';\n\nexport interface Command {\n
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"","sourcesContent":["export type RecordingState =\n | 'initializing'\n | 'recording'\n | 'stopping'\n | 'stopped';\n\ninterface CommandVariable {\n key: string;\n type: 'enum' | 'string';\n enum?: string[];\n}\n\nexport interface Command {\n id: string;\n phrases: string[];\n variables?: CommandVariable[];\n}\n\nexport interface DictationConfig {\n primaryLanguage: string;\n interimResults: boolean;\n spokenPunctuation: boolean;\n automaticPunctuation: boolean;\n model: string;\n commands?: Command[];\n}\n\nexport type PartialDictationConfig = Partial<DictationConfig>;\n\nexport interface ServerConfig {\n environment?: string;\n tenant?: string;\n token?: string;\n}\n"]}
|
package/dist/utils.d.ts
CHANGED
package/dist/utils.js
CHANGED
|
@@ -99,7 +99,7 @@ export function decodeToken(token) {
|
|
|
99
99
|
// Validate the token structure (should contain at least header and payload parts)
|
|
100
100
|
const parts = token.split('.');
|
|
101
101
|
if (parts.length < 2) {
|
|
102
|
-
throw new Error('Invalid token format
|
|
102
|
+
throw new Error('Invalid token format');
|
|
103
103
|
}
|
|
104
104
|
// Retrieve the payload (second part) of the JWT token
|
|
105
105
|
const base64Url = parts[1];
|
|
@@ -143,4 +143,26 @@ export function decodeToken(token) {
|
|
|
143
143
|
};
|
|
144
144
|
}
|
|
145
145
|
}
|
|
146
|
+
export async function getMediaStream(deviceId) {
|
|
147
|
+
if (!deviceId) {
|
|
148
|
+
throw new Error('No device ID provided');
|
|
149
|
+
}
|
|
150
|
+
if (deviceId === 'display_audio') {
|
|
151
|
+
const stream = await navigator.mediaDevices.getDisplayMedia({
|
|
152
|
+
audio: true,
|
|
153
|
+
video: true,
|
|
154
|
+
});
|
|
155
|
+
stream.getTracks().forEach(track => {
|
|
156
|
+
if (track.kind === 'video') {
|
|
157
|
+
stream.removeTrack(track);
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
return stream;
|
|
161
|
+
}
|
|
162
|
+
// Get media stream and initialize audio service.
|
|
163
|
+
const constraints = deviceId !== 'default'
|
|
164
|
+
? { audio: { deviceId: { exact: deviceId } } }
|
|
165
|
+
: { audio: true };
|
|
166
|
+
return await navigator.mediaDevices.getUserMedia(constraints);
|
|
167
|
+
}
|
|
146
168
|
//# sourceMappingURL=utils.js.map
|
package/dist/utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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"]}
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"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","sourcesContent":["/* eslint-disable no-console */\r\n/**\r\n * Returns the localized name of a language given its BCP-47 code.\r\n *\r\n * @param languageCode - The BCP-47 language code (e.g. \"en\")\r\n * @returns The localized language name (e.g. \"English\") or the original code if unavailable.\r\n */\r\nexport function getLanguageName(languageCode: string): string {\r\n const userLocale = navigator.language || 'en';\r\n const displayNames = new Intl.DisplayNames([userLocale], {\r\n type: 'language',\r\n });\r\n const languageName = displayNames.of(languageCode);\r\n return languageName || languageCode;\r\n}\r\n\r\n/**\r\n * Requests access to the microphone.\r\n *\r\n * This function checks if the microphone permission is in \"prompt\" state, then requests\r\n * access and stops any active tracks immediately. It also logs if permission is already granted.\r\n *\r\n * @returns A promise that resolves when the permission request is complete.\r\n */\r\nexport async function requestMicAccess(): Promise<void> {\r\n try {\r\n // Fallback if Permissions API is not available\r\n if (!navigator.permissions) {\r\n const stream = await navigator.mediaDevices.getUserMedia({ audio: true });\r\n stream.getTracks().forEach(track => track.stop());\r\n return;\r\n }\r\n\r\n const permissionStatus = await navigator.permissions.query({\r\n // eslint-disable-next-line no-undef\r\n name: 'microphone' as PermissionName,\r\n });\r\n\r\n if (permissionStatus.state === 'prompt') {\r\n const stream = await navigator.mediaDevices.getUserMedia({ audio: true });\r\n stream.getTracks().forEach(track => track.stop());\r\n } else if (permissionStatus.state === 'denied') {\r\n console.warn('Microphone permission is denied.');\r\n }\r\n } catch (error) {\r\n console.error('Error checking/requesting microphone permission:', error);\r\n }\r\n}\r\n\r\n/**\r\n * Retrieves available audio input devices.\r\n *\r\n * This function uses the mediaDevices API to enumerate devices and filters out those\r\n * which are audio inputs. In some browsers, you may need to request user media before\r\n * device labels are populated.\r\n *\r\n * @returns A promise that resolves with an object containing:\r\n * - `devices`: an array of MediaDeviceInfo objects for audio inputs.\r\n * - `defaultDeviceId`: the deviceId of the first audio input, if available.\r\n */\r\nexport async function getAudioDevices(): Promise<{\r\n devices: MediaDeviceInfo[];\r\n defaultDevice?: MediaDeviceInfo;\r\n}> {\r\n if (!navigator.mediaDevices?.enumerateDevices) {\r\n console.error('Media devices API not supported.');\r\n return { devices: [] };\r\n }\r\n\r\n await requestMicAccess();\r\n\r\n try {\r\n // Optionally: await navigator.mediaDevices.getUserMedia({ audio: true });\r\n const devices = await navigator.mediaDevices.enumerateDevices();\r\n const audioDevices = devices.filter(device => device.kind === 'audioinput');\r\n const defaultDevice = audioDevices.length > 0 ? audioDevices[0] : undefined;\r\n return { devices: audioDevices, defaultDevice };\r\n } catch (error) {\r\n console.error('Error enumerating devices:', error);\r\n return { devices: [] };\r\n }\r\n}\r\n\r\n/**\r\n * Decodes a JWT token and extracts environment and tenant details from its issuer URL.\r\n *\r\n * This function assumes the JWT token follows the standard header.payload.signature format.\r\n * It decodes the payload from base64 URL format, parses it as JSON, and then uses a regex\r\n * to extract the `environment` and `tenant` from the issuer URL (iss field) if it matches the pattern:\r\n * https://keycloak.{environment}.corti.app/realms/{tenant}.\r\n *\r\n * @param token - A JSON Web Token (JWT) string.\r\n * @returns An object containing:\r\n * - `environment`: The extracted environment from the issuer URL.\r\n * - `tenant`: The extracted tenant from the issuer URL.\r\n * - `token`: The original token string.\r\n * If the issuer URL doesn't match the expected format, the function returns the full decoded token details.\r\n *\r\n * @throws Will throw an error if:\r\n * - The token format is invalid.\r\n * - The base64 decoding or URI decoding fails.\r\n * - The JSON payload is invalid.\r\n * - The token payload does not contain an issuer (iss) field.\r\n */\r\nexport function decodeToken(token: string) {\r\n // Validate the token structure (should contain at least header and payload parts)\r\n const parts = token.split('.');\r\n if (parts.length < 2) {\r\n throw new Error('Invalid token format');\r\n }\r\n\r\n // Retrieve the payload (second part) of the JWT token\r\n const base64Url = parts[1];\r\n\r\n // Replace URL-safe characters to match standard base64 encoding\r\n const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');\r\n\r\n // Decode the base64 string into a JSON string\r\n let jsonPayload: string;\r\n try {\r\n jsonPayload = decodeURIComponent(\r\n atob(base64)\r\n .split('')\r\n .map(c => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))\r\n .join(''),\r\n );\r\n } catch (error) {\r\n throw new Error('Failed to decode token payload');\r\n }\r\n\r\n // Parse the JSON string to obtain token details\r\n let tokenDetails: { iss: string; [key: string]: unknown };\r\n try {\r\n tokenDetails = JSON.parse(jsonPayload);\r\n } catch (error) {\r\n throw new Error('Invalid JSON payload in token');\r\n }\r\n\r\n // Extract the issuer URL from the token details\r\n const issuerUrl: string = tokenDetails.iss;\r\n if (!issuerUrl) {\r\n throw new Error('Token payload does not contain an issuer (iss) field');\r\n }\r\n\r\n // Regex to extract environment and tenant from issuer URL:\r\n // Expected format: https://keycloak.{environment}.corti.app/realms/{tenant}\r\n // Note: Unnecessary escapes in character classes have been removed.\r\n const regex =\r\n /^https:\\/\\/(keycloak|auth)\\.([^.]+)\\.corti\\.app\\/realms\\/([^/]+)/;\r\n const match = issuerUrl.match(regex);\r\n\r\n // If the issuer URL matches the expected pattern, return the extracted values along with the token\r\n if (match) {\r\n return {\r\n environment: match[2],\r\n tenant: match[3],\r\n token,\r\n };\r\n }\r\n}\r\n\r\nexport async function getMediaStream(deviceId?: string): Promise<MediaStream> {\r\n if (!deviceId) {\r\n throw new Error('No device ID provided');\r\n }\r\n\r\n if (deviceId === 'display_audio') {\r\n const stream = await navigator.mediaDevices.getDisplayMedia({\r\n audio: true,\r\n video: true,\r\n });\r\n stream.getTracks().forEach(track => {\r\n if (track.kind === 'video') {\r\n stream.removeTrack(track);\r\n }\r\n });\r\n return stream;\r\n }\r\n\r\n // Get media stream and initialize audio service.\r\n const constraints: MediaStreamConstraints =\r\n deviceId !== 'default'\r\n ? { audio: { deviceId: { exact: deviceId } } }\r\n : { audio: true };\r\n\r\n return await navigator.mediaDevices.getUserMedia(constraints);\r\n}\r\n"]}
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@corti/dictation-web",
|
|
3
3
|
"description": "Web component for Corti Dictation",
|
|
4
4
|
"author": "Corti ApS",
|
|
5
|
-
"version": "0.1.
|
|
5
|
+
"version": "0.1.7",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "dist/index.js",
|
|
8
8
|
"module": "dist/index.js",
|
|
@@ -11,7 +11,9 @@
|
|
|
11
11
|
"default": "./dist/bundle.js"
|
|
12
12
|
},
|
|
13
13
|
"unpkg": "dist/bundle.js",
|
|
14
|
-
"files": [
|
|
14
|
+
"files": [
|
|
15
|
+
"dist"
|
|
16
|
+
],
|
|
15
17
|
"scripts": {
|
|
16
18
|
"analyze": "cem analyze --litelement",
|
|
17
19
|
"build": "tsc && npm run analyze -- --exclude dist",
|
|
@@ -21,7 +23,7 @@
|
|
|
21
23
|
"prepublish": "tsc && npm run analyze -- --exclude dist",
|
|
22
24
|
"lint": "eslint --ext .ts,.tsx src --ignore-path .gitignore && prettier \"src/**/*.ts\" --check --ignore-path .gitignore",
|
|
23
25
|
"format": "eslint --ext .ts,.tsx src --fix --ignore-path .gitignore && prettier \"src/**/*.ts\" --write --ignore-path .gitignore",
|
|
24
|
-
"prepare": "husky",
|
|
26
|
+
"prepare": "husky && husky install",
|
|
25
27
|
"test": "tsc && wtr --coverage",
|
|
26
28
|
"test:watch": "tsc && concurrently -k -r \"tsc --watch --preserveWatchOutput\" \"wtr --watch\"",
|
|
27
29
|
"storybook": "tsc && npm run analyze -- --exclude dist && concurrently -k -r \"tsc --watch --preserveWatchOutput\" \"storybook dev -p 8080\"",
|
|
@@ -31,7 +33,6 @@
|
|
|
31
33
|
"lit": "^3.1.4"
|
|
32
34
|
},
|
|
33
35
|
"devDependencies": {
|
|
34
|
-
"sinon": "^19.0.2",
|
|
35
36
|
"@custom-elements-manifest/analyzer": "^0.10.3",
|
|
36
37
|
"@open-wc/eslint-config": "^12.0.3",
|
|
37
38
|
"@open-wc/testing": "^4.0.0",
|
|
@@ -51,9 +52,10 @@
|
|
|
51
52
|
"eslint": "^8.57.0",
|
|
52
53
|
"eslint-config-prettier": "^9.1.0",
|
|
53
54
|
"eslint-plugin-html": "^8.1.2",
|
|
54
|
-
"husky": "^
|
|
55
|
+
"husky": "^8.0.0",
|
|
55
56
|
"lint-staged": "^15.2.7",
|
|
56
57
|
"prettier": "^3.3.2",
|
|
58
|
+
"sinon": "^19.0.2",
|
|
57
59
|
"storybook": "^7.6.20",
|
|
58
60
|
"tslib": "^2.6.3",
|
|
59
61
|
"typescript": "^5.5.3"
|