maidr 2.23.0 → 2.23.2

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