@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.
Files changed (121) hide show
  1. package/.editorconfig +29 -0
  2. package/.eslintrc.json +16 -0
  3. package/.husky/pre-commit +1 -0
  4. package/.storybook/main.js +8 -0
  5. package/README.md +120 -0
  6. package/demo/index.html +98 -0
  7. package/dist/src/CortiDictation.d.ts +19 -0
  8. package/dist/src/CortiDictation.js +137 -0
  9. package/dist/src/CortiDictation.js.map +1 -0
  10. package/dist/src/DictationService.d.ts +13 -0
  11. package/dist/src/DictationService.js +70 -0
  12. package/dist/src/DictationService.js.map +1 -0
  13. package/dist/src/RecorderManager.d.ts +20 -0
  14. package/dist/src/RecorderManager.js +85 -0
  15. package/dist/src/RecorderManager.js.map +1 -0
  16. package/dist/src/audioRecorderManager.d.ts +17 -0
  17. package/dist/src/audioRecorderManager.js +78 -0
  18. package/dist/src/audioRecorderManager.js.map +1 -0
  19. package/dist/src/audioService.d.ts +6 -0
  20. package/dist/src/audioService.js +21 -0
  21. package/dist/src/audioService.js.map +1 -0
  22. package/dist/src/componentStyles.d.ts +1 -0
  23. package/dist/src/componentStyles.js +51 -0
  24. package/dist/src/componentStyles.js.map +1 -0
  25. package/dist/src/components/audio-visualiser.d.ts +12 -0
  26. package/dist/src/components/audio-visualiser.js +60 -0
  27. package/dist/src/components/audio-visualiser.js.map +1 -0
  28. package/dist/src/components/settings-menu.d.ts +15 -0
  29. package/dist/src/components/settings-menu.js +148 -0
  30. package/dist/src/components/settings-menu.js.map +1 -0
  31. package/dist/src/components/visualiser.d.ts +7 -0
  32. package/dist/src/components/visualiser.js +62 -0
  33. package/dist/src/components/visualiser.js.map +1 -0
  34. package/dist/src/constants.d.ts +3 -0
  35. package/dist/src/constants.js +9 -0
  36. package/dist/src/constants.js.map +1 -0
  37. package/dist/src/corti-dictation.d.ts +1 -0
  38. package/dist/src/corti-dictation.js +3 -0
  39. package/dist/src/corti-dictation.js.map +1 -0
  40. package/dist/src/dictationService.d.ts +13 -0
  41. package/dist/src/dictationService.js +70 -0
  42. package/dist/src/dictationService.js.map +1 -0
  43. package/dist/src/icons/icons.d.ts +17 -0
  44. package/dist/src/icons/icons.js +153 -0
  45. package/dist/src/icons/icons.js.map +1 -0
  46. package/dist/src/icons/index.d.ts +0 -0
  47. package/dist/src/icons/index.js +2 -0
  48. package/dist/src/icons/index.js.map +1 -0
  49. package/dist/src/icons/micOn.d.ts +7 -0
  50. package/dist/src/icons/micOn.js +25 -0
  51. package/dist/src/icons/micOn.js.map +1 -0
  52. package/dist/src/index.d.ts +20 -0
  53. package/dist/src/index.js +147 -0
  54. package/dist/src/index.js.map +1 -0
  55. package/dist/src/mediaRecorderService.d.ts +6 -0
  56. package/dist/src/mediaRecorderService.js +31 -0
  57. package/dist/src/mediaRecorderService.js.map +1 -0
  58. package/dist/src/mic-selector.d.ts +18 -0
  59. package/dist/src/mic-selector.js +131 -0
  60. package/dist/src/mic-selector.js.map +1 -0
  61. package/dist/src/settings-menu.d.ts +18 -0
  62. package/dist/src/settings-menu.js +131 -0
  63. package/dist/src/settings-menu.js.map +1 -0
  64. package/dist/src/settings-popover.d.ts +18 -0
  65. package/dist/src/settings-popover.js +131 -0
  66. package/dist/src/settings-popover.js.map +1 -0
  67. package/dist/src/settings.d.ts +18 -0
  68. package/dist/src/settings.js +131 -0
  69. package/dist/src/settings.js.map +1 -0
  70. package/dist/src/styles/ComponentStyles.d.ts +2 -0
  71. package/dist/src/styles/ComponentStyles.js +52 -0
  72. package/dist/src/styles/ComponentStyles.js.map +1 -0
  73. package/dist/src/styles/buttons.d.ts +2 -0
  74. package/dist/src/styles/buttons.js +58 -0
  75. package/dist/src/styles/buttons.js.map +1 -0
  76. package/dist/src/styles/callout.d.ts +2 -0
  77. package/dist/src/styles/callout.js +26 -0
  78. package/dist/src/styles/callout.js.map +1 -0
  79. package/dist/src/styles/select.d.ts +2 -0
  80. package/dist/src/styles/select.js +36 -0
  81. package/dist/src/styles/select.js.map +1 -0
  82. package/dist/src/styles/theme.d.ts +2 -0
  83. package/dist/src/styles/theme.js +74 -0
  84. package/dist/src/styles/theme.js.map +1 -0
  85. package/dist/src/types.d.ts +20 -0
  86. package/dist/src/types.js +2 -0
  87. package/dist/src/types.js.map +1 -0
  88. package/dist/src/utils.d.ts +31 -0
  89. package/dist/src/utils.js +77 -0
  90. package/dist/src/utils.js.map +1 -0
  91. package/dist/stories/index.stories.d.ts +33 -0
  92. package/dist/stories/index.stories.js +37 -0
  93. package/dist/stories/index.stories.js.map +1 -0
  94. package/dist/test/corti-dictation.test.d.ts +1 -0
  95. package/dist/test/corti-dictation.test.js +100 -0
  96. package/dist/test/corti-dictation.test.js.map +1 -0
  97. package/dist/tsconfig.tsbuildinfo +1 -0
  98. package/docs/DEV_README.md +80 -0
  99. package/package.json +92 -0
  100. package/src/DictationService.ts +99 -0
  101. package/src/RecorderManager.ts +114 -0
  102. package/src/audioService.ts +25 -0
  103. package/src/components/audio-visualiser.ts +56 -0
  104. package/src/components/settings-menu.ts +152 -0
  105. package/src/constants.ts +10 -0
  106. package/src/corti-dictation.ts +3 -0
  107. package/src/icons/icons.ts +141 -0
  108. package/src/icons/index.ts +0 -0
  109. package/src/index.ts +154 -0
  110. package/src/styles/ComponentStyles.ts +53 -0
  111. package/src/styles/buttons.ts +59 -0
  112. package/src/styles/callout.ts +27 -0
  113. package/src/styles/select.ts +37 -0
  114. package/src/styles/theme.ts +75 -0
  115. package/src/types.ts +28 -0
  116. package/src/utils.ts +83 -0
  117. package/stories/index.stories.ts +60 -0
  118. package/test/corti-dictation.test.ts +124 -0
  119. package/tsconfig.json +22 -0
  120. package/web-dev-server.config.js +27 -0
  121. 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
@@ -0,0 +1,8 @@
1
+ const config = {
2
+ stories: ['../**/dist/stories/*.stories.{js,md,mdx}'],
3
+ framework: {
4
+ name: '@web/storybook-framework-web-components',
5
+ },
6
+ };
7
+
8
+ export default config;
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
+
@@ -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