@corti/dictation-web 0.6.0 → 0.7.0-ambient
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/dist/bundle.js +1838 -1455
- package/dist/components/ambient-recording-button.d.ts +16 -0
- package/dist/components/ambient-recording-button.js +68 -0
- package/dist/components/ambient-recording-button.js.map +1 -0
- package/dist/components/corti-ambient.d.ts +27 -0
- package/dist/components/corti-ambient.js +97 -0
- package/dist/components/corti-ambient.js.map +1 -0
- package/dist/components/corti-dictation.d.ts +9 -109
- package/dist/components/corti-dictation.js +12 -187
- package/dist/components/corti-dictation.js.map +1 -1
- package/dist/components/corti-root.d.ts +121 -0
- package/dist/components/corti-root.js +196 -0
- package/dist/components/corti-root.js.map +1 -0
- package/dist/components/device-selector.js +1 -1
- package/dist/components/device-selector.js.map +1 -1
- package/dist/components/dictation-recording-button.d.ts +13 -0
- package/dist/components/dictation-recording-button.js +30 -0
- package/dist/components/dictation-recording-button.js.map +1 -0
- package/dist/components/keybinding-input.js +1 -1
- package/dist/components/keybinding-input.js.map +1 -1
- package/dist/components/keybinding-selector.js +1 -1
- package/dist/components/keybinding-selector.js.map +1 -1
- package/dist/components/language-selector.js +2 -1
- package/dist/components/language-selector.js.map +1 -1
- package/dist/components/{recording-button.d.ts → recording-button-base.d.ts} +7 -9
- package/dist/components/recording-button-base.js +321 -0
- package/dist/components/recording-button-base.js.map +1 -0
- package/dist/components/settings-menu.js +1 -1
- package/dist/components/settings-menu.js.map +1 -1
- package/dist/constants.d.ts +1 -0
- package/dist/constants.js +9 -0
- package/dist/constants.js.map +1 -1
- package/dist/contexts/ambient-context.d.ts +18 -0
- package/dist/contexts/ambient-context.js +48 -0
- package/dist/contexts/ambient-context.js.map +1 -0
- package/dist/contexts/dictation-context.d.ts +3 -92
- package/dist/contexts/dictation-context.js +5 -257
- package/dist/contexts/dictation-context.js.map +1 -1
- package/dist/contexts/mixins/auth-context.d.ts +32 -0
- package/dist/contexts/mixins/auth-context.js +116 -0
- package/dist/contexts/mixins/auth-context.js.map +1 -0
- package/dist/contexts/mixins/devices-context.d.ts +14 -0
- package/dist/contexts/mixins/devices-context.js +58 -0
- package/dist/contexts/mixins/devices-context.js.map +1 -0
- package/dist/contexts/mixins/keybindings-context.d.ts +13 -0
- package/dist/contexts/mixins/keybindings-context.js +62 -0
- package/dist/contexts/mixins/keybindings-context.js.map +1 -0
- package/dist/contexts/mixins/languages-context.d.ts +11 -0
- package/dist/contexts/mixins/languages-context.js +54 -0
- package/dist/contexts/mixins/languages-context.js.map +1 -0
- package/dist/contexts/mixins/proxy-context.d.ts +14 -0
- package/dist/contexts/mixins/proxy-context.js +24 -0
- package/dist/contexts/mixins/proxy-context.js.map +1 -0
- package/dist/contexts/mixins/recording-state-context.d.ts +13 -0
- package/dist/contexts/mixins/recording-state-context.js +30 -0
- package/dist/contexts/mixins/recording-state-context.js.map +1 -0
- package/dist/contexts/mixins/types.d.ts +5 -0
- package/dist/contexts/mixins/types.js +2 -0
- package/dist/contexts/mixins/types.js.map +1 -0
- package/dist/contexts/root-context.d.ts +8 -0
- package/dist/contexts/root-context.js +34 -0
- package/dist/contexts/root-context.js.map +1 -0
- package/dist/controllers/ambient-controller.d.ts +16 -0
- package/dist/controllers/ambient-controller.js +24 -0
- package/dist/controllers/ambient-controller.js.map +1 -0
- package/dist/controllers/dictation-controller.d.ts +8 -31
- package/dist/controllers/dictation-controller.js +17 -283
- package/dist/controllers/dictation-controller.js.map +1 -1
- package/dist/controllers/languages-controller.js +0 -4
- package/dist/controllers/languages-controller.js.map +1 -1
- package/dist/controllers/socket-controller.d.ts +51 -0
- package/dist/controllers/socket-controller.js +281 -0
- package/dist/controllers/socket-controller.js.map +1 -0
- package/dist/index.d.ts +5 -2
- package/dist/index.js +12 -2
- package/dist/index.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types.d.ts +3 -0
- package/dist/types.js.map +1 -1
- package/dist/utils/devices.d.ts +12 -8
- package/dist/utils/devices.js +39 -28
- package/dist/utils/devices.js.map +1 -1
- package/dist/utils/events.d.ts +5 -3
- package/dist/utils/events.js +7 -0
- package/dist/utils/events.js.map +1 -1
- package/dist/utils/keybinding.d.ts +2 -1
- package/dist/utils/keybinding.js +12 -6
- package/dist/utils/keybinding.js.map +1 -1
- package/package.json +1 -1
- package/dist/components/recording-button.js +0 -331
- package/dist/components/recording-button.js.map +0 -1
package/dist/types.d.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import type { StreamAmbientMessage } from "./controllers/ambient-controller.js";
|
|
2
|
+
import type { TranscribeMessage } from "./controllers/dictation-controller.js";
|
|
3
|
+
export type RecordingSocketInboundMessage = TranscribeMessage | StreamAmbientMessage;
|
|
1
4
|
export type RecordingState = "initializing" | "recording" | "stopping" | "stopped";
|
|
2
5
|
export type Keybinding = string;
|
|
3
6
|
export type ConfigurableSettings = "device" | "language" | "keybinding";
|
package/dist/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"","sourcesContent":["
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { StreamAmbientMessage } from \"./controllers/ambient-controller.js\";\nimport type { TranscribeMessage } from \"./controllers/dictation-controller.js\";\n\nexport type RecordingSocketInboundMessage =\n | TranscribeMessage\n | StreamAmbientMessage;\n\nexport type RecordingState =\n | \"initializing\"\n | \"recording\"\n | \"stopping\"\n | \"stopped\";\n\nexport type Keybinding = string;\n\nexport type ConfigurableSettings = \"device\" | \"language\" | \"keybinding\";\n\nexport type ProxyOptions = {\n url: string;\n protocols?: string[];\n queryParameters?: Record<string, string>;\n};\n"]}
|
package/dist/utils/devices.d.ts
CHANGED
|
@@ -1,19 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Primes the document with an active microphone stream.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Opens (and immediately stops) a media stream so the document holds an
|
|
5
|
+
* active mic permission for this session. In Firefox this is what makes
|
|
6
|
+
* real deviceIds and labels appear in subsequent enumerateDevices() calls.
|
|
7
|
+
* Throws early if the user has already denied permission.
|
|
6
8
|
*
|
|
7
|
-
* @returns A promise that resolves
|
|
9
|
+
* @returns A promise that resolves once the stream has been opened and stopped.
|
|
8
10
|
* @throws Error if microphone access is denied or unavailable.
|
|
9
11
|
*/
|
|
10
|
-
export declare function
|
|
12
|
+
export declare function primeMicStream(): Promise<void>;
|
|
11
13
|
/**
|
|
12
14
|
* Retrieves available audio input devices.
|
|
13
15
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
16
|
+
* Enumerates devices first; if the result looks like Firefox's pre-permission
|
|
17
|
+
* placeholder (an entry with empty deviceId/label even though the Permissions
|
|
18
|
+
* API reports access), primes the document with getUserMedia and re-enumerates.
|
|
19
|
+
* Browsers that already return populated entries (Chrome, Safari) skip the
|
|
20
|
+
* priming call entirely.
|
|
17
21
|
*
|
|
18
22
|
* @returns A promise that resolves with an object containing:
|
|
19
23
|
* - `devices`: an array of MediaDeviceInfo objects for audio inputs.
|
package/dist/utils/devices.js
CHANGED
|
@@ -1,39 +1,36 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Primes the document with an active microphone stream.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Opens (and immediately stops) a media stream so the document holds an
|
|
5
|
+
* active mic permission for this session. In Firefox this is what makes
|
|
6
|
+
* real deviceIds and labels appear in subsequent enumerateDevices() calls.
|
|
7
|
+
* Throws early if the user has already denied permission.
|
|
6
8
|
*
|
|
7
|
-
* @returns A promise that resolves
|
|
9
|
+
* @returns A promise that resolves once the stream has been opened and stopped.
|
|
8
10
|
* @throws Error if microphone access is denied or unavailable.
|
|
9
11
|
*/
|
|
10
|
-
export async function
|
|
11
|
-
if (
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
track.stop();
|
|
12
|
+
export async function primeMicStream() {
|
|
13
|
+
if (navigator.permissions) {
|
|
14
|
+
const permissionStatus = await navigator.permissions.query({
|
|
15
|
+
name: "microphone",
|
|
15
16
|
});
|
|
16
|
-
|
|
17
|
+
if (permissionStatus.state === "denied") {
|
|
18
|
+
throw new Error("Microphone permission is denied");
|
|
19
|
+
}
|
|
17
20
|
}
|
|
18
|
-
const
|
|
19
|
-
|
|
21
|
+
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
22
|
+
stream.getTracks().forEach((track) => {
|
|
23
|
+
track.stop();
|
|
20
24
|
});
|
|
21
|
-
if (permissionStatus.state === "denied") {
|
|
22
|
-
throw new Error("Microphone permission is denied");
|
|
23
|
-
}
|
|
24
|
-
if (permissionStatus.state === "prompt") {
|
|
25
|
-
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
26
|
-
stream.getTracks().forEach((track) => {
|
|
27
|
-
track.stop();
|
|
28
|
-
});
|
|
29
|
-
}
|
|
30
25
|
}
|
|
31
26
|
/**
|
|
32
27
|
* Retrieves available audio input devices.
|
|
33
28
|
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
29
|
+
* Enumerates devices first; if the result looks like Firefox's pre-permission
|
|
30
|
+
* placeholder (an entry with empty deviceId/label even though the Permissions
|
|
31
|
+
* API reports access), primes the document with getUserMedia and re-enumerates.
|
|
32
|
+
* Browsers that already return populated entries (Chrome, Safari) skip the
|
|
33
|
+
* priming call entirely.
|
|
37
34
|
*
|
|
38
35
|
* @returns A promise that resolves with an object containing:
|
|
39
36
|
* - `devices`: an array of MediaDeviceInfo objects for audio inputs.
|
|
@@ -44,10 +41,24 @@ export async function getAudioDevices() {
|
|
|
44
41
|
if (!navigator.mediaDevices?.enumerateDevices) {
|
|
45
42
|
throw new Error("MediaDevices API is not available");
|
|
46
43
|
}
|
|
47
|
-
await
|
|
44
|
+
let audioDevices = await listAudioInputs();
|
|
45
|
+
if (needsMicPriming(audioDevices)) {
|
|
46
|
+
await primeMicStream();
|
|
47
|
+
audioDevices = await listAudioInputs();
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
defaultDevice: audioDevices[0],
|
|
51
|
+
devices: audioDevices,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
async function listAudioInputs() {
|
|
48
55
|
const devices = await navigator.mediaDevices.enumerateDevices();
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
56
|
+
return devices.filter((device) => device.kind === "audioinput");
|
|
57
|
+
}
|
|
58
|
+
function needsMicPriming(audioInputs) {
|
|
59
|
+
if (audioInputs.length === 0) {
|
|
60
|
+
return false; // no mic hardware — don't trigger a permission prompt
|
|
61
|
+
}
|
|
62
|
+
return audioInputs.some((d) => d.deviceId === "" || d.label === "");
|
|
52
63
|
}
|
|
53
64
|
//# sourceMappingURL=devices.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"devices.js","sourceRoot":"","sources":["../../src/utils/devices.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"devices.js","sourceRoot":"","sources":["../../src/utils/devices.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc;IAClC,IAAI,SAAS,CAAC,WAAW,EAAE,CAAC;QAC1B,MAAM,gBAAgB,GAAG,MAAM,SAAS,CAAC,WAAW,CAAC,KAAK,CAAC;YACzD,IAAI,EAAE,YAA8B;SACrC,CAAC,CAAC;QAEH,IAAI,gBAAgB,CAAC,KAAK,KAAK,QAAQ,EAAE,CAAC;YACxC,MAAM,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAC;QACrD,CAAC;IACH,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,YAAY,CAAC,YAAY,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAE1E,MAAM,CAAC,SAAS,EAAE,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE;QACnC,KAAK,CAAC,IAAI,EAAE,CAAC;IACf,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe;IAInC,IAAI,CAAC,SAAS,CAAC,YAAY,EAAE,gBAAgB,EAAE,CAAC;QAC9C,MAAM,IAAI,KAAK,CAAC,mCAAmC,CAAC,CAAC;IACvD,CAAC;IAED,IAAI,YAAY,GAAG,MAAM,eAAe,EAAE,CAAC;IAE3C,IAAI,eAAe,CAAC,YAAY,CAAC,EAAE,CAAC;QAClC,MAAM,cAAc,EAAE,CAAC;QACvB,YAAY,GAAG,MAAM,eAAe,EAAE,CAAC;IACzC,CAAC;IAED,OAAO;QACL,aAAa,EAAE,YAAY,CAAC,CAAC,CAAC;QAC9B,OAAO,EAAE,YAAY;KACtB,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,eAAe;IAC5B,MAAM,OAAO,GAAG,MAAM,SAAS,CAAC,YAAY,CAAC,gBAAgB,EAAE,CAAC;IAChE,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,YAAY,CAAC,CAAC;AAClE,CAAC;AAED,SAAS,eAAe,CAAC,WAA8B;IACrD,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC7B,OAAO,KAAK,CAAC,CAAC,sDAAsD;IACtE,CAAC;IACD,OAAO,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,EAAE,IAAI,CAAC,CAAC,KAAK,KAAK,EAAE,CAAC,CAAC;AACtE,CAAC","sourcesContent":["/**\n * Primes the document with an active microphone stream.\n *\n * Opens (and immediately stops) a media stream so the document holds an\n * active mic permission for this session. In Firefox this is what makes\n * real deviceIds and labels appear in subsequent enumerateDevices() calls.\n * Throws early if the user has already denied permission.\n *\n * @returns A promise that resolves once the stream has been opened and stopped.\n * @throws Error if microphone access is denied or unavailable.\n */\nexport async function primeMicStream(): Promise<void> {\n if (navigator.permissions) {\n const permissionStatus = await navigator.permissions.query({\n name: \"microphone\" as PermissionName,\n });\n\n if (permissionStatus.state === \"denied\") {\n throw new Error(\"Microphone permission is denied\");\n }\n }\n\n const stream = await navigator.mediaDevices.getUserMedia({ audio: true });\n\n stream.getTracks().forEach((track) => {\n track.stop();\n });\n}\n\n/**\n * Retrieves available audio input devices.\n *\n * Enumerates devices first; if the result looks like Firefox's pre-permission\n * placeholder (an entry with empty deviceId/label even though the Permissions\n * API reports access), primes the document with getUserMedia and re-enumerates.\n * Browsers that already return populated entries (Chrome, Safari) skip the\n * priming call entirely.\n *\n * @returns A promise that resolves with an object containing:\n * - `devices`: an array of MediaDeviceInfo objects for audio inputs.\n * - `defaultDevice`: the first audio input device, if available.\n * @throws Error if mediaDevices API is unavailable or device enumeration fails.\n */\nexport async function getAudioDevices(): Promise<{\n devices: MediaDeviceInfo[];\n defaultDevice?: MediaDeviceInfo;\n}> {\n if (!navigator.mediaDevices?.enumerateDevices) {\n throw new Error(\"MediaDevices API is not available\");\n }\n\n let audioDevices = await listAudioInputs();\n\n if (needsMicPriming(audioDevices)) {\n await primeMicStream();\n audioDevices = await listAudioInputs();\n }\n\n return {\n defaultDevice: audioDevices[0],\n devices: audioDevices,\n };\n}\n\nasync function listAudioInputs(): Promise<MediaDeviceInfo[]> {\n const devices = await navigator.mediaDevices.enumerateDevices();\n return devices.filter((device) => device.kind === \"audioinput\");\n}\n\nfunction needsMicPriming(audioInputs: MediaDeviceInfo[]): boolean {\n if (audioInputs.length === 0) {\n return false; // no mic hardware — don't trigger a permission prompt\n }\n return audioInputs.some((d) => d.deviceId === \"\" || d.label === \"\");\n}\n"]}
|
package/dist/utils/events.d.ts
CHANGED
|
@@ -19,10 +19,11 @@ export type RecordingStateChangedEventDetail = {
|
|
|
19
19
|
export type AudioLevelChangedEventDetail = {
|
|
20
20
|
audioLevel: number;
|
|
21
21
|
};
|
|
22
|
-
export type TranscriptEventDetail = Corti.TranscribeTranscriptMessage;
|
|
22
|
+
export type TranscriptEventDetail = Corti.TranscribeTranscriptMessage | Corti.StreamTranscriptMessage;
|
|
23
23
|
export type CommandEventDetail = Corti.TranscribeCommandMessage;
|
|
24
|
-
export type UsageEventDetail = Corti.TranscribeUsageMessage;
|
|
25
|
-
export type DeltaUsageEventDetail = Corti.TranscribeDeltaUsageMessage;
|
|
24
|
+
export type UsageEventDetail = Corti.TranscribeUsageMessage | Corti.StreamUsageMessage;
|
|
25
|
+
export type DeltaUsageEventDetail = Corti.TranscribeDeltaUsageMessage | Corti.StreamDeltaUsageMessage;
|
|
26
|
+
export type FactsEventDetail = Corti.StreamFactsMessage;
|
|
26
27
|
export type ErrorEventDetail = {
|
|
27
28
|
message: string;
|
|
28
29
|
};
|
|
@@ -40,6 +41,7 @@ export declare function transcriptEvent(detail: TranscriptEventDetail): CustomEv
|
|
|
40
41
|
export declare function commandEvent(detail: CommandEventDetail): CustomEvent<CommandEventDetail>;
|
|
41
42
|
export declare function usageEvent(detail: UsageEventDetail): CustomEvent<UsageEventDetail>;
|
|
42
43
|
export declare function deltaUsageEvent(detail: DeltaUsageEventDetail): CustomEvent<DeltaUsageEventDetail>;
|
|
44
|
+
export declare function factsEvent(detail: FactsEventDetail): CustomEvent<FactsEventDetail>;
|
|
43
45
|
export declare function errorEvent(error: unknown): CustomEvent<ErrorEventDetail>;
|
|
44
46
|
/**
|
|
45
47
|
* @deprecated Use recording-state-changed event with detail.connection field instead.
|
package/dist/utils/events.js
CHANGED
|
@@ -61,6 +61,13 @@ export function deltaUsageEvent(detail) {
|
|
|
61
61
|
detail,
|
|
62
62
|
});
|
|
63
63
|
}
|
|
64
|
+
export function factsEvent(detail) {
|
|
65
|
+
return new CustomEvent("facts", {
|
|
66
|
+
bubbles: true,
|
|
67
|
+
composed: true,
|
|
68
|
+
detail,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
64
71
|
function errorToMessage(error) {
|
|
65
72
|
if (error instanceof Error) {
|
|
66
73
|
return error.message;
|
package/dist/utils/events.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"events.js","sourceRoot":"","sources":["../../src/utils/events.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"events.js","sourceRoot":"","sources":["../../src/utils/events.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Corti } from \"@corti/sdk\";\nimport type { RecordingState } from \"../types.js\";\n\nexport type LanguagesChangedEventDetail = {\n languages: Corti.TranscribeSupportedLanguage[];\n selectedLanguage: string | undefined;\n};\n\nexport type LanguageChangedEventDetail = {\n language: string;\n};\n\nexport type RecordingDevicesChangedEventDetail = {\n devices: MediaDeviceInfo[];\n selectedDevice: MediaDeviceInfo | undefined;\n};\n\nexport type RecordingStateChangedEventDetail = {\n state: RecordingState;\n connection?: \"CONNECTING\" | \"OPEN\" | \"CLOSING\" | \"CLOSED\" | null;\n processing?: boolean;\n};\n\nexport type AudioLevelChangedEventDetail = {\n audioLevel: number;\n};\n\nexport type TranscriptEventDetail =\n | Corti.TranscribeTranscriptMessage\n | Corti.StreamTranscriptMessage;\n\nexport type CommandEventDetail = Corti.TranscribeCommandMessage;\n\nexport type UsageEventDetail =\n | Corti.TranscribeUsageMessage\n | Corti.StreamUsageMessage;\n\nexport type DeltaUsageEventDetail =\n | Corti.TranscribeDeltaUsageMessage\n | Corti.StreamDeltaUsageMessage;\n\nexport type FactsEventDetail = Corti.StreamFactsMessage;\n\nexport type ErrorEventDetail = {\n message: string;\n};\n\nexport function languagesChangedEvent(\n languages: Corti.TranscribeSupportedLanguage[],\n selectedLanguage: string | undefined,\n): CustomEvent<LanguagesChangedEventDetail> {\n return new CustomEvent(\"languages-changed\", {\n bubbles: true,\n composed: true,\n detail: { languages, selectedLanguage },\n });\n}\n\n/**\n * @deprecated Use languagesChangedEvent instead. This event is kept for backward compatibility.\n */\nexport function languageChangedEvent(\n language: string,\n): CustomEvent<LanguageChangedEventDetail> {\n return new CustomEvent(\"language-changed\", {\n bubbles: true,\n composed: true,\n detail: { language },\n });\n}\n\nexport function recordingDevicesChangedEvent(\n devices: MediaDeviceInfo[],\n selectedDevice: MediaDeviceInfo | undefined,\n): CustomEvent<RecordingDevicesChangedEventDetail> {\n return new CustomEvent(\"recording-devices-changed\", {\n bubbles: true,\n composed: true,\n detail: { devices, selectedDevice },\n });\n}\n\nexport function recordingStateChangedEvent(\n state: RecordingState,\n options: Partial<{\n connection: \"CONNECTING\" | \"OPEN\" | \"CLOSING\" | \"CLOSED\" | null;\n processing: boolean;\n }> = {},\n): CustomEvent<RecordingStateChangedEventDetail> {\n return new CustomEvent(\"recording-state-changed\", {\n bubbles: true,\n composed: true,\n detail: {\n connection: options.connection,\n processing: options.processing,\n state,\n },\n });\n}\n\nexport function transcriptEvent(\n detail: TranscriptEventDetail,\n): CustomEvent<TranscriptEventDetail> {\n return new CustomEvent(\"transcript\", {\n bubbles: true,\n composed: true,\n detail,\n });\n}\n\nexport function commandEvent(\n detail: CommandEventDetail,\n): CustomEvent<CommandEventDetail> {\n return new CustomEvent(\"command\", {\n bubbles: true,\n composed: true,\n detail,\n });\n}\n\nexport function usageEvent(\n detail: UsageEventDetail,\n): CustomEvent<UsageEventDetail> {\n return new CustomEvent(\"usage\", {\n bubbles: true,\n composed: true,\n detail,\n });\n}\n\nexport function deltaUsageEvent(\n detail: DeltaUsageEventDetail,\n): CustomEvent<DeltaUsageEventDetail> {\n return new CustomEvent(\"delta-usage\", {\n bubbles: true,\n composed: true,\n detail,\n });\n}\n\nexport function factsEvent(\n detail: FactsEventDetail,\n): CustomEvent<FactsEventDetail> {\n return new CustomEvent(\"facts\", {\n bubbles: true,\n composed: true,\n detail,\n });\n}\n\nfunction errorToMessage(error: unknown): string {\n if (error instanceof Error) {\n return error.message;\n }\n\n if (typeof error === \"object\" && error !== null) {\n try {\n return JSON.stringify(error);\n } catch {\n return String(error);\n }\n }\n\n return String(error);\n}\n\nexport function errorEvent(error: unknown): CustomEvent<ErrorEventDetail> {\n const message = errorToMessage(error);\n\n return new CustomEvent(\"error\", {\n bubbles: false,\n composed: true,\n detail: { message },\n });\n}\n\n/**\n * @deprecated Use recording-state-changed event with detail.connection field instead.\n */\nexport function streamClosedEvent(detail: unknown): CustomEvent {\n return new CustomEvent(\"stream-closed\", {\n bubbles: true,\n composed: true,\n detail,\n });\n}\n\nexport function readyEvent(): CustomEvent {\n return new CustomEvent(\"ready\", {\n bubbles: true,\n composed: true,\n });\n}\n\nexport function audioLevelChangedEvent(\n audioLevel: number,\n): CustomEvent<AudioLevelChangedEventDetail> {\n return new CustomEvent(\"audio-level-changed\", {\n bubbles: true,\n composed: true,\n detail: { audioLevel },\n });\n}\n\nexport type NetworkActivityEventDetail = {\n direction: \"sent\" | \"received\";\n data: unknown;\n};\n\nexport function networkActivityEvent(\n direction: \"sent\" | \"received\",\n data: unknown,\n): CustomEvent<NetworkActivityEventDetail> {\n return new CustomEvent(\"network-activity\", {\n bubbles: true,\n composed: true,\n detail: { data, direction },\n });\n}\n\nexport type KeybindingChangedEventDetail = {\n key: string | null | undefined;\n code: string | null | undefined;\n keybinding: string | null;\n type?: \"push-to-talk\" | \"toggle-to-talk\";\n};\n\nexport type KeybindingActivatedEventDetail = {\n keyboardEvent: KeyboardEvent;\n};\n\nexport function keybindingChangedEvent(\n key: string | null | undefined,\n code: string | null | undefined,\n keybinding: string | null,\n type?: \"push-to-talk\" | \"toggle-to-talk\",\n): CustomEvent<KeybindingChangedEventDetail> {\n return new CustomEvent(\"keybinding-changed\", {\n bubbles: true,\n composed: true,\n detail: { code, key, keybinding, type },\n });\n}\n\nexport function keybindingActivatedEvent(\n keyboardEvent: KeyboardEvent,\n): CustomEvent<KeybindingActivatedEventDetail> {\n return new CustomEvent(\"keybinding-activated\", {\n bubbles: true,\n cancelable: true,\n composed: true,\n detail: { keyboardEvent },\n });\n}\n"]}
|
|
@@ -5,8 +5,9 @@
|
|
|
5
5
|
* @returns Normalized keybinding string or null if empty
|
|
6
6
|
*
|
|
7
7
|
* @example
|
|
8
|
-
* normalizeKeybinding("k") // "
|
|
8
|
+
* normalizeKeybinding("k") // "K"
|
|
9
9
|
* normalizeKeybinding("meta") // "Cmd" on Mac
|
|
10
|
+
* normalizeKeybinding(" ") // "Space"
|
|
10
11
|
* normalizeKeybinding(" space ") // "Space"
|
|
11
12
|
*/
|
|
12
13
|
export declare function normalizeKeybinding(keybinding: string | null | undefined): string | null;
|
package/dist/utils/keybinding.js
CHANGED
|
@@ -27,7 +27,7 @@ function capitalize(str) {
|
|
|
27
27
|
* Normalizes a key string to the keybinding format.
|
|
28
28
|
* Handles platform-specific mappings and capitalization.
|
|
29
29
|
*
|
|
30
|
-
* @param key - Key string to normalize (will be trimmed and
|
|
30
|
+
* @param key - Key string to normalize (will be trimmed and uppercased)
|
|
31
31
|
* @returns Formatted key string for keybinding
|
|
32
32
|
*
|
|
33
33
|
* @example
|
|
@@ -35,7 +35,8 @@ function capitalize(str) {
|
|
|
35
35
|
* normalizeKeyForKeybinding("META") // "Cmd" on Mac, "Meta" elsewhere
|
|
36
36
|
* normalizeKeyForKeybinding(" alt ") // "Opt" on Mac, "Alt" elsewhere
|
|
37
37
|
* normalizeKeyForKeybinding("shift") // "Shift"
|
|
38
|
-
* normalizeKeyForKeybinding("k") // "
|
|
38
|
+
* normalizeKeyForKeybinding("k") // "K"
|
|
39
|
+
* normalizeKeyForKeybinding(" ") // "Space"
|
|
39
40
|
*/
|
|
40
41
|
function normalizeKeyForKeybinding(key) {
|
|
41
42
|
if (key === " ") {
|
|
@@ -54,6 +55,11 @@ function normalizeKeyForKeybinding(key) {
|
|
|
54
55
|
if (normalized === "space") {
|
|
55
56
|
return "Space";
|
|
56
57
|
}
|
|
58
|
+
// TODO: Uncomment this for v1 to avoid breaking changes now
|
|
59
|
+
// Capitalize single letters (a-z) for consistent display
|
|
60
|
+
// if (/^[a-z]$/.test(normalized)) {
|
|
61
|
+
// return normalized.toUpperCase();
|
|
62
|
+
// }
|
|
57
63
|
return normalized.length > 1 ? capitalize(normalized) : normalized;
|
|
58
64
|
}
|
|
59
65
|
/**
|
|
@@ -63,19 +69,19 @@ function normalizeKeyForKeybinding(key) {
|
|
|
63
69
|
* @returns Normalized keybinding string or null if empty
|
|
64
70
|
*
|
|
65
71
|
* @example
|
|
66
|
-
* normalizeKeybinding("k") // "
|
|
72
|
+
* normalizeKeybinding("k") // "K"
|
|
67
73
|
* normalizeKeybinding("meta") // "Cmd" on Mac
|
|
74
|
+
* normalizeKeybinding(" ") // "Space"
|
|
68
75
|
* normalizeKeybinding(" space ") // "Space"
|
|
69
76
|
*/
|
|
70
77
|
export function normalizeKeybinding(keybinding) {
|
|
71
78
|
if (!keybinding) {
|
|
72
79
|
return null;
|
|
73
80
|
}
|
|
74
|
-
|
|
75
|
-
if (trimmed === "") {
|
|
81
|
+
if (keybinding !== " " && keybinding.trim() === "") {
|
|
76
82
|
return null;
|
|
77
83
|
}
|
|
78
|
-
return normalizeKeyForKeybinding(
|
|
84
|
+
return normalizeKeyForKeybinding(keybinding);
|
|
79
85
|
}
|
|
80
86
|
/**
|
|
81
87
|
* Checks if a pressed key matches the keybinding.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"keybinding.js","sourceRoot":"","sources":["../../src/utils/keybinding.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,SAAS,KAAK;IACZ,IAAI,OAAO,SAAS,KAAK,WAAW,EAAE,CAAC;QACrC,OAAO,KAAK,CAAC;IACf,CAAC;IAED,2DAA2D;IAC3D,OAAO,sBAAsB,CAAC,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;AAC1D,CAAC;AAED;;;;;GAKG;AACH,SAAS,UAAU,CAAC,GAAW;IAC7B,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACrB,OAAO,GAAG,CAAC;IACb,CAAC;IAED,OAAO,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AACpD,CAAC;AAED
|
|
1
|
+
{"version":3,"file":"keybinding.js","sourceRoot":"","sources":["../../src/utils/keybinding.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Checks if the current platform is macOS.\n * Uses userAgent string for reliable cross-browser detection.\n *\n * @returns true if running on macOS\n */\nfunction isMac(): boolean {\n if (typeof navigator === \"undefined\") {\n return false;\n }\n\n // Check user agent for Mac patterns (most reliable method)\n return /Mac|iPhone|iPad|iPod/.test(navigator.userAgent);\n}\n\n/**\n * Capitalizes the first letter of a string.\n *\n * @param str - String to capitalize\n * @returns String with first letter capitalized\n */\nfunction capitalize(str: string): string {\n if (str.length === 0) {\n return str;\n }\n\n return str.charAt(0).toUpperCase() + str.slice(1);\n}\n\n/**\n * Normalizes a key string to the keybinding format.\n * Handles platform-specific mappings and capitalization.\n *\n * @param key - Key string to normalize (will be trimmed and uppercased)\n * @returns Formatted key string for keybinding\n *\n * @example\n * normalizeKeyForKeybinding(\"Control\") // \"Ctrl\"\n * normalizeKeyForKeybinding(\"META\") // \"Cmd\" on Mac, \"Meta\" elsewhere\n * normalizeKeyForKeybinding(\" alt \") // \"Opt\" on Mac, \"Alt\" elsewhere\n * normalizeKeyForKeybinding(\"shift\") // \"Shift\"\n * normalizeKeyForKeybinding(\"k\") // \"K\"\n * normalizeKeyForKeybinding(\" \") // \"Space\"\n */\nfunction normalizeKeyForKeybinding(key: string): string {\n if (key === \" \") {\n return \"Space\";\n }\n\n const normalized = key.trim().toLowerCase();\n\n if (normalized === \"control\") {\n return \"Ctrl\";\n }\n if (normalized === \"meta\" || normalized === \"cmd\") {\n return isMac() ? \"Cmd\" : \"Meta\";\n }\n if (normalized === \"alt\" || normalized === \"opt\") {\n return isMac() ? \"Opt\" : \"Alt\";\n }\n if (normalized === \"space\") {\n return \"Space\";\n }\n\n // TODO: Uncomment this for v1 to avoid breaking changes now\n // Capitalize single letters (a-z) for consistent display\n // if (/^[a-z]$/.test(normalized)) {\n // return normalized.toUpperCase();\n // }\n\n return normalized.length > 1 ? capitalize(normalized) : normalized;\n}\n\n/**\n * Normalizes a keybinding string.\n *\n * @param keybinding - Keybinding string to normalize\n * @returns Normalized keybinding string or null if empty\n *\n * @example\n * normalizeKeybinding(\"k\") // \"K\"\n * normalizeKeybinding(\"meta\") // \"Cmd\" on Mac\n * normalizeKeybinding(\" \") // \"Space\"\n * normalizeKeybinding(\" space \") // \"Space\"\n */\nexport function normalizeKeybinding(\n keybinding: string | null | undefined,\n): string | null {\n if (!keybinding) {\n return null;\n }\n if (keybinding !== \" \" && keybinding.trim() === \"\") {\n return null;\n }\n\n return normalizeKeyForKeybinding(keybinding);\n}\n\n/**\n * Checks if a pressed key matches the keybinding.\n * Checks both event.key and event.code for better reliability.\n *\n * @param event - KeyboardEvent to check\n * @param keybinding - Keybinding string to match against\n * @returns true if either the key or code matches the keybinding\n *\n * @example\n * matchesKeybinding(event, \"k\") // true if event.key is \"k\" or event.code is \"KeyK\"\n * matchesKeybinding(event, \"`\") // true if event.key is \"`\" or event.code is \"Backquote\"\n */\nexport function matchesKeybinding(\n event: KeyboardEvent,\n keybinding: string | null | undefined,\n): boolean {\n const normalizedKeybinding = normalizeKeybinding(keybinding);\n\n if (!normalizedKeybinding) {\n return false;\n }\n\n const normalizedKey = normalizeKeyForKeybinding(event.key);\n const normalizedCode = normalizeKeyForKeybinding(event.code);\n\n return (\n normalizedKey === normalizedKeybinding ||\n normalizedCode === normalizedKeybinding\n );\n}\n\n/**\n * Checks if keybindings should be ignored for the current active element.\n * Returns true if the user is typing in an input field.\n *\n * @param element - Element to check\n * @returns true if keybindings should be ignored for this element\n *\n * @example\n * shouldIgnoreKeybinding(document.activeElement) // true if input/textarea/contenteditable\n */\nexport function shouldIgnoreKeybinding(element: Element | null): boolean {\n if (!element) {\n return false;\n }\n\n const tagName = element.tagName.toLowerCase();\n\n if (tagName === \"input\" || tagName === \"textarea\") {\n return true;\n }\n\n if (element instanceof HTMLElement && element.contentEditable === \"true\") {\n return true;\n }\n\n return false;\n}\n"]}
|