@corti/dictation-web 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.editorconfig +29 -0
- package/.eslintrc.json +16 -0
- package/.husky/pre-commit +1 -0
- package/.storybook/main.js +8 -0
- package/README.md +120 -0
- package/demo/index.html +98 -0
- package/dist/src/CortiDictation.d.ts +19 -0
- package/dist/src/CortiDictation.js +137 -0
- package/dist/src/CortiDictation.js.map +1 -0
- package/dist/src/DictationService.d.ts +13 -0
- package/dist/src/DictationService.js +70 -0
- package/dist/src/DictationService.js.map +1 -0
- package/dist/src/RecorderManager.d.ts +20 -0
- package/dist/src/RecorderManager.js +85 -0
- package/dist/src/RecorderManager.js.map +1 -0
- package/dist/src/audioRecorderManager.d.ts +17 -0
- package/dist/src/audioRecorderManager.js +78 -0
- package/dist/src/audioRecorderManager.js.map +1 -0
- package/dist/src/audioService.d.ts +6 -0
- package/dist/src/audioService.js +21 -0
- package/dist/src/audioService.js.map +1 -0
- package/dist/src/componentStyles.d.ts +1 -0
- package/dist/src/componentStyles.js +51 -0
- package/dist/src/componentStyles.js.map +1 -0
- package/dist/src/components/audio-visualiser.d.ts +12 -0
- package/dist/src/components/audio-visualiser.js +60 -0
- package/dist/src/components/audio-visualiser.js.map +1 -0
- package/dist/src/components/settings-menu.d.ts +15 -0
- package/dist/src/components/settings-menu.js +148 -0
- package/dist/src/components/settings-menu.js.map +1 -0
- package/dist/src/components/visualiser.d.ts +7 -0
- package/dist/src/components/visualiser.js +62 -0
- package/dist/src/components/visualiser.js.map +1 -0
- package/dist/src/constants.d.ts +3 -0
- package/dist/src/constants.js +9 -0
- package/dist/src/constants.js.map +1 -0
- package/dist/src/corti-dictation.d.ts +1 -0
- package/dist/src/corti-dictation.js +3 -0
- package/dist/src/corti-dictation.js.map +1 -0
- package/dist/src/dictationService.d.ts +13 -0
- package/dist/src/dictationService.js +70 -0
- package/dist/src/dictationService.js.map +1 -0
- package/dist/src/icons/icons.d.ts +17 -0
- package/dist/src/icons/icons.js +153 -0
- package/dist/src/icons/icons.js.map +1 -0
- package/dist/src/icons/index.d.ts +0 -0
- package/dist/src/icons/index.js +2 -0
- package/dist/src/icons/index.js.map +1 -0
- package/dist/src/icons/micOn.d.ts +7 -0
- package/dist/src/icons/micOn.js +25 -0
- package/dist/src/icons/micOn.js.map +1 -0
- package/dist/src/index.d.ts +20 -0
- package/dist/src/index.js +147 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/mediaRecorderService.d.ts +6 -0
- package/dist/src/mediaRecorderService.js +31 -0
- package/dist/src/mediaRecorderService.js.map +1 -0
- package/dist/src/mic-selector.d.ts +18 -0
- package/dist/src/mic-selector.js +131 -0
- package/dist/src/mic-selector.js.map +1 -0
- package/dist/src/settings-menu.d.ts +18 -0
- package/dist/src/settings-menu.js +131 -0
- package/dist/src/settings-menu.js.map +1 -0
- package/dist/src/settings-popover.d.ts +18 -0
- package/dist/src/settings-popover.js +131 -0
- package/dist/src/settings-popover.js.map +1 -0
- package/dist/src/settings.d.ts +18 -0
- package/dist/src/settings.js +131 -0
- package/dist/src/settings.js.map +1 -0
- package/dist/src/styles/ComponentStyles.d.ts +2 -0
- package/dist/src/styles/ComponentStyles.js +52 -0
- package/dist/src/styles/ComponentStyles.js.map +1 -0
- package/dist/src/styles/buttons.d.ts +2 -0
- package/dist/src/styles/buttons.js +58 -0
- package/dist/src/styles/buttons.js.map +1 -0
- package/dist/src/styles/callout.d.ts +2 -0
- package/dist/src/styles/callout.js +26 -0
- package/dist/src/styles/callout.js.map +1 -0
- package/dist/src/styles/select.d.ts +2 -0
- package/dist/src/styles/select.js +36 -0
- package/dist/src/styles/select.js.map +1 -0
- package/dist/src/styles/theme.d.ts +2 -0
- package/dist/src/styles/theme.js +74 -0
- package/dist/src/styles/theme.js.map +1 -0
- package/dist/src/types.d.ts +20 -0
- package/dist/src/types.js +2 -0
- package/dist/src/types.js.map +1 -0
- package/dist/src/utils.d.ts +31 -0
- package/dist/src/utils.js +77 -0
- package/dist/src/utils.js.map +1 -0
- package/dist/stories/index.stories.d.ts +33 -0
- package/dist/stories/index.stories.js +37 -0
- package/dist/stories/index.stories.js.map +1 -0
- package/dist/test/corti-dictation.test.d.ts +1 -0
- package/dist/test/corti-dictation.test.js +100 -0
- package/dist/test/corti-dictation.test.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/docs/DEV_README.md +80 -0
- package/package.json +92 -0
- package/src/DictationService.ts +99 -0
- package/src/RecorderManager.ts +114 -0
- package/src/audioService.ts +25 -0
- package/src/components/audio-visualiser.ts +56 -0
- package/src/components/settings-menu.ts +152 -0
- package/src/constants.ts +10 -0
- package/src/corti-dictation.ts +3 -0
- package/src/icons/icons.ts +141 -0
- package/src/icons/index.ts +0 -0
- package/src/index.ts +154 -0
- package/src/styles/ComponentStyles.ts +53 -0
- package/src/styles/buttons.ts +59 -0
- package/src/styles/callout.ts +27 -0
- package/src/styles/select.ts +37 -0
- package/src/styles/theme.ts +75 -0
- package/src/types.ts +28 -0
- package/src/utils.ts +83 -0
- package/stories/index.stories.ts +60 -0
- package/test/corti-dictation.test.ts +124 -0
- package/tsconfig.json +22 -0
- package/web-dev-server.config.js +27 -0
- package/web-test-runner.config.js +41 -0
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
// mic-selector.ts
|
|
2
|
+
import { LitElement, html, css, TemplateResult, CSSResultGroup } from 'lit';
|
|
3
|
+
import { customElement, property } from 'lit/decorators.js';
|
|
4
|
+
|
|
5
|
+
import ButtonStyles from '../styles/buttons';
|
|
6
|
+
import SelectStyles from '../styles/select';
|
|
7
|
+
import { LANGUAGES_SUPPORTED } from '../constants';
|
|
8
|
+
import { getLanguageName } from '../utils';
|
|
9
|
+
import CalloutStyles from '../styles/callout';
|
|
10
|
+
|
|
11
|
+
@customElement('settings-menu')
|
|
12
|
+
export class SettingsMenu extends LitElement {
|
|
13
|
+
@property({ type: Array })
|
|
14
|
+
devices: MediaDeviceInfo[] = [];
|
|
15
|
+
|
|
16
|
+
@property({ type: String })
|
|
17
|
+
selectedDevice: string = '';
|
|
18
|
+
|
|
19
|
+
@property({ type: String })
|
|
20
|
+
selectedLanguage: string = '';
|
|
21
|
+
|
|
22
|
+
@property({ type: Boolean })
|
|
23
|
+
settingsDisabled: boolean = false;
|
|
24
|
+
|
|
25
|
+
static styles: CSSResultGroup = [
|
|
26
|
+
css`
|
|
27
|
+
:host {
|
|
28
|
+
display: block;
|
|
29
|
+
font-family: var(--component-font-family);
|
|
30
|
+
}
|
|
31
|
+
/* Retain the anchor-name styling for this component */
|
|
32
|
+
#settings-popover-button {
|
|
33
|
+
anchor-name: --settings_popover_btn;
|
|
34
|
+
}
|
|
35
|
+
[popover] {
|
|
36
|
+
margin: 0;
|
|
37
|
+
padding: 16px;
|
|
38
|
+
border: 0;
|
|
39
|
+
background: var(--card-background);
|
|
40
|
+
border: 1px solid var(--card-border-color);
|
|
41
|
+
border-radius: var(--card-border-radius);
|
|
42
|
+
box-shadow: var(--card-box-shadow);
|
|
43
|
+
z-index: 1000;
|
|
44
|
+
max-width: 260px;
|
|
45
|
+
width: 100%;
|
|
46
|
+
min-width: 200px;
|
|
47
|
+
position-anchor: --settings_popover_btn;
|
|
48
|
+
position-area: bottom;
|
|
49
|
+
position-visibility: always;
|
|
50
|
+
/* inset: unset; */
|
|
51
|
+
transform: translateX(40%);
|
|
52
|
+
overflow-x: hidden;
|
|
53
|
+
}
|
|
54
|
+
.settings-wrapper {
|
|
55
|
+
display: flex;
|
|
56
|
+
flex-direction: column;
|
|
57
|
+
gap: 20px;
|
|
58
|
+
}
|
|
59
|
+
`,
|
|
60
|
+
ButtonStyles,
|
|
61
|
+
SelectStyles,
|
|
62
|
+
CalloutStyles,
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
private _selectDevice(deviceId: string): void {
|
|
66
|
+
this.selectedDevice = deviceId;
|
|
67
|
+
// Find the device object
|
|
68
|
+
const device = this.devices.find(d => d.deviceId === deviceId);
|
|
69
|
+
this.dispatchEvent(
|
|
70
|
+
new CustomEvent('recording-device-changed', {
|
|
71
|
+
detail: device,
|
|
72
|
+
bubbles: true,
|
|
73
|
+
composed: true,
|
|
74
|
+
}),
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
render(): TemplateResult {
|
|
79
|
+
return html`
|
|
80
|
+
<div class="mic-selector">
|
|
81
|
+
<button id="settings-popover-button" popovertarget="settings-popover">
|
|
82
|
+
<icon-settings></icon-settings>
|
|
83
|
+
</button>
|
|
84
|
+
<div id="settings-popover" popover>
|
|
85
|
+
<div class="settings-wrapper">
|
|
86
|
+
${this.settingsDisabled
|
|
87
|
+
? html`
|
|
88
|
+
<div class="callout orange">
|
|
89
|
+
Recording is in progress. Stop recording to change settings.
|
|
90
|
+
</div>
|
|
91
|
+
`
|
|
92
|
+
: ''}
|
|
93
|
+
<div class="form-group">
|
|
94
|
+
<label id="device-select-label" for="device-select">
|
|
95
|
+
Recording Device
|
|
96
|
+
</label>
|
|
97
|
+
<select
|
|
98
|
+
id="device-select"
|
|
99
|
+
aria-labelledby="device-select-label"
|
|
100
|
+
@change=${(e: Event) => {
|
|
101
|
+
this._selectDevice((e.target as HTMLSelectElement).value);
|
|
102
|
+
}}
|
|
103
|
+
?disabled=${this.settingsDisabled}
|
|
104
|
+
>
|
|
105
|
+
${this.devices.map(
|
|
106
|
+
device => html`
|
|
107
|
+
<option
|
|
108
|
+
value=${device.deviceId}
|
|
109
|
+
?selected=${this.selectedDevice === device.deviceId}
|
|
110
|
+
>
|
|
111
|
+
${device.label || 'Unknown Device'}
|
|
112
|
+
</option>
|
|
113
|
+
`,
|
|
114
|
+
)}
|
|
115
|
+
</select>
|
|
116
|
+
</div>
|
|
117
|
+
<div class="form-group">
|
|
118
|
+
<label id="language-select-label" for="language-select">
|
|
119
|
+
Dictation Language
|
|
120
|
+
</label>
|
|
121
|
+
<select
|
|
122
|
+
id="language-select"
|
|
123
|
+
aria-labelledby="language-select-label"
|
|
124
|
+
@change=${(e: Event) => {
|
|
125
|
+
this._selectDevice((e.target as HTMLSelectElement).value);
|
|
126
|
+
}}
|
|
127
|
+
?disabled=${this.settingsDisabled}
|
|
128
|
+
>
|
|
129
|
+
${LANGUAGES_SUPPORTED.map(
|
|
130
|
+
language => html`
|
|
131
|
+
<option
|
|
132
|
+
value=${language}
|
|
133
|
+
?selected=${this.selectedLanguage === language}
|
|
134
|
+
>
|
|
135
|
+
${getLanguageName(language)}
|
|
136
|
+
</option>
|
|
137
|
+
`,
|
|
138
|
+
)}
|
|
139
|
+
</select>
|
|
140
|
+
</div>
|
|
141
|
+
</div>
|
|
142
|
+
</div>
|
|
143
|
+
</div>
|
|
144
|
+
`;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
declare global {
|
|
149
|
+
interface HTMLElementTagNameMap {
|
|
150
|
+
'settings-menu': SettingsMenu;
|
|
151
|
+
}
|
|
152
|
+
}
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { DictationConfig } from './types';
|
|
2
|
+
|
|
3
|
+
export const LANGUAGES_SUPPORTED = ['en', 'da'];
|
|
4
|
+
export const DEFAULT_DICTATION_CONFIG: DictationConfig = {
|
|
5
|
+
primaryLanguage: 'en',
|
|
6
|
+
interimResults: true,
|
|
7
|
+
spokenPunctuation: true,
|
|
8
|
+
automaticPunctuation: true,
|
|
9
|
+
model: 'others',
|
|
10
|
+
};
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/* eslint-disable max-classes-per-file */
|
|
2
|
+
import { LitElement, css, html } from 'lit';
|
|
3
|
+
import { customElement } from 'lit/decorators.js';
|
|
4
|
+
|
|
5
|
+
@customElement('icon-mic-on')
|
|
6
|
+
export class IconMicOn extends LitElement {
|
|
7
|
+
render() {
|
|
8
|
+
return html`
|
|
9
|
+
<div style="display: flex">
|
|
10
|
+
<svg
|
|
11
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
12
|
+
width="20"
|
|
13
|
+
height="20"
|
|
14
|
+
viewBox="0 0 24 24"
|
|
15
|
+
fill="none"
|
|
16
|
+
stroke="currentColor"
|
|
17
|
+
stroke-width="2"
|
|
18
|
+
stroke-linecap="round"
|
|
19
|
+
stroke-linejoin="round"
|
|
20
|
+
class="lucide lucide-mic"
|
|
21
|
+
>
|
|
22
|
+
<path d="M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z" />
|
|
23
|
+
<path d="M19 10v2a7 7 0 0 1-14 0v-2" />
|
|
24
|
+
<line x1="12" x2="12" y1="19" y2="22" />
|
|
25
|
+
</svg>
|
|
26
|
+
</div>
|
|
27
|
+
`;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
@customElement('icon-mic-off')
|
|
32
|
+
export class IconMicOff extends LitElement {
|
|
33
|
+
render() {
|
|
34
|
+
return html` <div style="display: flex">
|
|
35
|
+
<svg
|
|
36
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
37
|
+
width="20"
|
|
38
|
+
height="20"
|
|
39
|
+
viewBox="0 0 24 24"
|
|
40
|
+
fill="none"
|
|
41
|
+
stroke="currentColor"
|
|
42
|
+
stroke-width="2"
|
|
43
|
+
stroke-linecap="round"
|
|
44
|
+
stroke-linejoin="round"
|
|
45
|
+
class="lucide lucide-mic-off"
|
|
46
|
+
>
|
|
47
|
+
<line x1="2" x2="22" y1="2" y2="22" />
|
|
48
|
+
<path d="M18.89 13.23A7.12 7.12 0 0 0 19 12v-2" />
|
|
49
|
+
<path d="M5 10v2a7 7 0 0 0 12 5" />
|
|
50
|
+
<path d="M15 9.34V5a3 3 0 0 0-5.68-1.33" />
|
|
51
|
+
<path d="M9 9v3a3 3 0 0 0 5.12 2.12" />
|
|
52
|
+
<line x1="12" x2="12" y1="19" y2="22" />
|
|
53
|
+
</svg>
|
|
54
|
+
</div>`;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
@customElement('icon-recording')
|
|
59
|
+
export class IconRecording extends LitElement {
|
|
60
|
+
render() {
|
|
61
|
+
return html`
|
|
62
|
+
<div style="display: flex;">
|
|
63
|
+
<svg
|
|
64
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
65
|
+
width="20"
|
|
66
|
+
height="20"
|
|
67
|
+
viewBox="0 0 24 24"
|
|
68
|
+
fill="none"
|
|
69
|
+
stroke="currentColor"
|
|
70
|
+
stroke-width="2"
|
|
71
|
+
stroke-linecap="round"
|
|
72
|
+
stroke-linejoin="round"
|
|
73
|
+
class="lucide lucide-circle-stop"
|
|
74
|
+
>
|
|
75
|
+
<circle cx="12" cy="12" r="10" />
|
|
76
|
+
<rect x="9" y="9" width="6" height="6" rx="1" fill="currentColor" />
|
|
77
|
+
</svg>
|
|
78
|
+
</div>
|
|
79
|
+
`;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
@customElement('icon-settings')
|
|
83
|
+
export class IconSettings extends LitElement {
|
|
84
|
+
render() {
|
|
85
|
+
return html`<div style="display: flex">
|
|
86
|
+
<svg
|
|
87
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
88
|
+
width="20"
|
|
89
|
+
height="20"
|
|
90
|
+
viewBox="0 0 24 24"
|
|
91
|
+
fill="none"
|
|
92
|
+
stroke="currentColor"
|
|
93
|
+
stroke-width="2"
|
|
94
|
+
stroke-linecap="round"
|
|
95
|
+
stroke-linejoin="round"
|
|
96
|
+
class="lucide lucide-settings-2"
|
|
97
|
+
>
|
|
98
|
+
<path d="M20 7h-9" />
|
|
99
|
+
<path d="M14 17H5" />
|
|
100
|
+
<circle cx="17" cy="17" r="3" />
|
|
101
|
+
<circle cx="7" cy="7" r="3" />
|
|
102
|
+
</svg>
|
|
103
|
+
</div>`;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
@customElement('icon-loading-spinner')
|
|
108
|
+
export class IconLoadingSpinner extends LitElement {
|
|
109
|
+
static styles = css`
|
|
110
|
+
@keyframes spin {
|
|
111
|
+
0% {
|
|
112
|
+
transform: rotate(0deg);
|
|
113
|
+
}
|
|
114
|
+
100% {
|
|
115
|
+
transform: rotate(360deg);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
.spin {
|
|
119
|
+
animation: spin 1s linear infinite;
|
|
120
|
+
}
|
|
121
|
+
`;
|
|
122
|
+
|
|
123
|
+
render() {
|
|
124
|
+
return html`<div style="display: flex">
|
|
125
|
+
<svg
|
|
126
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
127
|
+
width="20"
|
|
128
|
+
height="20"
|
|
129
|
+
viewBox="0 0 24 24"
|
|
130
|
+
fill="none"
|
|
131
|
+
stroke="currentColor"
|
|
132
|
+
stroke-width="2"
|
|
133
|
+
stroke-linecap="round"
|
|
134
|
+
stroke-linejoin="round"
|
|
135
|
+
class="lucide lucide-loader-circle spin"
|
|
136
|
+
>
|
|
137
|
+
<path d="M21 12a9 9 0 1 1-6.219-8.56" />
|
|
138
|
+
</svg>
|
|
139
|
+
</div>`;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
File without changes
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
// corti-dictation.ts
|
|
2
|
+
import { html, LitElement } from 'lit';
|
|
3
|
+
import { property, state } from 'lit/decorators.js';
|
|
4
|
+
import { RecorderManager } from './RecorderManager';
|
|
5
|
+
import './components/settings-menu';
|
|
6
|
+
import './components/audio-visualiser';
|
|
7
|
+
import './icons/icons';
|
|
8
|
+
import ThemeStyles from './styles/theme';
|
|
9
|
+
import ButtonStyles from './styles/buttons';
|
|
10
|
+
import ComponentStyles from './styles/ComponentStyles';
|
|
11
|
+
|
|
12
|
+
import { DictationConfig, ServerConfig } from './types';
|
|
13
|
+
import { DEFAULT_DICTATION_CONFIG } from './constants';
|
|
14
|
+
import CalloutStyles from './styles/callout';
|
|
15
|
+
|
|
16
|
+
export class CortiDictation extends LitElement {
|
|
17
|
+
static styles = [ButtonStyles, ThemeStyles, ComponentStyles, CalloutStyles];
|
|
18
|
+
|
|
19
|
+
@property({ type: Array })
|
|
20
|
+
devices: MediaDeviceInfo[] = [];
|
|
21
|
+
|
|
22
|
+
@property({ type: String, reflect: true })
|
|
23
|
+
recordingState = 'stopped';
|
|
24
|
+
|
|
25
|
+
@property({ type: Object })
|
|
26
|
+
dictationConfig: DictationConfig = DEFAULT_DICTATION_CONFIG;
|
|
27
|
+
|
|
28
|
+
@property({ type: Object })
|
|
29
|
+
serverConfig: ServerConfig = {};
|
|
30
|
+
|
|
31
|
+
@state()
|
|
32
|
+
private _audioLevel = 0;
|
|
33
|
+
|
|
34
|
+
private recorderManager = new RecorderManager();
|
|
35
|
+
|
|
36
|
+
async connectedCallback() {
|
|
37
|
+
super.connectedCallback();
|
|
38
|
+
await this.recorderManager.initialize();
|
|
39
|
+
this.devices = this.recorderManager.devices;
|
|
40
|
+
|
|
41
|
+
// Map event names to any extra handling logic
|
|
42
|
+
const eventHandlers: Record<string, (e: CustomEvent) => void> = {
|
|
43
|
+
'recording-state-changed': e => {
|
|
44
|
+
this.recordingState = e.detail.state;
|
|
45
|
+
},
|
|
46
|
+
'audio-level-changed': e => {
|
|
47
|
+
this._audioLevel = e.detail.audioLevel;
|
|
48
|
+
this.requestUpdate();
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const eventsToRelay = [
|
|
53
|
+
'recording-state-changed',
|
|
54
|
+
'audio-level-changed',
|
|
55
|
+
'error',
|
|
56
|
+
'transcript',
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
eventsToRelay.forEach(eventName => {
|
|
60
|
+
this.recorderManager.addEventListener(eventName, (e: Event) => {
|
|
61
|
+
const customEvent = e as CustomEvent;
|
|
62
|
+
// Perform any additional handling if defined
|
|
63
|
+
if (eventHandlers[eventName]) {
|
|
64
|
+
eventHandlers[eventName](customEvent);
|
|
65
|
+
}
|
|
66
|
+
// Re-dispatch the event from the component
|
|
67
|
+
this.dispatchEvent(
|
|
68
|
+
new CustomEvent(eventName, {
|
|
69
|
+
detail: customEvent.detail,
|
|
70
|
+
bubbles: true,
|
|
71
|
+
composed: true,
|
|
72
|
+
}),
|
|
73
|
+
);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
public toggleRecording() {
|
|
79
|
+
this._toggleRecording();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
_toggleRecording() {
|
|
83
|
+
if (this.recordingState === 'recording') {
|
|
84
|
+
this.recorderManager.stopRecording();
|
|
85
|
+
} else if (this.recordingState === 'stopped') {
|
|
86
|
+
this.recorderManager.startRecording({
|
|
87
|
+
dictationConfig: this.dictationConfig,
|
|
88
|
+
serverConfig: this.serverConfig,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Handle device change events if needed
|
|
94
|
+
async _onRecordingDeviceChanged(event: Event) {
|
|
95
|
+
const customEvent = event as CustomEvent;
|
|
96
|
+
this.recorderManager.selectedDevice = customEvent.detail.deviceId;
|
|
97
|
+
if (this.recordingState === 'recording') {
|
|
98
|
+
await this.recorderManager.stopRecording();
|
|
99
|
+
await this.recorderManager.startRecording({
|
|
100
|
+
dictationConfig: this.dictationConfig,
|
|
101
|
+
serverConfig: this.serverConfig,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
render() {
|
|
107
|
+
const isConfigured =
|
|
108
|
+
this.serverConfig &&
|
|
109
|
+
this.serverConfig.token &&
|
|
110
|
+
this.serverConfig.environment &&
|
|
111
|
+
this.serverConfig.tenant;
|
|
112
|
+
if (!isConfigured) {
|
|
113
|
+
return html`
|
|
114
|
+
<div class="wrapper">
|
|
115
|
+
<div class="callout red tiny">
|
|
116
|
+
Please configure the server settings in the parent component.
|
|
117
|
+
</div>
|
|
118
|
+
</div>
|
|
119
|
+
`;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const isLoading =
|
|
123
|
+
this.recordingState === 'initializing' ||
|
|
124
|
+
this.recordingState === 'stopping';
|
|
125
|
+
const isRecording = this.recordingState === 'recording';
|
|
126
|
+
return html`
|
|
127
|
+
<div class="wrapper">
|
|
128
|
+
<button
|
|
129
|
+
@click=${this._toggleRecording}
|
|
130
|
+
class=${isRecording ? 'red' : 'accent'}
|
|
131
|
+
>
|
|
132
|
+
${isLoading
|
|
133
|
+
? html`<icon-loading-spinner></icon-loading-spinner>`
|
|
134
|
+
: isRecording
|
|
135
|
+
? html`<icon-recording></icon-recording>`
|
|
136
|
+
: html`<icon-mic-on></icon-mic-on>`}
|
|
137
|
+
<audio-visualiser
|
|
138
|
+
.level=${this._audioLevel}
|
|
139
|
+
.active=${isRecording}
|
|
140
|
+
></audio-visualiser>
|
|
141
|
+
</button>
|
|
142
|
+
|
|
143
|
+
<settings-menu
|
|
144
|
+
.devices=${this.devices}
|
|
145
|
+
.selectedDevice=${this.recorderManager.selectedDevice}
|
|
146
|
+
?settingsDisabled=${this.recordingState !== 'stopped'}
|
|
147
|
+
@recording-device-changed=${this._onRecordingDeviceChanged}
|
|
148
|
+
></settings-menu>
|
|
149
|
+
</div>
|
|
150
|
+
`;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export default CortiDictation;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { css } from 'lit';
|
|
2
|
+
|
|
3
|
+
const ComponentStyles = css`
|
|
4
|
+
.wrapper {
|
|
5
|
+
background-color: var(--card-background);
|
|
6
|
+
border: 1px solid var(--card-border-color);
|
|
7
|
+
border-radius: var(--card-border-radius);
|
|
8
|
+
box-shadow: var(--card-box-shadow);
|
|
9
|
+
padding: var(--card-padding);
|
|
10
|
+
display: flex;
|
|
11
|
+
width: min-content;
|
|
12
|
+
gap: 4px;
|
|
13
|
+
height: 46px;
|
|
14
|
+
box-sizing: border-box;
|
|
15
|
+
overflow: hidden;
|
|
16
|
+
}
|
|
17
|
+
h2 {
|
|
18
|
+
margin: 0 0 10px;
|
|
19
|
+
font-size: 1rem;
|
|
20
|
+
font-weight: 500;
|
|
21
|
+
}
|
|
22
|
+
label {
|
|
23
|
+
font-size: 0.9rem;
|
|
24
|
+
margin-right: 8px;
|
|
25
|
+
}
|
|
26
|
+
select {
|
|
27
|
+
padding: 4px 6px;
|
|
28
|
+
font-size: 0.9rem;
|
|
29
|
+
border: 1px solid var(--card-border-color);
|
|
30
|
+
border-radius: 4px;
|
|
31
|
+
background-color: var(--card-background);
|
|
32
|
+
color: inherit;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.visualiser {
|
|
36
|
+
width: 16px;
|
|
37
|
+
height: 100%;
|
|
38
|
+
background: var(--visualiser-background);
|
|
39
|
+
margin-top: 8px;
|
|
40
|
+
border-radius: 5px;
|
|
41
|
+
overflow: hidden;
|
|
42
|
+
display: flex;
|
|
43
|
+
align-items: flex-end;
|
|
44
|
+
}
|
|
45
|
+
.level {
|
|
46
|
+
height: 100%;
|
|
47
|
+
width: 100%;
|
|
48
|
+
background: var(--visualiser-level-color);
|
|
49
|
+
transition: width 0.1s ease-in-out;
|
|
50
|
+
}
|
|
51
|
+
`;
|
|
52
|
+
|
|
53
|
+
export default ComponentStyles;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { css } from 'lit';
|
|
2
|
+
|
|
3
|
+
const ButtonStyles = css`
|
|
4
|
+
/* Default (plain) button styling */
|
|
5
|
+
button,
|
|
6
|
+
.button {
|
|
7
|
+
background: var(--action-plain-background);
|
|
8
|
+
/* border: 1px solid var(--action-plain-border-color); */
|
|
9
|
+
border: none;
|
|
10
|
+
color: var(--component-text-color);
|
|
11
|
+
cursor: pointer;
|
|
12
|
+
padding: 8px;
|
|
13
|
+
border-radius: var(--card-inner-border-radius);
|
|
14
|
+
display: inline-flex;
|
|
15
|
+
gap: 4px;
|
|
16
|
+
align-items: center;
|
|
17
|
+
justify-content: center;
|
|
18
|
+
transition: background 0.3s ease;
|
|
19
|
+
font-family: var(--component-font-family);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
button:hover,
|
|
23
|
+
.button:hover {
|
|
24
|
+
background: var(--action-plain-background-hover);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
button:focus-visible {
|
|
28
|
+
outline: 2px solid var(--action-accent-background);
|
|
29
|
+
outline-offset: 2px;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/* Accent variant */
|
|
33
|
+
button.accent,
|
|
34
|
+
.button.accent {
|
|
35
|
+
background: var(--action-accent-background);
|
|
36
|
+
color: var(--action-accent-text-color);
|
|
37
|
+
border: none;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
button.accent:hover,
|
|
41
|
+
.button.accent:hover {
|
|
42
|
+
background: var(--action-accent-background-hover);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/* Accent variant */
|
|
46
|
+
button.red,
|
|
47
|
+
.button.red {
|
|
48
|
+
background: var(--action-red-background);
|
|
49
|
+
color: var(--action-red-text-color);
|
|
50
|
+
border: none;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
button.red:hover,
|
|
54
|
+
.button.red:hover {
|
|
55
|
+
background: var(--action-red-background-hover);
|
|
56
|
+
}
|
|
57
|
+
`;
|
|
58
|
+
|
|
59
|
+
export default ButtonStyles;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { css } from 'lit';
|
|
2
|
+
|
|
3
|
+
const CalloutStyles = css`
|
|
4
|
+
.callout {
|
|
5
|
+
background: var(--callout-accent-background);
|
|
6
|
+
border: 1px solid var(--callout-accent-border);
|
|
7
|
+
color: var(--callout-accent-text);
|
|
8
|
+
padding: 8px;
|
|
9
|
+
border-radius: var(--card-inner-border-radius);
|
|
10
|
+
display: flex;
|
|
11
|
+
font-size: 0.9rem;
|
|
12
|
+
gap: 8px;
|
|
13
|
+
align-items: flex-start;
|
|
14
|
+
&.red {
|
|
15
|
+
background: var(--callout-red-background);
|
|
16
|
+
border: 1px solid var(--callout-red-border);
|
|
17
|
+
color: var(--callout-red-text);
|
|
18
|
+
}
|
|
19
|
+
&.orange {
|
|
20
|
+
background: var(--callout-orange-background);
|
|
21
|
+
border: 1px solid var(--callout-orange-border);
|
|
22
|
+
color: var(--callout-orange-text);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
`;
|
|
26
|
+
|
|
27
|
+
export default CalloutStyles;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { css } from 'lit';
|
|
2
|
+
|
|
3
|
+
const SelectStyles = css`
|
|
4
|
+
label {
|
|
5
|
+
display: block;
|
|
6
|
+
font-size: 0.8rem;
|
|
7
|
+
padding-bottom: 0.5rem;
|
|
8
|
+
font-weight: 500;
|
|
9
|
+
color: var(--component-text-color);
|
|
10
|
+
pointer-events: none;
|
|
11
|
+
}
|
|
12
|
+
select {
|
|
13
|
+
background: var(--card-background);
|
|
14
|
+
color: var(--component-text-color);
|
|
15
|
+
border: 1px solid var(--card-border-color);
|
|
16
|
+
padding: var(--card-padding);
|
|
17
|
+
border-radius: var(--card-inner-border-radius);
|
|
18
|
+
outline: none;
|
|
19
|
+
width: 100%;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
select:disabled {
|
|
23
|
+
opacity: 0.5;
|
|
24
|
+
cursor: not-allowed;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
select:hover {
|
|
28
|
+
background: var(--action-plain-background-hover);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
select:focus-visible {
|
|
32
|
+
outline: 2px solid var(--action-accent-background);
|
|
33
|
+
/* outline-offset: 2px; */
|
|
34
|
+
}
|
|
35
|
+
`;
|
|
36
|
+
|
|
37
|
+
export default SelectStyles;
|