@corti/dictation-web 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. package/.editorconfig +29 -0
  2. package/.eslintrc.json +16 -0
  3. package/.husky/pre-commit +1 -0
  4. package/.storybook/main.js +8 -0
  5. package/README.md +120 -0
  6. package/demo/index.html +98 -0
  7. package/dist/src/CortiDictation.d.ts +19 -0
  8. package/dist/src/CortiDictation.js +137 -0
  9. package/dist/src/CortiDictation.js.map +1 -0
  10. package/dist/src/DictationService.d.ts +13 -0
  11. package/dist/src/DictationService.js +70 -0
  12. package/dist/src/DictationService.js.map +1 -0
  13. package/dist/src/RecorderManager.d.ts +20 -0
  14. package/dist/src/RecorderManager.js +85 -0
  15. package/dist/src/RecorderManager.js.map +1 -0
  16. package/dist/src/audioRecorderManager.d.ts +17 -0
  17. package/dist/src/audioRecorderManager.js +78 -0
  18. package/dist/src/audioRecorderManager.js.map +1 -0
  19. package/dist/src/audioService.d.ts +6 -0
  20. package/dist/src/audioService.js +21 -0
  21. package/dist/src/audioService.js.map +1 -0
  22. package/dist/src/componentStyles.d.ts +1 -0
  23. package/dist/src/componentStyles.js +51 -0
  24. package/dist/src/componentStyles.js.map +1 -0
  25. package/dist/src/components/audio-visualiser.d.ts +12 -0
  26. package/dist/src/components/audio-visualiser.js +60 -0
  27. package/dist/src/components/audio-visualiser.js.map +1 -0
  28. package/dist/src/components/settings-menu.d.ts +15 -0
  29. package/dist/src/components/settings-menu.js +148 -0
  30. package/dist/src/components/settings-menu.js.map +1 -0
  31. package/dist/src/components/visualiser.d.ts +7 -0
  32. package/dist/src/components/visualiser.js +62 -0
  33. package/dist/src/components/visualiser.js.map +1 -0
  34. package/dist/src/constants.d.ts +3 -0
  35. package/dist/src/constants.js +9 -0
  36. package/dist/src/constants.js.map +1 -0
  37. package/dist/src/corti-dictation.d.ts +1 -0
  38. package/dist/src/corti-dictation.js +3 -0
  39. package/dist/src/corti-dictation.js.map +1 -0
  40. package/dist/src/dictationService.d.ts +13 -0
  41. package/dist/src/dictationService.js +70 -0
  42. package/dist/src/dictationService.js.map +1 -0
  43. package/dist/src/icons/icons.d.ts +17 -0
  44. package/dist/src/icons/icons.js +153 -0
  45. package/dist/src/icons/icons.js.map +1 -0
  46. package/dist/src/icons/index.d.ts +0 -0
  47. package/dist/src/icons/index.js +2 -0
  48. package/dist/src/icons/index.js.map +1 -0
  49. package/dist/src/icons/micOn.d.ts +7 -0
  50. package/dist/src/icons/micOn.js +25 -0
  51. package/dist/src/icons/micOn.js.map +1 -0
  52. package/dist/src/index.d.ts +20 -0
  53. package/dist/src/index.js +147 -0
  54. package/dist/src/index.js.map +1 -0
  55. package/dist/src/mediaRecorderService.d.ts +6 -0
  56. package/dist/src/mediaRecorderService.js +31 -0
  57. package/dist/src/mediaRecorderService.js.map +1 -0
  58. package/dist/src/mic-selector.d.ts +18 -0
  59. package/dist/src/mic-selector.js +131 -0
  60. package/dist/src/mic-selector.js.map +1 -0
  61. package/dist/src/settings-menu.d.ts +18 -0
  62. package/dist/src/settings-menu.js +131 -0
  63. package/dist/src/settings-menu.js.map +1 -0
  64. package/dist/src/settings-popover.d.ts +18 -0
  65. package/dist/src/settings-popover.js +131 -0
  66. package/dist/src/settings-popover.js.map +1 -0
  67. package/dist/src/settings.d.ts +18 -0
  68. package/dist/src/settings.js +131 -0
  69. package/dist/src/settings.js.map +1 -0
  70. package/dist/src/styles/ComponentStyles.d.ts +2 -0
  71. package/dist/src/styles/ComponentStyles.js +52 -0
  72. package/dist/src/styles/ComponentStyles.js.map +1 -0
  73. package/dist/src/styles/buttons.d.ts +2 -0
  74. package/dist/src/styles/buttons.js +58 -0
  75. package/dist/src/styles/buttons.js.map +1 -0
  76. package/dist/src/styles/callout.d.ts +2 -0
  77. package/dist/src/styles/callout.js +26 -0
  78. package/dist/src/styles/callout.js.map +1 -0
  79. package/dist/src/styles/select.d.ts +2 -0
  80. package/dist/src/styles/select.js +36 -0
  81. package/dist/src/styles/select.js.map +1 -0
  82. package/dist/src/styles/theme.d.ts +2 -0
  83. package/dist/src/styles/theme.js +74 -0
  84. package/dist/src/styles/theme.js.map +1 -0
  85. package/dist/src/types.d.ts +20 -0
  86. package/dist/src/types.js +2 -0
  87. package/dist/src/types.js.map +1 -0
  88. package/dist/src/utils.d.ts +31 -0
  89. package/dist/src/utils.js +77 -0
  90. package/dist/src/utils.js.map +1 -0
  91. package/dist/stories/index.stories.d.ts +33 -0
  92. package/dist/stories/index.stories.js +37 -0
  93. package/dist/stories/index.stories.js.map +1 -0
  94. package/dist/test/corti-dictation.test.d.ts +1 -0
  95. package/dist/test/corti-dictation.test.js +100 -0
  96. package/dist/test/corti-dictation.test.js.map +1 -0
  97. package/dist/tsconfig.tsbuildinfo +1 -0
  98. package/docs/DEV_README.md +80 -0
  99. package/package.json +92 -0
  100. package/src/DictationService.ts +99 -0
  101. package/src/RecorderManager.ts +114 -0
  102. package/src/audioService.ts +25 -0
  103. package/src/components/audio-visualiser.ts +56 -0
  104. package/src/components/settings-menu.ts +152 -0
  105. package/src/constants.ts +10 -0
  106. package/src/corti-dictation.ts +3 -0
  107. package/src/icons/icons.ts +141 -0
  108. package/src/icons/index.ts +0 -0
  109. package/src/index.ts +154 -0
  110. package/src/styles/ComponentStyles.ts +53 -0
  111. package/src/styles/buttons.ts +59 -0
  112. package/src/styles/callout.ts +27 -0
  113. package/src/styles/select.ts +37 -0
  114. package/src/styles/theme.ts +75 -0
  115. package/src/types.ts +28 -0
  116. package/src/utils.ts +83 -0
  117. package/stories/index.stories.ts +60 -0
  118. package/test/corti-dictation.test.ts +124 -0
  119. package/tsconfig.json +22 -0
  120. package/web-dev-server.config.js +27 -0
  121. package/web-test-runner.config.js +41 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RecorderManager.js","sourceRoot":"","sources":["../../src/RecorderManager.ts"],"names":[],"mappings":"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","sourcesContent":["import { getAudioDevices } from './utils';\nimport { AudioService } from './audioService';\nimport { DictationService } from './DictationService';\nimport type { DictationConfig, RecordingState, ServerConfig } from './types';\n\nexport class RecorderManager extends EventTarget {\n public devices: MediaDeviceInfo[] = [];\n\n public selectedDevice: string = '';\n\n public recordingState: RecordingState = 'stopped';\n\n private _mediaStream: MediaStream | null = null;\n\n private _audioService: AudioService | null = null;\n\n private _dictationService: DictationService | null = null;\n\n private _visualiserInterval?: number;\n\n async initialize() {\n const deviceResponse = await getAudioDevices();\n this.devices = deviceResponse.devices;\n this.selectedDevice = deviceResponse.defaultDeviceId || '';\n return deviceResponse;\n }\n\n async startRecording(params: {\n dictationConfig: DictationConfig;\n serverConfig: ServerConfig;\n }) {\n this._updateRecordingState('initializing');\n try {\n this._mediaStream = await navigator.mediaDevices.getUserMedia({\n audio: { deviceId: this.selectedDevice },\n });\n this._audioService = new AudioService(this._mediaStream);\n this._dictationService = new DictationService(this._mediaStream, params);\n\n // Forward custom events from dictation service\n this._dictationService.addEventListener('error', e =>\n this.dispatchEvent(\n new CustomEvent('error', {\n detail: (e as CustomEvent).detail,\n bubbles: true,\n composed: true,\n }),\n ),\n );\n this._dictationService.addEventListener('stream-closed', () => this.stopRecording());\n this._dictationService.addEventListener('transcript', e =>\n this.dispatchEvent(\n new CustomEvent('transcript', {\n detail: (e as CustomEvent).detail,\n bubbles: true,\n composed: true,\n }),\n ),\n );\n } catch (error) {\n this.dispatchEvent(\n new CustomEvent('error', {\n detail: error,\n bubbles: true,\n composed: true,\n }),\n );\n this._updateRecordingState('stopped');\n return;\n }\n\n this._dictationService?.startRecording();\n this._updateRecordingState('recording');\n this._visualiserInterval = window.setInterval(() => {\n const level = this._audioService\n ? this._audioService.getAudioLevel() * 3\n : 0;\n this.dispatchEvent(\n new CustomEvent('audio-level-changed', {\n detail: { audioLevel: level },\n bubbles: true,\n composed: true,\n }),\n );\n }, 150);\n }\n\n async stopRecording() {\n this._updateRecordingState('stopping');\n if (this._visualiserInterval) {\n clearInterval(this._visualiserInterval);\n this._visualiserInterval = undefined;\n }\n if (this._mediaStream) {\n this._mediaStream.getTracks().forEach(track => track.stop());\n this._mediaStream = null;\n }\n await this._dictationService?.stopRecording();\n this._updateRecordingState('stopped');\n }\n\n private _updateRecordingState(state: RecordingState) {\n this.recordingState = state;\n this.dispatchEvent(\n new CustomEvent('recording-state-changed', {\n detail: { state },\n bubbles: true,\n composed: true,\n }),\n );\n }\n}\n"]}
@@ -0,0 +1,17 @@
1
+ import type { RecordingState } from './types';
2
+ export declare class RecorderManager extends EventTarget {
3
+ devices: MediaDeviceInfo[];
4
+ selectedDevice: string;
5
+ recordingState: RecordingState;
6
+ private _mediaStream;
7
+ private _audioService;
8
+ private _dictationService;
9
+ private _visualiserInterval?;
10
+ initialize(): Promise<{
11
+ devices: MediaDeviceInfo[];
12
+ defaultDeviceId?: string;
13
+ }>;
14
+ startRecording(): Promise<void>;
15
+ stopRecording(): Promise<void>;
16
+ private _updateRecordingState;
17
+ }
@@ -0,0 +1,78 @@
1
+ import { getAudioDevices } from './utils';
2
+ import { AudioService } from './audioService';
3
+ import { DictationService } from './dictationService';
4
+ export class RecorderManager extends EventTarget {
5
+ constructor() {
6
+ super(...arguments);
7
+ this.devices = [];
8
+ this.selectedDevice = '';
9
+ this.recordingState = 'stopped';
10
+ this._mediaStream = null;
11
+ this._audioService = null;
12
+ this._dictationService = null;
13
+ }
14
+ async initialize() {
15
+ const deviceResponse = await getAudioDevices();
16
+ this.devices = deviceResponse.devices;
17
+ this.selectedDevice = deviceResponse.defaultDeviceId || '';
18
+ return deviceResponse;
19
+ }
20
+ async startRecording() {
21
+ this._updateRecordingState('initializing');
22
+ try {
23
+ this._mediaStream = await navigator.mediaDevices.getUserMedia({
24
+ audio: { deviceId: this.selectedDevice },
25
+ });
26
+ this._audioService = new AudioService(this._mediaStream);
27
+ this._dictationService = new DictationService(this._mediaStream);
28
+ // Forward custom events from dictation service
29
+ this._dictationService.addEventListener('audio-packet', e => this.dispatchEvent(new CustomEvent('audio-packet', {
30
+ detail: e.detail,
31
+ bubbles: true,
32
+ composed: true,
33
+ })));
34
+ this._dictationService.addEventListener('transcript', e => this.dispatchEvent(new CustomEvent('transcript', {
35
+ detail: e.detail,
36
+ bubbles: true,
37
+ composed: true,
38
+ })));
39
+ }
40
+ catch (error) {
41
+ console.error('Error getting user media:', error);
42
+ this._updateRecordingState('stopped');
43
+ return;
44
+ }
45
+ this._dictationService?.startRecording();
46
+ this._updateRecordingState('recording');
47
+ this._visualiserInterval = window.setInterval(() => {
48
+ const level = this._audioService ? this._audioService.getAudioLevel() * 3 : 0;
49
+ this.dispatchEvent(new CustomEvent('audio-level-changed', {
50
+ detail: { audioLevel: level },
51
+ bubbles: true,
52
+ composed: true,
53
+ }));
54
+ }, 150);
55
+ }
56
+ async stopRecording() {
57
+ this._updateRecordingState('stopping');
58
+ if (this._visualiserInterval) {
59
+ clearInterval(this._visualiserInterval);
60
+ this._visualiserInterval = undefined;
61
+ }
62
+ this._dictationService?.stopRecording();
63
+ if (this._mediaStream) {
64
+ this._mediaStream.getTracks().forEach(track => track.stop());
65
+ this._mediaStream = null;
66
+ }
67
+ this._updateRecordingState('stopped');
68
+ }
69
+ _updateRecordingState(state) {
70
+ this.recordingState = state;
71
+ this.dispatchEvent(new CustomEvent('recording-state-changed', {
72
+ detail: { state },
73
+ bubbles: true,
74
+ composed: true,
75
+ }));
76
+ }
77
+ }
78
+ //# sourceMappingURL=audioRecorderManager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audioRecorderManager.js","sourceRoot":"","sources":["../../src/audioRecorderManager.ts"],"names":[],"mappings":"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","sourcesContent":["import { getAudioDevices } from './utils';\nimport { AudioService } from './audioService';\nimport { DictationService } from './dictationService';\nimport type { RecordingState } from './types';\n\nexport class RecorderManager extends EventTarget {\n public devices: MediaDeviceInfo[] = [];\n public selectedDevice: string = '';\n public recordingState: RecordingState = 'stopped';\n\n private _mediaStream: MediaStream | null = null;\n private _audioService: AudioService | null = null;\n private _dictationService: DictationService | null = null;\n private _visualiserInterval?: number;\n\n async initialize() {\n const deviceResponse = await getAudioDevices();\n this.devices = deviceResponse.devices;\n this.selectedDevice = deviceResponse.defaultDeviceId || '';\n return deviceResponse;\n }\n\n async startRecording() {\n this._updateRecordingState('initializing');\n try {\n this._mediaStream = await navigator.mediaDevices.getUserMedia({\n audio: { deviceId: this.selectedDevice },\n });\n this._audioService = new AudioService(this._mediaStream);\n this._dictationService = new DictationService(this._mediaStream);\n\n // Forward custom events from dictation service\n this._dictationService.addEventListener('audio-packet', e =>\n this.dispatchEvent(new CustomEvent('audio-packet', {\n detail: (e as CustomEvent).detail,\n bubbles: true,\n composed: true,\n }))\n );\n this._dictationService.addEventListener('transcript', e =>\n this.dispatchEvent(new CustomEvent('transcript', {\n detail: (e as CustomEvent).detail,\n bubbles: true,\n composed: true,\n }))\n );\n } catch (error) {\n console.error('Error getting user media:', error);\n this._updateRecordingState('stopped');\n return;\n }\n\n this._dictationService?.startRecording();\n this._updateRecordingState('recording');\n this._visualiserInterval = window.setInterval(() => {\n const level = this._audioService ? this._audioService.getAudioLevel() * 3 : 0;\n this.dispatchEvent(new CustomEvent('audio-level-changed', {\n detail: { audioLevel: level },\n bubbles: true,\n composed: true,\n }));\n }, 150);\n }\n\n async stopRecording() {\n this._updateRecordingState('stopping');\n if (this._visualiserInterval) {\n clearInterval(this._visualiserInterval);\n this._visualiserInterval = undefined;\n }\n this._dictationService?.stopRecording();\n if (this._mediaStream) {\n this._mediaStream.getTracks().forEach(track => track.stop());\n this._mediaStream = null;\n }\n this._updateRecordingState('stopped');\n }\n\n private _updateRecordingState(state: RecordingState) {\n this.recordingState = state;\n this.dispatchEvent(new CustomEvent('recording-state-changed', {\n detail: { state },\n bubbles: true,\n composed: true,\n }));\n }\n}\n"]}
@@ -0,0 +1,6 @@
1
+ export declare class AudioService {
2
+ private audioContext;
3
+ private analyser;
4
+ constructor(mediaStream: MediaStream);
5
+ getAudioLevel(): number;
6
+ }
@@ -0,0 +1,21 @@
1
+ export class AudioService {
2
+ constructor(mediaStream) {
3
+ this.audioContext = new AudioContext();
4
+ const source = this.audioContext.createMediaStreamSource(mediaStream);
5
+ this.analyser = this.audioContext.createAnalyser();
6
+ this.analyser.fftSize = 8192;
7
+ source.connect(this.analyser);
8
+ }
9
+ getAudioLevel() {
10
+ const bufferLength = this.analyser.fftSize;
11
+ const dataArray = new Uint8Array(bufferLength);
12
+ this.analyser.getByteTimeDomainData(dataArray);
13
+ let sum = 0;
14
+ for (let i = 0; i < bufferLength; i += 1) {
15
+ const normalized = (dataArray[i] - 128) / 128;
16
+ sum += normalized * normalized;
17
+ }
18
+ return Math.sqrt(sum / bufferLength);
19
+ }
20
+ }
21
+ //# sourceMappingURL=audioService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audioService.js","sourceRoot":"","sources":["../../src/audioService.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,YAAY;IAKvB,YAAY,WAAwB;QAClC,IAAI,CAAC,YAAY,GAAG,IAAI,YAAY,EAAE,CAAC;QACvC,MAAM,MAAM,GAAG,IAAI,CAAC,YAAY,CAAC,uBAAuB,CAAC,WAAW,CAAC,CAAC;QACtE,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,YAAY,CAAC,cAAc,EAAE,CAAC;QACnD,IAAI,CAAC,QAAQ,CAAC,OAAO,GAAG,IAAI,CAAC;QAC7B,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAChC,CAAC;IAEM,aAAa;QAClB,MAAM,YAAY,GAAG,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC;QAC3C,MAAM,SAAS,GAAG,IAAI,UAAU,CAAC,YAAY,CAAC,CAAC;QAC/C,IAAI,CAAC,QAAQ,CAAC,qBAAqB,CAAC,SAAS,CAAC,CAAC;QAC/C,IAAI,GAAG,GAAG,CAAC,CAAC;QACZ,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,YAAY,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;YACzC,MAAM,UAAU,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC;YAC9C,GAAG,IAAI,UAAU,GAAG,UAAU,CAAC;QACjC,CAAC;QACD,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,GAAG,YAAY,CAAC,CAAC;IACvC,CAAC;CACF","sourcesContent":["export class AudioService {\n private audioContext: AudioContext;\n\n private analyser: AnalyserNode;\n\n constructor(mediaStream: MediaStream) {\n this.audioContext = new AudioContext();\n const source = this.audioContext.createMediaStreamSource(mediaStream);\n this.analyser = this.audioContext.createAnalyser();\n this.analyser.fftSize = 8192;\n source.connect(this.analyser);\n }\n\n public getAudioLevel(): number {\n const bufferLength = this.analyser.fftSize;\n const dataArray = new Uint8Array(bufferLength);\n this.analyser.getByteTimeDomainData(dataArray);\n let sum = 0;\n for (let i = 0; i < bufferLength; i += 1) {\n const normalized = (dataArray[i] - 128) / 128;\n sum += normalized * normalized;\n }\n return Math.sqrt(sum / bufferLength);\n }\n}\n"]}
@@ -0,0 +1 @@
1
+ export declare const componentStyles: import("lit").CSSResult;
@@ -0,0 +1,51 @@
1
+ import { css } from 'lit';
2
+ export const componentStyles = css `
3
+ .wrapper {
4
+ background-color: var(--card-background);
5
+ border: 1px solid var(--card-border-color);
6
+ border-radius: var(--card-border-radius);
7
+ box-shadow: var(--card-box-shadow);
8
+ padding: var(--card-padding);
9
+ display: flex;
10
+ width: min-content;
11
+ gap: 4px;
12
+ height: 46px;
13
+ box-sizing: border-box;
14
+ overflow: hidden;
15
+ }
16
+ h2 {
17
+ margin: 0 0 10px;
18
+ font-size: 1rem;
19
+ font-weight: 500;
20
+ }
21
+ label {
22
+ font-size: 0.9rem;
23
+ margin-right: 8px;
24
+ }
25
+ select {
26
+ padding: 4px 6px;
27
+ font-size: 0.9rem;
28
+ border: 1px solid var(--card-border-color);
29
+ border-radius: 4px;
30
+ background-color: var(--card-background);
31
+ color: inherit;
32
+ }
33
+
34
+ .visualiser {
35
+ width: 16px;
36
+ height: 100%;
37
+ background: var(--visualiser-background);
38
+ margin-top: 8px;
39
+ border-radius: 5px;
40
+ overflow: hidden;
41
+ display: flex;
42
+ align-items: flex-end;
43
+ }
44
+ .level {
45
+ height: 100%;
46
+ width: 100%;
47
+ background: var(--visualiser-level-color);
48
+ transition: width 0.1s ease-in-out;
49
+ }
50
+ `;
51
+ //# sourceMappingURL=componentStyles.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"componentStyles.js","sourceRoot":"","sources":["../../src/componentStyles.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAE1B,MAAM,CAAC,MAAM,eAAe,GAAG,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgDjC,CAAC","sourcesContent":["import { css } from 'lit';\n\nexport const componentStyles = css`\n .wrapper {\n background-color: var(--card-background);\n border: 1px solid var(--card-border-color);\n border-radius: var(--card-border-radius);\n box-shadow: var(--card-box-shadow);\n padding: var(--card-padding);\n display: flex;\n width: min-content;\n gap: 4px;\n height: 46px;\n box-sizing: border-box;\n overflow: hidden;\n }\n h2 {\n margin: 0 0 10px;\n font-size: 1rem;\n font-weight: 500;\n }\n label {\n font-size: 0.9rem;\n margin-right: 8px;\n }\n select {\n padding: 4px 6px;\n font-size: 0.9rem;\n border: 1px solid var(--card-border-color);\n border-radius: 4px;\n background-color: var(--card-background);\n color: inherit;\n }\n\n .visualiser {\n width: 16px;\n height: 100%;\n background: var(--visualiser-background);\n margin-top: 8px;\n border-radius: 5px;\n overflow: hidden;\n display: flex;\n align-items: flex-end;\n }\n .level {\n height: 100%;\n width: 100%;\n background: var(--visualiser-level-color);\n transition: width 0.1s ease-in-out;\n }\n`;\n"]}
@@ -0,0 +1,12 @@
1
+ import { LitElement } from 'lit';
2
+ export declare class AudioVisualiser extends LitElement {
3
+ level: number;
4
+ active: boolean;
5
+ static styles: import("lit").CSSResult;
6
+ render(): import("lit-html").TemplateResult<1>;
7
+ }
8
+ declare global {
9
+ interface HTMLElementTagNameMap {
10
+ 'audio-visualiser': AudioVisualiser;
11
+ }
12
+ }
@@ -0,0 +1,60 @@
1
+ import { __decorate } from "tslib";
2
+ import { LitElement, html, css } from 'lit';
3
+ import { property, customElement } from 'lit/decorators.js';
4
+ let AudioVisualiser = class AudioVisualiser extends LitElement {
5
+ constructor() {
6
+ super(...arguments);
7
+ this.level = 0; // expects a value from 0 to 100
8
+ this.active = true;
9
+ }
10
+ render() {
11
+ // Each segment represents 20%. Using Math.ceil to fill segments optimistically.
12
+ const activeSegments = Math.round(this.level * 5);
13
+ const segments = [];
14
+ for (let i = 0; i < 5; i += 1) {
15
+ segments.push(html `
16
+ <div class="segment ${i < activeSegments ? 'active' : ''}"></div>
17
+ `);
18
+ }
19
+ return html `
20
+ <div class="container ${this.active ? 'active' : ''}">${segments}</div>
21
+ `;
22
+ }
23
+ };
24
+ AudioVisualiser.styles = css `
25
+ :host {
26
+ height: 100%;
27
+ }
28
+ .container {
29
+ display: flex;
30
+ width: 8px;
31
+ flex-direction: column-reverse; /* Bottom-up stacking */
32
+ height: 100%;
33
+ gap: 1px;
34
+ opacity: 0.5;
35
+ &.active {
36
+ opacity: 1;
37
+ }
38
+ }
39
+ .segment {
40
+ flex: 1;
41
+ background-color: var(--action-accent-text-color);
42
+ transition: background-color 0.25s;
43
+ border-radius: 1px;
44
+ opacity: 0.5;
45
+ }
46
+ .segment.active {
47
+ opacity: 1;
48
+ }
49
+ `;
50
+ __decorate([
51
+ property({ type: Number })
52
+ ], AudioVisualiser.prototype, "level", void 0);
53
+ __decorate([
54
+ property({ type: Boolean })
55
+ ], AudioVisualiser.prototype, "active", void 0);
56
+ AudioVisualiser = __decorate([
57
+ customElement('audio-visualiser')
58
+ ], AudioVisualiser);
59
+ export { AudioVisualiser };
60
+ //# sourceMappingURL=audio-visualiser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audio-visualiser.js","sourceRoot":"","sources":["../../../src/components/audio-visualiser.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAC5C,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAGrD,IAAM,eAAe,GAArB,MAAM,eAAgB,SAAQ,UAAU;IAAxC;;QACuB,UAAK,GAAG,CAAC,CAAC,CAAC,gCAAgC;QAE1C,WAAM,GAAG,IAAI,CAAC;IA0C7C,CAAC;IAbC,MAAM;QACJ,gFAAgF;QAChF,MAAM,cAAc,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;QAClD,MAAM,QAAQ,GAAG,EAAE,CAAC;QACpB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;YAC9B,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAA;8BACM,CAAC,GAAG,cAAc,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE;OACzD,CAAC,CAAC;QACL,CAAC;QACD,OAAO,IAAI,CAAA;8BACe,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,KAAK,QAAQ;KACjE,CAAC;IACJ,CAAC;;AAvCM,sBAAM,GAAG,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBlB,AAzBY,CAyBX;AA7B0B;IAA3B,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;8CAAW;AAET;IAA5B,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;+CAAe;AAHhC,eAAe;IAD3B,aAAa,CAAC,kBAAkB,CAAC;GACrB,eAAe,CA6C3B","sourcesContent":["import { LitElement, html, css } from 'lit';\nimport { property, customElement } from 'lit/decorators.js';\n\n@customElement('audio-visualiser')\nexport class AudioVisualiser extends LitElement {\n @property({ type: Number }) level = 0; // expects a value from 0 to 100\n\n @property({ type: Boolean }) active = true;\n\n static styles = css`\n :host {\n height: 100%;\n }\n .container {\n display: flex;\n width: 8px;\n flex-direction: column-reverse; /* Bottom-up stacking */\n height: 100%;\n gap: 1px;\n opacity: 0.5;\n &.active {\n opacity: 1;\n }\n }\n .segment {\n flex: 1;\n background-color: var(--action-accent-text-color);\n transition: background-color 0.25s;\n border-radius: 1px;\n opacity: 0.5;\n }\n .segment.active {\n opacity: 1;\n }\n `;\n\n render() {\n // Each segment represents 20%. Using Math.ceil to fill segments optimistically.\n const activeSegments = Math.round(this.level * 5);\n const segments = [];\n for (let i = 0; i < 5; i += 1) {\n segments.push(html`\n <div class=\"segment ${i < activeSegments ? 'active' : ''}\"></div>\n `);\n }\n return html`\n <div class=\"container ${this.active ? 'active' : ''}\">${segments}</div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'audio-visualiser': AudioVisualiser;\n }\n}\n"]}
@@ -0,0 +1,15 @@
1
+ import { LitElement, TemplateResult, CSSResultGroup } from 'lit';
2
+ export declare class SettingsMenu extends LitElement {
3
+ devices: MediaDeviceInfo[];
4
+ selectedDevice: string;
5
+ selectedLanguage: string;
6
+ settingsDisabled: boolean;
7
+ static styles: CSSResultGroup;
8
+ private _selectDevice;
9
+ render(): TemplateResult;
10
+ }
11
+ declare global {
12
+ interface HTMLElementTagNameMap {
13
+ 'settings-menu': SettingsMenu;
14
+ }
15
+ }
@@ -0,0 +1,148 @@
1
+ import { __decorate } from "tslib";
2
+ // mic-selector.ts
3
+ import { LitElement, html, css } from 'lit';
4
+ import { customElement, property } from 'lit/decorators.js';
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
+ let SettingsMenu = class SettingsMenu extends LitElement {
11
+ constructor() {
12
+ super(...arguments);
13
+ this.devices = [];
14
+ this.selectedDevice = '';
15
+ this.selectedLanguage = '';
16
+ this.settingsDisabled = false;
17
+ }
18
+ _selectDevice(deviceId) {
19
+ this.selectedDevice = deviceId;
20
+ // Find the device object
21
+ const device = this.devices.find(d => d.deviceId === deviceId);
22
+ this.dispatchEvent(new CustomEvent('recording-device-changed', {
23
+ detail: device,
24
+ bubbles: true,
25
+ composed: true,
26
+ }));
27
+ }
28
+ render() {
29
+ return html `
30
+ <div class="mic-selector">
31
+ <button id="settings-popover-button" popovertarget="settings-popover">
32
+ <icon-settings></icon-settings>
33
+ </button>
34
+ <div id="settings-popover" popover>
35
+ <div class="settings-wrapper">
36
+ ${this.settingsDisabled
37
+ ? html `
38
+ <div class="callout orange">
39
+ Recording is in progress. Stop recording to change settings.
40
+ </div>
41
+ `
42
+ : ''}
43
+ <div class="form-group">
44
+ <label id="device-select-label" for="device-select">
45
+ Recording Device
46
+ </label>
47
+ <select
48
+ id="device-select"
49
+ aria-labelledby="device-select-label"
50
+ @change=${(e) => {
51
+ this._selectDevice(e.target.value);
52
+ }}
53
+ ?disabled=${this.settingsDisabled}
54
+ >
55
+ ${this.devices.map(device => html `
56
+ <option
57
+ value=${device.deviceId}
58
+ ?selected=${this.selectedDevice === device.deviceId}
59
+ >
60
+ ${device.label || 'Unknown Device'}
61
+ </option>
62
+ `)}
63
+ </select>
64
+ </div>
65
+ <div class="form-group">
66
+ <label id="language-select-label" for="language-select">
67
+ Dictation Language
68
+ </label>
69
+ <select
70
+ id="language-select"
71
+ aria-labelledby="language-select-label"
72
+ @change=${(e) => {
73
+ this._selectDevice(e.target.value);
74
+ }}
75
+ ?disabled=${this.settingsDisabled}
76
+ >
77
+ ${LANGUAGES_SUPPORTED.map(language => html `
78
+ <option
79
+ value=${language}
80
+ ?selected=${this.selectedLanguage === language}
81
+ >
82
+ ${getLanguageName(language)}
83
+ </option>
84
+ `)}
85
+ </select>
86
+ </div>
87
+ </div>
88
+ </div>
89
+ </div>
90
+ `;
91
+ }
92
+ };
93
+ SettingsMenu.styles = [
94
+ css `
95
+ :host {
96
+ display: block;
97
+ font-family: var(--component-font-family);
98
+ }
99
+ /* Retain the anchor-name styling for this component */
100
+ #settings-popover-button {
101
+ anchor-name: --settings_popover_btn;
102
+ }
103
+ [popover] {
104
+ margin: 0;
105
+ padding: 16px;
106
+ border: 0;
107
+ background: var(--card-background);
108
+ border: 1px solid var(--card-border-color);
109
+ border-radius: var(--card-border-radius);
110
+ box-shadow: var(--card-box-shadow);
111
+ z-index: 1000;
112
+ max-width: 260px;
113
+ width: 100%;
114
+ min-width: 200px;
115
+ position-anchor: --settings_popover_btn;
116
+ position-area: bottom;
117
+ position-visibility: always;
118
+ /* inset: unset; */
119
+ transform: translateX(40%);
120
+ overflow-x: hidden;
121
+ }
122
+ .settings-wrapper {
123
+ display: flex;
124
+ flex-direction: column;
125
+ gap: 20px;
126
+ }
127
+ `,
128
+ ButtonStyles,
129
+ SelectStyles,
130
+ CalloutStyles,
131
+ ];
132
+ __decorate([
133
+ property({ type: Array })
134
+ ], SettingsMenu.prototype, "devices", void 0);
135
+ __decorate([
136
+ property({ type: String })
137
+ ], SettingsMenu.prototype, "selectedDevice", void 0);
138
+ __decorate([
139
+ property({ type: String })
140
+ ], SettingsMenu.prototype, "selectedLanguage", void 0);
141
+ __decorate([
142
+ property({ type: Boolean })
143
+ ], SettingsMenu.prototype, "settingsDisabled", void 0);
144
+ SettingsMenu = __decorate([
145
+ customElement('settings-menu')
146
+ ], SettingsMenu);
147
+ export { SettingsMenu };
148
+ //# sourceMappingURL=settings-menu.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settings-menu.js","sourceRoot":"","sources":["../../../src/components/settings-menu.ts"],"names":[],"mappings":";AAAA,kBAAkB;AAClB,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,GAAG,EAAkC,MAAM,KAAK,CAAC;AAC5E,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAE5D,OAAO,YAAY,MAAM,mBAAmB,CAAC;AAC7C,OAAO,YAAY,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAC3C,OAAO,aAAa,MAAM,mBAAmB,CAAC;AAGvC,IAAM,YAAY,GAAlB,MAAM,YAAa,SAAQ,UAAU;IAArC;;QAEL,YAAO,GAAsB,EAAE,CAAC;QAGhC,mBAAc,GAAW,EAAE,CAAC;QAG5B,qBAAgB,GAAW,EAAE,CAAC;QAG9B,qBAAgB,GAAY,KAAK,CAAC;IA2HpC,CAAC;IAjFS,aAAa,CAAC,QAAgB;QACpC,IAAI,CAAC,cAAc,GAAG,QAAQ,CAAC;QAC/B,yBAAyB;QACzB,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC;QAC/D,IAAI,CAAC,aAAa,CAChB,IAAI,WAAW,CAAC,0BAA0B,EAAE;YAC1C,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,IAAI;YACb,QAAQ,EAAE,IAAI;SACf,CAAC,CACH,CAAC;IACJ,CAAC;IAED,MAAM;QACJ,OAAO,IAAI,CAAA;;;;;;;cAOD,IAAI,CAAC,gBAAgB;YACrB,CAAC,CAAC,IAAI,CAAA;;;;iBAIH;YACH,CAAC,CAAC,EAAE;;;;;;;;0BAQQ,CAAC,CAAQ,EAAE,EAAE;YACrB,IAAI,CAAC,aAAa,CAAE,CAAC,CAAC,MAA4B,CAAC,KAAK,CAAC,CAAC;QAC5D,CAAC;4BACW,IAAI,CAAC,gBAAgB;;kBAE/B,IAAI,CAAC,OAAO,CAAC,GAAG,CAChB,MAAM,CAAC,EAAE,CAAC,IAAI,CAAA;;8BAEF,MAAM,CAAC,QAAQ;kCACX,IAAI,CAAC,cAAc,KAAK,MAAM,CAAC,QAAQ;;wBAEjD,MAAM,CAAC,KAAK,IAAI,gBAAgB;;mBAErC,CACF;;;;;;;;;;0BAUS,CAAC,CAAQ,EAAE,EAAE;YACrB,IAAI,CAAC,aAAa,CAAE,CAAC,CAAC,MAA4B,CAAC,KAAK,CAAC,CAAC;QAC5D,CAAC;4BACW,IAAI,CAAC,gBAAgB;;kBAE/B,mBAAmB,CAAC,GAAG,CACvB,QAAQ,CAAC,EAAE,CAAC,IAAI,CAAA;;8BAEJ,QAAQ;kCACJ,IAAI,CAAC,gBAAgB,KAAK,QAAQ;;wBAE5C,eAAe,CAAC,QAAQ,CAAC;;mBAE9B,CACF;;;;;;KAMZ,CAAC;IACJ,CAAC;;AAxHM,mBAAM,GAAmB;IAC9B,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAiCF;IACD,YAAY;IACZ,YAAY;IACZ,aAAa;CACd,AAtCY,CAsCX;AAjDF;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;6CACM;AAGhC;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;oDACC;AAG5B;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;sDACG;AAG9B;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;sDACM;AAXvB,YAAY;IADxB,aAAa,CAAC,eAAe,CAAC;GAClB,YAAY,CAsIxB","sourcesContent":["// mic-selector.ts\nimport { LitElement, html, css, TemplateResult, CSSResultGroup } from 'lit';\nimport { customElement, property } from 'lit/decorators.js';\n\nimport ButtonStyles from '../styles/buttons';\nimport SelectStyles from '../styles/select';\nimport { LANGUAGES_SUPPORTED } from '../constants';\nimport { getLanguageName } from '../utils';\nimport CalloutStyles from '../styles/callout';\n\n@customElement('settings-menu')\nexport class SettingsMenu extends LitElement {\n @property({ type: Array })\n devices: MediaDeviceInfo[] = [];\n\n @property({ type: String })\n selectedDevice: string = '';\n\n @property({ type: String })\n selectedLanguage: string = '';\n\n @property({ type: Boolean })\n settingsDisabled: boolean = false;\n\n static styles: CSSResultGroup = [\n css`\n :host {\n display: block;\n font-family: var(--component-font-family);\n }\n /* Retain the anchor-name styling for this component */\n #settings-popover-button {\n anchor-name: --settings_popover_btn;\n }\n [popover] {\n margin: 0;\n padding: 16px;\n border: 0;\n background: var(--card-background);\n border: 1px solid var(--card-border-color);\n border-radius: var(--card-border-radius);\n box-shadow: var(--card-box-shadow);\n z-index: 1000;\n max-width: 260px;\n width: 100%;\n min-width: 200px;\n position-anchor: --settings_popover_btn;\n position-area: bottom;\n position-visibility: always;\n /* inset: unset; */\n transform: translateX(40%);\n overflow-x: hidden;\n }\n .settings-wrapper {\n display: flex;\n flex-direction: column;\n gap: 20px;\n }\n `,\n ButtonStyles,\n SelectStyles,\n CalloutStyles,\n ];\n\n private _selectDevice(deviceId: string): void {\n this.selectedDevice = deviceId;\n // Find the device object\n const device = this.devices.find(d => d.deviceId === deviceId);\n this.dispatchEvent(\n new CustomEvent('recording-device-changed', {\n detail: device,\n bubbles: true,\n composed: true,\n }),\n );\n }\n\n render(): TemplateResult {\n return html`\n <div class=\"mic-selector\">\n <button id=\"settings-popover-button\" popovertarget=\"settings-popover\">\n <icon-settings></icon-settings>\n </button>\n <div id=\"settings-popover\" popover>\n <div class=\"settings-wrapper\">\n ${this.settingsDisabled\n ? html`\n <div class=\"callout orange\">\n Recording is in progress. Stop recording to change settings.\n </div>\n `\n : ''}\n <div class=\"form-group\">\n <label id=\"device-select-label\" for=\"device-select\">\n Recording Device\n </label>\n <select\n id=\"device-select\"\n aria-labelledby=\"device-select-label\"\n @change=${(e: Event) => {\n this._selectDevice((e.target as HTMLSelectElement).value);\n }}\n ?disabled=${this.settingsDisabled}\n >\n ${this.devices.map(\n device => html`\n <option\n value=${device.deviceId}\n ?selected=${this.selectedDevice === device.deviceId}\n >\n ${device.label || 'Unknown Device'}\n </option>\n `,\n )}\n </select>\n </div>\n <div class=\"form-group\">\n <label id=\"language-select-label\" for=\"language-select\">\n Dictation Language\n </label>\n <select\n id=\"language-select\"\n aria-labelledby=\"language-select-label\"\n @change=${(e: Event) => {\n this._selectDevice((e.target as HTMLSelectElement).value);\n }}\n ?disabled=${this.settingsDisabled}\n >\n ${LANGUAGES_SUPPORTED.map(\n language => html`\n <option\n value=${language}\n ?selected=${this.selectedLanguage === language}\n >\n ${getLanguageName(language)}\n </option>\n `,\n )}\n </select>\n </div>\n </div>\n </div>\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'settings-menu': SettingsMenu;\n }\n}\n"]}
@@ -0,0 +1,7 @@
1
+ import { LitElement } from 'lit-element';
2
+ export declare class Visualiser extends LitElement {
3
+ level: number;
4
+ active: boolean;
5
+ static styles: import("lit").CSSResult;
6
+ render(): import("lit-html").TemplateResult<1>;
7
+ }
@@ -0,0 +1,62 @@
1
+ import { __decorate } from "tslib";
2
+ import { LitElement, html, css } from 'lit-element';
3
+ import { property, customElement } from 'lit/decorators.js';
4
+ let Visualiser = class Visualiser extends LitElement {
5
+ constructor() {
6
+ super(...arguments);
7
+ this.level = 0; // expects a value from 0 to 100
8
+ this.active = true;
9
+ }
10
+ render() {
11
+ // Each segment represents 20%. Using Math.ceil to fill segments optimistically.
12
+ const activeSegments = Math.ceil((this.level / 100) * 5);
13
+ const segments = [];
14
+ for (let i = 0; i < 5; i++) {
15
+ segments.push(html `
16
+ <div class="segment ${i < activeSegments ? 'active' : ''}"></div>
17
+ `);
18
+ }
19
+ return html `
20
+ <div class="container">
21
+ ${segments}
22
+ </div>
23
+ `;
24
+ }
25
+ };
26
+ Visualiser.styles = css `
27
+ :host {
28
+ display: inline-block;
29
+ width: 16px;
30
+ height: 100%;
31
+ opacity: 1;
32
+ transition: opacity 0.3s;
33
+ }
34
+ :host([active="false"]) {
35
+ opacity: 0.4;
36
+ }
37
+ .container {
38
+ display: flex;
39
+ flex-direction: column-reverse; /* Bottom-up stacking */
40
+ height: 100%;
41
+ gap: 2px;
42
+ }
43
+ .segment {
44
+ flex: 1;
45
+ background-color: lightgray;
46
+ transition: background-color 0.3s;
47
+ }
48
+ .segment.active {
49
+ background-color: green;
50
+ }
51
+ `;
52
+ __decorate([
53
+ property({ type: Number })
54
+ ], Visualiser.prototype, "level", void 0);
55
+ __decorate([
56
+ property({ type: Boolean })
57
+ ], Visualiser.prototype, "active", void 0);
58
+ Visualiser = __decorate([
59
+ customElement('audio-visualiser')
60
+ ], Visualiser);
61
+ export { Visualiser };
62
+ //# sourceMappingURL=visualiser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"visualiser.js","sourceRoot":"","sources":["../../../src/components/visualiser.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,GAAG,EAAC,MAAM,aAAa,CAAC;AACnD,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAGrD,IAAM,UAAU,GAAhB,MAAM,UAAW,SAAQ,UAAU;IAAnC;;QACuB,UAAK,GAAG,CAAC,CAAC,CAAC,gCAAgC;QAC1C,WAAM,GAAG,IAAI,CAAC;IA4C7C,CAAC;IAfC,MAAM;QACJ,gFAAgF;QAChF,MAAM,cAAc,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,KAAK,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;QACzD,MAAM,QAAQ,GAAG,EAAE,CAAC;QACpB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;YAC3B,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAA;8BACM,CAAC,GAAG,cAAc,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE;OACzD,CAAC,CAAC;QACL,CAAC;QACD,OAAO,IAAI,CAAA;;UAEL,QAAQ;;KAEb,CAAC;IACJ,CAAC;;AAzCM,iBAAM,GAAG,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBlB,AAzBY,CAyBX;AA5B0B;IAA3B,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;yCAAW;AACT;IAA5B,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;0CAAe;AAFhC,UAAU;IADtB,aAAa,CAAC,kBAAkB,CAAC;GACrB,UAAU,CA8CtB","sourcesContent":["import { LitElement, html, css} from 'lit-element';\nimport { property, customElement } from 'lit/decorators.js';\n\n@customElement('audio-visualiser')\nexport class Visualiser extends LitElement {\n @property({ type: Number }) level = 0; // expects a value from 0 to 100\n @property({ type: Boolean }) active = true;\n\n static styles = css`\n :host {\n display: inline-block;\n width: 16px;\n height: 100%;\n opacity: 1;\n transition: opacity 0.3s;\n }\n :host([active=\"false\"]) {\n opacity: 0.4;\n }\n .container {\n display: flex;\n flex-direction: column-reverse; /* Bottom-up stacking */\n height: 100%;\n gap: 2px;\n }\n .segment {\n flex: 1;\n background-color: lightgray;\n transition: background-color 0.3s;\n }\n .segment.active {\n background-color: green;\n }\n `;\n\n render() {\n // Each segment represents 20%. Using Math.ceil to fill segments optimistically.\n const activeSegments = Math.ceil((this.level / 100) * 5);\n const segments = [];\n for (let i = 0; i < 5; i++) {\n segments.push(html`\n <div class=\"segment ${i < activeSegments ? 'active' : ''}\"></div>\n `);\n }\n return html`\n <div class=\"container\">\n ${segments}\n </div>\n `;\n }\n}\n"]}
@@ -0,0 +1,3 @@
1
+ import { DictationConfig } from './types';
2
+ export declare const LANGUAGES_SUPPORTED: string[];
3
+ export declare const DEFAULT_DICTATION_CONFIG: DictationConfig;
@@ -0,0 +1,9 @@
1
+ export const LANGUAGES_SUPPORTED = ['en', 'da'];
2
+ export const DEFAULT_DICTATION_CONFIG = {
3
+ primaryLanguage: 'en',
4
+ interimResults: true,
5
+ spokenPunctuation: true,
6
+ automaticPunctuation: true,
7
+ model: 'others',
8
+ };
9
+ //# sourceMappingURL=constants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.js","sourceRoot":"","sources":["../../src/constants.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;AAChD,MAAM,CAAC,MAAM,wBAAwB,GAAoB;IACvD,eAAe,EAAE,IAAI;IACrB,cAAc,EAAE,IAAI;IACpB,iBAAiB,EAAE,IAAI;IACvB,oBAAoB,EAAE,IAAI;IAC1B,KAAK,EAAE,QAAQ;CAChB,CAAC","sourcesContent":["import { DictationConfig } from './types';\n\nexport const LANGUAGES_SUPPORTED = ['en', 'da'];\nexport const DEFAULT_DICTATION_CONFIG: DictationConfig = {\n primaryLanguage: 'en',\n interimResults: true,\n spokenPunctuation: true,\n automaticPunctuation: true,\n model: 'others',\n};\n"]}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,3 @@
1
+ import CortiDictation from './index.js';
2
+ window.customElements.define('corti-dictation', CortiDictation);
3
+ //# sourceMappingURL=corti-dictation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"corti-dictation.js","sourceRoot":"","sources":["../../src/corti-dictation.ts"],"names":[],"mappings":"AAAA,OAAO,cAAc,MAAM,YAAY,CAAC;AAExC,MAAM,CAAC,cAAc,CAAC,MAAM,CAAC,iBAAiB,EAAE,cAAc,CAAC,CAAC","sourcesContent":["import CortiDictation from './index.js';\n\nwindow.customElements.define('corti-dictation', CortiDictation);\n"]}
@@ -0,0 +1,13 @@
1
+ import { DictationConfig, ServerConfig } from "./types";
2
+ export declare class DictationService extends EventTarget {
3
+ private mediaRecorder;
4
+ private webSocket;
5
+ private serverConfig;
6
+ private dictationConfig;
7
+ constructor(mediaStream: MediaStream, { dictationConfig, serverConfig }: {
8
+ dictationConfig: DictationConfig;
9
+ serverConfig: ServerConfig;
10
+ });
11
+ startRecording(): void;
12
+ stopRecording(): Promise<void>;
13
+ }