@corti/dictation-web 0.4.0-rc.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -9,157 +9,159 @@
9
9
 
10
10
  The **Corti Dictation Web Component** 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.
11
11
 
12
- > **Note:** OAuth 2.0 authentication is not handled by this library. The client must provide an API key or authorization token before using the component.
12
+ This library offers two approaches:
13
+ - **Opinionated Component**: Use `<corti-dictation>` for a complete, ready-to-use solution with built-in UI
14
+ - **Modular Components**: Use individual components for maximum flexibility and custom UI implementations
15
+
16
+ > **Note:** OAuth 2.0 authentication is not handled by this library. The client must provide an authorization token or token refresh function while using the component.
17
+
18
+ ## Component Architecture
19
+
20
+ ### Opinionated Component
21
+
22
+ **`<corti-dictation>`** - A complete, ready-to-use component that includes:
23
+ - Recording button with visual feedback
24
+ - Settings menu for device and language selection
25
+ - Automatic state management
26
+ - Built-in styling and theming
27
+
28
+ This is the easiest way to get started and works out of the box.
29
+
30
+ ### Modular Components
31
+
32
+ For more control and flexibility, you can use individual components:
33
+
34
+ - **`<dictation-root>`** - Context provider that manages authentication, configuration, and shared state
35
+ - **`<dictation-recording-button>`** - Standalone recording button with audio visualization
36
+ - **`<dictation-settings-menu>`** - Settings menu with device and language selectors
37
+ - **`<dictation-device-selector>`** - Device selection dropdown
38
+ - **`<dictation-language-selector>`** - Language selection dropdown
39
+
40
+ These components share state through a context system, allowing you to build custom UIs while leveraging the same underlying functionality.
13
41
 
14
42
  ## Installation
15
43
 
16
- Include the Web Component in your project by importing the JavaScript module:
44
+ Install the package using your preferred package manager:
17
45
 
18
- ```html
46
+ ```bash
47
+ # npm
19
48
  npm i @corti/dictation-web
49
+
50
+ # yarn
51
+ yarn add @corti/dictation-web
52
+
53
+ # pnpm
54
+ pnpm add @corti/dictation-web
55
+
56
+ # bun
57
+ bun add @corti/dictation-web
20
58
  ```
21
59
 
22
- Then import the module like so:
60
+ Then import the module in your code. You can either use a side-effect import to auto-register the component:
23
61
 
24
62
  ```js
25
- // Import the Corti Dictation Web Component
63
+ // Side-effect import - automatically registers the component
26
64
  import '@corti/dictation-web';
27
65
  ```
28
66
 
29
- Alternatively, use a CDN to start quickly (not recommended).
67
+ Or import the component class directly:
68
+
69
+ ```js
70
+ // Named import - register the component manually if needed
71
+ import { CortiDictation } from '@corti/dictation-web';
72
+ ```
73
+
74
+ Alternatively, use a CDN to start quickly (not recommended for production):
30
75
 
31
76
  ```html
32
77
  <script
33
- src="https://cdn.jsdelivr.net/npm/@corti/dictation-web/dist/bundle.min.js"
34
- preload
78
+ src="https://cdn.jsdelivr.net/npm/@corti/dictation-web/dist/bundle.js"
35
79
  type="module"
36
80
  ></script>
37
81
  ```
38
82
 
39
- ## Usage
40
-
41
- ### Demo
83
+ ## Demo
42
84
 
43
85
  🚀 [Hosted Demo](https://codepen.io/hccullen/pen/OPJmxQR)
44
86
 
45
- ### Basic Example
87
+ ## Quick Start
88
+
89
+ Here's a simple example to get you started:
46
90
 
47
91
  ```html
48
92
  <!DOCTYPE html>
49
93
  <html lang="en">
