@corti/dictation-web 0.0.1
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/.editorconfig +29 -0
- package/.eslintrc.json +16 -0
- package/.husky/pre-commit +1 -0
- package/.storybook/main.js +8 -0
- package/README.md +120 -0
- package/demo/index.html +98 -0
- package/dist/src/CortiDictation.d.ts +19 -0
- package/dist/src/CortiDictation.js +137 -0
- package/dist/src/CortiDictation.js.map +1 -0
- package/dist/src/DictationService.d.ts +13 -0
- package/dist/src/DictationService.js +70 -0
- package/dist/src/DictationService.js.map +1 -0
- package/dist/src/RecorderManager.d.ts +20 -0
- package/dist/src/RecorderManager.js +85 -0
- package/dist/src/RecorderManager.js.map +1 -0
- package/dist/src/audioRecorderManager.d.ts +17 -0
- package/dist/src/audioRecorderManager.js +78 -0
- package/dist/src/audioRecorderManager.js.map +1 -0
- package/dist/src/audioService.d.ts +6 -0
- package/dist/src/audioService.js +21 -0
- package/dist/src/audioService.js.map +1 -0
- package/dist/src/componentStyles.d.ts +1 -0
- package/dist/src/componentStyles.js +51 -0
- package/dist/src/componentStyles.js.map +1 -0
- package/dist/src/components/audio-visualiser.d.ts +12 -0
- package/dist/src/components/audio-visualiser.js +60 -0
- package/dist/src/components/audio-visualiser.js.map +1 -0
- package/dist/src/components/settings-menu.d.ts +15 -0
- package/dist/src/components/settings-menu.js +148 -0
- package/dist/src/components/settings-menu.js.map +1 -0
- package/dist/src/components/visualiser.d.ts +7 -0
- package/dist/src/components/visualiser.js +62 -0
- package/dist/src/components/visualiser.js.map +1 -0
- package/dist/src/constants.d.ts +3 -0
- package/dist/src/constants.js +9 -0
- package/dist/src/constants.js.map +1 -0
- package/dist/src/corti-dictation.d.ts +1 -0
- package/dist/src/corti-dictation.js +3 -0
- package/dist/src/corti-dictation.js.map +1 -0
- package/dist/src/dictationService.d.ts +13 -0
- package/dist/src/dictationService.js +70 -0
- package/dist/src/dictationService.js.map +1 -0
- package/dist/src/icons/icons.d.ts +17 -0
- package/dist/src/icons/icons.js +153 -0
- package/dist/src/icons/icons.js.map +1 -0
- package/dist/src/icons/index.d.ts +0 -0
- package/dist/src/icons/index.js +2 -0
- package/dist/src/icons/index.js.map +1 -0
- package/dist/src/icons/micOn.d.ts +7 -0
- package/dist/src/icons/micOn.js +25 -0
- package/dist/src/icons/micOn.js.map +1 -0
- package/dist/src/index.d.ts +20 -0
- package/dist/src/index.js +147 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/mediaRecorderService.d.ts +6 -0
- package/dist/src/mediaRecorderService.js +31 -0
- package/dist/src/mediaRecorderService.js.map +1 -0
- package/dist/src/mic-selector.d.ts +18 -0
- package/dist/src/mic-selector.js +131 -0
- package/dist/src/mic-selector.js.map +1 -0
- package/dist/src/settings-menu.d.ts +18 -0
- package/dist/src/settings-menu.js +131 -0
- package/dist/src/settings-menu.js.map +1 -0
- package/dist/src/settings-popover.d.ts +18 -0
- package/dist/src/settings-popover.js +131 -0
- package/dist/src/settings-popover.js.map +1 -0
- package/dist/src/settings.d.ts +18 -0
- package/dist/src/settings.js +131 -0
- package/dist/src/settings.js.map +1 -0
- package/dist/src/styles/ComponentStyles.d.ts +2 -0
- package/dist/src/styles/ComponentStyles.js +52 -0
- package/dist/src/styles/ComponentStyles.js.map +1 -0
- package/dist/src/styles/buttons.d.ts +2 -0
- package/dist/src/styles/buttons.js +58 -0
- package/dist/src/styles/buttons.js.map +1 -0
- package/dist/src/styles/callout.d.ts +2 -0
- package/dist/src/styles/callout.js +26 -0
- package/dist/src/styles/callout.js.map +1 -0
- package/dist/src/styles/select.d.ts +2 -0
- package/dist/src/styles/select.js +36 -0
- package/dist/src/styles/select.js.map +1 -0
- package/dist/src/styles/theme.d.ts +2 -0
- package/dist/src/styles/theme.js +74 -0
- package/dist/src/styles/theme.js.map +1 -0
- package/dist/src/types.d.ts +20 -0
- package/dist/src/types.js +2 -0
- package/dist/src/types.js.map +1 -0
- package/dist/src/utils.d.ts +31 -0
- package/dist/src/utils.js +77 -0
- package/dist/src/utils.js.map +1 -0
- package/dist/stories/index.stories.d.ts +33 -0
- package/dist/stories/index.stories.js +37 -0
- package/dist/stories/index.stories.js.map +1 -0
- package/dist/test/corti-dictation.test.d.ts +1 -0
- package/dist/test/corti-dictation.test.js +100 -0
- package/dist/test/corti-dictation.test.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/docs/DEV_README.md +80 -0
- package/package.json +92 -0
- package/src/DictationService.ts +99 -0
- package/src/RecorderManager.ts +114 -0
- package/src/audioService.ts +25 -0
- package/src/components/audio-visualiser.ts +56 -0
- package/src/components/settings-menu.ts +152 -0
- package/src/constants.ts +10 -0
- package/src/corti-dictation.ts +3 -0
- package/src/icons/icons.ts +141 -0
- package/src/icons/index.ts +0 -0
- package/src/index.ts +154 -0
- package/src/styles/ComponentStyles.ts +53 -0
- package/src/styles/buttons.ts +59 -0
- package/src/styles/callout.ts +27 -0
- package/src/styles/select.ts +37 -0
- package/src/styles/theme.ts +75 -0
- package/src/types.ts +28 -0
- package/src/utils.ts +83 -0
- package/stories/index.stories.ts +60 -0
- package/test/corti-dictation.test.ts +124 -0
- package/tsconfig.json +22 -0
- package/web-dev-server.config.js +27 -0
- package/web-test-runner.config.js +41 -0
package/.editorconfig
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# EditorConfig helps developers define and maintain consistent
|
|
2
|
+
# coding styles between different editors and IDEs
|
|
3
|
+
# editorconfig.org
|
|
4
|
+
|
|
5
|
+
root = true
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
[*]
|
|
9
|
+
|
|
10
|
+
# Change these settings to your own preference
|
|
11
|
+
indent_style = space
|
|
12
|
+
indent_size = 2
|
|
13
|
+
|
|
14
|
+
# We recommend you to keep these unchanged
|
|
15
|
+
end_of_line = lf
|
|
16
|
+
charset = utf-8
|
|
17
|
+
trim_trailing_whitespace = true
|
|
18
|
+
insert_final_newline = true
|
|
19
|
+
|
|
20
|
+
[*.md]
|
|
21
|
+
trim_trailing_whitespace = false
|
|
22
|
+
|
|
23
|
+
[*.json]
|
|
24
|
+
indent_size = 2
|
|
25
|
+
|
|
26
|
+
[*.{html,js,md}]
|
|
27
|
+
block_comment_start = /**
|
|
28
|
+
block_comment = *
|
|
29
|
+
block_comment_end = */
|
package/.eslintrc.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"parser": "@typescript-eslint/parser",
|
|
3
|
+
"extends": [
|
|
4
|
+
"eslint:recommended",
|
|
5
|
+
"plugin:@typescript-eslint/recommended"
|
|
6
|
+
],
|
|
7
|
+
"plugins": ["@typescript-eslint", "html"],
|
|
8
|
+
"overrides": [
|
|
9
|
+
{
|
|
10
|
+
"files": ["*.ts", "*.tsx"],
|
|
11
|
+
"rules": {
|
|
12
|
+
"import/extensions": "off"
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
./node_modules/.bin/lint-staged
|
package/README.md
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# Corti Dictation SDK
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
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
|
+
> **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
|
+
---
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
Include the SDK in your project by importing the JavaScript module:
|
|
13
|
+
|
|
14
|
+
```html
|
|
15
|
+
<script type="module" src="path-to/corti-dictation.js"></script>
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
### Basic Example
|
|
23
|
+
|
|
24
|
+
```html
|
|
25
|
+
<!DOCTYPE html>
|
|
26
|
+
<html lang="en">
|
|
27
|
+
<head>
|
|
28
|
+
<meta charset="UTF-8">
|
|
29
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
30
|
+
</head>
|
|
31
|
+
<body>
|
|
32
|
+
<corti-dictation></corti-dictation>
|
|
33
|
+
<textarea id="transcript" placeholder="Transcript will appear here..."></textarea>
|
|
34
|
+
|
|
35
|
+
<script type="module">
|
|
36
|
+
import './path-to/corti-dictation.js';
|
|
37
|
+
|
|
38
|
+
const dictationEl = document.querySelector('corti-dictation');
|
|
39
|
+
|
|
40
|
+
// Provide server configuration
|
|
41
|
+
dictationEl.serverConfig = {
|
|
42
|
+
environment: "dev-weu",
|
|
43
|
+
tenant: "copsdev",
|
|
44
|
+
token: "your-api-token"
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// Listen for events
|
|
48
|
+
dictationEl.addEventListener('transcript', (e) => {
|
|
49
|
+
document.getElementById('transcript').value += e.detail.data.text + ' ';
|
|
50
|
+
});
|
|
51
|
+
</script>
|
|
52
|
+
</body>
|
|
53
|
+
</html>
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## API Reference
|
|
59
|
+
|
|
60
|
+
### Properties
|
|
61
|
+
|
|
62
|
+
| Property | Type | Description |
|
|
63
|
+
|-----------------|---------|-------------|
|
|
64
|
+
| `devices` | Array | List of available recording devices. |
|
|
65
|
+
| `recordingState` | String | Current state of recording (`stopped`, `recording`). |
|
|
66
|
+
| `dictationConfig` | Object | Configuration settings for dictation. |
|
|
67
|
+
| `serverConfig` | Object | Server authentication and API settings. Must include `environment`, `tenant`, and `token`. |
|
|
68
|
+
|
|
69
|
+
### Methods
|
|
70
|
+
|
|
71
|
+
| Method | Description |
|
|
72
|
+
|---------------------|-------------|
|
|
73
|
+
| `toggleRecording()` | Starts or stops recording. |
|
|
74
|
+
|
|
75
|
+
### Events
|
|
76
|
+
|
|
77
|
+
| Event | Description |
|
|
78
|
+
|----------------------------|-------------|
|
|
79
|
+
| `recording-state-changed` | Fired when the recording state changes. `detail.state` contains the new state. |
|
|
80
|
+
| `recording-device-changed` | Fired when the user switches recording devices. `detail.deviceId` contains the new device ID. |
|
|
81
|
+
| `transcript` | Fired when a new transcript is received. `detail.data.text` contains the transcribed text. |
|
|
82
|
+
| `audio-level-changed` | Fired when the input audio level changes. `detail.audioLevel` contains the new level. |
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Authentication
|
|
87
|
+
|
|
88
|
+
This SDK does not handle OAuth 2.0 authentication. The client must provide an API key or access token in `serverConfig.token`.
|
|
89
|
+
|
|
90
|
+
Example:
|
|
91
|
+
```js
|
|
92
|
+
dictationEl.serverConfig = {
|
|
93
|
+
environment: "prod-eu",
|
|
94
|
+
tenant: "your-tenant-id",
|
|
95
|
+
token: "your-api-key"
|
|
96
|
+
};
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Notes
|
|
102
|
+
- Ensure the provided API token is valid.
|
|
103
|
+
- The component requires microphone access permissions.
|
|
104
|
+
- Works in modern browsers that support Web Components and MediaRecorder API.
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## License
|
|
109
|
+
This SDK is not licensed.
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Developer Guide
|
|
114
|
+
See [Developer Setup](docs/DEV_README.md) for installation and development details.
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Support
|
|
119
|
+
For issues or questions, contact **Corti Support** at [support@corti.ai](mailto:support@corti.ai).
|
|
120
|
+
|
package/demo/index.html
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en-GB">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
|
|
6
|
+
<style>
|
|
7
|
+
body {
|
|
8
|
+
background: #fafafa;
|
|
9
|
+
font-family: Arial, sans-serif;
|
|
10
|
+
margin: 20px;
|
|
11
|
+
}
|
|
12
|
+
/* Dark mode support */
|
|
13
|
+
@media (prefers-color-scheme: dark) {
|
|
14
|
+
body {
|
|
15
|
+
background: #1c1e2b;
|
|
16
|
+
color: #fff;
|
|
17
|
+
}
|
|
18
|
+
textarea {
|
|
19
|
+
background: #333;
|
|
20
|
+
color: #fff;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
textarea {
|
|
24
|
+
width: 100%;
|
|
25
|
+
max-width: 800px;
|
|
26
|
+
height: 150px;
|
|
27
|
+
margin-top: 20px;
|
|
28
|
+
padding: 10px;
|
|
29
|
+
font-size: 14px;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
#interimTranscript {
|
|
33
|
+
background: #333333aa;
|
|
34
|
+
color: #fff;
|
|
35
|
+
font-size: 24px;
|
|
36
|
+
margin-top: 10px;
|
|
37
|
+
position: fixed;
|
|
38
|
+
bottom: 40px;
|
|
39
|
+
left: 50%;
|
|
40
|
+
transform: translateX(-50%);
|
|
41
|
+
padding: 10px;
|
|
42
|
+
display: none;
|
|
43
|
+
}
|
|
44
|
+
</style>
|
|
45
|
+
</head>
|
|
46
|
+
<body>
|
|
47
|
+
<!-- Directly using the custom element without Lit -->
|
|
48
|
+
<corti-dictation></corti-dictation>
|
|
49
|
+
|
|
50
|
+
<!-- Textarea to display transcript text -->
|
|
51
|
+
<textarea id="transcript" placeholder="Transcript will appear here..."></textarea>
|
|
52
|
+
|
|
53
|
+
<div id="interimTranscript"></div>
|
|
54
|
+
|
|
55
|
+
<script type="module">
|
|
56
|
+
// Import the custom component (assumes corti-dictation.js registers the element)
|
|
57
|
+
import '../dist/src/corti-dictation.js';
|
|
58
|
+
|
|
59
|
+
// Server configuration for the dictation component
|
|
60
|
+
const serverConfig = {
|
|
61
|
+
environment: "a",
|
|
62
|
+
tenant: "a",
|
|
63
|
+
token: "a",
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
// Get the corti-dictation element and assign the server config
|
|
67
|
+
const dictationEl = document.querySelector('corti-dictation');
|
|
68
|
+
dictationEl.serverConfig = serverConfig;
|
|
69
|
+
|
|
70
|
+
// Listen to events from the dictation component
|
|
71
|
+
dictationEl.addEventListener('recording-state-changed', (e) => {
|
|
72
|
+
console.log('Recording state:', e.detail.state);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
dictationEl.addEventListener('recording-device-changed', (e) => {
|
|
76
|
+
console.log('Recording Device Changed:', e.detail);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
dictationEl.addEventListener('error', (e) => {
|
|
80
|
+
console.error(e.detail);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// Update the textarea with transcript text when received
|
|
84
|
+
dictationEl.addEventListener('transcript', (e) => {
|
|
85
|
+
const {data} = e.detail
|
|
86
|
+
const transcriptArea = document.getElementById('transcript');
|
|
87
|
+
const interimTranscript = document.getElementById('interimTranscript');
|
|
88
|
+
if(data.isFinal){
|
|
89
|
+
transcriptArea.value += data.text + " ";
|
|
90
|
+
interimTranscript.style.display = 'none';
|
|
91
|
+
} else {
|
|
92
|
+
interimTranscript.style.display = 'block';
|
|
93
|
+
interimTranscript.innerText = data.rawTranscriptText;
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
</script>
|
|
97
|
+
</body>
|
|
98
|
+
</html>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { LitElement } from 'lit';
|
|
2
|
+
import './components/settings-menu';
|
|
3
|
+
import './components/audio-visualiser';
|
|
4
|
+
import './icons/icons';
|
|
5
|
+
import { DictationConfig, ServerConfig } from './types';
|
|
6
|
+
export declare class CortiDictation extends LitElement {
|
|
7
|
+
static styles: import("lit").CSSResult[];
|
|
8
|
+
devices: MediaDeviceInfo[];
|
|
9
|
+
recordingState: string;
|
|
10
|
+
dictationConfig: DictationConfig;
|
|
11
|
+
serverConfig: ServerConfig;
|
|
12
|
+
private _audioLevel;
|
|
13
|
+
private recorderManager;
|
|
14
|
+
connectedCallback(): Promise<void>;
|
|
15
|
+
toggleRecording(): void;
|
|
16
|
+
_toggleRecording(): void;
|
|
17
|
+
_onRecordingDeviceChanged(event: Event): Promise<void>;
|
|
18
|
+
render(): import("lit-html").TemplateResult<1>;
|
|
19
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { __decorate } from "tslib";
|
|
2
|
+
// corti-dictation.ts
|
|
3
|
+
import { html, LitElement } from 'lit';
|
|
4
|
+
import { property, state } from 'lit/decorators.js';
|
|
5
|
+
import { RecorderManager } from './RecorderManager';
|
|
6
|
+
import './components/settings-menu';
|
|
7
|
+
import './components/audio-visualiser';
|
|
8
|
+
import './icons/icons';
|
|
9
|
+
import ThemeStyles from './styles/theme';
|
|
10
|
+
import ButtonStyles from './styles/buttons';
|
|
11
|
+
import { componentStyles } from './componentStyles';
|
|
12
|
+
import { DEFAULT_DICTATION_CONFIG } from './constants';
|
|
13
|
+
import CalloutStyles from './styles/callout';
|
|
14
|
+
export class CortiDictation extends LitElement {
|
|
15
|
+
constructor() {
|
|
16
|
+
super(...arguments);
|
|
17
|
+
this.devices = [];
|
|
18
|
+
this.recordingState = 'stopped';
|
|
19
|
+
this.dictationConfig = DEFAULT_DICTATION_CONFIG;
|
|
20
|
+
this.serverConfig = {};
|
|
21
|
+
this._audioLevel = 0;
|
|
22
|
+
this.recorderManager = new RecorderManager();
|
|
23
|
+
}
|
|
24
|
+
async connectedCallback() {
|
|
25
|
+
super.connectedCallback();
|
|
26
|
+
await this.recorderManager.initialize();
|
|
27
|
+
this.devices = this.recorderManager.devices;
|
|
28
|
+
// Map event names to any extra handling logic
|
|
29
|
+
const eventHandlers = {
|
|
30
|
+
'recording-state-changed': (e) => {
|
|
31
|
+
this.recordingState = e.detail.state;
|
|
32
|
+
},
|
|
33
|
+
'audio-level-changed': (e) => {
|
|
34
|
+
this._audioLevel = e.detail.audioLevel;
|
|
35
|
+
this.requestUpdate();
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
const eventsToRelay = [
|
|
39
|
+
'recording-state-changed',
|
|
40
|
+
'audio-level-changed',
|
|
41
|
+
'audio-packet',
|
|
42
|
+
'transcript',
|
|
43
|
+
];
|
|
44
|
+
eventsToRelay.forEach((eventName) => {
|
|
45
|
+
this.recorderManager.addEventListener(eventName, (e) => {
|
|
46
|
+
const customEvent = e;
|
|
47
|
+
// Perform any additional handling if defined
|
|
48
|
+
if (eventHandlers[eventName]) {
|
|
49
|
+
eventHandlers[eventName](customEvent);
|
|
50
|
+
}
|
|
51
|
+
// Re-dispatch the event from the component
|
|
52
|
+
this.dispatchEvent(new CustomEvent(eventName, {
|
|
53
|
+
detail: customEvent.detail,
|
|
54
|
+
bubbles: true,
|
|
55
|
+
composed: true,
|
|
56
|
+
}));
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
toggleRecording() {
|
|
61
|
+
this._toggleRecording();
|
|
62
|
+
}
|
|
63
|
+
_toggleRecording() {
|
|
64
|
+
if (this.recordingState === 'recording') {
|
|
65
|
+
this.recorderManager.stopRecording();
|
|
66
|
+
}
|
|
67
|
+
else if (this.recordingState === 'stopped') {
|
|
68
|
+
this.recorderManager.startRecording({ dictationConfig: this.dictationConfig, serverConfig: this.serverConfig });
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
// Handle device change events if needed
|
|
72
|
+
async _onRecordingDeviceChanged(event) {
|
|
73
|
+
const customEvent = event;
|
|
74
|
+
this.recorderManager.selectedDevice = customEvent.detail.deviceId;
|
|
75
|
+
if (this.recordingState === 'recording') {
|
|
76
|
+
await this.recorderManager.stopRecording();
|
|
77
|
+
await this.recorderManager.startRecording({ dictationConfig: this.dictationConfig, serverConfig: this.serverConfig });
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
render() {
|
|
81
|
+
const isConfigured = this.serverConfig && this.serverConfig.token && this.serverConfig.environment && this.serverConfig.tenant;
|
|
82
|
+
if (!isConfigured) {
|
|
83
|
+
return html `
|
|
84
|
+
<div class="wrapper">
|
|
85
|
+
<div class="callout red tiny">
|
|
86
|
+
Please configure the server settings in the parent component.
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
89
|
+
`;
|
|
90
|
+
}
|
|
91
|
+
const isLoading = this.recordingState === 'initializing' ||
|
|
92
|
+
this.recordingState === 'stopping';
|
|
93
|
+
const isRecording = this.recordingState === 'recording';
|
|
94
|
+
return html `
|
|
95
|
+
<div class="wrapper">
|
|
96
|
+
<button
|
|
97
|
+
@click=${this._toggleRecording}
|
|
98
|
+
class=${isRecording ? 'red' : 'accent'}
|
|
99
|
+
>
|
|
100
|
+
${isLoading
|
|
101
|
+
? html `<icon-loading-spinner></icon-loading-spinner>`
|
|
102
|
+
: isRecording
|
|
103
|
+
? html `<icon-recording></icon-recording>`
|
|
104
|
+
: html `<icon-mic-on></icon-mic-on>`}
|
|
105
|
+
<audio-visualiser
|
|
106
|
+
.level=${this._audioLevel}
|
|
107
|
+
.active=${isRecording}
|
|
108
|
+
></audio-visualiser>
|
|
109
|
+
</button>
|
|
110
|
+
|
|
111
|
+
<settings-menu
|
|
112
|
+
.devices=${this.devices}
|
|
113
|
+
.selectedDevice=${this.recorderManager.selectedDevice}
|
|
114
|
+
?settingsDisabled=${this.recordingState !== 'stopped'}
|
|
115
|
+
@recording-device-changed=${this._onRecordingDeviceChanged}
|
|
116
|
+
></settings-menu>
|
|
117
|
+
</div>
|
|
118
|
+
`;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
CortiDictation.styles = [ButtonStyles, ThemeStyles, componentStyles, CalloutStyles];
|
|
122
|
+
__decorate([
|
|
123
|
+
property({ type: Array })
|
|
124
|
+
], CortiDictation.prototype, "devices", void 0);
|
|
125
|
+
__decorate([
|
|
126
|
+
property({ type: String, reflect: true })
|
|
127
|
+
], CortiDictation.prototype, "recordingState", void 0);
|
|
128
|
+
__decorate([
|
|
129
|
+
property({ type: Object })
|
|
130
|
+
], CortiDictation.prototype, "dictationConfig", void 0);
|
|
131
|
+
__decorate([
|
|
132
|
+
property({ type: Object })
|
|
133
|
+
], CortiDictation.prototype, "serverConfig", void 0);
|
|
134
|
+
__decorate([
|
|
135
|
+
state()
|
|
136
|
+
], CortiDictation.prototype, "_audioLevel", void 0);
|
|
137
|
+
//# sourceMappingURL=CortiDictation.js.map
|
|
@@ -0,0 +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, css } from 'lit';\nimport { property, state } from 'lit/decorators.js';\nimport { RecorderManager } from './RecorderManager';\nimport './components/settings-menu';\nimport './components/audio-visualiser';\nimport './icons/icons';\nimport ThemeStyles from './styles/theme';\nimport ButtonStyles from './styles/buttons';\nimport { componentStyles } from './componentStyles';\nimport { DictationConfig, ServerConfig } from './types';\nimport { DEFAULT_DICTATION_CONFIG } from './constants';\nimport CalloutStyles from './styles/callout';\n\nexport class CortiDictation extends LitElement {\n static styles = [ButtonStyles, ThemeStyles, componentStyles, CalloutStyles];\n\n @property({ type: Array })\n devices: MediaDeviceInfo[] = [];\n\n @property({ type: String, reflect: true })\n recordingState = 'stopped';\n\n @property({ type: Object })\n dictationConfig: DictationConfig = DEFAULT_DICTATION_CONFIG;\n\n @property({ type: Object })\n serverConfig: ServerConfig = {};\n\n @state()\n private _audioLevel = 0;\n\n private recorderManager = new RecorderManager();\n\n async connectedCallback() {\n super.connectedCallback();\n await this.recorderManager.initialize();\n this.devices = this.recorderManager.devices;\n \n // Map event names to any extra handling logic\n const eventHandlers: Record<string, (e: CustomEvent) => void> = {\n 'recording-state-changed': (e) => {\n this.recordingState = e.detail.state;\n },\n 'audio-level-changed': (e) => {\n this._audioLevel = e.detail.audioLevel;\n this.requestUpdate();\n },\n };\n \n const eventsToRelay = [\n 'recording-state-changed',\n 'audio-level-changed',\n 'audio-packet',\n 'transcript',\n ];\n \n eventsToRelay.forEach((eventName) => {\n this.recorderManager.addEventListener(eventName, (e: Event) => {\n const customEvent = e as CustomEvent;\n // Perform any additional handling if defined\n if (eventHandlers[eventName]) {\n eventHandlers[eventName](customEvent);\n }\n // Re-dispatch the event from the component\n this.dispatchEvent(\n new CustomEvent(eventName, {\n detail: customEvent.detail,\n bubbles: true,\n composed: true,\n })\n );\n });\n });\n }\n \n public toggleRecording() {\n this._toggleRecording();\n }\n\n _toggleRecording() {\n if (this.recordingState === 'recording') {\n this.recorderManager.stopRecording();\n } else if (this.recordingState === 'stopped') {\n this.recorderManager.startRecording({dictationConfig: this.dictationConfig, serverConfig: this.serverConfig});\n }\n }\n\n // Handle device change events if needed\n async _onRecordingDeviceChanged(event: Event) {\n const customEvent = event as CustomEvent;\n this.recorderManager.selectedDevice = customEvent.detail.deviceId;\n if (this.recordingState === 'recording') {\n await this.recorderManager.stopRecording();\n await this.recorderManager.startRecording({dictationConfig: this.dictationConfig, serverConfig: this.serverConfig});\n }\n }\n\n render() {\n\n const isConfigured = this.serverConfig && this.serverConfig.token && this.serverConfig.environment && this.serverConfig.tenant;\n if (!isConfigured) {\n return html`\n <div class=\"wrapper\">\n <div class=\"callout red tiny\">\n Please configure the server settings in the parent component.\n </div>\n </div>\n `;\n }\n\n const isLoading =\n this.recordingState === 'initializing' ||\n this.recordingState === 'stopping';\n const isRecording = this.recordingState === 'recording';\n return html`\n <div class=\"wrapper\">\n <button\n @click=${this._toggleRecording}\n class=${isRecording ? 'red' : 'accent'}\n >\n ${isLoading\n ? html`<icon-loading-spinner></icon-loading-spinner>`\n : isRecording\n ? html`<icon-recording></icon-recording>`\n : html`<icon-mic-on></icon-mic-on>`}\n <audio-visualiser\n .level=${this._audioLevel}\n .active=${isRecording}\n ></audio-visualiser>\n </button>\n \n <settings-menu\n .devices=${this.devices}\n .selectedDevice=${this.recorderManager.selectedDevice}\n ?settingsDisabled=${this.recordingState !== 'stopped'}\n @recording-device-changed=${this._onRecordingDeviceChanged}\n ></settings-menu>\n </div>\n `;\n }\n}\n"]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { DictationConfig, ServerConfig } from './types';
|
|
2
|
+
export declare class DictationService extends EventTarget {
|
|
3
|
+
private mediaRecorder;
|
|
4
|
+
private webSocket;
|
|
5
|
+
private serverConfig;
|
|
6
|
+
private dictationConfig;
|
|
7
|
+
constructor(mediaStream: MediaStream, { dictationConfig, serverConfig, }: {
|
|
8
|
+
dictationConfig: DictationConfig;
|
|
9
|
+
serverConfig: ServerConfig;
|
|
10
|
+
});
|
|
11
|
+
startRecording(): void;
|
|
12
|
+
stopRecording(): Promise<void>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
export class DictationService extends EventTarget {
|
|
2
|
+
constructor(mediaStream, { dictationConfig, serverConfig, }) {
|
|
3
|
+
super();
|
|
4
|
+
this.mediaRecorder = new MediaRecorder(mediaStream);
|
|
5
|
+
this.serverConfig = serverConfig;
|
|
6
|
+
this.dictationConfig = dictationConfig;
|
|
7
|
+
this.mediaRecorder.ondataavailable = event => {
|
|
8
|
+
// if webSocket is open, send the data
|
|
9
|
+
if (this.webSocket?.readyState === WebSocket.OPEN) {
|
|
10
|
+
this.webSocket.send(event.data);
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
startRecording() {
|
|
15
|
+
const url = `wss://api.${this.serverConfig.environment}.corti.app/audio-bridge/v2/transcribe?tenant-name=${this.serverConfig.tenant}&token=Bearer%20${this.serverConfig.token}`;
|
|
16
|
+
this.webSocket = new WebSocket(url);
|
|
17
|
+
this.webSocket.onopen = () => {
|
|
18
|
+
this.webSocket.send(JSON.stringify({
|
|
19
|
+
type: 'config',
|
|
20
|
+
configuration: this.dictationConfig,
|
|
21
|
+
}));
|
|
22
|
+
};
|
|
23
|
+
this.webSocket.onmessage = event => {
|
|
24
|
+
const message = JSON.parse(event.data);
|
|
25
|
+
if (message.type === 'config') {
|
|
26
|
+
this.mediaRecorder.start(250);
|
|
27
|
+
}
|
|
28
|
+
else if (message.type === 'transcript') {
|
|
29
|
+
this.dispatchEvent(new CustomEvent('transcript', {
|
|
30
|
+
detail: message,
|
|
31
|
+
bubbles: true,
|
|
32
|
+
composed: true,
|
|
33
|
+
}));
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
this.webSocket.onerror = event => {
|
|
37
|
+
this.dispatchEvent(new CustomEvent('error', {
|
|
38
|
+
detail: event,
|
|
39
|
+
bubbles: true,
|
|
40
|
+
composed: true,
|
|
41
|
+
}));
|
|
42
|
+
};
|
|
43
|
+
this.webSocket.onclose = (event) => {
|
|
44
|
+
this.dispatchEvent(new CustomEvent('stream-closed', {
|
|
45
|
+
detail: event,
|
|
46
|
+
bubbles: true,
|
|
47
|
+
composed: true,
|
|
48
|
+
}));
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
async stopRecording() {
|
|
52
|
+
this.mediaRecorder.stop();
|
|
53
|
+
if (this.webSocket?.readyState === WebSocket.OPEN) {
|
|
54
|
+
this.webSocket.send(JSON.stringify({
|
|
55
|
+
type: 'end',
|
|
56
|
+
}));
|
|
57
|
+
}
|
|
58
|
+
const timeOut = setTimeout(() => {
|
|
59
|
+
if (this.webSocket?.readyState === WebSocket.OPEN) {
|
|
60
|
+
this.webSocket.close();
|
|
61
|
+
}
|
|
62
|
+
}, 10000);
|
|
63
|
+
// This implementation should be replaced by handling a proper 'ended' message from the server
|
|
64
|
+
this.webSocket.onclose = () => {
|
|
65
|
+
this.webSocket?.close();
|
|
66
|
+
clearTimeout(timeOut);
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=DictationService.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DictationService.js","sourceRoot":"","sources":["../../src/DictationService.ts"],"names":[],"mappings":"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","sourcesContent":["import { DictationConfig, ServerConfig } from './types';\n\nexport class DictationService extends EventTarget {\n private mediaRecorder: MediaRecorder;\n\n private webSocket!: WebSocket;\n\n private serverConfig!: ServerConfig;\n\n private dictationConfig!: DictationConfig;\n\n constructor(\n mediaStream: MediaStream,\n {\n dictationConfig,\n serverConfig,\n }: { dictationConfig: DictationConfig; serverConfig: ServerConfig },\n ) {\n super();\n this.mediaRecorder = new MediaRecorder(mediaStream);\n this.serverConfig = serverConfig;\n this.dictationConfig = dictationConfig;\n this.mediaRecorder.ondataavailable = event => {\n // if webSocket is open, send the data\n if (this.webSocket?.readyState === WebSocket.OPEN) {\n this.webSocket.send(event.data);\n }\n };\n }\n\n public startRecording() {\n const url = `wss://api.${this.serverConfig.environment}.corti.app/audio-bridge/v2/transcribe?tenant-name=${this.serverConfig.tenant}&token=Bearer%20${this.serverConfig.token}`;\n this.webSocket = new WebSocket(url);\n this.webSocket.onopen = () => {\n this.webSocket.send(\n JSON.stringify({\n type: 'config',\n configuration: this.dictationConfig,\n }),\n );\n };\n this.webSocket.onmessage = event => {\n const message = JSON.parse(event.data);\n if (message.type === 'config') {\n this.mediaRecorder.start(250);\n } else if (message.type === 'transcript') {\n this.dispatchEvent(\n new CustomEvent('transcript', {\n detail: message,\n bubbles: true,\n composed: true,\n }),\n );\n }\n };\n this.webSocket.onerror = event => {\n this.dispatchEvent(\n new CustomEvent('error', {\n detail: event,\n bubbles: true,\n composed: true,\n }),\n );\n };\n this.webSocket.onclose = (event) => {\n this.dispatchEvent(\n new CustomEvent('stream-closed', {\n detail: event,\n bubbles: true,\n composed: true,\n }),\n );\n };\n }\n\n public async stopRecording() {\n this.mediaRecorder.stop();\n\n if (this.webSocket?.readyState === WebSocket.OPEN) {\n this.webSocket.send(\n JSON.stringify({\n type: 'end',\n }),\n );\n }\n\n const timeOut: NodeJS.Timeout = setTimeout(() => {\n if (this.webSocket?.readyState === WebSocket.OPEN) {\n this.webSocket.close();\n }\n }, 10000);\n\n // This implementation should be replaced by handling a proper 'ended' message from the server\n this.webSocket.onclose = () => {\n this.webSocket?.close();\n clearTimeout(timeOut);\n };\n }\n}\n"]}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { DictationConfig, RecordingState, ServerConfig } from './types';
|
|
2
|
+
export declare class RecorderManager extends EventTarget {
|
|
3
|
+
devices: MediaDeviceInfo[];
|
|
4
|
+
selectedDevice: string;
|
|
5
|
+
recordingState: RecordingState;
|
|
6
|
+
private _mediaStream;
|
|
7
|
+
private _audioService;
|
|
8
|
+
private _dictationService;
|
|
9
|
+
private _visualiserInterval?;
|
|
10
|
+
initialize(): Promise<{
|
|
11
|
+
devices: MediaDeviceInfo[];
|
|
12
|
+
defaultDeviceId?: string;
|
|
13
|
+
}>;
|
|
14
|
+
startRecording(params: {
|
|
15
|
+
dictationConfig: DictationConfig;
|
|
16
|
+
serverConfig: ServerConfig;
|
|
17
|
+
}): Promise<void>;
|
|
18
|
+
stopRecording(): Promise<void>;
|
|
19
|
+
private _updateRecordingState;
|
|
20
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { getAudioDevices } from './utils';
|
|
2
|
+
import { AudioService } from './audioService';
|
|
3
|
+
import { DictationService } from './DictationService';
|
|
4
|
+
export class RecorderManager extends EventTarget {
|
|
5
|
+
constructor() {
|
|
6
|
+
super(...arguments);
|
|
7
|
+
this.devices = [];
|
|
8
|
+
this.selectedDevice = '';
|
|
9
|
+
this.recordingState = 'stopped';
|
|
10
|
+
this._mediaStream = null;
|
|
11
|
+
this._audioService = null;
|
|
12
|
+
this._dictationService = null;
|
|
13
|
+
}
|
|
14
|
+
async initialize() {
|
|
15
|
+
const deviceResponse = await getAudioDevices();
|
|
16
|
+
this.devices = deviceResponse.devices;
|
|
17
|
+
this.selectedDevice = deviceResponse.defaultDeviceId || '';
|
|
18
|
+
return deviceResponse;
|
|
19
|
+
}
|
|
20
|
+
async startRecording(params) {
|
|
21
|
+
this._updateRecordingState('initializing');
|
|
22
|
+
try {
|
|
23
|
+
this._mediaStream = await navigator.mediaDevices.getUserMedia({
|
|
24
|
+
audio: { deviceId: this.selectedDevice },
|
|
25
|
+
});
|
|
26
|
+
this._audioService = new AudioService(this._mediaStream);
|
|
27
|
+
this._dictationService = new DictationService(this._mediaStream, params);
|
|
28
|
+
// Forward custom events from dictation service
|
|
29
|
+
this._dictationService.addEventListener('error', e => this.dispatchEvent(new CustomEvent('error', {
|
|
30
|
+
detail: e.detail,
|
|
31
|
+
bubbles: true,
|
|
32
|
+
composed: true,
|
|
33
|
+
})));
|
|
34
|
+
this._dictationService.addEventListener('stream-closed', () => this.stopRecording());
|
|
35
|
+
this._dictationService.addEventListener('transcript', e => this.dispatchEvent(new CustomEvent('transcript', {
|
|
36
|
+
detail: e.detail,
|
|
37
|
+
bubbles: true,
|
|
38
|
+
composed: true,
|
|
39
|
+
})));
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
this.dispatchEvent(new CustomEvent('error', {
|
|
43
|
+
detail: error,
|
|
44
|
+
bubbles: true,
|
|
45
|
+
composed: true,
|
|
46
|
+
}));
|
|
47
|
+
this._updateRecordingState('stopped');
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
this._dictationService?.startRecording();
|
|
51
|
+
this._updateRecordingState('recording');
|
|
52
|
+
this._visualiserInterval = window.setInterval(() => {
|
|
53
|
+
const level = this._audioService
|
|
54
|
+
? this._audioService.getAudioLevel() * 3
|
|
55
|
+
: 0;
|
|
56
|
+
this.dispatchEvent(new CustomEvent('audio-level-changed', {
|
|
57
|
+
detail: { audioLevel: level },
|
|
58
|
+
bubbles: true,
|
|
59
|
+
composed: true,
|
|
60
|
+
}));
|
|
61
|
+
}, 150);
|
|
62
|
+
}
|
|
63
|
+
async stopRecording() {
|
|
64
|
+
this._updateRecordingState('stopping');
|
|
65
|
+
if (this._visualiserInterval) {
|
|
66
|
+
clearInterval(this._visualiserInterval);
|
|
67
|
+
this._visualiserInterval = undefined;
|
|
68
|
+
}
|
|
69
|
+
if (this._mediaStream) {
|
|
70
|
+
this._mediaStream.getTracks().forEach(track => track.stop());
|
|
71
|
+
this._mediaStream = null;
|
|
72
|
+
}
|
|
73
|
+
await this._dictationService?.stopRecording();
|
|
74
|
+
this._updateRecordingState('stopped');
|
|
75
|
+
}
|
|
76
|
+
_updateRecordingState(state) {
|
|
77
|
+
this.recordingState = state;
|
|
78
|
+
this.dispatchEvent(new CustomEvent('recording-state-changed', {
|
|
79
|
+
detail: { state },
|
|
80
|
+
bubbles: true,
|
|
81
|
+
composed: true,
|
|
82
|
+
}));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=RecorderManager.js.map
|