@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
|
-
|
|
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
|
-
|
|
44
|
+
Install the package using your preferred package manager:
|
|
17
45
|
|
|
18
|
-
```
|
|
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
|
|
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
|
-
//
|
|
63
|
+
// Side-effect import - automatically registers the component
|
|
26
64
|
import '@corti/dictation-web';
|
|
27
65
|
```
|
|
28
66
|
|
|
29
|
-
|
|
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.
|
|
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
|
-
##
|
|
40
|
-
|
|
41
|
-
### Demo
|
|
83
|
+
## Demo
|
|
42
84
|
|
|
43
85
|
🚀 [Hosted Demo](https://codepen.io/hccullen/pen/OPJmxQR)
|
|
44
86
|
|
|
45
|
-
|
|
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
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
-
|
|
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
|
-
|
|
123
|
+
For more control, use individual components to build a custom UI:
|
|
152
124
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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
|
-
|
|
159
|
+
## Documentation
|
|
162
160
|
|
|
163
|
-
|
|
161
|
+
For more detailed information, see:
|
|
164
162
|
|
|
165
|
-
|
|
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, `/
|
|
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, "/
|
|
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
|
|
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
|
|
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
|
|
61
|
+
"@corti/sdk": "^0.8.0",
|
|
62
62
|
"@lit/context": "^1.1.6",
|
|
63
63
|
"lit": "^3.3.1"
|
|
64
64
|
},
|