maidr 2.23.0 → 2.23.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/dist/maidr.js CHANGED
@@ -12,77 +12,77 @@ class Constants {
12
12
  * @memberof HtmlIds
13
13
  * @default 'chart-container'
14
14
  */
15
- chart_container_id = "chart-container";
15
+ chart_container_id = 'chart-container';
16
16
  /**
17
17
  * HTML id of the main container div.
18
18
  * @type {string}
19
19
  * @memberof HtmlIds
20
20
  * @default 'maidr-container'
21
21
  */
22
- main_container_id = "maidr-container";
22
+ main_container_id = 'maidr-container';
23
23
  /**
24
24
  * HTML id of the div containing the braille display input
25
25
  * @type {string}
26
26
  * @memberof HtmlIds
27
27
  * @default 'braille-div'
28
28
  */
29
- braille_container_id = "braille-div";
29
+ braille_container_id = 'braille-div';
30
30
  /**
31
31
  * HTML id of the actual braille input element.
32
32
  * @type {string}
33
33
  * @memberof HtmlIds
34
34
  * @default 'braille-input'
35
35
  */
36
- braille_input_id = "braille-input";
36
+ braille_input_id = 'braille-input';
37
37
  /**
38
38
  * HTML id of the div containing the info box.
39
39
  * @type {string}
40
40
  * @memberof HtmlIds
41
41
  * @default 'info'
42
42
  */
43
- info_id = "info";
43
+ info_id = 'info';
44
44
  /**
45
45
  * HTML id of the div containing announcements that hook directly into the screen reader via aria-live.
46
46
  * @type {string}
47
47
  * @memberof HtmlIds
48
48
  * @default 'announcements'
49
49
  */
50
- announcement_container_id = "announcements";
50
+ announcement_container_id = 'announcements';
51
51
  /**
52
52
  * HTML id of the div containing the end chime. To be implemented in the future.
53
53
  * @type {string}
54
54
  * @memberof HtmlIds
55
55
  * @default 'end_chime'
56
56
  */
57
- end_chime_id = "end_chime";
57
+ end_chime_id = 'end_chime';
58
58
  /**
59
59
  * HTML id of the main container div.
60
60
  * @type {string}
61
61
  * @memberof HtmlIds
62
62
  * @default 'container'
63
63
  */
64
- container_id = "container";
64
+ container_id = 'container';
65
65
  /**
66
66
  * The main project id, used throughout the application.
67
67
  * @type {string}
68
68
  * @memberof HtmlIds
69
69
  * @default 'maidr'
70
70
  */
71
- project_id = "maidr";
71
+ project_id = 'maidr';
72
72
  /**
73
73
  * HTML id of the div containing the review text.
74
74
  * @type {string}
75
75
  * @memberof HtmlIds
76
76
  * @default 'review_container'
77
77
  */
78
- review_id_container = "review_container";
78
+ review_id_container = 'review_container';
79
79
  /**
80
80
  * HTML id of the review input element.
81
81
  * @type {string}
82
82
  * @memberof HtmlIds
83
83
  * @default 'review'
84
84
  */
85
- review_id = "review";
85
+ review_id = 'review';
86
86
  /**
87
87
  * Storage element, used to store the last focused element before moving to the review input so we can switch back to it easily.
88
88
  * @type {HTMLElement}
@@ -99,7 +99,7 @@ class Constants {
99
99
  * @type {string}
100
100
  * @memberof HtmlIds
101
101
  */
102
- chartId = "";
102
+ chartId = '';
103
103
  /**
104
104
  * @typedef {Object} EventListenerSetupObject
105
105
  * @property {HTMLElement} element - The element to attach the event listener to.
@@ -128,7 +128,7 @@ class Constants {
128
128
  * @memberof BTSModes
129
129
  * @default 'verbose'
130
130
  */
131
- textMode = "verbose";
131
+ textMode = 'verbose';
132
132
 
133
133
  /**
134
134
  * The current braille mode. Can be 'off' or 'on'.
@@ -136,7 +136,7 @@ class Constants {
136
136
  * @memberof BTSModes
137
137
  * @default 'off'
138
138
  */
139
- brailleMode = "off";
139
+ brailleMode = 'off';
140
140
 
141
141
  /**
142
142
  * We lock the selection so we don't pick up programatic selection changes
@@ -151,14 +151,14 @@ class Constants {
151
151
  * @memberof BTSModes
152
152
  * @default 'on'
153
153
  */
154
- sonifMode = "on";
154
+ sonifMode = 'on';
155
155
  /**
156
156
  * The current review mode. Can be 'on' or 'off'.
157
157
  * @type {("on"|"off")}
158
158
  * @memberof BTSModes
159
159
  * @default 'off'
160
160
  */
161
- reviewMode = "off";
161
+ reviewMode = 'off';
162
162
 
163
163
  // basic chart properties
164
164
  /**
@@ -199,14 +199,14 @@ class Constants {
199
199
  * @memberof HtmlIds
200
200
  * @default ''
201
201
  */
202
- plotId = ""; // update with id in chart specific js
202
+ plotId = ''; // update with id in chart specific js
203
203
  /**
204
204
  * The chart type, sort of a short name of the chart such as 'box', 'bar', 'line', etc.
205
205
  * @type {string}
206
206
  * @default ''
207
207
  * @memberof BasicChartProperties
208
208
  */
209
- chartType = "";
209
+ chartType = '';
210
210
  /**
211
211
  * The navigation orientation of the chart. 0 = row navigation (up/down), 1 = col navigation (left/right).
212
212
  * @type {number}
@@ -220,7 +220,7 @@ class Constants {
220
220
  * @default 'horz'
221
221
  * @memberof BasicChartProperties
222
222
  */
223
- plotOrientation = "horz";
223
+ plotOrientation = 'horz';
224
224
 
225
225
  /**
226
226
  * @namespace AudioProperties
@@ -330,7 +330,7 @@ class Constants {
330
330
  * @default '#03C809' (green)
331
331
  * @memberof UserSettings
332
332
  */
333
- colorSelected = "#03C809";
333
+ colorSelected = '#03C809';
334
334
  /**
335
335
  * The length of the braille display in characters. Braille displays have a variety of sizes; 40 is pretty common, 32 is quite reliable. Set this to your actual display length so that the system can scale and display braille properly for you (where possible).
336
336
  * @type {number}
@@ -398,7 +398,7 @@ class Constants {
398
398
  * @default 50
399
399
  * @memberof AdvancedUserSettings
400
400
  */
401
- colorUnselected = "#595959"; // deprecated, todo: find all instances replace with storing old color method
401
+ colorUnselected = '#595959'; // deprecated, todo: find all instances replace with storing old color method
402
402
  /**
403
403
  * Whether or not we're logging user data. This is off by default, but is used for research purposes.
404
404
  * @type {boolean}
@@ -419,33 +419,34 @@ class Constants {
419
419
  * @default 'assertive'
420
420
  * @memberof AdvancedUserSettings
421
421
  */
422
- ariaMode = "assertive";
422
+ ariaMode = 'assertive';
423
423
 
424
424
  /**
425
425
  * Full list of user settings, used internally to save and load settings.
426
426
  * @type {string[]}
427
427
  */
428
428
  userSettingsKeys = [
429
- "vol",
430
- "autoPlayRate",
431
- "brailleDisplayLength",
432
- "colorSelected",
433
- "MIN_FREQUENCY",
434
- "MAX_FREQUENCY",
435
- "AUTOPLAY_DURATION",
436
- "ariaMode",
437
- "openAIAuthKey",
438
- "geminiAuthKey",
439
- "claudeAuthKey",
440
- "emailAuthKey",
441
- "skillLevel",
442
- "skillLevelOther",
443
- "LLMModel",
444
- "LLMPreferences",
445
- "LLMOpenAiMulti",
446
- "LLMGeminiMulti",
447
- "LLMModels",
448
- "autoInitLLM",
429
+ 'vol',
430
+ 'autoPlayRate',
431
+ 'brailleDisplayLength',
432
+ 'colorSelected',
433
+ 'MIN_FREQUENCY',
434
+ 'MAX_FREQUENCY',
435
+ 'AUTOPLAY_DURATION',
436
+ 'ariaMode',
437
+ 'openAIAuthKey',
438
+ 'geminiAuthKey',
439
+ 'claudeAuthKey',
440
+ 'emailAuthKey',
441
+ 'skillLevel',
442
+ 'skillLevelOther',
443
+ 'LLMModel',
444
+ 'LLMPreferences',
445
+ 'LLMOpenAiMulti',
446
+ 'LLMGeminiMulti',
447
+ 'LLMModels',
448
+ 'autoInitLLM',
449
+ 'clientToken',
449
450
  ];
450
451
 
451
452
  // LLM settings
@@ -486,14 +487,14 @@ class Constants {
486
487
  * @default 'high'
487
488
  * @memberof LLMSettings
488
489
  */
489
- LLMDetail = "high"; // low (default for testing, like 100 tokens) / high (default for real, like 1000 tokens)
490
+ LLMDetail = 'high'; // low (default for testing, like 100 tokens) / high (default for real, like 1000 tokens)
490
491
  /**
491
492
  * Current LLM model in use. Can be 'openai' (default) or 'gemini' or 'multi'. More to be added.
492
493
  * @type {("openai"|"gemini"|"multi")}
493
494
  * @default 'openai'
494
495
  * @memberof LLMSettings
495
496
  */
496
- LLMModel = "openai";
497
+ LLMModel = 'openai';
497
498
  /**
498
499
  * Current LLM model in use. Can be 'openai' (default) or 'gemini' or 'claude'. More to be added.
499
500
  * @type {("openai"|"gemini"|"claude")}
@@ -508,21 +509,21 @@ class Constants {
508
509
  * @memberof LLMSettings
509
510
  */
510
511
  LLMSystemMessage =
511
- "You are a helpful assistant describing the chart to a blind person. ";
512
+ 'You are a helpful assistant describing the chart to a blind person. ';
512
513
  /**
513
514
  * The level of skill the user has with statistical charts. Can be 'basic', 'intermediate', 'expert', or 'other'. This is passed to the LLM on the initial message to help it speak correctly to the user. If 'other' is selected, the user can provide a custom skill level.
514
515
  * @type {("basic"|"intermediate"|"expert"|"other")}
515
516
  * @default 'basic'
516
517
  * @memberof LLMSettings
517
518
  */
518
- skillLevel = "basic"; // basic / intermediate / expert
519
+ skillLevel = 'basic'; // basic / intermediate / expert
519
520
  /**
520
521
  * Custom skill level, used if the user selects 'other' as their skill level.
521
522
  * @type {string}
522
523
  * @default ''
523
524
  * @memberof LLMSettings
524
525
  */
525
- skillLevelOther = ""; // custom skill level
526
+ skillLevelOther = ''; // custom skill level
526
527
  /**
527
528
  * The LLM can send the first default message containing the chart image on initialization, so when the user opens the chat window the LLM already has an initial response and is ready for a conversation.
528
529
  * @type {boolean}
@@ -536,7 +537,7 @@ class Constants {
536
537
  * @default ''
537
538
  * @memberof LLMSettings
538
539
  */
539
- verboseText = "";
540
+ verboseText = '';
540
541
  /**
541
542
  * An internal variable used to turn the waiting beep on and off.
542
543
  * @type {number}
@@ -584,31 +585,31 @@ class Constants {
584
585
  * @type {boolean}
585
586
  * @memberof PlatformControls
586
587
  */
587
- isMac = navigator.userAgent.toLowerCase().includes("mac"); // true if macOS
588
+ isMac = navigator.userAgent.toLowerCase().includes('mac'); // true if macOS
588
589
  /**
589
590
  * The control key for the user's platform. Can be 'Cmd' or 'Ctrl'. Used in keyboard shortcut display in help.
590
591
  * @type {"Cmd"|"Ctrl"}
591
592
  * @memberof PlatformControls
592
593
  */
593
- control = this.isMac ? "Cmd" : "Ctrl";
594
+ control = this.isMac ? 'Cmd' : 'Ctrl';
594
595
  /**
595
596
  * The alt key for the user's platform. Can be 'option' or 'Alt'. Used in keyboard shortcut display in help.
596
597
  * @type {"option"|"Alt"}
597
598
  * @memberof PlatformControls
598
599
  */
599
- alt = this.isMac ? "option" : "Alt";
600
+ alt = this.isMac ? 'option' : 'Alt';
600
601
  /**
601
602
  * The home key for the user's platform. Can be 'fn + Left arrow' or 'Home'. Used in keyboard shortcut display in help.
602
603
  * @type {"fn + Left arrow"|"Home"}
603
604
  * @memberof PlatformControls
604
605
  */
605
- home = this.isMac ? "fn + Left arrow" : "Home";
606
+ home = this.isMac ? 'fn + Left arrow' : 'Home';
606
607
  /**
607
608
  * The end key for the user's platform. Can be 'fn + Right arrow' or 'End'. Used in keyboard shortcut display in help.
608
609
  * @type {"fn + Right arrow"|"End"}
609
610
  * @memberof PlatformControls
610
611
  */
611
- end = this.isMac ? "fn + Right arrow" : "End";
612
+ end = this.isMac ? 'fn + Right arrow' : 'End';
612
613
  /**
613
614
  * The interval we wait for an L + X prefix event
614
615
  */
@@ -644,6 +645,17 @@ class Constants {
644
645
  */
645
646
  manualData = true; // pull from manual data like chart2music (true), or do the old method where we pull from the chart (false)
646
647
 
648
+ /**
649
+ * Base URL for the API calls to backend services.
650
+ */
651
+ // baseURL = 'https://maidr-service.azurewebsites.net/api/';
652
+ baseURL = 'http://localhost:7071/api/';
653
+
654
+ // code = '?code=I8Aa2PlPspjQ8Hks0QzGyszP8_i2-XJ3bq7Xh8-ykEe4AzFuYn_QWA%3D%3D';
655
+ code = '';
656
+
657
+ clientToken = null;
658
+
647
659
  /**
648
660
  * Stops the autoplay if it is currently running.
649
661
  *
@@ -704,13 +716,13 @@ class Constants {
704
716
  */
705
717
  ConvertHexToRGBString(hexColorString) {
706
718
  return (
707
- "rgb(" +
719
+ 'rgb(' +
708
720
  parseInt(hexColorString.slice(1, 3), 16) +
709
- "," +
721
+ ',' +
710
722
  parseInt(hexColorString.slice(3, 5), 16) +
711
- "," +
723
+ ',' +
712
724
  parseInt(hexColorString.slice(5, 7), 16) +
713
- ")"
725
+ ')'
714
726
  );
715
727
  }
716
728
 
@@ -720,12 +732,12 @@ class Constants {
720
732
  * @returns {string} - hexadecimal color (e.g., "#595959").
721
733
  */
722
734
  ConvertRGBStringToHex(rgbColorString) {
723
- let rgb = rgbColorString.replace(/[^\d,]/g, "").split(",");
735
+ let rgb = rgbColorString.replace(/[^\d,]/g, '').split(',');
724
736
  return (
725
- "#" +
726
- rgb[0].toString(16).padStart(2, "0") +
727
- rgb[1].toString(16).padStart(2, "0") +
728
- rgb[2].toString(16).padStart(2, "0")
737
+ '#' +
738
+ rgb[0].toString(16).padStart(2, '0') +
739
+ rgb[1].toString(16).padStart(2, '0') +
740
+ rgb[2].toString(16).padStart(2, '0')
729
741
  );
730
742
  }
731
743
 
@@ -737,11 +749,11 @@ class Constants {
737
749
  */
738
750
  ColorInvert(color) {
739
751
  // invert an rgb color
740
- let rgb = color.replace(/[^\d,]/g, "").split(",");
752
+ let rgb = color.replace(/[^\d,]/g, '').split(',');
741
753
  let r = 255 - rgb[0];
742
754
  let g = 255 - rgb[1];
743
755
  let b = 255 - rgb[2];
744
- return "rgb(" + r + "," + g + "," + b + ")";
756
+ return 'rgb(' + r + ',' + g + ',' + b + ')';
745
757
  }
746
758
 
747
759
  /**
@@ -750,11 +762,11 @@ class Constants {
750
762
  * @returns {string} The better color
751
763
  */
752
764
  GetBetterColor(oldColor) {
753
- if (oldColor.indexOf("#") !== -1) {
765
+ if (oldColor.indexOf('#') !== -1) {
754
766
  oldColor = this.ConvertHexToRGBString(oldColor);
755
767
  }
756
768
  let newColor = this.ColorInvert(oldColor);
757
- let rgb = newColor.replace(/[^\d,]/g, "").split(",");
769
+ let rgb = newColor.replace(/[^\d,]/g, '').split(',');
758
770
  if (
759
771
  rgb[1] < rgb[0] + 10 &&
760
772
  rgb[1] > rgb[0] - 10 &&
@@ -776,7 +788,7 @@ class Constants {
776
788
  */
777
789
  GetStyleArrayFromString(styleString) {
778
790
  // Get an array of CSS style attributes and values from a style string
779
- return styleString.replaceAll(" ", "").split(/[:;]/);
791
+ return styleString.replaceAll(' ', '').split(/[:;]/);
780
792
  }
781
793
 
782
794
  /**
@@ -786,16 +798,16 @@ class Constants {
786
798
  */
787
799
  GetStyleStringFromArray(styleArray) {
788
800
  // Get CSS style string from an array of style attributes and values
789
- let styleString = "";
801
+ let styleString = '';
790
802
  for (let i = 0; i < styleArray.length; i++) {
791
803
  if (i % 2 === 0) {
792
804
  if (i !== styleArray.length - 1) {
793
- styleString += styleArray[i] + ": ";
805
+ styleString += styleArray[i] + ': ';
794
806
  } else {
795
807
  styleString += styleArray[i];
796
808
  }
797
809
  } else {
798
- styleString += styleArray[i] + "; ";
810
+ styleString += styleArray[i] + '; ';
799
811
  }
800
812
  }
801
813
  return styleString;
@@ -808,36 +820,36 @@ class Constants {
808
820
  class Resources {
809
821
  constructor() {}
810
822
 
811
- language = "en"; // Current language, 2 char lang code
812
- knowledgeLevel = "basic"; // basic, intermediate, expert
823
+ language = 'en'; // Current language, 2 char lang code
824
+ knowledgeLevel = 'basic'; // basic, intermediate, expert
813
825
 
814
826
  // language strings, per 2 char language code
815
827
  strings = {
816
828
  en: {
817
829
  basic: {
818
- upper_outlier: "Upper Outlier",
819
- lower_outlier: "Lower Outlier",
820
- min: "Minimum",
821
- max: "Maximum",
822
- 25: "25%",
823
- 50: "50%",
824
- 75: "75%",
825
- q1: "25%",
826
- q2: "50%",
827
- q3: "75%",
828
- son_on: "Sonification on",
829
- son_off: "Sonification off",
830
- son_des: "Sonification descrete",
831
- son_comp: "Sonification compare",
832
- son_ch: "Sonification chord",
833
- son_sep: "Sonification separate",
834
- son_same: "Sonification combined",
835
- empty: "Empty",
836
- openai: "OpenAI Vision",
837
- gemini: "Gemini Pro Vision",
838
- claude: "Claude",
839
- multi: "Multiple AI",
840
- processing: "Processing Chart...",
830
+ upper_outlier: 'Upper Outlier',
831
+ lower_outlier: 'Lower Outlier',
832
+ min: 'Minimum',
833
+ max: 'Maximum',
834
+ 25: '25%',
835
+ 50: '50%',
836
+ 75: '75%',
837
+ q1: '25%',
838
+ q2: '50%',
839
+ q3: '75%',
840
+ son_on: 'Sonification on',
841
+ son_off: 'Sonification off',
842
+ son_des: 'Sonification descrete',
843
+ son_comp: 'Sonification compare',
844
+ son_ch: 'Sonification chord',
845
+ son_sep: 'Sonification separate',
846
+ son_same: 'Sonification combined',
847
+ empty: 'Empty',
848
+ openai: 'OpenAI Vision',
849
+ gemini: 'Gemini Pro Vision',
850
+ claude: 'Claude',
851
+ multi: 'Multiple AI',
852
+ processing: 'Processing Chart...',
841
853
  },
842
854
  },
843
855
  };
@@ -1007,12 +1019,12 @@ class Menu {
1007
1019
  <div><fieldset>
1008
1020
  <legend>Aria Mode</legend>
1009
1021
  <p><input type="radio" id="aria_mode_assertive" name="aria_mode" value="assertive" ${
1010
- constants.ariaMode == "assertive"
1011
- ? "checked"
1012
- : ""
1022
+ constants.ariaMode == 'assertive'
1023
+ ? 'checked'
1024
+ : ''
1013
1025
  }><label for="aria_mode_assertive">Assertive</label></p>
1014
1026
  <p><input type="radio" id="aria_mode_polite" name="aria_mode" value="polite" ${
1015
- constants.ariaMode == "polite" ? "checked" : ""
1027
+ constants.ariaMode == 'polite' ? 'checked' : ''
1016
1028
  }><label for="aria_mode_polite">Polite</label></p>
1017
1029
  </fieldset></div>
1018
1030
  <p class="hidden">
@@ -1051,7 +1063,7 @@ class Menu {
1051
1063
  <input type="password" size="50" id="claude_auth_key"><button aria-label="Delete Claude key" title="Delete Claude key" id="delete_claude_key" class="invis_button">&times;</button><label for="claude_auth_key">Claude Authentication Key</label>
1052
1064
  </p>
1053
1065
  <p><input type="checkbox" ${
1054
- constants.autoInitLLM ? "checked" : ""
1066
+ constants.autoInitLLM ? 'checked' : ''
1055
1067
  } id="init_llm_on_load" name="init_llm_on_load"><label for="init_llm_on_load">Start LLM right away</label></p>
1056
1068
  <p>
1057
1069
  <select id="skill_level">
@@ -1088,40 +1100,40 @@ class Menu {
1088
1100
  CreateMenu() {
1089
1101
  // menu element creation
1090
1102
  document
1091
- .querySelector("body")
1092
- .insertAdjacentHTML("beforeend", this.menuHtml);
1103
+ .querySelector('body')
1104
+ .insertAdjacentHTML('beforeend', this.menuHtml);
1093
1105
 
1094
1106
  // menu close events
1095
- let allClose = document.querySelectorAll("#close_menu, #menu .close");
1107
+ let allClose = document.querySelectorAll('#close_menu, #menu .close');
1096
1108
  for (let i = 0; i < allClose.length; i++) {
1097
1109
  constants.events.push([
1098
1110
  allClose[i],
1099
- "click",
1111
+ 'click',
1100
1112
  function (e) {
1101
1113
  menu.Toggle(false);
1102
1114
  },
1103
1115
  ]);
1104
1116
  }
1105
1117
  constants.events.push([
1106
- document.getElementById("save_and_close_menu"),
1107
- "click",
1118
+ document.getElementById('save_and_close_menu'),
1119
+ 'click',
1108
1120
  function (e) {
1109
1121
  menu.SaveData();
1110
1122
  menu.Toggle(false);
1111
1123
  },
1112
1124
  ]);
1113
1125
  constants.events.push([
1114
- document.getElementById("verify"),
1115
- "click",
1126
+ document.getElementById('verify'),
1127
+ 'click',
1116
1128
  function (e) {
1117
1129
  menu.VerifyEmail();
1118
1130
  },
1119
1131
  ]);
1120
1132
  constants.events.push([
1121
- document.getElementById("menu"),
1122
- "keyup",
1133
+ document.getElementById('menu'),
1134
+ 'keyup',
1123
1135
  function (e) {
1124
- if (e.key == "Esc") {
1136
+ if (e.key == 'Esc') {
1125
1137
  // esc
1126
1138
  menu.Toggle(false);
1127
1139
  }
@@ -1131,15 +1143,15 @@ class Menu {
1131
1143
  // Menu open events
1132
1144
  constants.events.push([
1133
1145
  document,
1134
- "keyup",
1146
+ 'keyup',
1135
1147
  function (e) {
1136
1148
  // don't fire on input elements
1137
1149
  if (
1138
- e.target.tagName.toLowerCase() == "input" ||
1139
- e.target.tagName.toLowerCase() == "textarea"
1150
+ e.target.tagName.toLowerCase() == 'input' ||
1151
+ e.target.tagName.toLowerCase() == 'textarea'
1140
1152
  ) {
1141
1153
  return;
1142
- } else if (e.key == "h") {
1154
+ } else if (e.key == 'h') {
1143
1155
  menu.Toggle(true);
1144
1156
  }
1145
1157
  },
@@ -1147,119 +1159,135 @@ class Menu {
1147
1159
 
1148
1160
  // toggle auth key fields
1149
1161
  constants.events.push([
1150
- document.getElementById("LLM_model"),
1151
- "change",
1162
+ document.getElementById('LLM_model'),
1163
+ 'change',
1152
1164
  function (e) {
1153
- if (e.target.value == "openai") {
1165
+ if (e.target.value == 'openai') {
1154
1166
  document
1155
- .getElementById("openai_auth_key_container")
1156
- .classList.remove("hidden");
1167
+ .getElementById('openai_auth_key_container')
1168
+ .classList.remove('hidden');
1157
1169
  document
1158
- .getElementById("gemini_auth_key_container")
1159
- .classList.add("hidden");
1170
+ .getElementById('gemini_auth_key_container')
1171
+ .classList.add('hidden');
1160
1172
  document
1161
- .getElementById("openai_multi_container")
1162
- .classList.add("hidden");
1173
+ .getElementById('openai_multi_container')
1174
+ .classList.add('hidden');
1163
1175
  document
1164
- .getElementById("gemini_multi_container")
1165
- .classList.add("hidden");
1166
- document.getElementById("openai_multi").checked = true;
1167
- document.getElementById("gemini_multi").checked = false;
1168
- } else if (e.target.value == "gemini") {
1176
+ .getElementById('gemini_multi_container')
1177
+ .classList.add('hidden');
1178
+ document.getElementById('openai_multi').checked = true;
1179
+ document.getElementById('gemini_multi').checked = false;
1180
+ } else if (e.target.value == 'gemini') {
1169
1181
  document
1170
- .getElementById("openai_auth_key_container")
1171
- .classList.add("hidden");
1182
+ .getElementById('openai_auth_key_container')
1183
+ .classList.add('hidden');
1172
1184
  document
1173
- .getElementById("gemini_auth_key_container")
1174
- .classList.remove("hidden");
1185
+ .getElementById('gemini_auth_key_container')
1186
+ .classList.remove('hidden');
1175
1187
  document
1176
- .getElementById("openai_multi_container")
1177
- .classList.add("hidden");
1188
+ .getElementById('openai_multi_container')
1189
+ .classList.add('hidden');
1178
1190
  document
1179
- .getElementById("gemini_multi_container")
1180
- .classList.add("hidden");
1181
- document.getElementById("openai_multi").checked = false;
1182
- document.getElementById("gemini_multi").checked = true;
1183
- } else if (e.target.value == "multi") {
1191
+ .getElementById('gemini_multi_container')
1192
+ .classList.add('hidden');
1193
+ document.getElementById('openai_multi').checked = false;
1194
+ document.getElementById('gemini_multi').checked = true;
1195
+ } else if (e.target.value == 'multi') {
1184
1196
  document
1185
- .getElementById("openai_auth_key_container")
1186
- .classList.remove("hidden");
1197
+ .getElementById('openai_auth_key_container')
1198
+ .classList.remove('hidden');
1187
1199
  document
1188
- .getElementById("gemini_auth_key_container")
1189
- .classList.remove("hidden");
1200
+ .getElementById('gemini_auth_key_container')
1201
+ .classList.remove('hidden');
1190
1202
  document
1191
- .getElementById("openai_multi_container")
1192
- .classList.remove("hidden");
1203
+ .getElementById('openai_multi_container')
1204
+ .classList.remove('hidden');
1193
1205
  document
1194
- .getElementById("gemini_multi_container")
1195
- .classList.remove("hidden");
1196
- document.getElementById("openai_multi").checked = true;
1197
- document.getElementById("gemini_multi").checked = true;
1206
+ .getElementById('gemini_multi_container')
1207
+ .classList.remove('hidden');
1208
+ document.getElementById('openai_multi').checked = true;
1209
+ document.getElementById('gemini_multi').checked = true;
1198
1210
  }
1199
1211
  },
1200
1212
  ]);
1201
1213
 
1202
1214
  constants.events.push([
1203
- document.getElementById("LLM_model_openai"),
1204
- "change",
1215
+ document.getElementById('LLM_model_openai'),
1216
+ 'change',
1205
1217
  function (e) {
1206
- if (e.target.checked) {
1207
- document
1208
- .getElementById("openai_auth_key_container")
1209
- .classList.remove("hidden");
1218
+ if (constants.clientToken && constants.emailAuthKey) {
1219
+ console.log('Client token and email auth key already set');
1210
1220
  } else {
1211
- document
1212
- .getElementById("openai_auth_key_container")
1213
- .classList.add("hidden");
1221
+ if (e.target.checked) {
1222
+ document
1223
+ .getElementById('openai_auth_key_container')
1224
+ .classList.remove('hidden');
1225
+ } else {
1226
+ document
1227
+ .getElementById('openai_auth_key_container')
1228
+ .classList.add('hidden');
1229
+ }
1214
1230
  }
1215
1231
  },
1216
1232
  ]);
1217
1233
 
1218
1234
  constants.events.push([
1219
- document.getElementById("LLM_model_gemini"),
1220
- "change",
1235
+ document.getElementById('LLM_model_gemini'),
1236
+ 'change',
1221
1237
  function (e) {
1222
- if (e.target.checked) {
1223
- document
1224
- .getElementById("gemini_auth_key_container")
1225
- .classList.remove("hidden");
1238
+ if (constants.clientToken && constants.emailAuthKey) {
1239
+ console.log('Client token and email auth key already set');
1226
1240
  } else {
1227
- document
1228
- .getElementById("gemini_auth_key_container")
1229
- .classList.add("hidden");
1241
+ if (e.target.checked) {
1242
+ document
1243
+ .getElementById('gemini_auth_key_container')
1244
+ .classList.remove('hidden');
1245
+ } else {
1246
+ document
1247
+ .getElementById('gemini_auth_key_container')
1248
+ .classList.add('hidden');
1249
+ }
1230
1250
  }
1231
1251
  },
1232
1252
  ]);
1233
1253
 
1234
1254
  constants.events.push([
1235
- document.getElementById("LLM_model_claude"),
1236
- "change",
1255
+ document.getElementById('LLM_model_claude'),
1256
+ 'change',
1237
1257
  function (e) {
1238
- // if (e.target.checked) {
1239
- document
1240
- .getElementById("claude_auth_key_container")
1241
- .classList.add("hidden");
1242
- // } else {
1243
- // document
1244
- // .getElementById('claude_auth_key_container')
1245
- // .classList.add('hidden');
1246
- // }
1258
+ if (constants.clientToken && constants.emailAuthKey) {
1259
+ console.log('Client token and email auth key already set');
1260
+ } else {
1261
+ document
1262
+ .getElementById('claude_auth_key_container')
1263
+ .classList.add('hidden');
1264
+ }
1247
1265
  },
1248
1266
  ]);
1249
1267
 
1268
+ constants.events.push([
1269
+ document
1270
+ .getElementById('email_auth_key')
1271
+ .addEventListener('keydown', function (event) {
1272
+ if (event.key === 'Enter') {
1273
+ document.getElementById('verify').click();
1274
+ }
1275
+ }),
1276
+ ]);
1277
+
1250
1278
  // Skill level other events
1251
1279
  constants.events.push([
1252
- document.getElementById("skill_level"),
1253
- "change",
1280
+ document.getElementById('skill_level'),
1281
+ 'change',
1254
1282
  function (e) {
1255
- if (e.target.value == "other") {
1283
+ if (e.target.value == 'other') {
1256
1284
  document
1257
- .getElementById("skill_level_other_container")
1258
- .classList.remove("hidden");
1285
+ .getElementById('skill_level_other_container')
1286
+ .classList.remove('hidden');
1259
1287
  } else {
1260
1288
  document
1261
- .getElementById("skill_level_other_container")
1262
- .classList.add("hidden");
1289
+ .getElementById('skill_level_other_container')
1290
+ .classList.add('hidden');
1263
1291
  }
1264
1292
  },
1265
1293
  ]);
@@ -1267,16 +1295,16 @@ class Menu {
1267
1295
  // trigger notification that LLM will be reset
1268
1296
  // this is done on change of LLM model, multi settings, or skill level
1269
1297
  let LLMResetIds = [
1270
- "LLM_model",
1271
- "openai_multi",
1272
- "gemini_multi",
1273
- "skill_level",
1274
- "LLM_preferences",
1298
+ 'LLM_model',
1299
+ 'openai_multi',
1300
+ 'gemini_multi',
1301
+ 'skill_level',
1302
+ 'LLM_preferences',
1275
1303
  ];
1276
1304
  for (let i = 0; i < LLMResetIds.length; i++) {
1277
1305
  constants.events.push([
1278
1306
  document.getElementById(LLMResetIds[i]),
1279
- "change",
1307
+ 'change',
1280
1308
  function (e) {
1281
1309
  menu.NotifyOfLLMReset();
1282
1310
  },
@@ -1286,13 +1314,16 @@ class Menu {
1286
1314
  // Limit selections to 2 AI models
1287
1315
  const llmCheckboxes = document.querySelectorAll('input[name="LLM_model"]');
1288
1316
  llmCheckboxes.forEach((checkbox) => {
1289
- checkbox.addEventListener("change", () => {
1317
+ checkbox.addEventListener('change', () => {
1290
1318
  const checked = document.querySelectorAll(
1291
1319
  'input[name="LLM_model"]:checked'
1292
1320
  );
1293
1321
  if (checked.length > 2) {
1294
1322
  checkbox.checked = false;
1295
- alert("You can select up to 2 AI models.");
1323
+ alert('You can select up to 2 AI models.');
1324
+ } else if (checked.length === 0) {
1325
+ checkbox.checked = true;
1326
+ alert('You must select at least one AI model.');
1296
1327
  }
1297
1328
  });
1298
1329
  });
@@ -1304,11 +1335,11 @@ class Menu {
1304
1335
  */
1305
1336
  Destroy() {
1306
1337
  // menu element destruction
1307
- let menu = document.getElementById("menu");
1338
+ let menu = document.getElementById('menu');
1308
1339
  if (menu) {
1309
1340
  menu.remove();
1310
1341
  }
1311
- let backdrop = document.getElementById("menu_modal_backdrop");
1342
+ let backdrop = document.getElementById('menu_modal_backdrop');
1312
1343
  if (backdrop) {
1313
1344
  backdrop.remove();
1314
1345
  }
@@ -1320,16 +1351,16 @@ class Menu {
1320
1351
  * @return {void}
1321
1352
  */
1322
1353
  Toggle(onoff = false) {
1323
- if (typeof onoff == "undefined") {
1324
- if (document.getElementById("menu").classList.contains("hidden")) {
1354
+ if (typeof onoff == 'undefined') {
1355
+ if (document.getElementById('menu').classList.contains('hidden')) {
1325
1356
  onoff = true;
1326
1357
  } else {
1327
1358
  onoff = false;
1328
1359
  }
1329
1360
  }
1330
1361
  // don't open if we have another modal open already
1331
- if (onoff && document.getElementById("chatLLM")) {
1332
- if (!document.getElementById("chatLLM").classList.contains("hidden")) {
1362
+ if (onoff && document.getElementById('chatLLM')) {
1363
+ if (!document.getElementById('chatLLM').classList.contains('hidden')) {
1333
1364
  return;
1334
1365
  }
1335
1366
  }
@@ -1338,13 +1369,13 @@ class Menu {
1338
1369
  this.whereWasMyFocus = document.activeElement;
1339
1370
  this.PopulateData();
1340
1371
  constants.tabMovement = 0;
1341
- document.getElementById("menu").classList.remove("hidden");
1342
- document.getElementById("menu_modal_backdrop").classList.remove("hidden");
1343
- document.querySelector("#menu .close").focus();
1372
+ document.getElementById('menu').classList.remove('hidden');
1373
+ document.getElementById('menu_modal_backdrop').classList.remove('hidden');
1374
+ document.querySelector('#menu .close').focus();
1344
1375
  } else {
1345
1376
  // close
1346
- document.getElementById("menu").classList.add("hidden");
1347
- document.getElementById("menu_modal_backdrop").classList.add("hidden");
1377
+ document.getElementById('menu').classList.add('hidden');
1378
+ document.getElementById('menu_modal_backdrop').classList.add('hidden');
1348
1379
  this.whereWasMyFocus.focus();
1349
1380
  this.whereWasMyFocus = null;
1350
1381
  }
@@ -1355,68 +1386,73 @@ class Menu {
1355
1386
  * @return {void}
1356
1387
  */
1357
1388
  PopulateData() {
1358
- document.getElementById("vol").value = constants.vol;
1359
- document.getElementById("braille_display_length").value =
1389
+ document.getElementById('vol').value = constants.vol;
1390
+ document.getElementById('braille_display_length').value =
1360
1391
  constants.brailleDisplayLength;
1361
- document.getElementById("color_selected").value = constants.colorSelected;
1362
- document.getElementById("min_freq").value = constants.MIN_FREQUENCY;
1363
- document.getElementById("max_freq").value = constants.MAX_FREQUENCY;
1364
- document.getElementById("AUTOPLAY_DURATION").value =
1392
+ document.getElementById('color_selected').value = constants.colorSelected;
1393
+ document.getElementById('min_freq').value = constants.MIN_FREQUENCY;
1394
+ document.getElementById('max_freq').value = constants.MAX_FREQUENCY;
1395
+ document.getElementById('AUTOPLAY_DURATION').value =
1365
1396
  constants.AUTOPLAY_DURATION;
1366
- if (typeof constants.openAIAuthKey == "string") {
1367
- document.getElementById("openai_auth_key").value =
1397
+ if (typeof constants.openAIAuthKey == 'string') {
1398
+ document.getElementById('openai_auth_key').value =
1368
1399
  constants.openAIAuthKey;
1369
1400
  }
1370
- if (typeof constants.emailAuthKey == "string") {
1371
- document.getElementById("email_auth_key").value = constants.emailAuthKey;
1401
+ if (typeof constants.emailAuthKey == 'string') {
1402
+ document.getElementById('email_auth_key').value = constants.emailAuthKey;
1372
1403
  }
1373
- if (typeof constants.geminiAuthKey == "string") {
1374
- document.getElementById("gemini_auth_key").value =
1404
+ if (typeof constants.geminiAuthKey == 'string') {
1405
+ document.getElementById('gemini_auth_key').value =
1375
1406
  constants.geminiAuthKey;
1376
1407
  }
1377
- if (typeof constants.claudeAuthKey == "string") {
1378
- document.getElementById("claude_auth_key").value =
1408
+ if (typeof constants.claudeAuthKey == 'string') {
1409
+ document.getElementById('claude_auth_key').value =
1379
1410
  constants.claudeAuthKey;
1380
1411
  }
1381
- document.getElementById("skill_level").value = constants.skillLevel;
1412
+ document.getElementById('skill_level').value = constants.skillLevel;
1382
1413
  if (constants.skillLevelOther) {
1383
- document.getElementById("skill_level_other").value =
1414
+ document.getElementById('skill_level_other').value =
1384
1415
  constants.skillLevelOther;
1385
1416
  }
1386
1417
 
1387
1418
  // aria mode
1388
- if (constants.ariaMode == "assertive") {
1389
- document.getElementById("aria_mode_assertive").checked = true;
1390
- document.getElementById("aria_mode_polite").checked = false;
1419
+ if (constants.ariaMode == 'assertive') {
1420
+ document.getElementById('aria_mode_assertive').checked = true;
1421
+ document.getElementById('aria_mode_polite').checked = false;
1391
1422
  } else {
1392
- document.getElementById("aria_mode_polite").checked = true;
1393
- document.getElementById("aria_mode_assertive").checked = false;
1423
+ document.getElementById('aria_mode_polite').checked = true;
1424
+ document.getElementById('aria_mode_assertive').checked = false;
1394
1425
  }
1395
1426
 
1396
- for (let model in constants.LLMModels) {
1397
- document.getElementById(`LLM_model_${model}`).checked = true;
1427
+ if (constants.emailAuthKey && constants.clientToken) {
1428
+ console.log('email auth key and client token found');
1429
+ this.DisableLLMAPIKeys();
1430
+ } else {
1431
+ for (let model in constants.LLMModels) {
1432
+ document.getElementById(`LLM_model_${model}`).checked = true;
1398
1433
 
1399
- document
1400
- .getElementById(`${model}_auth_key_container`)
1401
- .classList.remove("hidden");
1434
+ document
1435
+ .getElementById(`${model}_auth_key_container`)
1436
+ .classList.remove('hidden');
1437
+ }
1402
1438
  }
1403
1439
  document
1404
1440
  .getElementById(`claude_auth_key_container`)
1405
- .classList.add("hidden");
1441
+ .classList.add('hidden');
1406
1442
 
1407
1443
  // skill level other
1408
- if (constants.skillLevel == "other") {
1444
+ if (constants.skillLevel == 'other') {
1409
1445
  document
1410
- .getElementById("skill_level_other_container")
1411
- .classList.remove("hidden");
1446
+ .getElementById('skill_level_other_container')
1447
+ .classList.remove('hidden');
1412
1448
  }
1413
1449
  // LLM preferences
1414
1450
  if (constants.LLMPreferences) {
1415
- document.getElementById("LLM_preferences").value =
1451
+ document.getElementById('LLM_preferences').value =
1416
1452
  constants.LLMPreferences;
1417
1453
  }
1418
- if (document.getElementById("LLM_reset_notification")) {
1419
- document.getElementById("LLM_reset_notification").remove();
1454
+ if (document.getElementById('LLM_reset_notification')) {
1455
+ document.getElementById('LLM_reset_notification').remove();
1420
1456
  }
1421
1457
  }
1422
1458
 
@@ -1427,23 +1463,23 @@ class Menu {
1427
1463
  SaveData() {
1428
1464
  let shouldReset = this.ShouldLLMReset();
1429
1465
 
1430
- constants.vol = document.getElementById("vol").value;
1466
+ constants.vol = document.getElementById('vol').value;
1431
1467
  constants.brailleDisplayLength = document.getElementById(
1432
- "braille_display_length"
1468
+ 'braille_display_length'
1433
1469
  ).value;
1434
- constants.colorSelected = document.getElementById("color_selected").value;
1435
- constants.MIN_FREQUENCY = document.getElementById("min_freq").value;
1436
- constants.MAX_FREQUENCY = document.getElementById("max_freq").value;
1470
+ constants.colorSelected = document.getElementById('color_selected').value;
1471
+ constants.MIN_FREQUENCY = document.getElementById('min_freq').value;
1472
+ constants.MAX_FREQUENCY = document.getElementById('max_freq').value;
1437
1473
  constants.AUTOPLAY_DURATION =
1438
- document.getElementById("AUTOPLAY_DURATION").value;
1474
+ document.getElementById('AUTOPLAY_DURATION').value;
1439
1475
 
1440
- constants.openAIAuthKey = document.getElementById("openai_auth_key").value;
1441
- constants.geminiAuthKey = document.getElementById("gemini_auth_key").value;
1442
- constants.claudeAuthKey = document.getElementById("claude_auth_key").value;
1443
- constants.emailAuthKey = document.getElementById("email_auth_key").value;
1444
- constants.skillLevel = document.getElementById("skill_level").value;
1476
+ constants.openAIAuthKey = document.getElementById('openai_auth_key').value;
1477
+ constants.geminiAuthKey = document.getElementById('gemini_auth_key').value;
1478
+ constants.claudeAuthKey = document.getElementById('claude_auth_key').value;
1479
+ constants.emailAuthKey = document.getElementById('email_auth_key').value;
1480
+ constants.skillLevel = document.getElementById('skill_level').value;
1445
1481
  constants.skillLevelOther =
1446
- document.getElementById("skill_level_other").value;
1482
+ document.getElementById('skill_level_other').value;
1447
1483
  // constants.LLMModel = document.getElementById('LLM_model').value;
1448
1484
 
1449
1485
  const llmCheckboxes = document.querySelectorAll('input[name="LLM_model"]');
@@ -1455,16 +1491,16 @@ class Menu {
1455
1491
  }
1456
1492
  });
1457
1493
 
1458
- constants.LLMPreferences = document.getElementById("LLM_preferences").value;
1459
- constants.LLMOpenAiMulti = document.getElementById("openai_multi").checked;
1460
- constants.LLMGeminiMulti = document.getElementById("gemini_multi").checked;
1461
- constants.autoInitLLM = document.getElementById("init_llm_on_load").checked;
1494
+ constants.LLMPreferences = document.getElementById('LLM_preferences').value;
1495
+ constants.LLMOpenAiMulti = document.getElementById('openai_multi').checked;
1496
+ constants.LLMGeminiMulti = document.getElementById('gemini_multi').checked;
1497
+ constants.autoInitLLM = document.getElementById('init_llm_on_load').checked;
1462
1498
 
1463
1499
  // aria
1464
- if (document.getElementById("aria_mode_assertive").checked) {
1465
- constants.ariaMode = "assertive";
1466
- } else if (document.getElementById("aria_mode_polite").checked) {
1467
- constants.ariaMode = "polite";
1500
+ if (document.getElementById('aria_mode_assertive').checked) {
1501
+ constants.ariaMode = 'assertive';
1502
+ } else if (document.getElementById('aria_mode_polite').checked) {
1503
+ constants.ariaMode = 'polite';
1468
1504
  }
1469
1505
 
1470
1506
  this.SaveDataToLocalStorage();
@@ -1477,38 +1513,75 @@ class Menu {
1477
1513
  }
1478
1514
  }
1479
1515
 
1480
- VerifyEmail() {
1481
- let email = document.getElementById("email_auth_key").value;
1482
- if (email && email.indexOf("@") !== -1) {
1483
- let url = `https://maidr-service.azurewebsites.net/api/send_email?code=I8Aa2PlPspjQ8Hks0QzGyszP8_i2-XJ3bq7Xh8-ykEe4AzFuYn_QWA%3D%3D`;
1516
+ DisableLLMAPIKeys() {
1517
+ document.getElementById('email_auth_key').disabled = true;
1484
1518
 
1485
- let requestJson = {
1486
- email: email,
1487
- };
1519
+ // hide verify button
1520
+ document.getElementById('verify').classList.add('hidden');
1488
1521
 
1489
- fetch(url, {
1490
- method: "POST",
1491
- headers: {
1492
- "Content-Type": "application/json",
1493
- Authentication: constants.emailAuthKey,
1494
- },
1495
- body: JSON.stringify(requestJson),
1496
- })
1497
- .then((response) => response.json())
1498
- .then((data) => {
1499
- if (data && data.success) {
1500
- alert("Link sent to email address: " + email);
1501
- } else {
1502
- console.log(data);
1503
- alert(data.data);
1504
- }
1522
+ // remove listener on enter
1523
+ document
1524
+ .getElementById('email_auth_key')
1525
+ .removeEventListener('keydown', function (event) {
1526
+ if (event.key === 'Enter') {
1527
+ document.getElementById('verify').click();
1528
+ }
1529
+ });
1530
+ }
1531
+
1532
+ isEmailTriggered = false;
1533
+
1534
+ VerifyEmail() {
1535
+ console.log('verify email');
1536
+ if (!this.isEmailTriggered) {
1537
+ this.isEmailTriggered = true;
1538
+ let email = document.getElementById('email_auth_key').value;
1539
+ if (email && email.indexOf('@') !== -1) {
1540
+ let url = constants.baseURL + `send_email` + constants.code;
1541
+
1542
+ let requestJson = {
1543
+ email: email,
1544
+ };
1545
+
1546
+ fetch(url, {
1547
+ method: 'POST',
1548
+ headers: {
1549
+ 'Content-Type': 'application/json',
1550
+ Authentication: constants.emailAuthKey,
1551
+ },
1552
+ body: JSON.stringify(requestJson),
1505
1553
  })
1506
- .catch((error) => {
1507
- console.log(error);
1508
- alert(error.data);
1509
- });
1510
- } else {
1511
- alert("Please enter a valid email address.");
1554
+ .then((response) => response.json())
1555
+ .then((data) => {
1556
+ if (data && data.message && data.client_token) {
1557
+ alert(data.message);
1558
+ constants.clientToken = data.client_token;
1559
+
1560
+ this.DisableLLMAPIKeys();
1561
+
1562
+ for (let model in constants.LLMModels) {
1563
+ document
1564
+ .getElementById(`${model}_auth_key_container`)
1565
+ .classList.add('hidden');
1566
+ }
1567
+
1568
+ this.SaveDataToLocalStorage();
1569
+ this.isEmailTriggered = false;
1570
+ } else {
1571
+ console.log(data);
1572
+ alert(data.data);
1573
+ this.isEmailTriggered = false;
1574
+ }
1575
+ })
1576
+ .catch((error) => {
1577
+ console.log(error);
1578
+ alert(error.data);
1579
+ this.isEmailTriggered = false;
1580
+ });
1581
+ } else {
1582
+ alert('Please enter a valid email address.');
1583
+ this.isEmailTriggered = false;
1584
+ }
1512
1585
  }
1513
1586
  }
1514
1587
 
@@ -1518,29 +1591,29 @@ class Menu {
1518
1591
  */
1519
1592
  UpdateHtml() {
1520
1593
  // set aria attributes
1521
- constants.infoDiv.setAttribute("aria-live", constants.ariaMode);
1594
+ constants.infoDiv.setAttribute('aria-live', constants.ariaMode);
1522
1595
  document
1523
1596
  .getElementById(constants.announcement_container_id)
1524
- .setAttribute("aria-live", constants.ariaMode);
1597
+ .setAttribute('aria-live', constants.ariaMode);
1525
1598
 
1526
- document.getElementById("init_llm_on_load").checked = constants.autoInitLLM;
1527
- const scatter = document.getElementsByClassName("highlight_point");
1528
- const heatmap = document.getElementById("highlight_rect");
1529
- const line = document.getElementById("highlight_point");
1599
+ document.getElementById('init_llm_on_load').checked = constants.autoInitLLM;
1600
+ const scatter = document.getElementsByClassName('highlight_point');
1601
+ const heatmap = document.getElementById('highlight_rect');
1602
+ const line = document.getElementById('highlight_point');
1530
1603
 
1531
1604
  if (scatter !== null && scatter.length > 0) {
1532
1605
  for (let i = 0; i < scatter.length; i++) {
1533
- scatter[i].setAttribute("stroke", constants.colorSelected);
1534
- scatter[i].setAttribute("fill", constants.colorSelected);
1606
+ scatter[i].setAttribute('stroke', constants.colorSelected);
1607
+ scatter[i].setAttribute('fill', constants.colorSelected);
1535
1608
  }
1536
1609
  }
1537
1610
 
1538
1611
  if (heatmap !== null) {
1539
- heatmap.setAttribute("stroke", constants.colorSelected);
1612
+ heatmap.setAttribute('stroke', constants.colorSelected);
1540
1613
  }
1541
1614
 
1542
1615
  if (line !== null) {
1543
- line.setAttribute("stroke", constants.colorSelected);
1616
+ line.setAttribute('stroke', constants.colorSelected);
1544
1617
  }
1545
1618
  }
1546
1619
 
@@ -1552,19 +1625,19 @@ class Menu {
1552
1625
  let html =
1553
1626
  '<p id="LLM_reset_notification">Note: Changes in LLM settings will reset any existing conversation.</p>';
1554
1627
 
1555
- if (document.getElementById("LLM_reset_notification")) {
1556
- document.getElementById("LLM_reset_notification").remove();
1628
+ if (document.getElementById('LLM_reset_notification')) {
1629
+ document.getElementById('LLM_reset_notification').remove();
1557
1630
  }
1558
1631
  document
1559
- .getElementById("save_and_close_menu")
1560
- .parentElement.insertAdjacentHTML("afterend", html);
1632
+ .getElementById('save_and_close_menu')
1633
+ .parentElement.insertAdjacentHTML('afterend', html);
1561
1634
 
1562
1635
  // add to aria button text
1563
1636
  document
1564
- .getElementById("save_and_close_menu")
1637
+ .getElementById('save_and_close_menu')
1565
1638
  .setAttribute(
1566
- "aria-labelledby",
1567
- "save_and_close_text LLM_reset_notification"
1639
+ 'aria-labelledby',
1640
+ 'save_and_close_text LLM_reset_notification'
1568
1641
  );
1569
1642
  }
1570
1643
  /**
@@ -1576,20 +1649,20 @@ class Menu {
1576
1649
  let shouldReset = false;
1577
1650
  if (
1578
1651
  !shouldReset &&
1579
- constants.skillLevel != document.getElementById("skill_level").value
1652
+ constants.skillLevel != document.getElementById('skill_level').value
1580
1653
  ) {
1581
1654
  shouldReset = true;
1582
1655
  }
1583
1656
  if (
1584
1657
  !shouldReset &&
1585
1658
  constants.LLMPreferences !=
1586
- document.getElementById("LLM_preferences").value
1659
+ document.getElementById('LLM_preferences').value
1587
1660
  ) {
1588
1661
  shouldReset = true;
1589
1662
  }
1590
1663
  if (
1591
1664
  !shouldReset &&
1592
- constants.LLMModel != document.getElementById("LLM_model").value
1665
+ constants.LLMModel != document.getElementById('LLM_model').value
1593
1666
  ) {
1594
1667
  shouldReset = true;
1595
1668
  }
@@ -1618,24 +1691,24 @@ class Menu {
1618
1691
  data[constants.userSettingsKeys[i]] =
1619
1692
  constants[constants.userSettingsKeys[i]];
1620
1693
  }
1621
- localStorage.setItem("settings_data", JSON.stringify(data));
1694
+ localStorage.setItem('settings_data', JSON.stringify(data));
1622
1695
 
1623
1696
  // also save to tracking if we're doing that
1624
1697
  if (constants.canTrack) {
1625
1698
  // but not auth keys
1626
- data.openAIAuthKey = "hidden";
1627
- data.geminiAuthKey = "hidden";
1628
- data.claudeAuthKey = "hidden";
1699
+ data.openAIAuthKey = 'hidden';
1700
+ data.geminiAuthKey = 'hidden';
1701
+ data.claudeAuthKey = 'hidden';
1629
1702
  // and need a timestamp
1630
1703
  data.timestamp = new Date().toISOString();
1631
- tracker.SetData("settings", data);
1704
+ tracker.SetData('settings', data);
1632
1705
  }
1633
1706
  }
1634
1707
  /**
1635
1708
  * Loads data from 'settings_data' localStorage, and updates contants variables
1636
1709
  */
1637
1710
  LoadDataFromLocalStorage() {
1638
- let data = JSON.parse(localStorage.getItem("settings_data"));
1711
+ let data = JSON.parse(localStorage.getItem('settings_data'));
1639
1712
  if (data) {
1640
1713
  for (let i = 0; i < constants.userSettingsKeys.length; i++) {
1641
1714
  const key = constants.userSettingsKeys[i];
@@ -1663,9 +1736,9 @@ class ChatLLM {
1663
1736
  if (constants.autoInitLLM) {
1664
1737
  // only run if we have API keys set
1665
1738
  if (
1666
- ("gemini" in constants.LLMModels && constants.geminiAuthKey) ||
1667
- ("openai" in constants.LLMModels && constants.openAIAuthKey) ||
1668
- ("claude" in constants.LLMModels && constants.claudeAuthKey)
1739
+ ('gemini' in constants.LLMModels && constants.geminiAuthKey) ||
1740
+ ('openai' in constants.LLMModels && constants.openAIAuthKey) ||
1741
+ ('claude' in constants.LLMModels && constants.claudeAuthKey)
1669
1742
  ) {
1670
1743
  this.InitChatMessage();
1671
1744
  }
@@ -1718,7 +1791,7 @@ class ChatLLM {
1718
1791
  </div>
1719
1792
  <div id="chatLLM_modal_backdrop" class="modal-backdrop hidden"></div>
1720
1793
  `;
1721
- document.querySelector("body").insertAdjacentHTML("beforeend", html);
1794
+ document.querySelector('body').insertAdjacentHTML('beforeend', html);
1722
1795
  }
1723
1796
 
1724
1797
  /**
@@ -1727,21 +1800,21 @@ class ChatLLM {
1727
1800
  */
1728
1801
  SetEvents() {
1729
1802
  // chatLLM close events
1730
- let allClose = document.querySelectorAll("#close_chatLLM, #chatLLM .close");
1803
+ let allClose = document.querySelectorAll('#close_chatLLM, #chatLLM .close');
1731
1804
  for (let i = 0; i < allClose.length; i++) {
1732
1805
  constants.events.push([
1733
1806
  allClose[i],
1734
- "click",
1807
+ 'click',
1735
1808
  function (e) {
1736
1809
  chatLLM.Toggle(false);
1737
1810
  },
1738
1811
  ]);
1739
1812
  }
1740
1813
  constants.events.push([
1741
- document.getElementById("chatLLM"),
1742
- "keyup",
1814
+ document.getElementById('chatLLM'),
1815
+ 'keyup',
1743
1816
  function (e) {
1744
- if (e.key == "Esc") {
1817
+ if (e.key == 'Esc') {
1745
1818
  // esc
1746
1819
  chatLLM.Toggle(false);
1747
1820
  }
@@ -1751,9 +1824,9 @@ class ChatLLM {
1751
1824
  // ChatLLM open/close toggle
1752
1825
  constants.events.push([
1753
1826
  document,
1754
- "keyup",
1827
+ 'keyup',
1755
1828
  function (e) {
1756
- if ((e.key == "?" && (e.ctrlKey || e.metaKey)) || e.key == "¿") {
1829
+ if ((e.key == '?' && (e.ctrlKey || e.metaKey)) || e.key == '¿') {
1757
1830
  chatLLM.Toggle();
1758
1831
  }
1759
1832
  },
@@ -1761,21 +1834,21 @@ class ChatLLM {
1761
1834
 
1762
1835
  // ChatLLM request events
1763
1836
  constants.events.push([
1764
- document.getElementById("chatLLM_submit"),
1765
- "click",
1837
+ document.getElementById('chatLLM_submit'),
1838
+ 'click',
1766
1839
  function (e) {
1767
- let text = document.getElementById("chatLLM_input").value;
1768
- chatLLM.DisplayChatMessage("User", text);
1840
+ let text = document.getElementById('chatLLM_input').value;
1841
+ chatLLM.DisplayChatMessage('User', text);
1769
1842
  chatLLM.Submit(text);
1770
1843
  },
1771
1844
  ]);
1772
1845
  constants.events.push([
1773
- document.getElementById("chatLLM_input"),
1774
- "keyup",
1846
+ document.getElementById('chatLLM_input'),
1847
+ 'keyup',
1775
1848
  function (e) {
1776
- if (e.key == "Enter" && !e.shiftKey) {
1777
- let text = document.getElementById("chatLLM_input").value;
1778
- chatLLM.DisplayChatMessage("User", text);
1849
+ if (e.key == 'Enter' && !e.shiftKey) {
1850
+ let text = document.getElementById('chatLLM_input').value;
1851
+ chatLLM.DisplayChatMessage('User', text);
1779
1852
  chatLLM.Submit(text);
1780
1853
  }
1781
1854
  },
@@ -1784,15 +1857,15 @@ class ChatLLM {
1784
1857
  // ChatLLM suggestion events
1785
1858
  // actual suggestions:
1786
1859
  let suggestions = document.querySelectorAll(
1787
- "#chatLLM .LLM_suggestions button:not(#more_suggestions)"
1860
+ '#chatLLM .LLM_suggestions button:not(#more_suggestions)'
1788
1861
  );
1789
1862
  for (let i = 0; i < suggestions.length; i++) {
1790
1863
  constants.events.push([
1791
1864
  suggestions[i],
1792
- "click",
1865
+ 'click',
1793
1866
  function (e) {
1794
1867
  let text = e.target.innerHTML;
1795
- chatLLM.DisplayChatMessage("User", text);
1868
+ chatLLM.DisplayChatMessage('User', text);
1796
1869
  chatLLM.Submit(text);
1797
1870
  },
1798
1871
  ]);
@@ -1800,31 +1873,41 @@ class ChatLLM {
1800
1873
 
1801
1874
  // Delete OpenAI and Gemini keys
1802
1875
  constants.events.push([
1803
- document.getElementById("delete_openai_key"),
1804
- "click",
1876
+ document.getElementById('delete_openai_key'),
1877
+ 'click',
1805
1878
  function (e) {
1806
- document.getElementById("openai_auth_key").value = "";
1879
+ document.getElementById('openai_auth_key').value = '';
1807
1880
  },
1808
1881
  ]);
1809
1882
  constants.events.push([
1810
- document.getElementById("delete_email_key"),
1811
- "click",
1883
+ document.getElementById('delete_email_key'),
1884
+ 'click',
1812
1885
  function (e) {
1813
- document.getElementById("email_auth_key").value = "";
1886
+ document.getElementById('email_auth_key').value = '';
1887
+ document.getElementById('email_auth_key').disabled = false;
1888
+ constants.clientToken = '';
1889
+ document.getElementById('verify').classList.remove('hidden');
1890
+ document
1891
+ .getElementById('email_auth_key')
1892
+ .addEventListener('keydown', function (event) {
1893
+ if (event.key === 'Enter') {
1894
+ document.getElementById('verify').click();
1895
+ }
1896
+ });
1814
1897
  },
1815
1898
  ]);
1816
1899
  constants.events.push([
1817
- document.getElementById("delete_gemini_key"),
1818
- "click",
1900
+ document.getElementById('delete_gemini_key'),
1901
+ 'click',
1819
1902
  function (e) {
1820
- document.getElementById("gemini_auth_key").value = "";
1903
+ document.getElementById('gemini_auth_key').value = '';
1821
1904
  },
1822
1905
  ]);
1823
1906
 
1824
1907
  // Reset chatLLM
1825
1908
  constants.events.push([
1826
- document.getElementById("reset_chatLLM"),
1827
- "click",
1909
+ document.getElementById('reset_chatLLM'),
1910
+ 'click',
1828
1911
  function (e) {
1829
1912
  chatLLM.ResetLLM();
1830
1913
  },
@@ -1832,15 +1915,15 @@ class ChatLLM {
1832
1915
 
1833
1916
  // copy to clipboard
1834
1917
  constants.events.push([
1835
- document.getElementById("chatLLM"),
1836
- "click",
1918
+ document.getElementById('chatLLM'),
1919
+ 'click',
1837
1920
  function (e) {
1838
1921
  chatLLM.CopyChatHistory(e);
1839
1922
  },
1840
1923
  ]);
1841
1924
  constants.events.push([
1842
- document.getElementById("chatLLM"),
1843
- "keyup",
1925
+ document.getElementById('chatLLM'),
1926
+ 'keyup',
1844
1927
  function (e) {
1845
1928
  chatLLM.CopyChatHistory(e);
1846
1929
  },
@@ -1858,96 +1941,96 @@ class ChatLLM {
1858
1941
  * @param {Event|undefined} e - The event that triggered the copy action. If undefined, the entire chat history is copied.
1859
1942
  */
1860
1943
  CopyChatHistory(e) {
1861
- let text = "";
1862
- if (typeof e == "undefined") {
1944
+ let text = '';
1945
+ if (typeof e == 'undefined') {
1863
1946
  // check for passthrough
1864
1947
  // get html of the full chat history
1865
- text = document.getElementById("chatLLM_chat_history").innerHTML;
1866
- } else if (e.type == "click") {
1948
+ text = document.getElementById('chatLLM_chat_history').innerHTML;
1949
+ } else if (e.type == 'click') {
1867
1950
  // check for buttons
1868
- if (e.target.id == "chatLLM_copy_all") {
1951
+ if (e.target.id == 'chatLLM_copy_all') {
1869
1952
  // get html of the full chat history
1870
- text = document.getElementById("chatLLM_chat_history").innerHTML;
1871
- } else if (e.target.classList.contains("chatLLM_message_copy_button")) {
1953
+ text = document.getElementById('chatLLM_chat_history').innerHTML;
1954
+ } else if (e.target.classList.contains('chatLLM_message_copy_button')) {
1872
1955
  // get the text of the element before the button
1873
- text = e.target.closest("p").previousElementSibling.innerHTML;
1956
+ text = e.target.closest('p').previousElementSibling.innerHTML;
1874
1957
  }
1875
- } else if (e.type == "keyup") {
1958
+ } else if (e.type == 'keyup') {
1876
1959
  // check for alt shift c or ctrl shift c
1877
- if (e.key == "C" && (e.ctrlKey || e.metaKey || e.altKey) && e.shiftKey) {
1960
+ if (e.key == 'C' && (e.ctrlKey || e.metaKey || e.altKey) && e.shiftKey) {
1878
1961
  e.preventDefault();
1879
1962
  // get the last message
1880
1963
  let elem = document.querySelector(
1881
- "#chatLLM_chat_history > .chatLLM_message_other:last-of-type"
1964
+ '#chatLLM_chat_history > .chatLLM_message_other:last-of-type'
1882
1965
  );
1883
1966
  if (elem) {
1884
1967
  text = elem.innerHTML;
1885
1968
  }
1886
1969
  } else if (
1887
- e.key == "A" &&
1970
+ e.key == 'A' &&
1888
1971
  (e.ctrlKey || e.metaKey || e.altKey) &&
1889
1972
  e.shiftKey
1890
1973
  ) {
1891
1974
  e.preventDefault();
1892
1975
  // get html of the full chat history
1893
- text = document.getElementById("chatLLM_chat_history").innerHTML;
1976
+ text = document.getElementById('chatLLM_chat_history').innerHTML;
1894
1977
  }
1895
1978
  }
1896
1979
 
1897
- if (text == "") {
1980
+ if (text == '') {
1898
1981
  return;
1899
1982
  } else {
1900
1983
  // clear the html, removing buttons etc
1901
- let cleanElems = document.createElement("div");
1984
+ let cleanElems = document.createElement('div');
1902
1985
  cleanElems.innerHTML = text;
1903
- let removeThese = cleanElems.querySelectorAll(".chatLLM_message_copy");
1986
+ let removeThese = cleanElems.querySelectorAll('.chatLLM_message_copy');
1904
1987
  removeThese.forEach((elem) => elem.remove());
1905
1988
 
1906
1989
  // convert from html to markdown
1907
1990
  let markdown = this.htmlToMarkdown(cleanElems);
1908
1991
  // this messes up a bit with spacing, so kill more than 2 newlines in a row
1909
- markdown = markdown.replace(/\n{3,}/g, "\n\n");
1992
+ markdown = markdown.replace(/\n{3,}/g, '\n\n');
1910
1993
 
1911
1994
  try {
1912
1995
  navigator.clipboard.writeText(markdown); // note: this fails if you're on the inspector. That's fine as it'll never happen to real users
1913
1996
  } catch (err) {
1914
- console.error("Failed to copy: ", err);
1997
+ console.error('Failed to copy: ', err);
1915
1998
  }
1916
1999
  return markdown;
1917
2000
  }
1918
2001
  }
1919
2002
 
1920
2003
  htmlToMarkdown(element) {
1921
- let markdown = "";
2004
+ let markdown = '';
1922
2005
 
1923
2006
  const convertElementToMarkdown = (element) => {
1924
2007
  switch (element.tagName) {
1925
- case "H1":
2008
+ case 'H1':
1926
2009
  return `# ${element.textContent}`;
1927
- case "H2":
2010
+ case 'H2':
1928
2011
  return `## ${element.textContent}`;
1929
- case "H3":
2012
+ case 'H3':
1930
2013
  return `### ${element.textContent}`;
1931
- case "H4":
2014
+ case 'H4':
1932
2015
  return `#### ${element.textContent}`;
1933
- case "H5":
2016
+ case 'H5':
1934
2017
  return `##### ${element.textContent}`;
1935
- case "H6":
2018
+ case 'H6':
1936
2019
  return `###### ${element.textContent}`;
1937
- case "P":
2020
+ case 'P':
1938
2021
  return element.textContent;
1939
- case "DIV":
2022
+ case 'DIV':
1940
2023
  // For divs, process each child and add newlines as needed
1941
2024
  return (
1942
2025
  Array.from(element.childNodes)
1943
2026
  .map((child) => convertElementToMarkdown(child))
1944
- .join("\n") + "\n\n"
2027
+ .join('\n') + '\n\n'
1945
2028
  );
1946
2029
  default:
1947
2030
  // For any other element, process its children recursively
1948
2031
  return Array.from(element.childNodes)
1949
2032
  .map((child) => convertElementToMarkdown(child))
1950
- .join("");
2033
+ .join('');
1951
2034
  }
1952
2035
  };
1953
2036
 
@@ -1955,7 +2038,7 @@ class ChatLLM {
1955
2038
  markdown += convertElementToMarkdown(element);
1956
2039
  } else if (
1957
2040
  element.nodeType === Node.TEXT_NODE &&
1958
- element.textContent.trim() !== ""
2041
+ element.textContent.trim() !== ''
1959
2042
  ) {
1960
2043
  markdown += element.textContent.trim();
1961
2044
  }
@@ -1980,14 +2063,14 @@ class ChatLLM {
1980
2063
 
1981
2064
  // if this is the user's first message (or we're gemini, in which case we need to send every time), prepend prompt with user position
1982
2065
  if (
1983
- (this.firstOpen || "gemini" in constants.LLMModels) &&
2066
+ (this.firstOpen || 'gemini' in constants.LLMModels) &&
1984
2067
  !firsttime &&
1985
2068
  constants.verboseText.length > 0
1986
2069
  ) {
1987
2070
  text =
1988
2071
  "Here is the current position in the chart; no response necessarily needed, use this info only if it's relevant to future questions: " +
1989
2072
  constants.verboseText +
1990
- ". My question is: " +
2073
+ '. My question is: ' +
1991
2074
  text;
1992
2075
 
1993
2076
  this.firstOpen = false;
@@ -1998,9 +2081,9 @@ class ChatLLM {
1998
2081
  this.WaitingSound(true);
1999
2082
  }
2000
2083
 
2001
- if ("openai" in constants.LLMModels) {
2084
+ if ('openai' in constants.LLMModels) {
2002
2085
  if (firsttime) {
2003
- img = await this.ConvertSVGtoJPG(singleMaidr.id, "openai");
2086
+ img = await this.ConvertSVGtoJPG(singleMaidr.id, 'openai');
2004
2087
  }
2005
2088
  if (constants.openAIAuthKey) {
2006
2089
  chatLLM.OpenAIPrompt(text, img);
@@ -2008,9 +2091,9 @@ class ChatLLM {
2008
2091
  chatLLM.OpenAIPromptAPI(text, img);
2009
2092
  }
2010
2093
  }
2011
- if ("gemini" in constants.LLMModels) {
2094
+ if ('gemini' in constants.LLMModels) {
2012
2095
  if (firsttime) {
2013
- img = await this.ConvertSVGtoJPG(singleMaidr.id, "gemini");
2096
+ img = await this.ConvertSVGtoJPG(singleMaidr.id, 'gemini');
2014
2097
  }
2015
2098
  if (constants.geminiAuthKey) {
2016
2099
  chatLLM.GeminiPrompt(text, img);
@@ -2019,9 +2102,9 @@ class ChatLLM {
2019
2102
  }
2020
2103
  }
2021
2104
 
2022
- if ("claude" in constants.LLMModels) {
2105
+ if ('claude' in constants.LLMModels) {
2023
2106
  if (firsttime) {
2024
- img = await this.ConvertSVGtoJPG(singleMaidr.id, "claude");
2107
+ img = await this.ConvertSVGtoJPG(singleMaidr.id, 'claude');
2025
2108
  }
2026
2109
  if (constants.claudeAuthKey) {
2027
2110
  chatLLM.ClaudePrompt(text, img);
@@ -2086,7 +2169,7 @@ class ChatLLM {
2086
2169
  }, 30000);
2087
2170
 
2088
2171
  // set queue for multi
2089
- if (constants.LLMModel != "multi") {
2172
+ if (constants.LLMModel != 'multi') {
2090
2173
  constants.waitingQueue = 1;
2091
2174
  } else {
2092
2175
  constants.waitingQueue = 0;
@@ -2118,7 +2201,7 @@ class ChatLLM {
2118
2201
  // get name from resource]
2119
2202
  let LLMName = resources.GetString(constants.LLMModel);
2120
2203
  this.firstTime = false;
2121
- this.DisplayChatMessage(LLMName, resources.GetString("processing"), true);
2204
+ this.DisplayChatMessage(LLMName, resources.GetString('processing'), true);
2122
2205
  let defaultPrompt = this.GetDefaultPrompt();
2123
2206
  this.Submit(defaultPrompt, true);
2124
2207
  }
@@ -2130,47 +2213,47 @@ class ChatLLM {
2130
2213
  */
2131
2214
  ProcessLLMResponse(data, model) {
2132
2215
  chatLLM.WaitingSound(false);
2133
- let text = "";
2216
+ let text = '';
2134
2217
  let LLMName = resources.GetString(model);
2135
2218
 
2136
- if (model == "openai") {
2219
+ if (model == 'openai') {
2137
2220
  text = data.choices[0].message.content;
2138
2221
  let i = this.requestJson.messages.length;
2139
2222
  this.requestJson.messages[i] = {};
2140
- this.requestJson.messages[i].role = "assistant";
2223
+ this.requestJson.messages[i].role = 'assistant';
2141
2224
  this.requestJson.messages[i].content = text;
2142
2225
 
2143
2226
  if (data.error) {
2144
- chatLLM.DisplayChatMessage(LLMName, "Error processing request.", true);
2227
+ chatLLM.DisplayChatMessage(LLMName, 'Error processing request.', true);
2145
2228
  chatLLM.WaitingSound(false);
2146
2229
  } else {
2147
2230
  chatLLM.DisplayChatMessage(LLMName, text);
2148
2231
  }
2149
- } else if (model == "gemini") {
2232
+ } else if (model == 'gemini') {
2150
2233
  if (data.text()) {
2151
2234
  text = data.text();
2152
2235
  chatLLM.DisplayChatMessage(LLMName, text);
2153
2236
  } else {
2154
2237
  if (!data.error) {
2155
- data.error = "Error processing request.";
2238
+ data.error = 'Error processing request.';
2156
2239
  chatLLM.WaitingSound(false);
2157
2240
  }
2158
2241
  }
2159
2242
  if (data.error) {
2160
- chatLLM.DisplayChatMessage(LLMName, "Error processing request.", true);
2243
+ chatLLM.DisplayChatMessage(LLMName, 'Error processing request.', true);
2161
2244
  chatLLM.WaitingSound(false);
2162
2245
  } else {
2163
2246
  // todo: display actual response
2164
2247
  }
2165
2248
  }
2166
- if (model == "claude") {
2167
- console.log("Claude response: ", data);
2249
+ if (model == 'claude') {
2250
+ console.log('Claude response: ', data);
2168
2251
  if (data.text()) {
2169
2252
  text = data.text();
2170
2253
  chatLLM.DisplayChatMessage(LLMName, text);
2171
2254
  }
2172
2255
  if (data.error) {
2173
- chatLLM.DisplayChatMessage(LLMName, "Error processing request.", true);
2256
+ chatLLM.DisplayChatMessage(LLMName, 'Error processing request.', true);
2174
2257
  chatLLM.WaitingSound(false);
2175
2258
  }
2176
2259
  }
@@ -2178,7 +2261,7 @@ class ChatLLM {
2178
2261
  // if we're tracking, log the data
2179
2262
  if (constants.canTrack) {
2180
2263
  let chatHist = chatLLM.CopyChatHistory();
2181
- tracker.SetData("ChatHistory", chatHist);
2264
+ tracker.SetData('ChatHistory', chatHist);
2182
2265
  }
2183
2266
  }
2184
2267
 
@@ -2192,11 +2275,11 @@ class ChatLLM {
2192
2275
  if (this.requestJson.messages.length > 2) {
2193
2276
  // subsequent responses
2194
2277
  responseText = {
2195
- id: "chatcmpl-8Y44iRCRrohYbAqm8rfBbJqTUADC7",
2196
- object: "chat.completion",
2278
+ id: 'chatcmpl-8Y44iRCRrohYbAqm8rfBbJqTUADC7',
2279
+ object: 'chat.completion',
2197
2280
  created: 1703129508,
2198
2281
  //model: 'gpt-4-1106-vision-preview',
2199
- model: "gpt4-o",
2282
+ model: 'gpt4-o',
2200
2283
  usage: {
2201
2284
  prompt_tokens: 451,
2202
2285
  completion_tokens: 16,
@@ -2205,10 +2288,10 @@ class ChatLLM {
2205
2288
  choices: [
2206
2289
  {
2207
2290
  message: {
2208
- role: "assistant",
2209
- content: "A fake response from the LLM. Nice.",
2291
+ role: 'assistant',
2292
+ content: 'A fake response from the LLM. Nice.',
2210
2293
  },
2211
- finish_reason: "length",
2294
+ finish_reason: 'length',
2212
2295
  index: 0,
2213
2296
  },
2214
2297
  ],
@@ -2216,10 +2299,10 @@ class ChatLLM {
2216
2299
  } else {
2217
2300
  // first response
2218
2301
  responseText = {
2219
- id: "chatcmpl-8Y44iRCRrohYbAqm8rfBbJqTUADC7",
2220
- object: "chat.completion",
2302
+ id: 'chatcmpl-8Y44iRCRrohYbAqm8rfBbJqTUADC7',
2303
+ object: 'chat.completion',
2221
2304
  created: 1703129508,
2222
- model: "gpt-4-1106-vision-preview",
2305
+ model: 'gpt-4-1106-vision-preview',
2223
2306
  usage: {
2224
2307
  prompt_tokens: 451,
2225
2308
  completion_tokens: 16,
@@ -2228,11 +2311,11 @@ class ChatLLM {
2228
2311
  choices: [
2229
2312
  {
2230
2313
  message: {
2231
- role: "assistant",
2314
+ role: 'assistant',
2232
2315
  content:
2233
- "The chart you're referring to is a bar graph titled \"The Number of Diamonds",
2316
+ 'The chart you\'re referring to is a bar graph titled "The Number of Diamonds',
2234
2317
  },
2235
- finish_reason: "length",
2318
+ finish_reason: 'length',
2236
2319
  index: 0,
2237
2320
  },
2238
2321
  ],
@@ -2243,7 +2326,7 @@ class ChatLLM {
2243
2326
  }
2244
2327
 
2245
2328
  ClaudeJson(text, img = null) {
2246
- const anthropicVersion = "vertex-2023-10-16";
2329
+ const anthropicVersion = 'vertex-2023-10-16';
2247
2330
  const maxTokens = 256;
2248
2331
 
2249
2332
  const payload = {
@@ -2254,7 +2337,7 @@ class ChatLLM {
2254
2337
 
2255
2338
  // Construct the user message object
2256
2339
  const userMessage = {
2257
- role: "user",
2340
+ role: 'user',
2258
2341
  content: [],
2259
2342
  };
2260
2343
 
@@ -2262,22 +2345,22 @@ class ChatLLM {
2262
2345
  if (img) {
2263
2346
  userMessage.content.push(
2264
2347
  {
2265
- type: "image",
2348
+ type: 'image',
2266
2349
  source: {
2267
- type: "base64",
2268
- media_type: "image/jpeg", // Update if other formats are supported
2350
+ type: 'base64',
2351
+ media_type: 'image/jpeg', // Update if other formats are supported
2269
2352
  data: img,
2270
2353
  },
2271
2354
  },
2272
2355
  {
2273
- type: "text",
2356
+ type: 'text',
2274
2357
  text: text,
2275
2358
  }
2276
2359
  );
2277
2360
  } else {
2278
2361
  // Add only the text content if no image is provided
2279
2362
  userMessage.content.push({
2280
- type: "text",
2363
+ type: 'text',
2281
2364
  text: text,
2282
2365
  });
2283
2366
  }
@@ -2289,16 +2372,15 @@ class ChatLLM {
2289
2372
  }
2290
2373
 
2291
2374
  ClaudePromptAPI(text, imgBase64 = null) {
2292
- console.log("Claude prompt API");
2293
- let url =
2294
- "https://maidr-service.azurewebsites.net/api/claude?code=I8Aa2PlPspjQ8Hks0QzGyszP8_i2-XJ3bq7Xh8-ykEe4AzFuYn_QWA%3D%3D";
2375
+ console.log('Claude prompt API');
2376
+ let url = constants.baseURL + 'claude' + constants.code;
2295
2377
 
2296
2378
  // Create the prompt
2297
2379
  let prompt = constants.LLMSystemMessage;
2298
2380
  if (constants.LLMPreferences) {
2299
2381
  prompt += constants.LLMPreferences;
2300
2382
  }
2301
- prompt += "\n\n" + text; // Use the text parameter as the prompt
2383
+ prompt += '\n\n' + text; // Use the text parameter as the prompt
2302
2384
 
2303
2385
  if (imgBase64 == null) {
2304
2386
  imgBase64 = constants.LLMImage;
@@ -2310,10 +2392,10 @@ class ChatLLM {
2310
2392
  let requestJson = chatLLM.ClaudeJson(prompt, imgBase64);
2311
2393
 
2312
2394
  fetch(url, {
2313
- method: "POST",
2395
+ method: 'POST',
2314
2396
  headers: {
2315
- "Content-Type": "application/json",
2316
- Authentication: constants.emailAuthKey,
2397
+ 'Content-Type': 'application/json',
2398
+ Authentication: constants.emailAuthKey + ' ' + constants.clientToken,
2317
2399
  },
2318
2400
  body: JSON.stringify(requestJson),
2319
2401
  })
@@ -2322,12 +2404,12 @@ class ChatLLM {
2322
2404
  data.text = function () {
2323
2405
  return data.content[0].text;
2324
2406
  };
2325
- chatLLM.ProcessLLMResponse(data, "claude");
2407
+ chatLLM.ProcessLLMResponse(data, 'claude');
2326
2408
  })
2327
2409
  .catch((error) => {
2328
2410
  chatLLM.WaitingSound(false);
2329
- console.error("Error:", error);
2330
- chatLLM.DisplayChatMessage("Claude", "Error processing request.", true);
2411
+ console.error('Error:', error);
2412
+ chatLLM.DisplayChatMessage('Claude', 'Error processing request.', true);
2331
2413
  // also todo: handle errors somehow
2332
2414
  });
2333
2415
  }
@@ -2341,55 +2423,53 @@ class ChatLLM {
2341
2423
  */
2342
2424
  OpenAIPrompt(text, img = null) {
2343
2425
  // request init
2344
- let url = "https://api.openai.com/v1/chat/completions";
2426
+ let url = 'https://api.openai.com/v1/chat/completions';
2345
2427
  let auth = constants.openAIAuthKey;
2346
2428
  let requestJson = chatLLM.OpenAIJson(text, img);
2347
2429
  //console.log('LLM request: ', requestJson);
2348
2430
 
2349
2431
  fetch(url, {
2350
- method: "POST",
2432
+ method: 'POST',
2351
2433
  headers: {
2352
- "Content-Type": "application/json",
2353
- Authorization: "Bearer " + auth,
2434
+ 'Content-Type': 'application/json',
2435
+ Authorization: 'Bearer ' + auth,
2354
2436
  },
2355
2437
  body: JSON.stringify(requestJson),
2356
2438
  })
2357
2439
  .then((response) => response.json())
2358
2440
  .then((data) => {
2359
- chatLLM.ProcessLLMResponse(data, "openai");
2441
+ chatLLM.ProcessLLMResponse(data, 'openai');
2360
2442
  })
2361
2443
  .catch((error) => {
2362
2444
  chatLLM.WaitingSound(false);
2363
- console.error("Error:", error);
2364
- chatLLM.DisplayChatMessage("OpenAI", "Error processing request.", true);
2445
+ console.error('Error:', error);
2446
+ chatLLM.DisplayChatMessage('OpenAI', 'Error processing request.', true);
2365
2447
  // also todo: handle errors somehow
2366
2448
  });
2367
2449
  }
2368
2450
 
2369
2451
  OpenAIPromptAPI(text, img = null) {
2370
2452
  // request init
2371
- let url =
2372
- "https://maidr-service.azurewebsites.net/api/openai?code=I8Aa2PlPspjQ8Hks0QzGyszP8_i2-XJ3bq7Xh8-ykEe4AzFuYn_QWA%3D%3D";
2453
+ let url = constants.baseURL + 'openai' + constants.code;
2373
2454
  let auth = constants.openAIAuthKey;
2374
2455
  let requestJson = chatLLM.OpenAIJson(text, img);
2375
- console.log("LLM request: ", requestJson);
2376
2456
 
2377
2457
  fetch(url, {
2378
- method: "POST",
2458
+ method: 'POST',
2379
2459
  headers: {
2380
- "Content-Type": "application/json",
2381
- Authentication: constants.emailAuthKey,
2460
+ 'Content-Type': 'application/json',
2461
+ Authentication: constants.emailAuthKey + ' ' + constants.clientToken,
2382
2462
  },
2383
2463
  body: JSON.stringify(requestJson),
2384
2464
  })
2385
2465
  .then((response) => response.json())
2386
2466
  .then((data) => {
2387
- chatLLM.ProcessLLMResponse(data, "openai");
2467
+ chatLLM.ProcessLLMResponse(data, 'openai');
2388
2468
  })
2389
2469
  .catch((error) => {
2390
2470
  chatLLM.WaitingSound(false);
2391
- console.error("Error:", error);
2392
- chatLLM.DisplayChatMessage("OpenAI", "Error processing request.", true);
2471
+ console.error('Error:', error);
2472
+ chatLLM.DisplayChatMessage('OpenAI', 'Error processing request.', true);
2393
2473
  // also todo: handle errors somehow
2394
2474
  });
2395
2475
  }
@@ -2397,22 +2477,22 @@ class ChatLLM {
2397
2477
  OpenAIJson(text, img = null) {
2398
2478
  let sysMessage = constants.LLMSystemMessage;
2399
2479
  let backupMessage =
2400
- "Describe " + singleMaidr.type + " charts to a blind person";
2480
+ 'Describe ' + singleMaidr.type + ' charts to a blind person';
2401
2481
  // headers and sys message
2402
2482
  if (!this.requestJson) {
2403
2483
  this.requestJson = {};
2404
2484
  //this.requestJson.model = 'gpt-4-vision-preview';
2405
- this.requestJson.model = "gpt-4o-2024-11-20";
2485
+ this.requestJson.model = 'gpt-4o-2024-11-20';
2406
2486
  this.requestJson.max_tokens = constants.LLMmaxResponseTokens; // note: if this is too short (tested with less than 200), the response gets cut off
2407
2487
 
2408
2488
  // sys message
2409
2489
  this.requestJson.messages = [];
2410
2490
  this.requestJson.messages[0] = {};
2411
- this.requestJson.messages[0].role = "system";
2491
+ this.requestJson.messages[0].role = 'system';
2412
2492
  this.requestJson.messages[0].content = sysMessage;
2413
2493
  if (constants.LLMPreferences) {
2414
2494
  this.requestJson.messages[1] = {};
2415
- this.requestJson.messages[1].role = "system";
2495
+ this.requestJson.messages[1].role = 'system';
2416
2496
  this.requestJson.messages[1].content = constants.LLMPreferences;
2417
2497
  }
2418
2498
  }
@@ -2421,16 +2501,16 @@ class ChatLLM {
2421
2501
  // if we have an image (first time only), send the image and the text, otherwise just the text
2422
2502
  let i = this.requestJson.messages.length;
2423
2503
  this.requestJson.messages[i] = {};
2424
- this.requestJson.messages[i].role = "user";
2504
+ this.requestJson.messages[i].role = 'user';
2425
2505
  if (img) {
2426
2506
  // first message, include the img
2427
2507
  this.requestJson.messages[i].content = [
2428
2508
  {
2429
- type: "text",
2509
+ type: 'text',
2430
2510
  text: text,
2431
2511
  },
2432
2512
  {
2433
- type: "image_url",
2513
+ type: 'image_url',
2434
2514
  image_url: { url: img },
2435
2515
  },
2436
2516
  ];
@@ -2445,7 +2525,7 @@ class ChatLLM {
2445
2525
  GeminiJson(text, img = null) {
2446
2526
  let sysMessage = constants.LLMSystemMessage;
2447
2527
  let backupMessage =
2448
- "Describe " + singleMaidr.type + " charts to a blind person";
2528
+ 'Describe ' + singleMaidr.type + ' charts to a blind person';
2449
2529
 
2450
2530
  let payload = {
2451
2531
  generationConfig: {},
@@ -2455,7 +2535,7 @@ class ChatLLM {
2455
2535
 
2456
2536
  // System message as the initial "role" and "text" content for context
2457
2537
  let sysContent = {
2458
- role: "user",
2538
+ role: 'user',
2459
2539
  parts: [
2460
2540
  {
2461
2541
  text: sysMessage || backupMessage, // Fallback if sysMessage is unavailable
@@ -2474,7 +2554,7 @@ class ChatLLM {
2474
2554
 
2475
2555
  // Add user input content, including image if available
2476
2556
  let userContent = {
2477
- role: "user",
2557
+ role: 'user',
2478
2558
  parts: [],
2479
2559
  };
2480
2560
 
@@ -2487,7 +2567,7 @@ class ChatLLM {
2487
2567
  {
2488
2568
  inlineData: {
2489
2569
  data: img, // Expecting base64-encoded image data
2490
- mimeType: "image/png", // Adjust if different image formats are possible
2570
+ mimeType: 'image/png', // Adjust if different image formats are possible
2491
2571
  },
2492
2572
  }
2493
2573
  );
@@ -2505,15 +2585,14 @@ class ChatLLM {
2505
2585
  }
2506
2586
 
2507
2587
  async GeminiPromptAPI(text, imgBase64 = null) {
2508
- let url =
2509
- "https://maidr-service.azurewebsites.net/api/gemini?code=I8Aa2PlPspjQ8Hks0QzGyszP8_i2-XJ3bq7Xh8-ykEe4AzFuYn_QWA%3D%3D";
2588
+ let url = constants.baseURL + 'gemini' + constants.code;
2510
2589
 
2511
2590
  // Create the prompt
2512
2591
  let prompt = constants.LLMSystemMessage;
2513
2592
  if (constants.LLMPreferences) {
2514
2593
  prompt += constants.LLMPreferences;
2515
2594
  }
2516
- prompt += "\n\n" + text; // Use the text parameter as the prompt
2595
+ prompt += '\n\n' + text; // Use the text parameter as the prompt
2517
2596
 
2518
2597
  if (imgBase64 == null) {
2519
2598
  imgBase64 = constants.LLMImage;
@@ -2525,10 +2604,10 @@ class ChatLLM {
2525
2604
  let requestJson = chatLLM.GeminiJson(prompt, imgBase64);
2526
2605
 
2527
2606
  const response = await fetch(url, {
2528
- method: "POST",
2607
+ method: 'POST',
2529
2608
  headers: {
2530
- "Content-Type": "application/json",
2531
- Authentication: constants.emailAuthKey,
2609
+ 'Content-Type': 'application/json',
2610
+ Authentication: constants.emailAuthKey + ' ' + constants.clientToken,
2532
2611
  },
2533
2612
  body: JSON.stringify(requestJson),
2534
2613
  });
@@ -2537,11 +2616,11 @@ class ChatLLM {
2537
2616
  responseJson.text = () => {
2538
2617
  return responseJson.candidates[0].content.parts[0].text;
2539
2618
  };
2540
- chatLLM.ProcessLLMResponse(responseJson, "gemini");
2619
+ chatLLM.ProcessLLMResponse(responseJson, 'gemini');
2541
2620
  } else {
2542
2621
  chatLLM.WaitingSound(false);
2543
- console.error("Error:", error);
2544
- chatLLM.DisplayChatMessage("OpenAI", "Error processing request.", true);
2622
+ console.error('Error:', error);
2623
+ chatLLM.DisplayChatMessage('OpenAI', 'Error processing request.', true);
2545
2624
  // also todo: handle errors somehow
2546
2625
  }
2547
2626
  }
@@ -2559,12 +2638,12 @@ class ChatLLM {
2559
2638
 
2560
2639
  // Import the module
2561
2640
  const { GoogleGenerativeAI } = await import(
2562
- "https://esm.run/@google/generative-ai"
2641
+ 'https://esm.run/@google/generative-ai'
2563
2642
  );
2564
2643
  const API_KEY = constants.geminiAuthKey;
2565
2644
  const genAI = new GoogleGenerativeAI(API_KEY);
2566
2645
  const model = genAI.getGenerativeModel({
2567
- model: "gemini-1.5-pro-latest",
2646
+ model: 'gemini-1.5-pro-latest',
2568
2647
  }); // old model was 'gemini-pro-vision'
2569
2648
 
2570
2649
  // Create the prompt
@@ -2572,11 +2651,11 @@ class ChatLLM {
2572
2651
  if (constants.LLMPreferences) {
2573
2652
  prompt += constants.LLMPreferences;
2574
2653
  }
2575
- prompt += "\n\n" + text; // Use the text parameter as the prompt
2654
+ prompt += '\n\n' + text; // Use the text parameter as the prompt
2576
2655
  const image = {
2577
2656
  inlineData: {
2578
2657
  data: imgBase64, // Use the base64 image string
2579
- mimeType: "image/png", // Or the appropriate mime type of your image
2658
+ mimeType: 'image/png', // Or the appropriate mime type of your image
2580
2659
  },
2581
2660
  };
2582
2661
 
@@ -2586,11 +2665,11 @@ class ChatLLM {
2586
2665
  //console.log(result.response.text());
2587
2666
 
2588
2667
  // Process the response
2589
- chatLLM.ProcessLLMResponse(result.response, "gemini");
2668
+ chatLLM.ProcessLLMResponse(result.response, 'gemini');
2590
2669
  } catch (error) {
2591
2670
  chatLLM.WaitingSound(false);
2592
- chatLLM.DisplayChatMessage("Gemini", "Error processing request.", true);
2593
- console.error("Error in GeminiPrompt:", error);
2671
+ chatLLM.DisplayChatMessage('Gemini', 'Error processing request.', true);
2672
+ console.error('Error in GeminiPrompt:', error);
2594
2673
  throw error; // Rethrow the error for further handling if necessary
2595
2674
  }
2596
2675
  }
@@ -2602,11 +2681,11 @@ class ChatLLM {
2602
2681
  * @memberof module:constants
2603
2682
  * @returns {void}
2604
2683
  */
2605
- DisplayChatMessage(user = "User", text = "", isSystem = false) {
2606
- let hLevel = "h3";
2607
- if (!isSystem && constants.LLMModel == "multi" && user != "User") {
2684
+ DisplayChatMessage(user = 'User', text = '', isSystem = false) {
2685
+ let hLevel = 'h3';
2686
+ if (!isSystem && constants.LLMModel == 'multi' && user != 'User') {
2608
2687
  if (this.firstMulti) {
2609
- let multiAIName = resources.GetString("multi");
2688
+ let multiAIName = resources.GetString('multi');
2610
2689
  let titleHtml = `
2611
2690
  <div class="chatLLM_message chatLLM_message_other">
2612
2691
  <h3 class="chatLLM_message_user">${multiAIName} Responses</h3>
@@ -2615,20 +2694,20 @@ class ChatLLM {
2615
2694
  this.RenderChatMessage(titleHtml);
2616
2695
  this.firstMulti = false;
2617
2696
  }
2618
- hLevel = "h4";
2697
+ hLevel = 'h4';
2619
2698
  }
2620
2699
  let html = `
2621
2700
  <div class="chatLLM_message ${
2622
- user == "User" ? "chatLLM_message_self" : "chatLLM_message_other"
2701
+ user == 'User' ? 'chatLLM_message_self' : 'chatLLM_message_other'
2623
2702
  }">`;
2624
- if (text != resources.GetString("processing")) {
2703
+ if (text != resources.GetString('processing')) {
2625
2704
  html += `<${hLevel} class="chatLLM_message_user">${user}</${hLevel}>`;
2626
2705
  }
2627
2706
  html += `<p class="chatLLM_message_text">${text}</p>
2628
2707
  </div>
2629
2708
  `;
2630
2709
  // add a copy button to actual messages
2631
- if (user != "User" && text != resources.GetString("processing")) {
2710
+ if (user != 'User' && text != resources.GetString('processing')) {
2632
2711
  html += `
2633
2712
  <p class="chatLLM_message_copy"><button class="chatLLM_message_copy_button">Copy</button></p>
2634
2713
  `;
@@ -2638,13 +2717,13 @@ class ChatLLM {
2638
2717
  }
2639
2718
  RenderChatMessage(html) {
2640
2719
  document
2641
- .getElementById("chatLLM_chat_history")
2642
- .insertAdjacentHTML("beforeend", html);
2643
- document.getElementById("chatLLM_input").value = "";
2720
+ .getElementById('chatLLM_chat_history')
2721
+ .insertAdjacentHTML('beforeend', html);
2722
+ document.getElementById('chatLLM_input').value = '';
2644
2723
 
2645
2724
  // scroll to bottom
2646
- document.getElementById("chatLLM_chat_history").scrollTop =
2647
- document.getElementById("chatLLM_chat_history").scrollHeight;
2725
+ document.getElementById('chatLLM_chat_history').scrollTop =
2726
+ document.getElementById('chatLLM_chat_history').scrollHeight;
2648
2727
  }
2649
2728
 
2650
2729
  /**
@@ -2652,7 +2731,7 @@ class ChatLLM {
2652
2731
  */
2653
2732
  ResetLLM() {
2654
2733
  // clear the main chat history
2655
- document.getElementById("chatLLM_chat_history").innerHTML = "";
2734
+ document.getElementById('chatLLM_chat_history').innerHTML = '';
2656
2735
 
2657
2736
  // reset the data
2658
2737
  this.requestJson = null;
@@ -2673,11 +2752,11 @@ class ChatLLM {
2673
2752
  */
2674
2753
  Destroy() {
2675
2754
  // chatLLM element destruction
2676
- let chatLLM = document.getElementById("chatLLM");
2755
+ let chatLLM = document.getElementById('chatLLM');
2677
2756
  if (chatLLM) {
2678
2757
  chatLLM.remove();
2679
2758
  }
2680
- let backdrop = document.getElementById("chatLLM_modal_backdrop");
2759
+ let backdrop = document.getElementById('chatLLM_modal_backdrop');
2681
2760
  if (backdrop) {
2682
2761
  backdrop.remove();
2683
2762
  }
@@ -2688,8 +2767,8 @@ class ChatLLM {
2688
2767
  * @param {boolean} [onoff=false] - Whether to turn the chatLLM on or off. Defaults to false (close).
2689
2768
  */
2690
2769
  Toggle(onoff) {
2691
- if (typeof onoff == "undefined") {
2692
- if (document.getElementById("chatLLM").classList.contains("hidden")) {
2770
+ if (typeof onoff == 'undefined') {
2771
+ if (document.getElementById('chatLLM').classList.contains('hidden')) {
2693
2772
  onoff = true;
2694
2773
  } else {
2695
2774
  onoff = false;
@@ -2700,19 +2779,19 @@ class ChatLLM {
2700
2779
  // open
2701
2780
  this.whereWasMyFocus = document.activeElement;
2702
2781
  constants.tabMovement = 0;
2703
- document.getElementById("chatLLM").classList.remove("hidden");
2782
+ document.getElementById('chatLLM').classList.remove('hidden');
2704
2783
  document
2705
- .getElementById("chatLLM_modal_backdrop")
2706
- .classList.remove("hidden");
2707
- document.querySelector("#chatLLM .close").focus();
2784
+ .getElementById('chatLLM_modal_backdrop')
2785
+ .classList.remove('hidden');
2786
+ document.querySelector('#chatLLM .close').focus();
2708
2787
 
2709
2788
  if (this.firstTime) {
2710
2789
  this.InitChatMessage();
2711
2790
  }
2712
2791
  } else {
2713
2792
  // close
2714
- document.getElementById("chatLLM").classList.add("hidden");
2715
- document.getElementById("chatLLM_modal_backdrop").classList.add("hidden");
2793
+ document.getElementById('chatLLM').classList.add('hidden');
2794
+ document.getElementById('chatLLM_modal_backdrop').classList.add('hidden');
2716
2795
  this.whereWasMyFocus.focus();
2717
2796
  this.whereWasMyFocus = null;
2718
2797
  this.firstOpen = true;
@@ -2726,11 +2805,11 @@ class ChatLLM {
2726
2805
  async ConvertSVGtoJPG(id, model) {
2727
2806
  let svgElement = document.getElementById(id);
2728
2807
  return new Promise((resolve, reject) => {
2729
- var canvas = document.createElement("canvas");
2730
- var ctx = canvas.getContext("2d");
2808
+ var canvas = document.createElement('canvas');
2809
+ var ctx = canvas.getContext('2d');
2731
2810
 
2732
2811
  var svgData = new XMLSerializer().serializeToString(svgElement);
2733
- if (!svgData.startsWith("<svg xmlns")) {
2812
+ if (!svgData.startsWith('<svg xmlns')) {
2734
2813
  svgData = `<svg xmlns="http://www.w3.org/2000/svg" ${svgData.slice(4)}`;
2735
2814
  }
2736
2815
 
@@ -2742,11 +2821,11 @@ class ChatLLM {
2742
2821
  var img = new Image();
2743
2822
  img.onload = function () {
2744
2823
  ctx.drawImage(img, 0, 0, svgSize.width, svgSize.height);
2745
- var jpegData = canvas.toDataURL("image/jpeg", 0.9); // 0.9 is the quality parameter
2746
- if (model == "openai") {
2824
+ var jpegData = canvas.toDataURL('image/jpeg', 0.9); // 0.9 is the quality parameter
2825
+ if (model == 'openai') {
2747
2826
  resolve(jpegData);
2748
- } else if (model == "gemini" || model == "claude") {
2749
- let base64Data = jpegData.split(",")[1];
2827
+ } else if (model == 'gemini' || model == 'claude') {
2828
+ let base64Data = jpegData.split(',')[1];
2750
2829
  resolve(base64Data);
2751
2830
  //resolve(jpegData);
2752
2831
  }
@@ -2754,11 +2833,11 @@ class ChatLLM {
2754
2833
  };
2755
2834
 
2756
2835
  img.onerror = function () {
2757
- reject(new Error("Error loading SVG"));
2836
+ reject(new Error('Error loading SVG'));
2758
2837
  };
2759
2838
 
2760
2839
  var svgBlob = new Blob([svgData], {
2761
- type: "image/svg+xml;charset=utf-8",
2840
+ type: 'image/svg+xml;charset=utf-8',
2762
2841
  });
2763
2842
  var url = URL.createObjectURL(svgBlob);
2764
2843
  img.src = url;
@@ -2771,25 +2850,25 @@ class ChatLLM {
2771
2850
  * The prompt includes information about the blind person's skill level and the chart's image and raw data, if available.
2772
2851
  */
2773
2852
  GetDefaultPrompt() {
2774
- let text = "Describe this chart to a blind person";
2853
+ let text = 'Describe this chart to a blind person';
2775
2854
  if (constants.skillLevel) {
2776
- if (constants.skillLevel == "other" && constants.skillLevelOther) {
2855
+ if (constants.skillLevel == 'other' && constants.skillLevelOther) {
2777
2856
  text +=
2778
- " who has a " +
2857
+ ' who has a ' +
2779
2858
  constants.skillLevelOther +
2780
- " understanding of statistical charts. ";
2859
+ ' understanding of statistical charts. ';
2781
2860
  } else {
2782
2861
  text +=
2783
- " who has a " +
2862
+ ' who has a ' +
2784
2863
  constants.skillLevel +
2785
- " understanding of statistical charts. ";
2864
+ ' understanding of statistical charts. ';
2786
2865
  }
2787
2866
  } else {
2788
- text += " who has a basic understanding of statistical charts. ";
2867
+ text += ' who has a basic understanding of statistical charts. ';
2789
2868
  }
2790
- text += "Here is a chart in image format";
2869
+ text += 'Here is a chart in image format';
2791
2870
  if (singleMaidr) {
2792
- text += " and raw data in json format: \n";
2871
+ text += ' and raw data in json format: \n';
2793
2872
  text += JSON.stringify(singleMaidr);
2794
2873
  }
2795
2874
 
@@ -2844,26 +2923,26 @@ class Description {
2844
2923
 
2845
2924
  `;
2846
2925
 
2847
- document.querySelector("body").insertAdjacentHTML("beforeend", html);
2926
+ document.querySelector('body').insertAdjacentHTML('beforeend', html);
2848
2927
 
2849
2928
  // close events
2850
2929
  let allClose = document.querySelectorAll(
2851
- "#close_desc, #description .close"
2930
+ '#close_desc, #description .close'
2852
2931
  );
2853
2932
  for (let i = 0; i < allClose.length; i++) {
2854
2933
  constants.events.push([
2855
2934
  allClose[i],
2856
- "click",
2935
+ 'click',
2857
2936
  function (e) {
2858
2937
  description.Toggle(false);
2859
2938
  },
2860
2939
  ]);
2861
2940
  }
2862
2941
  constants.events.push([
2863
- document.getElementById("description"),
2864
- "keyup",
2942
+ document.getElementById('description'),
2943
+ 'keyup',
2865
2944
  function (e) {
2866
- if (e.key == "Esc") {
2945
+ if (e.key == 'Esc') {
2867
2946
  // esc
2868
2947
  description.Toggle(false);
2869
2948
  }
@@ -2873,9 +2952,9 @@ class Description {
2873
2952
  // open events
2874
2953
  constants.events.push([
2875
2954
  document,
2876
- "keyup",
2955
+ 'keyup',
2877
2956
  function (e) {
2878
- if (e.key == "d") {
2957
+ if (e.key == 'd') {
2879
2958
  description.Toggle(true);
2880
2959
  }
2881
2960
  },
@@ -2887,11 +2966,11 @@ class Description {
2887
2966
  */
2888
2967
  Destroy() {
2889
2968
  // description element destruction
2890
- let description = document.getElementById("menu");
2969
+ let description = document.getElementById('menu');
2891
2970
  if (description) {
2892
2971
  description.remove();
2893
2972
  }
2894
- let backdrop = document.getElementById("desc_modal_backdrop");
2973
+ let backdrop = document.getElementById('desc_modal_backdrop');
2895
2974
  if (backdrop) {
2896
2975
  backdrop.remove();
2897
2976
  }
@@ -2902,8 +2981,8 @@ class Description {
2902
2981
  * @param {boolean} [onoff=false] - Whether to turn the description element on or off.
2903
2982
  */
2904
2983
  Toggle(onoff = false) {
2905
- if (typeof onoff == "undefined") {
2906
- if (document.getElementById("description").classList.contains("hidden")) {
2984
+ if (typeof onoff == 'undefined') {
2985
+ if (document.getElementById('description').classList.contains('hidden')) {
2907
2986
  onoff = true;
2908
2987
  } else {
2909
2988
  onoff = false;
@@ -2914,13 +2993,13 @@ class Description {
2914
2993
  this.whereWasMyFocus = document.activeElement;
2915
2994
  constants.tabMovement = 0;
2916
2995
  this.PopulateData();
2917
- document.getElementById("description").classList.remove("hidden");
2918
- document.getElementById("desc_modal_backdrop").classList.remove("hidden");
2919
- document.querySelector("#description .close").focus();
2996
+ document.getElementById('description').classList.remove('hidden');
2997
+ document.getElementById('desc_modal_backdrop').classList.remove('hidden');
2998
+ document.querySelector('#description .close').focus();
2920
2999
  } else {
2921
3000
  // close
2922
- document.getElementById("description").classList.add("hidden");
2923
- document.getElementById("desc_modal_backdrop").classList.add("hidden");
3001
+ document.getElementById('description').classList.add('hidden');
3002
+ document.getElementById('desc_modal_backdrop').classList.add('hidden');
2924
3003
  this.whereWasMyFocus.focus();
2925
3004
  this.whereWasMyFocus = null;
2926
3005
  }
@@ -2930,22 +3009,22 @@ class Description {
2930
3009
  * Populates the data for the chart and table based on the chart type and plot data.
2931
3010
  */
2932
3011
  PopulateData() {
2933
- let descHtml = "";
3012
+ let descHtml = '';
2934
3013
 
2935
3014
  // chart labels and descriptions
2936
- let descType = "";
2937
- if (constants.chartType == "bar") {
2938
- descType = "Bar chart";
2939
- } else if (constants.chartType == "heat") {
2940
- descType = "Heatmap";
2941
- } else if (constants.chartType == "box") {
2942
- descType = "Box plot";
2943
- } else if (constants.chartType == "scatter") {
2944
- descType = "Scatter plot";
2945
- } else if (constants.chartType == "line") {
2946
- descType = "Line chart";
2947
- } else if (constants.chartType == "hist") {
2948
- descType = "Histogram";
3015
+ let descType = '';
3016
+ if (constants.chartType == 'bar') {
3017
+ descType = 'Bar chart';
3018
+ } else if (constants.chartType == 'heat') {
3019
+ descType = 'Heatmap';
3020
+ } else if (constants.chartType == 'box') {
3021
+ descType = 'Box plot';
3022
+ } else if (constants.chartType == 'scatter') {
3023
+ descType = 'Scatter plot';
3024
+ } else if (constants.chartType == 'line') {
3025
+ descType = 'Line chart';
3026
+ } else if (constants.chartType == 'hist') {
3027
+ descType = 'Histogram';
2949
3028
  }
2950
3029
 
2951
3030
  if (descType) {
@@ -2962,7 +3041,7 @@ class Description {
2962
3041
  }
2963
3042
 
2964
3043
  // table of data, prep
2965
- let descTableHtml = "";
3044
+ let descTableHtml = '';
2966
3045
  let descLabelX = null;
2967
3046
  let descLabelY = null;
2968
3047
  let descTickX = null;
@@ -2972,7 +3051,7 @@ class Description {
2972
3051
  let descNumColsWithLabels = 0;
2973
3052
  let descNumRows = 0;
2974
3053
  let descNumRowsWithLabels = 0;
2975
- if (constants.chartType == "bar") {
3054
+ if (constants.chartType == 'bar') {
2976
3055
  if (plot.plotLegend.x != null) {
2977
3056
  descLabelX = plot.plotLegend.x;
2978
3057
  descNumColsWithLabels += 1;
@@ -2997,43 +3076,43 @@ class Description {
2997
3076
 
2998
3077
  // table of data, create
2999
3078
  if (descData != null) {
3000
- descTableHtml += "<table>";
3079
+ descTableHtml += '<table>';
3001
3080
 
3002
3081
  // header rows
3003
3082
  if (descLabelX != null || descTickX != null) {
3004
- descTableHtml += "<thead>";
3083
+ descTableHtml += '<thead>';
3005
3084
  if (descLabelX != null) {
3006
- descTableHtml += "<tr>";
3085
+ descTableHtml += '<tr>';
3007
3086
  if (descLabelY != null) {
3008
- descTableHtml += "<td></td>";
3087
+ descTableHtml += '<td></td>';
3009
3088
  }
3010
3089
  if (descTickY != null) {
3011
- descTableHtml += "<td></td>";
3090
+ descTableHtml += '<td></td>';
3012
3091
  }
3013
3092
  descTableHtml += `<th scope="col" colspan="${descNumCols}">${descLabelX}</th>`;
3014
- descTableHtml += "</tr>";
3093
+ descTableHtml += '</tr>';
3015
3094
  }
3016
3095
  if (descTickX != null) {
3017
- descTableHtml += "<tr>";
3096
+ descTableHtml += '<tr>';
3018
3097
  if (descLabelY != null) {
3019
- descTableHtml += "<td></td>";
3098
+ descTableHtml += '<td></td>';
3020
3099
  }
3021
3100
  if (descTickY != null) {
3022
- descTableHtml += "<td></td>";
3101
+ descTableHtml += '<td></td>';
3023
3102
  }
3024
3103
  for (let i = 0; i < descNumCols; i++) {
3025
3104
  descTableHtml += `<th scope="col">${descTickX[i]}</th>`;
3026
3105
  }
3027
- descTableHtml += "</tr>";
3106
+ descTableHtml += '</tr>';
3028
3107
  }
3029
- descTableHtml += "</thead>";
3108
+ descTableHtml += '</thead>';
3030
3109
  }
3031
3110
 
3032
3111
  // body rows
3033
3112
  if (descNumRows > 0) {
3034
- descTableHtml += "<tbody>";
3113
+ descTableHtml += '<tbody>';
3035
3114
  for (let i = 0; i < descNumRows; i++) {
3036
- descTableHtml += "<tr>";
3115
+ descTableHtml += '<tr>';
3037
3116
  if (descLabelY != null && i == 0) {
3038
3117
  descTableHtml += `<th scope="row" rowspan="${descNumRows}">${descLabelY}</th>`;
3039
3118
  }
@@ -3043,19 +3122,19 @@ class Description {
3043
3122
  for (let j = 0; j < descNumCols; j++) {
3044
3123
  descTableHtml += `<td>${descData[i][j]}</td>`;
3045
3124
  }
3046
- descTableHtml += "</tr>";
3125
+ descTableHtml += '</tr>';
3047
3126
  }
3048
- descTableHtml += "</tbody>";
3127
+ descTableHtml += '</tbody>';
3049
3128
  }
3050
3129
 
3051
- descTableHtml += "</table>";
3130
+ descTableHtml += '</table>';
3052
3131
  }
3053
3132
 
3054
3133
  // bar: don't need colspan or rowspan stuff, put legendX and Y as headers
3055
3134
 
3056
- document.getElementById("desc_title").innerHTML = descType + " description";
3057
- document.getElementById("desc_content").innerHTML = descHtml;
3058
- document.getElementById("desc_table").innerHTML = descTableHtml;
3135
+ document.getElementById('desc_title').innerHTML = descType + ' description';
3136
+ document.getElementById('desc_content').innerHTML = descHtml;
3137
+ document.getElementById('desc_table').innerHTML = descTableHtml;
3059
3138
  }
3060
3139
  }
3061
3140
 
@@ -3096,8 +3175,7 @@ class Helper {
3096
3175
  */
3097
3176
  class Tracker {
3098
3177
  // URL
3099
- logUrl =
3100
- "https://maidr-service.azurewebsites.net/api/log?code=I8Aa2PlPspjQ8Hks0QzGyszP8_i2-XJ3bq7Xh8-ykEe4AzFuYn_QWA%3D%3D"; // TODO Replace
3178
+ logUrl = constants.baseURL + 'log' + constants.code; // TODO Replace
3101
3179
  isLocal = false;
3102
3180
 
3103
3181
  constructor() {
@@ -3116,7 +3194,7 @@ class Tracker {
3116
3194
  data.language = Object.assign(navigator.language);
3117
3195
  data.platform = Object.assign(navigator.platform);
3118
3196
  data.geolocation = Object.assign(navigator.geolocation);
3119
- data.log_type = "system_data";
3197
+ data.log_type = 'system_data';
3120
3198
  data.events = [];
3121
3199
  data.settings = [];
3122
3200
 
@@ -3129,11 +3207,11 @@ class Tracker {
3129
3207
  * Downloads the tracker data as a JSON file.
3130
3208
  */
3131
3209
  DownloadTrackerData() {
3132
- let link = document.createElement("a");
3210
+ let link = document.createElement('a');
3133
3211
  let data = this.GetTrackerData();
3134
- let fileStr = new Blob([JSON.stringify(data)], { type: "text/plain" });
3212
+ let fileStr = new Blob([JSON.stringify(data)], { type: 'text/plain' });
3135
3213
  link.href = URL.createObjectURL(fileStr);
3136
- link.download = "tracking.json";
3214
+ link.download = 'tracking.json';
3137
3215
  link.click();
3138
3216
  }
3139
3217
 
@@ -3142,16 +3220,16 @@ class Tracker {
3142
3220
  * @param {Object} data - The data to be saved.
3143
3221
  */
3144
3222
  async SaveTrackerData(data) {
3145
- console.log("about to save data", data);
3223
+ console.log('about to save data', data);
3146
3224
  if (this.isLocal) {
3147
3225
  localStorage.setItem(constants.project_id, JSON.stringify(data));
3148
3226
  } else {
3149
3227
  // test this first
3150
3228
  try {
3151
3229
  const response = await fetch(this.logUrl, {
3152
- method: "POST",
3230
+ method: 'POST',
3153
3231
  headers: {
3154
- "Content-Type": "application/json",
3232
+ 'Content-Type': 'application/json',
3155
3233
  },
3156
3234
  body: JSON.stringify(data),
3157
3235
  });
@@ -3161,10 +3239,10 @@ class Tracker {
3161
3239
  }
3162
3240
 
3163
3241
  const result = await response.json();
3164
- console.log("Data saved successfully:", result);
3242
+ console.log('Data saved successfully:', result);
3165
3243
  return result;
3166
3244
  } catch (error) {
3167
- console.error("Error saving data:", error);
3245
+ console.error('Error saving data:', error);
3168
3246
  return null;
3169
3247
  }
3170
3248
  }
@@ -3187,7 +3265,7 @@ class Tracker {
3187
3265
  this.data = null;
3188
3266
 
3189
3267
  if (constants.debugLevel > 0) {
3190
- console.log("tracking data cleared");
3268
+ console.log('tracking data cleared');
3191
3269
  }
3192
3270
 
3193
3271
  this.DataSetup();
@@ -3195,16 +3273,16 @@ class Tracker {
3195
3273
 
3196
3274
  SaveSettings() {
3197
3275
  // fetch all settings, push to data.settings
3198
- let settings = JSON.parse(localStorage.getItem("settings_data"));
3276
+ let settings = JSON.parse(localStorage.getItem('settings_data'));
3199
3277
  if (settings) {
3200
3278
  // don't store their auth keys
3201
- settings.openAIAuthKey = "hidden";
3202
- settings.geminiAuthKey = "hidden";
3279
+ settings.openAIAuthKey = 'hidden';
3280
+ settings.geminiAuthKey = 'hidden';
3203
3281
  if (constants.emailAuthKey) {
3204
3282
  settings.username = constants.emailAuthKey;
3205
3283
  }
3206
3284
  settings;
3207
- this.SetData("settings", settings);
3285
+ this.SetData('settings', settings);
3208
3286
  }
3209
3287
  }
3210
3288
 
@@ -3301,7 +3379,7 @@ class Tracker {
3301
3379
  }
3302
3380
  if (!this.isUndefinedOrNull(constants.infoDiv.innerHTML)) {
3303
3381
  let textDisplay = Object.assign(constants.infoDiv.innerHTML);
3304
- textDisplay = textDisplay.replaceAll(/<[^>]*>?/gm, "");
3382
+ textDisplay = textDisplay.replaceAll(/<[^>]*>?/gm, '');
3305
3383
  eventToLog.text_display = textDisplay;
3306
3384
  }
3307
3385
  if (!this.isUndefinedOrNull(location.href)) {
@@ -3309,13 +3387,13 @@ class Tracker {
3309
3387
  }
3310
3388
 
3311
3389
  // chart specific values
3312
- let x_tickmark = "";
3313
- let y_tickmark = "";
3314
- let x_label = "";
3315
- let y_label = "";
3316
- let value = "";
3317
- let fill_value = "";
3318
- if (constants.chartType == "bar") {
3390
+ let x_tickmark = '';
3391
+ let y_tickmark = '';
3392
+ let x_label = '';
3393
+ let y_label = '';
3394
+ let value = '';
3395
+ let fill_value = '';
3396
+ if (constants.chartType == 'bar') {
3319
3397
  if (!this.isUndefinedOrNull(plot.columnLabels[position.x])) {
3320
3398
  x_tickmark = plot.columnLabels[position.x];
3321
3399
  }
@@ -3328,7 +3406,7 @@ class Tracker {
3328
3406
  if (!this.isUndefinedOrNull(plot.plotData[position.x])) {
3329
3407
  value = plot.plotData[position.x];
3330
3408
  }
3331
- } else if (constants.chartType == "heat") {
3409
+ } else if (constants.chartType == 'heat') {
3332
3410
  if (!this.isUndefinedOrNull(plot.x_labels[position.x])) {
3333
3411
  x_tickmark = plot.x_labels[position.x].trim();
3334
3412
  }
@@ -3349,11 +3427,11 @@ class Tracker {
3349
3427
  if (!this.isUndefinedOrNull(plot.group_labels[2])) {
3350
3428
  fill_value = plot.group_labels[2];
3351
3429
  }
3352
- } else if (constants.chartType == "box") {
3430
+ } else if (constants.chartType == 'box') {
3353
3431
  let plotPos =
3354
- constants.plotOrientation == "vert" ? position.x : position.y;
3432
+ constants.plotOrientation == 'vert' ? position.x : position.y;
3355
3433
  let sectionPos =
3356
- constants.plotOrientation == "vert" ? position.y : position.x;
3434
+ constants.plotOrientation == 'vert' ? position.y : position.x;
3357
3435
  let sectionLabel = plot.sections[sectionPos];
3358
3436
 
3359
3437
  if (!this.isUndefinedOrNull(plot.x_group_label)) {
@@ -3362,7 +3440,7 @@ class Tracker {
3362
3440
  if (!this.isUndefinedOrNull(plot.y_group_label)) {
3363
3441
  y_label = plot.y_group_label;
3364
3442
  }
3365
- if (constants.plotOrientation == "vert") {
3443
+ if (constants.plotOrientation == 'vert') {
3366
3444
  if (plotPos > -1 && sectionPos > -1) {
3367
3445
  if (!this.isUndefinedOrNull(sectionLabel)) {
3368
3446
  y_tickmark = sectionLabel;
@@ -3387,7 +3465,7 @@ class Tracker {
3387
3465
  }
3388
3466
  }
3389
3467
  }
3390
- } else if (constants.chartType == "point") {
3468
+ } else if (constants.chartType == 'point') {
3391
3469
  if (!this.isUndefinedOrNull(plot.x_group_label)) {
3392
3470
  x_label = plot.x_group_label;
3393
3471
  }
@@ -3414,7 +3492,7 @@ class Tracker {
3414
3492
 
3415
3493
  //console.log("x_tickmark: '", x_tickmark, "', y_tickmark: '", y_tickmark, "', x_label: '", x_label, "', y_label: '", y_label, "', value: '", value, "', fill_value: '", fill_value);
3416
3494
 
3417
- this.SetData("events", eventToLog);
3495
+ this.SetData('events', eventToLog);
3418
3496
  //console.log('logged an event');
3419
3497
  }
3420
3498
 
@@ -3427,7 +3505,7 @@ class Tracker {
3427
3505
  SetData(key, value) {
3428
3506
  if (this.isLocal) {
3429
3507
  let data = this.GetTrackerData();
3430
- let arrayKeys = ["events", "ChatHistory", "settings"];
3508
+ let arrayKeys = ['events', 'ChatHistory', 'settings'];
3431
3509
  if (!arrayKeys.includes(key)) {
3432
3510
  data[key] = value;
3433
3511
  } else {
@@ -3438,7 +3516,7 @@ class Tracker {
3438
3516
  }
3439
3517
  this.SaveTrackerData(data);
3440
3518
  } else {
3441
- value["log_type"] = key;
3519
+ value['log_type'] = key;
3442
3520
  this.SaveTrackerData(value);
3443
3521
  }
3444
3522
  }
@@ -3472,20 +3550,20 @@ class Review {
3472
3550
  // true means on or show
3473
3551
  if (onoff) {
3474
3552
  constants.reviewSaveSpot = document.activeElement;
3475
- constants.review_container.classList.remove("hidden");
3553
+ constants.review_container.classList.remove('hidden');
3476
3554
  constants.reviewSaveBrailleMode = constants.brailleMode;
3477
3555
  constants.review.focus();
3478
3556
 
3479
- display.announceText("Review on");
3557
+ display.announceText('Review on');
3480
3558
  } else {
3481
- constants.review_container.classList.add("hidden");
3482
- if (constants.reviewSaveBrailleMode == "on") {
3559
+ constants.review_container.classList.add('hidden');
3560
+ if (constants.reviewSaveBrailleMode == 'on') {
3483
3561
  // we have to turn braille mode back on
3484
- display.toggleBrailleMode("on");
3562
+ display.toggleBrailleMode('on');
3485
3563
  } else {
3486
3564
  constants.reviewSaveSpot.focus();
3487
3565
  }
3488
- display.announceText("Review off");
3566
+ display.announceText('Review off');
3489
3567
  }
3490
3568
  }
3491
3569
  }
@@ -3502,7 +3580,7 @@ class LogError {
3502
3580
  * @param {string} a - The absent element to log.
3503
3581
  */
3504
3582
  LogAbsentElement(a) {
3505
- console.log(a, "not found. Visual highlighting is turned off.");
3583
+ console.log(a, 'not found. Visual highlighting is turned off.');
3506
3584
  }
3507
3585
 
3508
3586
  /**
@@ -3510,7 +3588,7 @@ class LogError {
3510
3588
  * @param {string} a - The critical element to log.
3511
3589
  */
3512
3590
  LogCriticalElement(a) {
3513
- consolelog(a, "is critical. MAIDR unable to run");
3591
+ consolelog(a, 'is critical. MAIDR unable to run');
3514
3592
  }
3515
3593
 
3516
3594
  /**
@@ -3521,9 +3599,9 @@ class LogError {
3521
3599
  LogDifferentLengths(a, b) {
3522
3600
  console.log(
3523
3601
  a,
3524
- "and",
3602
+ 'and',
3525
3603
  b,
3526
- "do not have the same length. Visual highlighting is turned off."
3604
+ 'do not have the same length. Visual highlighting is turned off.'
3527
3605
  );
3528
3606
  }
3529
3607
 
@@ -3534,11 +3612,11 @@ class LogError {
3534
3612
  */
3535
3613
  LogTooManyElements(a, b) {
3536
3614
  console.log(
3537
- "Too many",
3615
+ 'Too many',
3538
3616
  a,
3539
- "elements. Only the first",
3617
+ 'elements. Only the first',
3540
3618
  b,
3541
- "will be highlighted."
3619
+ 'will be highlighted.'
3542
3620
  );
3543
3621
  }
3544
3622
 
@@ -3547,7 +3625,7 @@ class LogError {
3547
3625
  * @param {*} a - The parameter that is not an array.
3548
3626
  */
3549
3627
  LogNotArray(a) {
3550
- console.log(a, "is not an array. Visual highlighting is turned off.");
3628
+ console.log(a, 'is not an array. Visual highlighting is turned off.');
3551
3629
  }
3552
3630
  }
3553
3631
 
@@ -11800,13 +11878,13 @@ function SetEvents() {
11800
11878
  for (let i = 0; i < constants.events.length; i++) {
11801
11879
  if (Array.isArray(constants.events[i][0])) {
11802
11880
  for (let j = 0; j < constants.events[i][0].length; j++) {
11803
- constants.events[i][0][j].addEventListener(
11881
+ constants.events[i][0][j]?.addEventListener(
11804
11882
  constants.events[i][1],
11805
11883
  constants.events[i][2]
11806
11884
  );
11807
11885
  }
11808
11886
  } else {
11809
- constants.events[i][0].addEventListener(
11887
+ constants.events[i][0]?.addEventListener(
11810
11888
  constants.events[i][1],
11811
11889
  constants.events[i][2]
11812
11890
  );