50
- <body>
51
- <corti-dictation id="dictation"></corti-dictation>
52
- <textarea
53
- id="transcript"
54
- placeholder="Transcript will appear here..."
55
- ></textarea>
56
-
57
- <script>
58
- import '@corti/dictation-web';
59
- const dictationEl = document.getElementById('dictation');
60
- dictationEl.addEventListener('ready', () => {
61
- dictationEl.setAccessToken('YOUR_AUTH_TOKEN'); // Note: Never hardcode tokens
62
- });
63
- dictationEl.addEventListener('transcript', e => {
64
- document.getElementById('transcript').value += e.detail.data.text + ' ';
65
- });
66
- </script>
67
- </body>
94
+ <body>
95
+ <corti-dictation id="dictation"></corti-dictation>
96
+ <textarea
97
+ id="transcript"
98
+ placeholder="Transcript will appear here..."
99
+ ></textarea>
100
+
101
+ <script type="module">
102
+ import '@corti/dictation-web';
103
+
104
+ const dictationEl = document.getElementById('dictation');
105
+ const transcriptEl = document.getElementById('transcript');
106
+
107
+ dictationEl.addEventListener('ready', () => {
108
+ dictationEl.accessToken = 'YOUR_AUTH_TOKEN'; // Note: Never hardcode tokens
109
+ });
110
+
111
+ dictationEl.addEventListener('transcript', (e) => {
112
+ if (e.detail.data.isFinal) {
113
+ transcriptEl.value += e.detail.data.text + ' ';
114
+ }
115
+ });
116
+ </script>
117
+ </body>
68
118
  </html>
69
119
  ```
70
120
 
71
- ## API Reference
72
-
73
- ### Properties
74
-
75
- | Property | Type | Description |
76
- | -------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------- |
77
- | `devices` | Array | List of available recording devices. |
78
- | `selectedDevice` | Object | The selected device used for recording (MediaDeviceInfo). |
79
- | `recordingState` | String | Current state of recording (`stopped`, `recording`, `initializing` and `stopping`, ). |
80
- | `dictationConfig` | Object | Configuration settings for dictation. |
81
- | `settingsEnabled` | Array | Which settings should be available in the UI. If an empty array is passed, the settings will be disabled entirely. Options are `language` and `devices` |
82
- | `languagesSupported` | String[] | List of all language codes available for use with the Web Component. |
83
- | `debug_displayAudio` | Boolean | Overrides any device selection and instead uses getDisplayMedia to stream system audio. Should only be used for debugging |
84
- | `preventButtonFocus` | Boolean | When `true` (default), prevents the start/stop button from taking focus when clicked, allowing textareas or other input elements to maintain focus. Set to `false` to allow the button to receive focus on click. |
85
-
86
- ### Methods
87
-
88
- | Method | Description |
89
- | -------------------------------------- | ---------------------------------------------------------------- |
90
- | `startRecording()` | Starts a recording. |
91
- | `stopRecording()` | Stops a recording. |
92
- | `toggleRecording()` | Starts or stops recording. Convenience layer on top of the start/stop methods. |
93
- | `setAccessToken(access_token: string)` | Set the latest access token. This will return the server config. |
94
- | `setAuthConfig(config: AuthConfig)` | Set authentication configuration with optional refresh mechanism. |
95
-
96
- ### Events
97
-
98
- | Event | Description |
99
- | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
100
- | `ready` | Fired once the component is ready. |
101
- | `recording-state-changed` | Fired when the recording state changes. `detail.state` contains the new state. |
102
- | `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. |
103
- | `transcript` | Fired when a new transcript is received. `detail.data.text` contains the transcribed text. |
104
- | `command` | Fired whenever a new command is detected. |
105
- | `audio-level-changed` | Fired when the input audio level changes. `detail.audioLevel` contains the new level. |
106
- | `usage` | Fired when usage information is received from the server. `detail.credits` contains the usage data. |
107
- | `stream-closed` | Fired when the WebSocket stream is closed. `detail` contains the close event data. |
108
- | `error` | Fired on error. `detail` contains the full error. |
109
-
110
- ## Authentication
111
-
112
- This library supports multiple authentication methods:
113
-
114
- ### Basic Bearer Token
115
- ```javascript
116
- dictation.setAccessToken('YOUR_JWT_TOKEN');
117
- ```
118
-
119
- ### With Refresh Token Support
120
-
121
- The library can automatically refresh tokens when they expire:
122
-
123
- ```javascript
124
- dictation.setAuthConfig({
125
- // This function runs before any API call when the access_token is near expiration
126
- refreshAccessToken: async (refreshToken?: string) => {
127
- // Custom refresh logic -- get new access_token from server
128
- // if accessToken is not passed to AuthConfig, refreshToken will be `undefined` for the first call,
129
- // then it will be the refreshToken returned from the previous token request
130
- const response = await fetch("https://your-auth-server/token", {
131
- method: "POST",
132
- headers: { "Content-Type": "application/json" },
133
- body: JSON.stringify({ refreshToken }),
134
- });
135
-
136
- const result = await response.json();
137
-
138
- // Return in the expected format
139
- return {
140
- accessToken: result.accessToken,
141
- refreshToken: result.refreshToken,
142
- };
143
- }
144
- });
145
- ```
146
-
147
- The refresh mechanism automatically handles token renewal when the access token is near expiration, ensuring uninterrupted dictation sessions.
148
-
149
- ## Usage Examples
121
+ ### Modular Example
150
122
 
151
- Explore practical implementations and usage examples in the [Demo Folder](https://github.com/corticph/dictation-web/tree/main/demo). These demos can also be run locally.
123
+ For more control, use individual components to build a custom UI:
152
124
 
153
- ## Styling
154
-
155
- ![UI Overview](https://github.com/corticph/dictation-web/blob/main/docs/ui.png)
156
-
157
- The default UI is designed to be slotted into existing applications seamlessly, however, it also supports custom styling as well as theming. The UI can be fully customized using CSS properties. Refer to our [Styling Guide](https://github.com/corticph/dictation-web/blob/main/docs/styling.md) for detailed instructions.
158
-
159
- ## License
125
+ ```html
126
+ <!DOCTYPE html>
127
+ <html lang="en">
128
+ <body>
129
+ <dictation-root id="dictationRoot">
130
+ <dictation-recording-button></dictation-recording-button>
131
+ <dictation-settings-menu settingsEnabled="device,language"></dictation-settings-menu>
132
+ </dictation-root>
133
+
134
+ <textarea
135
+ id="transcript"
136
+ placeholder="Transcript will appear here..."
137
+ ></textarea>
138
+
139
+ <script type="module">
140
+ import '@corti/dictation-web';
141
+
142
+ const root = document.getElementById('dictationRoot');
143
+ const transcriptEl = document.getElementById('transcript');
144
+
145
+ root.addEventListener('ready', () => {
146
+ root.accessToken = 'YOUR_AUTH_TOKEN'; // Note: Never hardcode tokens
147
+ });
148
+
149
+ root.addEventListener('transcript', (e) => {
150
+ if (e.detail.data.isFinal) {
151
+ transcriptEl.value += e.detail.data.text + ' ';
152
+ }
153
+ });
154
+ </script>
155
+ </body>
156
+ </html>
157
+ ```
160
158
 
161
- This Web Component library is licensed under MIT.
159
+ ## Documentation
162
160
 
163
- ## Support
161
+ For more detailed information, see:
164
162
 
165
- For issues or questions, contact **Corti Support** at [help@corti.ai](mailto:help@corti.ai) or [join our Discord](https://discord.com/invite/zXeXHgnZXX).
163
+ - **[API Reference](docs/API_REFERENCE.md)** - Complete API documentation for properties, methods, and events
164
+ - **[Authentication Guide](docs/AUTHENTICATION.md)** - How to set up authentication with tokens and refresh mechanisms
165
+ - **[Styling Guide](docs/styling.md)** - Customize the component's appearance with CSS variables and themes
166
+ - **[Examples](demo/README.md)** - Practical usage examples and demos
167
+ - **[Development Guide](docs/DEV_README.md)** - Information for contributors and developers
package/dist/bundle.js CHANGED
@@ -6118,7 +6118,7 @@ var Auth = class {
6118
6118
  function getEnvironment(environment = "eu") {
6119
6119
  return typeof environment === "string" ? {
6120
6120
  base: `https://api.${environment}.corti.app/v2`,
6121
- wss: `wss://api.${environment}.corti.app`,
6121
+ wss: `wss://api.${environment}.corti.app/audio-bridge/v2`,
6122
6122
  login: `https://auth.${environment}.corti.app/realms`,
6123
6123
  agents: `https://api.${environment}.corti.app`
6124
6124
  } : environment;
@@ -10332,7 +10332,7 @@ var Stream = class {
10332
10332
  _queryParams["token"] = token;
10333
10333
  let _headers = Object.assign({}, headers);
10334
10334
  const socket = new ReconnectingWebSocket({
10335
- url: url_exports.join((_a = yield Supplier.get(this._options["baseUrl"])) !== null && _a !== void 0 ? _a : (yield Supplier.get(this._options["environment"])).wss, `/audio-bridge/v2/interactions/${encodeURIComponent(id)}/streams`),
10335
+ url: url_exports.join((_a = yield Supplier.get(this._options["baseUrl"])) !== null && _a !== void 0 ? _a : (yield Supplier.get(this._options["environment"])).wss, `/interactions/${encodeURIComponent(id)}/streams`),
10336
10336
  protocols: [],
10337
10337
  queryParameters: _queryParams,
10338
10338
  headers: _headers,
@@ -10711,7 +10711,7 @@ var Transcribe = class {
10711
10711
  _queryParams["token"] = token;
10712
10712
  let _headers = Object.assign({}, headers);
10713
10713
  const socket = new ReconnectingWebSocket({
10714
- url: url_exports.join((_a = yield Supplier.get(this._options["baseUrl"])) !== null && _a !== void 0 ? _a : (yield Supplier.get(this._options["environment"])).wss, "/audio-bridge/v2/transcribe"),
10714
+ url: url_exports.join((_a = yield Supplier.get(this._options["baseUrl"])) !== null && _a !== void 0 ? _a : (yield Supplier.get(this._options["environment"])).wss, "/transcribe"),
10715
10715
  protocols: [],
10716
10716
  queryParameters: _queryParams,
10717
10717
  headers: _headers,
@@ -10874,7 +10874,7 @@ var Transcribe2 = class extends Transcribe {
10874
10874
  };
10875
10875
 
10876
10876
  // node_modules/@corti/sdk/dist/esm/version.mjs
10877
- var SDK_VERSION = "0.8.0-rc";
10877
+ var SDK_VERSION = "0.8.0";
10878
10878
 
10879
10879
  // node_modules/@corti/sdk/dist/esm/custom/utils/resolveClientOptions.mjs
10880
10880
  var __awaiter30 = function(thisArg, _arguments, P2, generator) {
@@ -11080,13 +11080,13 @@ var CortiClient = class {
11080
11080
  var CortiEnvironment = {
11081
11081
  Eu: {
11082
11082
  base: "https://api.eu.corti.app/v2",
11083
- wss: "wss://api.eu.corti.app",
11083
+ wss: "wss://api.eu.corti.app/audio-bridge/v2",
11084
11084
  login: "https://auth.eu.corti.app/realms",
11085
11085
  agents: "https://api.eu.corti.app"
11086
11086
  },
11087
11087
  Us: {
11088
11088
  base: "https://api.us.corti.app/v2",
11089
- wss: "wss://api.us.corti.app",
11089
+ wss: "wss://api.us.corti.app/audio-bridge/v2",
11090
11090
  login: "https://auth.us.corti.app/realms",
11091
11091
  agents: "https://api.us.corti.app"
11092
11092
  }
@@ -11173,6 +11173,10 @@ var DictationController = class {
11173
11173
  }
11174
11174
  __classPrivateFieldSet3(this, _DictationController_webSocket, this.host._socketUrl || this.host._socketProxy ? await __classPrivateFieldGet4(this, _DictationController_instances, "m", _DictationController_connectProxy).call(this, dictationConfig) : await __classPrivateFieldGet4(this, _DictationController_instances, "m", _DictationController_connectAuth).call(this, dictationConfig), "f");
11175
11175
  __classPrivateFieldSet3(this, _DictationController_onNetworkActivity, callbacks.onNetworkActivity, "f");
11176
+ __classPrivateFieldGet4(this, _DictationController_onNetworkActivity, "f")?.call(this, "sent", {
11177
+ configuration: dictationConfig,
11178
+ type: "config"
11179
+ });
11176
11180
  __classPrivateFieldGet4(this, _DictationController_instances, "m", _DictationController_setupMediaRecorder).call(this, mediaRecorder);
11177
11181
  __classPrivateFieldGet4(this, _DictationController_instances, "m", _DictationController_setupWebSocketHandlers).call(this, callbacks);
11178
11182
  }
@@ -36,6 +36,10 @@ export class DictationController {
36
36
  ? await __classPrivateFieldGet(this, _DictationController_instances, "m", _DictationController_connectProxy).call(this, dictationConfig)
37
37
  : await __classPrivateFieldGet(this, _DictationController_instances, "m", _DictationController_connectAuth).call(this, dictationConfig), "f");
38
38
  __classPrivateFieldSet(this, _DictationController_onNetworkActivity, callbacks.onNetworkActivity, "f");
39
+ __classPrivateFieldGet(this, _DictationController_onNetworkActivity, "f")?.call(this, "sent", {
40
+ configuration: dictationConfig,
41
+ type: "config",
42
+ });
39
43
  __classPrivateFieldGet(this, _DictationController_instances, "m", _DictationController_setupMediaRecorder).call(this, mediaRecorder);
40
44
  __classPrivateFieldGet(this, _DictationController_instances, "m", _DictationController_setupWebSocketHandlers).call(this, callbacks);
41
45
  }
@@ -1 +1 @@
1
- {"version":3,"file":"dictation-controller.js","sourceRoot":"","sources":["../../src/controllers/dictation-controller.ts"],"names":[],"mappings":"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","sourcesContent":["import { type Corti, CortiClient, CortiWebSocketProxyClient } from \"@corti/sdk\";\nimport type { ReactiveController, ReactiveControllerHost } from \"lit\";\nimport { DEFAULT_DICTATION_CONFIG } from \"../constants.js\";\nimport type { ProxyOptions } from \"../types.js\";\n\ntype TranscribeSocket = Awaited<\n ReturnType<CortiClient[\"transcribe\"][\"connect\"]>\n>;\n\ninterface DictationControllerHost extends ReactiveControllerHost {\n _accessToken?: string;\n _authConfig?: Corti.BearerOptions;\n _region?: string;\n _tenantName?: string;\n _socketUrl?: string;\n _socketProxy?: ProxyOptions;\n}\n\nexport type TranscribeMessage =\n | Corti.TranscribeConfigStatusMessage\n | Corti.TranscribeUsageMessage\n | Corti.TranscribeEndedMessage\n | Corti.TranscribeErrorMessage\n | Corti.TranscribeTranscriptMessage\n | Corti.TranscribeCommandMessage\n | Corti.TranscribeFlushedMessage;\n\ninterface WebSocketCallbacks {\n onMessage?: (message: TranscribeMessage) => void;\n onError?: (error: Error) => void;\n onClose?: (event: unknown) => void;\n onNetworkActivity?: (direction: \"sent\" | \"received\", data: unknown) => void;\n}\n\nexport class DictationController implements ReactiveController {\n host: DictationControllerHost;\n\n #cortiClient: CortiClient | null = null;\n #webSocket: TranscribeSocket | null = null;\n #closeTimeout?: number;\n #onNetworkActivity?: WebSocketCallbacks[\"onNetworkActivity\"];\n\n constructor(host: DictationControllerHost) {\n this.host = host;\n host.addController(this);\n }\n\n hostDisconnected(): void {\n this.cleanup();\n }\n\n async connect(\n mediaRecorder: MediaRecorder | null,\n dictationConfig: Corti.TranscribeConfig = DEFAULT_DICTATION_CONFIG,\n callbacks: WebSocketCallbacks = {},\n ): Promise<void> {\n if (!mediaRecorder) {\n throw new Error(\"MediaRecorder is required to connect\");\n }\n\n if (this.#webSocket?.readyState === WebSocket.OPEN) {\n throw new Error(\"Already connected. Disconnect before reconnecting.\");\n }\n\n this.#webSocket =\n this.host._socketUrl || this.host._socketProxy\n ? await this.#connectProxy(dictationConfig)\n : await this.#connectAuth(dictationConfig);\n\n this.#onNetworkActivity = callbacks.onNetworkActivity;\n this.#setupMediaRecorder(mediaRecorder);\n this.#setupWebSocketHandlers(callbacks);\n }\n\n async #connectProxy(\n dictationConfig: Corti.TranscribeConfig,\n ): Promise<TranscribeSocket> {\n const proxyOptions = this.host._socketProxy || {\n url: this.host._socketUrl || \"\",\n };\n\n if (!proxyOptions.url) {\n throw new Error(\"Proxy URL is required when using proxy client\");\n }\n\n return await CortiWebSocketProxyClient.transcribe.connect({\n configuration: dictationConfig,\n proxy: proxyOptions,\n });\n }\n\n async #connectAuth(\n dictationConfig: Corti.TranscribeConfig,\n ): Promise<TranscribeSocket> {\n if (!this.host._authConfig && !this.host._accessToken) {\n throw new Error(\n \"Auth configuration or access token is required to connect\",\n );\n }\n\n // Use authConfig if available, otherwise create one from accessToken\n const auth: Corti.BearerOptions = this.host._authConfig || {\n accessToken: this.host._accessToken || \"\",\n refreshAccessToken: () => ({\n accessToken: this.host._accessToken || \"\",\n }),\n };\n\n this.#cortiClient = new CortiClient({\n auth,\n environment: this.host._region,\n tenantName: this.host._tenantName,\n });\n\n return await this.#cortiClient.transcribe.connect({\n configuration: dictationConfig,\n });\n }\n\n #setupWebSocketHandlers(callbacks: WebSocketCallbacks): void {\n if (!this.#webSocket) {\n throw new Error(\"WebSocket not initialized\");\n }\n\n this.#webSocket.on(\"message\", (message: TranscribeMessage) => {\n this.#onNetworkActivity?.(\"received\", message);\n\n if (callbacks.onMessage) {\n callbacks.onMessage(message);\n }\n });\n\n this.#webSocket.on(\"error\", (event: Error) => {\n if (callbacks.onError) {\n callbacks.onError(event);\n }\n });\n\n this.#webSocket.on(\"close\", (event: unknown) => {\n if (callbacks.onClose) {\n callbacks.onClose(event);\n }\n });\n }\n\n #setupMediaRecorder(mediaRecorder: MediaRecorder): void {\n mediaRecorder.ondataavailable = (event) => {\n this.#webSocket?.sendAudio(event.data);\n this.#onNetworkActivity?.(\"sent\", {\n size: event.data.size,\n type: \"audio\",\n });\n };\n }\n\n async disconnect(onClose?: (event: unknown) => void): Promise<void> {\n await new Promise<void>((resolve, reject) => {\n if (!this.#webSocket || this.#webSocket.readyState !== WebSocket.OPEN) {\n resolve();\n return;\n }\n\n this.#webSocket.on(\"close\", (event) => {\n if (this.#closeTimeout) {\n clearTimeout(this.#closeTimeout);\n this.#closeTimeout = undefined;\n }\n\n if (onClose) {\n onClose(event);\n }\n\n resolve();\n });\n\n this.#webSocket.sendEnd({ type: \"end\" });\n this.#onNetworkActivity?.(\"sent\", { type: \"end\" });\n\n this.#closeTimeout = window.setTimeout(() => {\n // Reject the promise before closing the web socket, so the promise rejects before close event fires\n reject(new Error(\"WebSocket close timeout\"));\n\n if (this.#webSocket?.readyState === WebSocket.OPEN) {\n this.#webSocket.close();\n }\n }, 10000);\n });\n\n this.cleanup();\n }\n\n cleanup(): void {\n if (this.#closeTimeout) {\n clearTimeout(this.#closeTimeout);\n this.#closeTimeout = undefined;\n }\n\n if (this.#webSocket?.readyState === WebSocket.OPEN) {\n this.#webSocket.close();\n }\n\n this.#webSocket = null;\n this.#cortiClient = null;\n }\n}\n"]}
1
+ {"version":3,"file":"dictation-controller.js","sourceRoot":"","sources":["../../src/controllers/dictation-controller.ts"],"names":[],"mappings":"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","sourcesContent":["import { type Corti, CortiClient, CortiWebSocketProxyClient } from \"@corti/sdk\";\nimport type { ReactiveController, ReactiveControllerHost } from \"lit\";\nimport { DEFAULT_DICTATION_CONFIG } from \"../constants.js\";\nimport type { ProxyOptions } from \"../types.js\";\n\ntype TranscribeSocket = Awaited<\n ReturnType<CortiClient[\"transcribe\"][\"connect\"]>\n>;\n\ninterface DictationControllerHost extends ReactiveControllerHost {\n _accessToken?: string;\n _authConfig?: Corti.BearerOptions;\n _region?: string;\n _tenantName?: string;\n _socketUrl?: string;\n _socketProxy?: ProxyOptions;\n}\n\nexport type TranscribeMessage =\n | Corti.TranscribeConfigStatusMessage\n | Corti.TranscribeUsageMessage\n | Corti.TranscribeEndedMessage\n | Corti.TranscribeErrorMessage\n | Corti.TranscribeTranscriptMessage\n | Corti.TranscribeCommandMessage\n | Corti.TranscribeFlushedMessage;\n\ninterface WebSocketCallbacks {\n onMessage?: (message: TranscribeMessage) => void;\n onError?: (error: Error) => void;\n onClose?: (event: unknown) => void;\n onNetworkActivity?: (direction: \"sent\" | \"received\", data: unknown) => void;\n}\n\nexport class DictationController implements ReactiveController {\n host: DictationControllerHost;\n\n #cortiClient: CortiClient | null = null;\n #webSocket: TranscribeSocket | null = null;\n #closeTimeout?: number;\n #onNetworkActivity?: WebSocketCallbacks[\"onNetworkActivity\"];\n\n constructor(host: DictationControllerHost) {\n this.host = host;\n host.addController(this);\n }\n\n hostDisconnected(): void {\n this.cleanup();\n }\n\n async connect(\n mediaRecorder: MediaRecorder | null,\n dictationConfig: Corti.TranscribeConfig = DEFAULT_DICTATION_CONFIG,\n callbacks: WebSocketCallbacks = {},\n ): Promise<void> {\n if (!mediaRecorder) {\n throw new Error(\"MediaRecorder is required to connect\");\n }\n\n if (this.#webSocket?.readyState === WebSocket.OPEN) {\n throw new Error(\"Already connected. Disconnect before reconnecting.\");\n }\n\n this.#webSocket =\n this.host._socketUrl || this.host._socketProxy\n ? await this.#connectProxy(dictationConfig)\n : await this.#connectAuth(dictationConfig);\n\n this.#onNetworkActivity = callbacks.onNetworkActivity;\n\n this.#onNetworkActivity?.(\"sent\", {\n configuration: dictationConfig,\n type: \"config\",\n });\n\n this.#setupMediaRecorder(mediaRecorder);\n this.#setupWebSocketHandlers(callbacks);\n }\n\n async #connectProxy(\n dictationConfig: Corti.TranscribeConfig,\n ): Promise<TranscribeSocket> {\n const proxyOptions = this.host._socketProxy || {\n url: this.host._socketUrl || \"\",\n };\n\n if (!proxyOptions.url) {\n throw new Error(\"Proxy URL is required when using proxy client\");\n }\n\n return await CortiWebSocketProxyClient.transcribe.connect({\n configuration: dictationConfig,\n proxy: proxyOptions,\n });\n }\n\n async #connectAuth(\n dictationConfig: Corti.TranscribeConfig,\n ): Promise<TranscribeSocket> {\n if (!this.host._authConfig && !this.host._accessToken) {\n throw new Error(\n \"Auth configuration or access token is required to connect\",\n );\n }\n\n // Use authConfig if available, otherwise create one from accessToken\n const auth: Corti.BearerOptions = this.host._authConfig || {\n accessToken: this.host._accessToken || \"\",\n refreshAccessToken: () => ({\n accessToken: this.host._accessToken || \"\",\n }),\n };\n\n this.#cortiClient = new CortiClient({\n auth,\n environment: this.host._region,\n tenantName: this.host._tenantName,\n });\n\n return await this.#cortiClient.transcribe.connect({\n configuration: dictationConfig,\n });\n }\n\n #setupWebSocketHandlers(callbacks: WebSocketCallbacks): void {\n if (!this.#webSocket) {\n throw new Error(\"WebSocket not initialized\");\n }\n\n this.#webSocket.on(\"message\", (message: TranscribeMessage) => {\n this.#onNetworkActivity?.(\"received\", message);\n\n if (callbacks.onMessage) {\n callbacks.onMessage(message);\n }\n });\n\n this.#webSocket.on(\"error\", (event: Error) => {\n if (callbacks.onError) {\n callbacks.onError(event);\n }\n });\n\n this.#webSocket.on(\"close\", (event: unknown) => {\n if (callbacks.onClose) {\n callbacks.onClose(event);\n }\n });\n }\n\n #setupMediaRecorder(mediaRecorder: MediaRecorder): void {\n mediaRecorder.ondataavailable = (event) => {\n this.#webSocket?.sendAudio(event.data);\n this.#onNetworkActivity?.(\"sent\", {\n size: event.data.size,\n type: \"audio\",\n });\n };\n }\n\n async disconnect(onClose?: (event: unknown) => void): Promise<void> {\n await new Promise<void>((resolve, reject) => {\n if (!this.#webSocket || this.#webSocket.readyState !== WebSocket.OPEN) {\n resolve();\n return;\n }\n\n this.#webSocket.on(\"close\", (event) => {\n if (this.#closeTimeout) {\n clearTimeout(this.#closeTimeout);\n this.#closeTimeout = undefined;\n }\n\n if (onClose) {\n onClose(event);\n }\n\n resolve();\n });\n\n this.#webSocket.sendEnd({ type: \"end\" });\n this.#onNetworkActivity?.(\"sent\", { type: \"end\" });\n\n this.#closeTimeout = window.setTimeout(() => {\n // Reject the promise before closing the web socket, so the promise rejects before close event fires\n reject(new Error(\"WebSocket close timeout\"));\n\n if (this.#webSocket?.readyState === WebSocket.OPEN) {\n this.#webSocket.close();\n }\n }, 10000);\n });\n\n this.cleanup();\n }\n\n cleanup(): void {\n if (this.#closeTimeout) {\n clearTimeout(this.#closeTimeout);\n this.#closeTimeout = undefined;\n }\n\n if (this.#webSocket?.readyState === WebSocket.OPEN) {\n this.#webSocket.close();\n }\n\n this.#webSocket = null;\n this.#cortiClient = null;\n }\n}\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.4.0-rc.1",
5
+ "version": "0.4.0",
6
6
  "license": "MIT",
7
7
  "type": "module",
8
8
  "main": "dist/index.js",
@@ -58,7 +58,7 @@
58
58
  "storybook:build": "tsc && tsc -p tsconfig.stories.json && npm run analyze -- --exclude dist && storybook build"
59
59
  },
60
60
  "dependencies": {
61
- "@corti/sdk": "^0.8.0-rc",
61
+ "@corti/sdk": "^0.8.0",
62
62
  "@lit/context": "^1.1.6",
63
63
  "lit": "^3.3.1"
64
64
  },