maidr 2.22.0 → 2.23.0

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,14 +398,14 @@ 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}
405
405
  * @default 0
406
406
  * @memberof AdvancedUserSettings
407
407
  */
408
- canTrack = 0; // 0 / 1, can we track user data
408
+ canTrack = 1; // 0 / 1, can we track user data
409
409
  /**
410
410
  * How are we representing braille? like, is it 1:1 with the chart, or do we do some compression and try to represent as accuratly as we can? Not currently in use.
411
411
  * @type {boolean}
@@ -419,33 +419,33 @@ 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
449
  ];
450
450
 
451
451
  // LLM settings
@@ -486,14 +486,14 @@ class Constants {
486
486
  * @default 'high'
487
487
  * @memberof LLMSettings
488
488
  */
489
- LLMDetail = 'high'; // low (default for testing, like 100 tokens) / high (default for real, like 1000 tokens)
489
+ LLMDetail = "high"; // low (default for testing, like 100 tokens) / high (default for real, like 1000 tokens)
490
490
  /**
491
491
  * Current LLM model in use. Can be 'openai' (default) or 'gemini' or 'multi'. More to be added.
492
492
  * @type {("openai"|"gemini"|"multi")}
493
493
  * @default 'openai'
494
494
  * @memberof LLMSettings
495
495
  */
496
- LLMModel = 'openai';
496
+ LLMModel = "openai";
497
497
  /**
498
498
  * Current LLM model in use. Can be 'openai' (default) or 'gemini' or 'claude'. More to be added.
499
499
  * @type {("openai"|"gemini"|"claude")}
@@ -508,21 +508,21 @@ class Constants {
508
508
  * @memberof LLMSettings
509
509
  */
510
510
  LLMSystemMessage =
511
- 'You are a helpful assistant describing the chart to a blind person. ';
511
+ "You are a helpful assistant describing the chart to a blind person. ";
512
512
  /**
513
513
  * 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
514
  * @type {("basic"|"intermediate"|"expert"|"other")}
515
515
  * @default 'basic'
516
516
  * @memberof LLMSettings
517
517
  */
518
- skillLevel = 'basic'; // basic / intermediate / expert
518
+ skillLevel = "basic"; // basic / intermediate / expert
519
519
  /**
520
520
  * Custom skill level, used if the user selects 'other' as their skill level.
521
521
  * @type {string}
522
522
  * @default ''
523
523
  * @memberof LLMSettings
524
524
  */
525
- skillLevelOther = ''; // custom skill level
525
+ skillLevelOther = ""; // custom skill level
526
526
  /**
527
527
  * 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
528
  * @type {boolean}
@@ -536,7 +536,7 @@ class Constants {
536
536
  * @default ''
537
537
  * @memberof LLMSettings
538
538
  */
539
- verboseText = '';
539
+ verboseText = "";
540
540
  /**
541
541
  * An internal variable used to turn the waiting beep on and off.
542
542
  * @type {number}
@@ -584,31 +584,31 @@ class Constants {
584
584
  * @type {boolean}
585
585
  * @memberof PlatformControls
586
586
  */
587
- isMac = navigator.userAgent.toLowerCase().includes('mac'); // true if macOS
587
+ isMac = navigator.userAgent.toLowerCase().includes("mac"); // true if macOS
588
588
  /**
589
589
  * The control key for the user's platform. Can be 'Cmd' or 'Ctrl'. Used in keyboard shortcut display in help.
590
590
  * @type {"Cmd"|"Ctrl"}
591
591
  * @memberof PlatformControls
592
592
  */
593
- control = this.isMac ? 'Cmd' : 'Ctrl';
593
+ control = this.isMac ? "Cmd" : "Ctrl";
594
594
  /**
595
595
  * The alt key for the user's platform. Can be 'option' or 'Alt'. Used in keyboard shortcut display in help.
596
596
  * @type {"option"|"Alt"}
597
597
  * @memberof PlatformControls
598
598
  */
599
- alt = this.isMac ? 'option' : 'Alt';
599
+ alt = this.isMac ? "option" : "Alt";
600
600
  /**
601
601
  * The home key for the user's platform. Can be 'fn + Left arrow' or 'Home'. Used in keyboard shortcut display in help.
602
602
  * @type {"fn + Left arrow"|"Home"}
603
603
  * @memberof PlatformControls
604
604
  */
605
- home = this.isMac ? 'fn + Left arrow' : 'Home';
605
+ home = this.isMac ? "fn + Left arrow" : "Home";
606
606
  /**
607
607
  * The end key for the user's platform. Can be 'fn + Right arrow' or 'End'. Used in keyboard shortcut display in help.
608
608
  * @type {"fn + Right arrow"|"End"}
609
609
  * @memberof PlatformControls
610
610
  */
611
- end = this.isMac ? 'fn + Right arrow' : 'End';
611
+ end = this.isMac ? "fn + Right arrow" : "End";
612
612
  /**
613
613
  * The interval we wait for an L + X prefix event
614
614
  */
@@ -704,13 +704,13 @@ class Constants {
704
704
  */
705
705
  ConvertHexToRGBString(hexColorString) {
706
706
  return (
707
- 'rgb(' +
707
+ "rgb(" +
708
708
  parseInt(hexColorString.slice(1, 3), 16) +
709
- ',' +
709
+ "," +
710
710
  parseInt(hexColorString.slice(3, 5), 16) +
711
- ',' +
711
+ "," +
712
712
  parseInt(hexColorString.slice(5, 7), 16) +
713
- ')'
713
+ ")"
714
714
  );
715
715
  }
716
716
 
@@ -720,12 +720,12 @@ class Constants {
720
720
  * @returns {string} - hexadecimal color (e.g., "#595959").
721
721
  */
722
722
  ConvertRGBStringToHex(rgbColorString) {
723
- let rgb = rgbColorString.replace(/[^\d,]/g, '').split(',');
723
+ let rgb = rgbColorString.replace(/[^\d,]/g, "").split(",");
724
724
  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')
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")
729
729
  );
730
730
  }
731
731
 
@@ -737,11 +737,11 @@ class Constants {
737
737
  */
738
738
  ColorInvert(color) {
739
739
  // invert an rgb color
740
- let rgb = color.replace(/[^\d,]/g, '').split(',');
740
+ let rgb = color.replace(/[^\d,]/g, "").split(",");
741
741
  let r = 255 - rgb[0];
742
742
  let g = 255 - rgb[1];
743
743
  let b = 255 - rgb[2];
744
- return 'rgb(' + r + ',' + g + ',' + b + ')';
744
+ return "rgb(" + r + "," + g + "," + b + ")";
745
745
  }
746
746
 
747
747
  /**
@@ -750,11 +750,11 @@ class Constants {
750
750
  * @returns {string} The better color
751
751
  */
752
752
  GetBetterColor(oldColor) {
753
- if (oldColor.indexOf('#') !== -1) {
753
+ if (oldColor.indexOf("#") !== -1) {
754
754
  oldColor = this.ConvertHexToRGBString(oldColor);
755
755
  }
756
756
  let newColor = this.ColorInvert(oldColor);
757
- let rgb = newColor.replace(/[^\d,]/g, '').split(',');
757
+ let rgb = newColor.replace(/[^\d,]/g, "").split(",");
758
758
  if (
759
759
  rgb[1] < rgb[0] + 10 &&
760
760
  rgb[1] > rgb[0] - 10 &&
@@ -776,7 +776,7 @@ class Constants {
776
776
  */
777
777
  GetStyleArrayFromString(styleString) {
778
778
  // Get an array of CSS style attributes and values from a style string
779
- return styleString.replaceAll(' ', '').split(/[:;]/);
779
+ return styleString.replaceAll(" ", "").split(/[:;]/);
780
780
  }
781
781
 
782
782
  /**
@@ -786,16 +786,16 @@ class Constants {
786
786
  */
787
787
  GetStyleStringFromArray(styleArray) {
788
788
  // Get CSS style string from an array of style attributes and values
789
- let styleString = '';
789
+ let styleString = "";
790
790
  for (let i = 0; i < styleArray.length; i++) {
791
791
  if (i % 2 === 0) {
792
792
  if (i !== styleArray.length - 1) {
793
- styleString += styleArray[i] + ': ';
793
+ styleString += styleArray[i] + ": ";
794
794
  } else {
795
795
  styleString += styleArray[i];
796
796
  }
797
797
  } else {
798
- styleString += styleArray[i] + '; ';
798
+ styleString += styleArray[i] + "; ";
799
799
  }
800
800
  }
801
801
  return styleString;
@@ -808,36 +808,36 @@ class Constants {
808
808
  class Resources {
809
809
  constructor() {}
810
810
 
811
- language = 'en'; // Current language, 2 char lang code
812
- knowledgeLevel = 'basic'; // basic, intermediate, expert
811
+ language = "en"; // Current language, 2 char lang code
812
+ knowledgeLevel = "basic"; // basic, intermediate, expert
813
813
 
814
814
  // language strings, per 2 char language code
815
815
  strings = {
816
816
  en: {
817
817
  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...',
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...",
841
841
  },
842
842
  },
843
843
  };
@@ -1007,12 +1007,12 @@ class Menu {
1007
1007
  <div><fieldset>
1008
1008
  <legend>Aria Mode</legend>
1009
1009
  <p><input type="radio" id="aria_mode_assertive" name="aria_mode" value="assertive" ${
1010
- constants.ariaMode == 'assertive'
1011
- ? 'checked'
1012
- : ''
1010
+ constants.ariaMode == "assertive"
1011
+ ? "checked"
1012
+ : ""
1013
1013
  }><label for="aria_mode_assertive">Assertive</label></p>
1014
1014
  <p><input type="radio" id="aria_mode_polite" name="aria_mode" value="polite" ${
1015
- constants.ariaMode == 'polite' ? 'checked' : ''
1015
+ constants.ariaMode == "polite" ? "checked" : ""
1016
1016
  }><label for="aria_mode_polite">Polite</label></p>
1017
1017
  </fieldset></div>
1018
1018
  <p class="hidden">
@@ -1033,7 +1033,10 @@ class Menu {
1033
1033
  </fieldset>
1034
1034
  </p>
1035
1035
  <p id="email_auth_key_container" class="multi_container">
1036
- <input type="email" size="50" id="email_auth_key"><button aria-label="Delete Email Address" title="Delete Email Address" id="delete_email_key" class="invis_button">&times;</button><label for="gemini_auth_key">Email Authentication</label><button type="button" id="verify">Verify</button>
1036
+ <input type="email" size="50" id="email_auth_key" aria-label="Enter your email address">
1037
+ <button aria-label="Delete Email Address" title="Delete Email Address" id="delete_email_key" class="invis_button">&times;</button>
1038
+ <label for="gemini_auth_key">Email Authentication</label>
1039
+ <button type="button" id="verify">Verify</button>
1037
1040
  </p>
1038
1041
  <p id="openai_auth_key_container" class="multi_container hidden">
1039
1042
  <span id="openai_multi_container" class="hidden"><input type="checkbox" id="openai_multi" name="openai_multi" aria-label="Use OpenAI in Multi modal mode"></span>
@@ -1048,7 +1051,7 @@ class Menu {
1048
1051
  <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>
1049
1052
  </p>
1050
1053
  <p><input type="checkbox" ${
1051
- constants.autoInitLLM ? 'checked' : ''
1054
+ constants.autoInitLLM ? "checked" : ""
1052
1055
  } id="init_llm_on_load" name="init_llm_on_load"><label for="init_llm_on_load">Start LLM right away</label></p>
1053
1056
  <p>
1054
1057
  <select id="skill_level">
@@ -1085,40 +1088,40 @@ class Menu {
1085
1088
  CreateMenu() {
1086
1089
  // menu element creation
1087
1090
  document
1088
- .querySelector('body')
1089
- .insertAdjacentHTML('beforeend', this.menuHtml);
1091
+ .querySelector("body")
1092
+ .insertAdjacentHTML("beforeend", this.menuHtml);
1090
1093
 
1091
1094
  // menu close events
1092
- let allClose = document.querySelectorAll('#close_menu, #menu .close');
1095
+ let allClose = document.querySelectorAll("#close_menu, #menu .close");
1093
1096
  for (let i = 0; i < allClose.length; i++) {
1094
1097
  constants.events.push([
1095
1098
  allClose[i],
1096
- 'click',
1099
+ "click",
1097
1100
  function (e) {
1098
1101
  menu.Toggle(false);
1099
1102
  },
1100
1103
  ]);
1101
1104
  }
1102
1105
  constants.events.push([
1103
- document.getElementById('save_and_close_menu'),
1104
- 'click',
1106
+ document.getElementById("save_and_close_menu"),
1107
+ "click",
1105
1108
  function (e) {
1106
1109
  menu.SaveData();
1107
1110
  menu.Toggle(false);
1108
1111
  },
1109
1112
  ]);
1110
1113
  constants.events.push([
1111
- document.getElementById('verify'),
1112
- 'click',
1114
+ document.getElementById("verify"),
1115
+ "click",
1113
1116
  function (e) {
1114
1117
  menu.VerifyEmail();
1115
1118
  },
1116
1119
  ]);
1117
1120
  constants.events.push([
1118
- document.getElementById('menu'),
1119
- 'keyup',
1121
+ document.getElementById("menu"),
1122
+ "keyup",
1120
1123
  function (e) {
1121
- if (e.key == 'Esc') {
1124
+ if (e.key == "Esc") {
1122
1125
  // esc
1123
1126
  menu.Toggle(false);
1124
1127
  }
@@ -1128,15 +1131,15 @@ class Menu {
1128
1131
  // Menu open events
1129
1132
  constants.events.push([
1130
1133
  document,
1131
- 'keyup',
1134
+ "keyup",
1132
1135
  function (e) {
1133
1136
  // don't fire on input elements
1134
1137
  if (
1135
- e.target.tagName.toLowerCase() == 'input' ||
1136
- e.target.tagName.toLowerCase() == 'textarea'
1138
+ e.target.tagName.toLowerCase() == "input" ||
1139
+ e.target.tagName.toLowerCase() == "textarea"
1137
1140
  ) {
1138
1141
  return;
1139
- } else if (e.key == 'h') {
1142
+ } else if (e.key == "h") {
1140
1143
  menu.Toggle(true);
1141
1144
  }
1142
1145
  },
@@ -1144,98 +1147,98 @@ class Menu {
1144
1147
 
1145
1148
  // toggle auth key fields
1146
1149
  constants.events.push([
1147
- document.getElementById('LLM_model'),
1148
- 'change',
1150
+ document.getElementById("LLM_model"),
1151
+ "change",
1149
1152
  function (e) {
1150
- if (e.target.value == 'openai') {
1153
+ if (e.target.value == "openai") {
1151
1154
  document
1152
- .getElementById('openai_auth_key_container')
1153
- .classList.remove('hidden');
1155
+ .getElementById("openai_auth_key_container")
1156
+ .classList.remove("hidden");
1154
1157
  document
1155
- .getElementById('gemini_auth_key_container')
1156
- .classList.add('hidden');
1158
+ .getElementById("gemini_auth_key_container")
1159
+ .classList.add("hidden");
1157
1160
  document
1158
- .getElementById('openai_multi_container')
1159
- .classList.add('hidden');
1161
+ .getElementById("openai_multi_container")
1162
+ .classList.add("hidden");
1160
1163
  document
1161
- .getElementById('gemini_multi_container')
1162
- .classList.add('hidden');
1163
- document.getElementById('openai_multi').checked = true;
1164
- document.getElementById('gemini_multi').checked = false;
1165
- } else if (e.target.value == 'gemini') {
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") {
1166
1169
  document
1167
- .getElementById('openai_auth_key_container')
1168
- .classList.add('hidden');
1170
+ .getElementById("openai_auth_key_container")
1171
+ .classList.add("hidden");
1169
1172
  document
1170
- .getElementById('gemini_auth_key_container')
1171
- .classList.remove('hidden');
1173
+ .getElementById("gemini_auth_key_container")
1174
+ .classList.remove("hidden");
1172
1175
  document
1173
- .getElementById('openai_multi_container')
1174
- .classList.add('hidden');
1176
+ .getElementById("openai_multi_container")
1177
+ .classList.add("hidden");
1175
1178
  document
1176
- .getElementById('gemini_multi_container')
1177
- .classList.add('hidden');
1178
- document.getElementById('openai_multi').checked = false;
1179
- document.getElementById('gemini_multi').checked = true;
1180
- } else if (e.target.value == 'multi') {
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") {
1181
1184
  document
1182
- .getElementById('openai_auth_key_container')
1183
- .classList.remove('hidden');
1185
+ .getElementById("openai_auth_key_container")
1186
+ .classList.remove("hidden");
1184
1187
  document
1185
- .getElementById('gemini_auth_key_container')
1186
- .classList.remove('hidden');
1188
+ .getElementById("gemini_auth_key_container")
1189
+ .classList.remove("hidden");
1187
1190
  document
1188
- .getElementById('openai_multi_container')
1189
- .classList.remove('hidden');
1191
+ .getElementById("openai_multi_container")
1192
+ .classList.remove("hidden");
1190
1193
  document
1191
- .getElementById('gemini_multi_container')
1192
- .classList.remove('hidden');
1193
- document.getElementById('openai_multi').checked = true;
1194
- document.getElementById('gemini_multi').checked = true;
1194
+ .getElementById("gemini_multi_container")
1195
+ .classList.remove("hidden");
1196
+ document.getElementById("openai_multi").checked = true;
1197
+ document.getElementById("gemini_multi").checked = true;
1195
1198
  }
1196
1199
  },
1197
1200
  ]);
1198
1201
 
1199
1202
  constants.events.push([
1200
- document.getElementById('LLM_model_openai'),
1201
- 'change',
1203
+ document.getElementById("LLM_model_openai"),
1204
+ "change",
1202
1205
  function (e) {
1203
1206
  if (e.target.checked) {
1204
1207
  document
1205
- .getElementById('openai_auth_key_container')
1206
- .classList.remove('hidden');
1208
+ .getElementById("openai_auth_key_container")
1209
+ .classList.remove("hidden");
1207
1210
  } else {
1208
1211
  document
1209
- .getElementById('openai_auth_key_container')
1210
- .classList.add('hidden');
1212
+ .getElementById("openai_auth_key_container")
1213
+ .classList.add("hidden");
1211
1214
  }
1212
1215
  },
1213
1216
  ]);
1214
1217
 
1215
1218
  constants.events.push([
1216
- document.getElementById('LLM_model_gemini'),
1217
- 'change',
1219
+ document.getElementById("LLM_model_gemini"),
1220
+ "change",
1218
1221
  function (e) {
1219
1222
  if (e.target.checked) {
1220
1223
  document
1221
- .getElementById('gemini_auth_key_container')
1222
- .classList.remove('hidden');
1224
+ .getElementById("gemini_auth_key_container")
1225
+ .classList.remove("hidden");
1223
1226
  } else {
1224
1227
  document
1225
- .getElementById('gemini_auth_key_container')
1226
- .classList.add('hidden');
1228
+ .getElementById("gemini_auth_key_container")
1229
+ .classList.add("hidden");
1227
1230
  }
1228
1231
  },
1229
1232
  ]);
1230
1233
 
1231
1234
  constants.events.push([
1232
- document.getElementById('LLM_model_claude'),
1233
- 'change',
1235
+ document.getElementById("LLM_model_claude"),
1236
+ "change",
1234
1237
  function (e) {
1235
1238
  // if (e.target.checked) {
1236
1239
  document
1237
- .getElementById('claude_auth_key_container')
1238
- .classList.add('hidden');
1240
+ .getElementById("claude_auth_key_container")
1241
+ .classList.add("hidden");
1239
1242
  // } else {
1240
1243
  // document
1241
1244
  // .getElementById('claude_auth_key_container')
@@ -1246,17 +1249,17 @@ class Menu {
1246
1249
 
1247
1250
  // Skill level other events
1248
1251
  constants.events.push([
1249
- document.getElementById('skill_level'),
1250
- 'change',
1252
+ document.getElementById("skill_level"),
1253
+ "change",
1251
1254
  function (e) {
1252
- if (e.target.value == 'other') {
1255
+ if (e.target.value == "other") {
1253
1256
  document
1254
- .getElementById('skill_level_other_container')
1255
- .classList.remove('hidden');
1257
+ .getElementById("skill_level_other_container")
1258
+ .classList.remove("hidden");
1256
1259
  } else {
1257
1260
  document
1258
- .getElementById('skill_level_other_container')
1259
- .classList.add('hidden');
1261
+ .getElementById("skill_level_other_container")
1262
+ .classList.add("hidden");
1260
1263
  }
1261
1264
  },
1262
1265
  ]);
@@ -1264,16 +1267,16 @@ class Menu {
1264
1267
  // trigger notification that LLM will be reset
1265
1268
  // this is done on change of LLM model, multi settings, or skill level
1266
1269
  let LLMResetIds = [
1267
- 'LLM_model',
1268
- 'openai_multi',
1269
- 'gemini_multi',
1270
- 'skill_level',
1271
- 'LLM_preferences',
1270
+ "LLM_model",
1271
+ "openai_multi",
1272
+ "gemini_multi",
1273
+ "skill_level",
1274
+ "LLM_preferences",
1272
1275
  ];
1273
1276
  for (let i = 0; i < LLMResetIds.length; i++) {
1274
1277
  constants.events.push([
1275
1278
  document.getElementById(LLMResetIds[i]),
1276
- 'change',
1279
+ "change",
1277
1280
  function (e) {
1278
1281
  menu.NotifyOfLLMReset();
1279
1282
  },
@@ -1283,13 +1286,13 @@ class Menu {
1283
1286
  // Limit selections to 2 AI models
1284
1287
  const llmCheckboxes = document.querySelectorAll('input[name="LLM_model"]');
1285
1288
  llmCheckboxes.forEach((checkbox) => {
1286
- checkbox.addEventListener('change', () => {
1289
+ checkbox.addEventListener("change", () => {
1287
1290
  const checked = document.querySelectorAll(
1288
1291
  'input[name="LLM_model"]:checked'
1289
1292
  );
1290
1293
  if (checked.length > 2) {
1291
1294
  checkbox.checked = false;
1292
- alert('You can select up to 2 AI models.');
1295
+ alert("You can select up to 2 AI models.");
1293
1296
  }
1294
1297
  });
1295
1298
  });
@@ -1301,11 +1304,11 @@ class Menu {
1301
1304
  */
1302
1305
  Destroy() {
1303
1306
  // menu element destruction
1304
- let menu = document.getElementById('menu');
1307
+ let menu = document.getElementById("menu");
1305
1308
  if (menu) {
1306
1309
  menu.remove();
1307
1310
  }
1308
- let backdrop = document.getElementById('menu_modal_backdrop');
1311
+ let backdrop = document.getElementById("menu_modal_backdrop");
1309
1312
  if (backdrop) {
1310
1313
  backdrop.remove();
1311
1314
  }
@@ -1317,16 +1320,16 @@ class Menu {
1317
1320
  * @return {void}
1318
1321
  */
1319
1322
  Toggle(onoff = false) {
1320
- if (typeof onoff == 'undefined') {
1321
- if (document.getElementById('menu').classList.contains('hidden')) {
1323
+ if (typeof onoff == "undefined") {
1324
+ if (document.getElementById("menu").classList.contains("hidden")) {
1322
1325
  onoff = true;
1323
1326
  } else {
1324
1327
  onoff = false;
1325
1328
  }
1326
1329
  }
1327
1330
  // don't open if we have another modal open already
1328
- if (onoff && document.getElementById('chatLLM')) {
1329
- if (!document.getElementById('chatLLM').classList.contains('hidden')) {
1331
+ if (onoff && document.getElementById("chatLLM")) {
1332
+ if (!document.getElementById("chatLLM").classList.contains("hidden")) {
1330
1333
  return;
1331
1334
  }
1332
1335
  }
@@ -1335,13 +1338,13 @@ class Menu {
1335
1338
  this.whereWasMyFocus = document.activeElement;
1336
1339
  this.PopulateData();
1337
1340
  constants.tabMovement = 0;
1338
- document.getElementById('menu').classList.remove('hidden');
1339
- document.getElementById('menu_modal_backdrop').classList.remove('hidden');
1340
- document.querySelector('#menu .close').focus();
1341
+ document.getElementById("menu").classList.remove("hidden");
1342
+ document.getElementById("menu_modal_backdrop").classList.remove("hidden");
1343
+ document.querySelector("#menu .close").focus();
1341
1344
  } else {
1342
1345
  // close
1343
- document.getElementById('menu').classList.add('hidden');
1344
- document.getElementById('menu_modal_backdrop').classList.add('hidden');
1346
+ document.getElementById("menu").classList.add("hidden");
1347
+ document.getElementById("menu_modal_backdrop").classList.add("hidden");
1345
1348
  this.whereWasMyFocus.focus();
1346
1349
  this.whereWasMyFocus = null;
1347
1350
  }
@@ -1352,42 +1355,42 @@ class Menu {
1352
1355
  * @return {void}
1353
1356
  */
1354
1357
  PopulateData() {
1355
- document.getElementById('vol').value = constants.vol;
1356
- document.getElementById('braille_display_length').value =
1358
+ document.getElementById("vol").value = constants.vol;
1359
+ document.getElementById("braille_display_length").value =
1357
1360
  constants.brailleDisplayLength;
1358
- document.getElementById('color_selected').value = constants.colorSelected;
1359
- document.getElementById('min_freq').value = constants.MIN_FREQUENCY;
1360
- document.getElementById('max_freq').value = constants.MAX_FREQUENCY;
1361
- document.getElementById('AUTOPLAY_DURATION').value =
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 =
1362
1365
  constants.AUTOPLAY_DURATION;
1363
- if (typeof constants.openAIAuthKey == 'string') {
1364
- document.getElementById('openai_auth_key').value =
1366
+ if (typeof constants.openAIAuthKey == "string") {
1367
+ document.getElementById("openai_auth_key").value =
1365
1368
  constants.openAIAuthKey;
1366
1369
  }
1367
- if (typeof constants.emailAuthKey == 'string') {
1368
- document.getElementById('email_auth_key').value = constants.emailAuthKey;
1370
+ if (typeof constants.emailAuthKey == "string") {
1371
+ document.getElementById("email_auth_key").value = constants.emailAuthKey;
1369
1372
  }
1370
- if (typeof constants.geminiAuthKey == 'string') {
1371
- document.getElementById('gemini_auth_key').value =
1373
+ if (typeof constants.geminiAuthKey == "string") {
1374
+ document.getElementById("gemini_auth_key").value =
1372
1375
  constants.geminiAuthKey;
1373
1376
  }
1374
- if (typeof constants.claudeAuthKey == 'string') {
1375
- document.getElementById('claude_auth_key').value =
1377
+ if (typeof constants.claudeAuthKey == "string") {
1378
+ document.getElementById("claude_auth_key").value =
1376
1379
  constants.claudeAuthKey;
1377
1380
  }
1378
- document.getElementById('skill_level').value = constants.skillLevel;
1381
+ document.getElementById("skill_level").value = constants.skillLevel;
1379
1382
  if (constants.skillLevelOther) {
1380
- document.getElementById('skill_level_other').value =
1383
+ document.getElementById("skill_level_other").value =
1381
1384
  constants.skillLevelOther;
1382
1385
  }
1383
1386
 
1384
1387
  // aria mode
1385
- if (constants.ariaMode == 'assertive') {
1386
- document.getElementById('aria_mode_assertive').checked = true;
1387
- document.getElementById('aria_mode_polite').checked = false;
1388
+ if (constants.ariaMode == "assertive") {
1389
+ document.getElementById("aria_mode_assertive").checked = true;
1390
+ document.getElementById("aria_mode_polite").checked = false;
1388
1391
  } else {
1389
- document.getElementById('aria_mode_polite').checked = true;
1390
- document.getElementById('aria_mode_assertive').checked = false;
1392
+ document.getElementById("aria_mode_polite").checked = true;
1393
+ document.getElementById("aria_mode_assertive").checked = false;
1391
1394
  }
1392
1395
 
1393
1396
  for (let model in constants.LLMModels) {
@@ -1395,25 +1398,25 @@ class Menu {
1395
1398
 
1396
1399
  document
1397
1400
  .getElementById(`${model}_auth_key_container`)
1398
- .classList.remove('hidden');
1401
+ .classList.remove("hidden");
1399
1402
  }
1400
1403
  document
1401
1404
  .getElementById(`claude_auth_key_container`)
1402
- .classList.add('hidden');
1405
+ .classList.add("hidden");
1403
1406
 
1404
1407
  // skill level other
1405
- if (constants.skillLevel == 'other') {
1408
+ if (constants.skillLevel == "other") {
1406
1409
  document
1407
- .getElementById('skill_level_other_container')
1408
- .classList.remove('hidden');
1410
+ .getElementById("skill_level_other_container")
1411
+ .classList.remove("hidden");
1409
1412
  }
1410
1413
  // LLM preferences
1411
1414
  if (constants.LLMPreferences) {
1412
- document.getElementById('LLM_preferences').value =
1415
+ document.getElementById("LLM_preferences").value =
1413
1416
  constants.LLMPreferences;
1414
1417
  }
1415
- if (document.getElementById('LLM_reset_notification')) {
1416
- document.getElementById('LLM_reset_notification').remove();
1418
+ if (document.getElementById("LLM_reset_notification")) {
1419
+ document.getElementById("LLM_reset_notification").remove();
1417
1420
  }
1418
1421
  }
1419
1422
 
@@ -1424,23 +1427,23 @@ class Menu {
1424
1427
  SaveData() {
1425
1428
  let shouldReset = this.ShouldLLMReset();
1426
1429
 
1427
- constants.vol = document.getElementById('vol').value;
1430
+ constants.vol = document.getElementById("vol").value;
1428
1431
  constants.brailleDisplayLength = document.getElementById(
1429
- 'braille_display_length'
1432
+ "braille_display_length"
1430
1433
  ).value;
1431
- constants.colorSelected = document.getElementById('color_selected').value;
1432
- constants.MIN_FREQUENCY = document.getElementById('min_freq').value;
1433
- constants.MAX_FREQUENCY = document.getElementById('max_freq').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;
1434
1437
  constants.AUTOPLAY_DURATION =
1435
- document.getElementById('AUTOPLAY_DURATION').value;
1438
+ document.getElementById("AUTOPLAY_DURATION").value;
1436
1439
 
1437
- constants.openAIAuthKey = document.getElementById('openai_auth_key').value;
1438
- constants.geminiAuthKey = document.getElementById('gemini_auth_key').value;
1439
- constants.claudeAuthKey = document.getElementById('claude_auth_key').value;
1440
- constants.emailAuthKey = document.getElementById('email_auth_key').value;
1441
- constants.skillLevel = document.getElementById('skill_level').value;
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;
1442
1445
  constants.skillLevelOther =
1443
- document.getElementById('skill_level_other').value;
1446
+ document.getElementById("skill_level_other").value;
1444
1447
  // constants.LLMModel = document.getElementById('LLM_model').value;
1445
1448
 
1446
1449
  const llmCheckboxes = document.querySelectorAll('input[name="LLM_model"]');
@@ -1452,16 +1455,16 @@ class Menu {
1452
1455
  }
1453
1456
  });
1454
1457
 
1455
- constants.LLMPreferences = document.getElementById('LLM_preferences').value;
1456
- constants.LLMOpenAiMulti = document.getElementById('openai_multi').checked;
1457
- constants.LLMGeminiMulti = document.getElementById('gemini_multi').checked;
1458
- constants.autoInitLLM = document.getElementById('init_llm_on_load').checked;
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;
1459
1462
 
1460
1463
  // aria
1461
- if (document.getElementById('aria_mode_assertive').checked) {
1462
- constants.ariaMode = 'assertive';
1463
- } else if (document.getElementById('aria_mode_polite').checked) {
1464
- constants.ariaMode = 'polite';
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";
1465
1468
  }
1466
1469
 
1467
1470
  this.SaveDataToLocalStorage();
@@ -1475,8 +1478,8 @@ class Menu {
1475
1478
  }
1476
1479
 
1477
1480
  VerifyEmail() {
1478
- let email = document.getElementById('email_auth_key').value;
1479
- if (email && email.indexOf('@') !== -1) {
1481
+ let email = document.getElementById("email_auth_key").value;
1482
+ if (email && email.indexOf("@") !== -1) {
1480
1483
  let url = `https://maidr-service.azurewebsites.net/api/send_email?code=I8Aa2PlPspjQ8Hks0QzGyszP8_i2-XJ3bq7Xh8-ykEe4AzFuYn_QWA%3D%3D`;
1481
1484
 
1482
1485
  let requestJson = {
@@ -1484,9 +1487,9 @@ class Menu {
1484
1487
  };
1485
1488
 
1486
1489
  fetch(url, {
1487
- method: 'POST',
1490
+ method: "POST",
1488
1491
  headers: {
1489
- 'Content-Type': 'application/json',
1492
+ "Content-Type": "application/json",
1490
1493
  Authentication: constants.emailAuthKey,
1491
1494
  },
1492
1495
  body: JSON.stringify(requestJson),
@@ -1494,7 +1497,7 @@ class Menu {
1494
1497
  .then((response) => response.json())
1495
1498
  .then((data) => {
1496
1499
  if (data && data.success) {
1497
- alert('Link sent to email address: ' + email);
1500
+ alert("Link sent to email address: " + email);
1498
1501
  } else {
1499
1502
  console.log(data);
1500
1503
  alert(data.data);
@@ -1505,7 +1508,7 @@ class Menu {
1505
1508
  alert(error.data);
1506
1509
  });
1507
1510
  } else {
1508
- alert('Please enter a valid email address.');
1511
+ alert("Please enter a valid email address.");
1509
1512
  }
1510
1513
  }
1511
1514
 
@@ -1515,29 +1518,29 @@ class Menu {
1515
1518
  */
1516
1519
  UpdateHtml() {
1517
1520
  // set aria attributes
1518
- constants.infoDiv.setAttribute('aria-live', constants.ariaMode);
1521
+ constants.infoDiv.setAttribute("aria-live", constants.ariaMode);
1519
1522
  document
1520
1523
  .getElementById(constants.announcement_container_id)
1521
- .setAttribute('aria-live', constants.ariaMode);
1524
+ .setAttribute("aria-live", constants.ariaMode);
1522
1525
 
1523
- document.getElementById('init_llm_on_load').checked = constants.autoInitLLM;
1524
- const scatter = document.getElementsByClassName('highlight_point');
1525
- const heatmap = document.getElementById('highlight_rect');
1526
- const line = document.getElementById('highlight_point');
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");
1527
1530
 
1528
1531
  if (scatter !== null && scatter.length > 0) {
1529
1532
  for (let i = 0; i < scatter.length; i++) {
1530
- scatter[i].setAttribute('stroke', constants.colorSelected);
1531
- scatter[i].setAttribute('fill', constants.colorSelected);
1533
+ scatter[i].setAttribute("stroke", constants.colorSelected);
1534
+ scatter[i].setAttribute("fill", constants.colorSelected);
1532
1535
  }
1533
1536
  }
1534
1537
 
1535
1538
  if (heatmap !== null) {
1536
- heatmap.setAttribute('stroke', constants.colorSelected);
1539
+ heatmap.setAttribute("stroke", constants.colorSelected);
1537
1540
  }
1538
1541
 
1539
1542
  if (line !== null) {
1540
- line.setAttribute('stroke', constants.colorSelected);
1543
+ line.setAttribute("stroke", constants.colorSelected);
1541
1544
  }
1542
1545
  }
1543
1546
 
@@ -1549,19 +1552,19 @@ class Menu {
1549
1552
  let html =
1550
1553
  '<p id="LLM_reset_notification">Note: Changes in LLM settings will reset any existing conversation.</p>';
1551
1554
 
1552
- if (document.getElementById('LLM_reset_notification')) {
1553
- document.getElementById('LLM_reset_notification').remove();
1555
+ if (document.getElementById("LLM_reset_notification")) {
1556
+ document.getElementById("LLM_reset_notification").remove();
1554
1557
  }
1555
1558
  document
1556
- .getElementById('save_and_close_menu')
1557
- .parentElement.insertAdjacentHTML('afterend', html);
1559
+ .getElementById("save_and_close_menu")
1560
+ .parentElement.insertAdjacentHTML("afterend", html);
1558
1561
 
1559
1562
  // add to aria button text
1560
1563
  document
1561
- .getElementById('save_and_close_menu')
1564
+ .getElementById("save_and_close_menu")
1562
1565
  .setAttribute(
1563
- 'aria-labelledby',
1564
- 'save_and_close_text LLM_reset_notification'
1566
+ "aria-labelledby",
1567
+ "save_and_close_text LLM_reset_notification"
1565
1568
  );
1566
1569
  }
1567
1570
  /**
@@ -1573,20 +1576,20 @@ class Menu {
1573
1576
  let shouldReset = false;
1574
1577
  if (
1575
1578
  !shouldReset &&
1576
- constants.skillLevel != document.getElementById('skill_level').value
1579
+ constants.skillLevel != document.getElementById("skill_level").value
1577
1580
  ) {
1578
1581
  shouldReset = true;
1579
1582
  }
1580
1583
  if (
1581
1584
  !shouldReset &&
1582
1585
  constants.LLMPreferences !=
1583
- document.getElementById('LLM_preferences').value
1586
+ document.getElementById("LLM_preferences").value
1584
1587
  ) {
1585
1588
  shouldReset = true;
1586
1589
  }
1587
1590
  if (
1588
1591
  !shouldReset &&
1589
- constants.LLMModel != document.getElementById('LLM_model').value
1592
+ constants.LLMModel != document.getElementById("LLM_model").value
1590
1593
  ) {
1591
1594
  shouldReset = true;
1592
1595
  }
@@ -1615,24 +1618,24 @@ class Menu {
1615
1618
  data[constants.userSettingsKeys[i]] =
1616
1619
  constants[constants.userSettingsKeys[i]];
1617
1620
  }
1618
- localStorage.setItem('settings_data', JSON.stringify(data));
1621
+ localStorage.setItem("settings_data", JSON.stringify(data));
1619
1622
 
1620
1623
  // also save to tracking if we're doing that
1621
1624
  if (constants.canTrack) {
1622
1625
  // but not auth keys
1623
- data.openAIAuthKey = 'hidden';
1624
- data.geminiAuthKey = 'hidden';
1625
- data.claudeAuthKey = 'hidden';
1626
+ data.openAIAuthKey = "hidden";
1627
+ data.geminiAuthKey = "hidden";
1628
+ data.claudeAuthKey = "hidden";
1626
1629
  // and need a timestamp
1627
1630
  data.timestamp = new Date().toISOString();
1628
- tracker.SetData('settings', data);
1631
+ tracker.SetData("settings", data);
1629
1632
  }
1630
1633
  }
1631
1634
  /**
1632
1635
  * Loads data from 'settings_data' localStorage, and updates contants variables
1633
1636
  */
1634
1637
  LoadDataFromLocalStorage() {
1635
- let data = JSON.parse(localStorage.getItem('settings_data'));
1638
+ let data = JSON.parse(localStorage.getItem("settings_data"));
1636
1639
  if (data) {
1637
1640
  for (let i = 0; i < constants.userSettingsKeys.length; i++) {
1638
1641
  const key = constants.userSettingsKeys[i];
@@ -1660,9 +1663,9 @@ class ChatLLM {
1660
1663
  if (constants.autoInitLLM) {
1661
1664
  // only run if we have API keys set
1662
1665
  if (
1663
- ('gemini' in constants.LLMModels && constants.geminiAuthKey) ||
1664
- ('openai' in constants.LLMModels && constants.openAIAuthKey) ||
1665
- ('claude' in constants.LLMModels && constants.claudeAuthKey)
1666
+ ("gemini" in constants.LLMModels && constants.geminiAuthKey) ||
1667
+ ("openai" in constants.LLMModels && constants.openAIAuthKey) ||
1668
+ ("claude" in constants.LLMModels && constants.claudeAuthKey)
1666
1669
  ) {
1667
1670
  this.InitChatMessage();
1668
1671
  }
@@ -1715,7 +1718,7 @@ class ChatLLM {
1715
1718
  </div>
1716
1719
  <div id="chatLLM_modal_backdrop" class="modal-backdrop hidden"></div>
1717
1720
  `;
1718
- document.querySelector('body').insertAdjacentHTML('beforeend', html);
1721
+ document.querySelector("body").insertAdjacentHTML("beforeend", html);
1719
1722
  }
1720
1723
 
1721
1724
  /**
@@ -1724,21 +1727,21 @@ class ChatLLM {
1724
1727
  */
1725
1728
  SetEvents() {
1726
1729
  // chatLLM close events
1727
- let allClose = document.querySelectorAll('#close_chatLLM, #chatLLM .close');
1730
+ let allClose = document.querySelectorAll("#close_chatLLM, #chatLLM .close");
1728
1731
  for (let i = 0; i < allClose.length; i++) {
1729
1732
  constants.events.push([
1730
1733
  allClose[i],
1731
- 'click',
1734
+ "click",
1732
1735
  function (e) {
1733
1736
  chatLLM.Toggle(false);
1734
1737
  },
1735
1738
  ]);
1736
1739
  }
1737
1740
  constants.events.push([
1738
- document.getElementById('chatLLM'),
1739
- 'keyup',
1741
+ document.getElementById("chatLLM"),
1742
+ "keyup",
1740
1743
  function (e) {
1741
- if (e.key == 'Esc') {
1744
+ if (e.key == "Esc") {
1742
1745
  // esc
1743
1746
  chatLLM.Toggle(false);
1744
1747
  }
@@ -1748,9 +1751,9 @@ class ChatLLM {
1748
1751
  // ChatLLM open/close toggle
1749
1752
  constants.events.push([
1750
1753
  document,
1751
- 'keyup',
1754
+ "keyup",
1752
1755
  function (e) {
1753
- if ((e.key == '?' && (e.ctrlKey || e.metaKey)) || e.key == '¿') {
1756
+ if ((e.key == "?" && (e.ctrlKey || e.metaKey)) || e.key == "¿") {
1754
1757
  chatLLM.Toggle();
1755
1758
  }
1756
1759
  },
@@ -1758,21 +1761,21 @@ class ChatLLM {
1758
1761
 
1759
1762
  // ChatLLM request events
1760
1763
  constants.events.push([
1761
- document.getElementById('chatLLM_submit'),
1762
- 'click',
1764
+ document.getElementById("chatLLM_submit"),
1765
+ "click",
1763
1766
  function (e) {
1764
- let text = document.getElementById('chatLLM_input').value;
1765
- chatLLM.DisplayChatMessage('User', text);
1767
+ let text = document.getElementById("chatLLM_input").value;
1768
+ chatLLM.DisplayChatMessage("User", text);
1766
1769
  chatLLM.Submit(text);
1767
1770
  },
1768
1771
  ]);
1769
1772
  constants.events.push([
1770
- document.getElementById('chatLLM_input'),
1771
- 'keyup',
1773
+ document.getElementById("chatLLM_input"),
1774
+ "keyup",
1772
1775
  function (e) {
1773
- if (e.key == 'Enter' && !e.shiftKey) {
1774
- let text = document.getElementById('chatLLM_input').value;
1775
- chatLLM.DisplayChatMessage('User', text);
1776
+ if (e.key == "Enter" && !e.shiftKey) {
1777
+ let text = document.getElementById("chatLLM_input").value;
1778
+ chatLLM.DisplayChatMessage("User", text);
1776
1779
  chatLLM.Submit(text);
1777
1780
  }
1778
1781
  },
@@ -1781,15 +1784,15 @@ class ChatLLM {
1781
1784
  // ChatLLM suggestion events
1782
1785
  // actual suggestions:
1783
1786
  let suggestions = document.querySelectorAll(
1784
- '#chatLLM .LLM_suggestions button:not(#more_suggestions)'
1787
+ "#chatLLM .LLM_suggestions button:not(#more_suggestions)"
1785
1788
  );
1786
1789
  for (let i = 0; i < suggestions.length; i++) {
1787
1790
  constants.events.push([
1788
1791
  suggestions[i],
1789
- 'click',
1792
+ "click",
1790
1793
  function (e) {
1791
1794
  let text = e.target.innerHTML;
1792
- chatLLM.DisplayChatMessage('User', text);
1795
+ chatLLM.DisplayChatMessage("User", text);
1793
1796
  chatLLM.Submit(text);
1794
1797
  },
1795
1798
  ]);
@@ -1797,31 +1800,31 @@ class ChatLLM {
1797
1800
 
1798
1801
  // Delete OpenAI and Gemini keys
1799
1802
  constants.events.push([
1800
- document.getElementById('delete_openai_key'),
1801
- 'click',
1803
+ document.getElementById("delete_openai_key"),
1804
+ "click",
1802
1805
  function (e) {
1803
- document.getElementById('openai_auth_key').value = '';
1806
+ document.getElementById("openai_auth_key").value = "";
1804
1807
  },
1805
1808
  ]);
1806
1809
  constants.events.push([
1807
- document.getElementById('delete_email_key'),
1808
- 'click',
1810
+ document.getElementById("delete_email_key"),
1811
+ "click",
1809
1812
  function (e) {
1810
- document.getElementById('email_auth_key').value = '';
1813
+ document.getElementById("email_auth_key").value = "";
1811
1814
  },
1812
1815
  ]);
1813
1816
  constants.events.push([
1814
- document.getElementById('delete_gemini_key'),
1815
- 'click',
1817
+ document.getElementById("delete_gemini_key"),
1818
+ "click",
1816
1819
  function (e) {
1817
- document.getElementById('gemini_auth_key').value = '';
1820
+ document.getElementById("gemini_auth_key").value = "";
1818
1821
  },
1819
1822
  ]);
1820
1823
 
1821
1824
  // Reset chatLLM
1822
1825
  constants.events.push([
1823
- document.getElementById('reset_chatLLM'),
1824
- 'click',
1826
+ document.getElementById("reset_chatLLM"),
1827
+ "click",
1825
1828
  function (e) {
1826
1829
  chatLLM.ResetLLM();
1827
1830
  },
@@ -1829,15 +1832,15 @@ class ChatLLM {
1829
1832
 
1830
1833
  // copy to clipboard
1831
1834
  constants.events.push([
1832
- document.getElementById('chatLLM'),
1833
- 'click',
1835
+ document.getElementById("chatLLM"),
1836
+ "click",
1834
1837
  function (e) {
1835
1838
  chatLLM.CopyChatHistory(e);
1836
1839
  },
1837
1840
  ]);
1838
1841
  constants.events.push([
1839
- document.getElementById('chatLLM'),
1840
- 'keyup',
1842
+ document.getElementById("chatLLM"),
1843
+ "keyup",
1841
1844
  function (e) {
1842
1845
  chatLLM.CopyChatHistory(e);
1843
1846
  },
@@ -1855,96 +1858,96 @@ class ChatLLM {
1855
1858
  * @param {Event|undefined} e - The event that triggered the copy action. If undefined, the entire chat history is copied.
1856
1859
  */
1857
1860
  CopyChatHistory(e) {
1858
- let text = '';
1859
- if (typeof e == 'undefined') {
1861
+ let text = "";
1862
+ if (typeof e == "undefined") {
1860
1863
  // check for passthrough
1861
1864
  // get html of the full chat history
1862
- text = document.getElementById('chatLLM_chat_history').innerHTML;
1863
- } else if (e.type == 'click') {
1865
+ text = document.getElementById("chatLLM_chat_history").innerHTML;
1866
+ } else if (e.type == "click") {
1864
1867
  // check for buttons
1865
- if (e.target.id == 'chatLLM_copy_all') {
1868
+ if (e.target.id == "chatLLM_copy_all") {
1866
1869
  // get html of the full chat history
1867
- text = document.getElementById('chatLLM_chat_history').innerHTML;
1868
- } else if (e.target.classList.contains('chatLLM_message_copy_button')) {
1870
+ text = document.getElementById("chatLLM_chat_history").innerHTML;
1871
+ } else if (e.target.classList.contains("chatLLM_message_copy_button")) {
1869
1872
  // get the text of the element before the button
1870
- text = e.target.closest('p').previousElementSibling.innerHTML;
1873
+ text = e.target.closest("p").previousElementSibling.innerHTML;
1871
1874
  }
1872
- } else if (e.type == 'keyup') {
1875
+ } else if (e.type == "keyup") {
1873
1876
  // check for alt shift c or ctrl shift c
1874
- if (e.key == 'C' && (e.ctrlKey || e.metaKey || e.altKey) && e.shiftKey) {
1877
+ if (e.key == "C" && (e.ctrlKey || e.metaKey || e.altKey) && e.shiftKey) {
1875
1878
  e.preventDefault();
1876
1879
  // get the last message
1877
1880
  let elem = document.querySelector(
1878
- '#chatLLM_chat_history > .chatLLM_message_other:last-of-type'
1881
+ "#chatLLM_chat_history > .chatLLM_message_other:last-of-type"
1879
1882
  );
1880
1883
  if (elem) {
1881
1884
  text = elem.innerHTML;
1882
1885
  }
1883
1886
  } else if (
1884
- e.key == 'A' &&
1887
+ e.key == "A" &&
1885
1888
  (e.ctrlKey || e.metaKey || e.altKey) &&
1886
1889
  e.shiftKey
1887
1890
  ) {
1888
1891
  e.preventDefault();
1889
1892
  // get html of the full chat history
1890
- text = document.getElementById('chatLLM_chat_history').innerHTML;
1893
+ text = document.getElementById("chatLLM_chat_history").innerHTML;
1891
1894
  }
1892
1895
  }
1893
1896
 
1894
- if (text == '') {
1897
+ if (text == "") {
1895
1898
  return;
1896
1899
  } else {
1897
1900
  // clear the html, removing buttons etc
1898
- let cleanElems = document.createElement('div');
1901
+ let cleanElems = document.createElement("div");
1899
1902
  cleanElems.innerHTML = text;
1900
- let removeThese = cleanElems.querySelectorAll('.chatLLM_message_copy');
1903
+ let removeThese = cleanElems.querySelectorAll(".chatLLM_message_copy");
1901
1904
  removeThese.forEach((elem) => elem.remove());
1902
1905
 
1903
1906
  // convert from html to markdown
1904
1907
  let markdown = this.htmlToMarkdown(cleanElems);
1905
1908
  // this messes up a bit with spacing, so kill more than 2 newlines in a row
1906
- markdown = markdown.replace(/\n{3,}/g, '\n\n');
1909
+ markdown = markdown.replace(/\n{3,}/g, "\n\n");
1907
1910
 
1908
1911
  try {
1909
1912
  navigator.clipboard.writeText(markdown); // note: this fails if you're on the inspector. That's fine as it'll never happen to real users
1910
1913
  } catch (err) {
1911
- console.error('Failed to copy: ', err);
1914
+ console.error("Failed to copy: ", err);
1912
1915
  }
1913
1916
  return markdown;
1914
1917
  }
1915
1918
  }
1916
1919
 
1917
1920
  htmlToMarkdown(element) {
1918
- let markdown = '';
1921
+ let markdown = "";
1919
1922
 
1920
1923
  const convertElementToMarkdown = (element) => {
1921
1924
  switch (element.tagName) {
1922
- case 'H1':
1925
+ case "H1":
1923
1926
  return `# ${element.textContent}`;
1924
- case 'H2':
1927
+ case "H2":
1925
1928
  return `## ${element.textContent}`;
1926
- case 'H3':
1929
+ case "H3":
1927
1930
  return `### ${element.textContent}`;
1928
- case 'H4':
1931
+ case "H4":
1929
1932
  return `#### ${element.textContent}`;
1930
- case 'H5':
1933
+ case "H5":
1931
1934
  return `##### ${element.textContent}`;
1932
- case 'H6':
1935
+ case "H6":
1933
1936
  return `###### ${element.textContent}`;
1934
- case 'P':
1937
+ case "P":
1935
1938
  return element.textContent;
1936
- case 'DIV':
1939
+ case "DIV":
1937
1940
  // For divs, process each child and add newlines as needed
1938
1941
  return (
1939
1942
  Array.from(element.childNodes)
1940
1943
  .map((child) => convertElementToMarkdown(child))
1941
- .join('\n') + '\n\n'
1944
+ .join("\n") + "\n\n"
1942
1945
  );
1943
1946
  default:
1944
1947
  // For any other element, process its children recursively
1945
1948
  return Array.from(element.childNodes)
1946
1949
  .map((child) => convertElementToMarkdown(child))
1947
- .join('');
1950
+ .join("");
1948
1951
  }
1949
1952
  };
1950
1953
 
@@ -1952,7 +1955,7 @@ class ChatLLM {
1952
1955
  markdown += convertElementToMarkdown(element);
1953
1956
  } else if (
1954
1957
  element.nodeType === Node.TEXT_NODE &&
1955
- element.textContent.trim() !== ''
1958
+ element.textContent.trim() !== ""
1956
1959
  ) {
1957
1960
  markdown += element.textContent.trim();
1958
1961
  }
@@ -1977,14 +1980,14 @@ class ChatLLM {
1977
1980
 
1978
1981
  // 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
1979
1982
  if (
1980
- (this.firstOpen || 'gemini' in constants.LLMModels) &&
1983
+ (this.firstOpen || "gemini" in constants.LLMModels) &&
1981
1984
  !firsttime &&
1982
1985
  constants.verboseText.length > 0
1983
1986
  ) {
1984
1987
  text =
1985
1988
  "Here is the current position in the chart; no response necessarily needed, use this info only if it's relevant to future questions: " +
1986
1989
  constants.verboseText +
1987
- '. My question is: ' +
1990
+ ". My question is: " +
1988
1991
  text;
1989
1992
 
1990
1993
  this.firstOpen = false;
@@ -1995,9 +1998,9 @@ class ChatLLM {
1995
1998
  this.WaitingSound(true);
1996
1999
  }
1997
2000
 
1998
- if ('openai' in constants.LLMModels) {
2001
+ if ("openai" in constants.LLMModels) {
1999
2002
  if (firsttime) {
2000
- img = await this.ConvertSVGtoJPG(singleMaidr.id, 'openai');
2003
+ img = await this.ConvertSVGtoJPG(singleMaidr.id, "openai");
2001
2004
  }
2002
2005
  if (constants.openAIAuthKey) {
2003
2006
  chatLLM.OpenAIPrompt(text, img);
@@ -2005,9 +2008,9 @@ class ChatLLM {
2005
2008
  chatLLM.OpenAIPromptAPI(text, img);
2006
2009
  }
2007
2010
  }
2008
- if ('gemini' in constants.LLMModels) {
2011
+ if ("gemini" in constants.LLMModels) {
2009
2012
  if (firsttime) {
2010
- img = await this.ConvertSVGtoJPG(singleMaidr.id, 'gemini');
2013
+ img = await this.ConvertSVGtoJPG(singleMaidr.id, "gemini");
2011
2014
  }
2012
2015
  if (constants.geminiAuthKey) {
2013
2016
  chatLLM.GeminiPrompt(text, img);
@@ -2016,9 +2019,9 @@ class ChatLLM {
2016
2019
  }
2017
2020
  }
2018
2021
 
2019
- if ('claude' in constants.LLMModels) {
2022
+ if ("claude" in constants.LLMModels) {
2020
2023
  if (firsttime) {
2021
- img = await this.ConvertSVGtoJPG(singleMaidr.id, 'claude');
2024
+ img = await this.ConvertSVGtoJPG(singleMaidr.id, "claude");
2022
2025
  }
2023
2026
  if (constants.claudeAuthKey) {
2024
2027
  chatLLM.ClaudePrompt(text, img);
@@ -2083,7 +2086,7 @@ class ChatLLM {
2083
2086
  }, 30000);
2084
2087
 
2085
2088
  // set queue for multi
2086
- if (constants.LLMModel != 'multi') {
2089
+ if (constants.LLMModel != "multi") {
2087
2090
  constants.waitingQueue = 1;
2088
2091
  } else {
2089
2092
  constants.waitingQueue = 0;
@@ -2115,7 +2118,7 @@ class ChatLLM {
2115
2118
  // get name from resource]
2116
2119
  let LLMName = resources.GetString(constants.LLMModel);
2117
2120
  this.firstTime = false;
2118
- this.DisplayChatMessage(LLMName, resources.GetString('processing'), true);
2121
+ this.DisplayChatMessage(LLMName, resources.GetString("processing"), true);
2119
2122
  let defaultPrompt = this.GetDefaultPrompt();
2120
2123
  this.Submit(defaultPrompt, true);
2121
2124
  }
@@ -2127,47 +2130,47 @@ class ChatLLM {
2127
2130
  */
2128
2131
  ProcessLLMResponse(data, model) {
2129
2132
  chatLLM.WaitingSound(false);
2130
- let text = '';
2133
+ let text = "";
2131
2134
  let LLMName = resources.GetString(model);
2132
2135
 
2133
- if (model == 'openai') {
2136
+ if (model == "openai") {
2134
2137
  text = data.choices[0].message.content;
2135
2138
  let i = this.requestJson.messages.length;
2136
2139
  this.requestJson.messages[i] = {};
2137
- this.requestJson.messages[i].role = 'assistant';
2140
+ this.requestJson.messages[i].role = "assistant";
2138
2141
  this.requestJson.messages[i].content = text;
2139
2142
 
2140
2143
  if (data.error) {
2141
- chatLLM.DisplayChatMessage(LLMName, 'Error processing request.', true);
2144
+ chatLLM.DisplayChatMessage(LLMName, "Error processing request.", true);
2142
2145
  chatLLM.WaitingSound(false);
2143
2146
  } else {
2144
2147
  chatLLM.DisplayChatMessage(LLMName, text);
2145
2148
  }
2146
- } else if (model == 'gemini') {
2149
+ } else if (model == "gemini") {
2147
2150
  if (data.text()) {
2148
2151
  text = data.text();
2149
2152
  chatLLM.DisplayChatMessage(LLMName, text);
2150
2153
  } else {
2151
2154
  if (!data.error) {
2152
- data.error = 'Error processing request.';
2155
+ data.error = "Error processing request.";
2153
2156
  chatLLM.WaitingSound(false);
2154
2157
  }
2155
2158
  }
2156
2159
  if (data.error) {
2157
- chatLLM.DisplayChatMessage(LLMName, 'Error processing request.', true);
2160
+ chatLLM.DisplayChatMessage(LLMName, "Error processing request.", true);
2158
2161
  chatLLM.WaitingSound(false);
2159
2162
  } else {
2160
2163
  // todo: display actual response
2161
2164
  }
2162
2165
  }
2163
- if (model == 'claude') {
2164
- console.log('Claude response: ', data);
2166
+ if (model == "claude") {
2167
+ console.log("Claude response: ", data);
2165
2168
  if (data.text()) {
2166
2169
  text = data.text();
2167
2170
  chatLLM.DisplayChatMessage(LLMName, text);
2168
2171
  }
2169
2172
  if (data.error) {
2170
- chatLLM.DisplayChatMessage(LLMName, 'Error processing request.', true);
2173
+ chatLLM.DisplayChatMessage(LLMName, "Error processing request.", true);
2171
2174
  chatLLM.WaitingSound(false);
2172
2175
  }
2173
2176
  }
@@ -2175,7 +2178,7 @@ class ChatLLM {
2175
2178
  // if we're tracking, log the data
2176
2179
  if (constants.canTrack) {
2177
2180
  let chatHist = chatLLM.CopyChatHistory();
2178
- tracker.SetData('ChatHistory', chatHist);
2181
+ tracker.SetData("ChatHistory", chatHist);
2179
2182
  }
2180
2183
  }
2181
2184
 
@@ -2189,11 +2192,11 @@ class ChatLLM {
2189
2192
  if (this.requestJson.messages.length > 2) {
2190
2193
  // subsequent responses
2191
2194
  responseText = {
2192
- id: 'chatcmpl-8Y44iRCRrohYbAqm8rfBbJqTUADC7',
2193
- object: 'chat.completion',
2195
+ id: "chatcmpl-8Y44iRCRrohYbAqm8rfBbJqTUADC7",
2196
+ object: "chat.completion",
2194
2197
  created: 1703129508,
2195
2198
  //model: 'gpt-4-1106-vision-preview',
2196
- model: 'gpt4-o',
2199
+ model: "gpt4-o",
2197
2200
  usage: {
2198
2201
  prompt_tokens: 451,
2199
2202
  completion_tokens: 16,
@@ -2202,10 +2205,10 @@ class ChatLLM {
2202
2205
  choices: [
2203
2206
  {
2204
2207
  message: {
2205
- role: 'assistant',
2206
- content: 'A fake response from the LLM. Nice.',
2208
+ role: "assistant",
2209
+ content: "A fake response from the LLM. Nice.",
2207
2210
  },
2208
- finish_reason: 'length',
2211
+ finish_reason: "length",
2209
2212
  index: 0,
2210
2213
  },
2211
2214
  ],
@@ -2213,10 +2216,10 @@ class ChatLLM {
2213
2216
  } else {
2214
2217
  // first response
2215
2218
  responseText = {
2216
- id: 'chatcmpl-8Y44iRCRrohYbAqm8rfBbJqTUADC7',
2217
- object: 'chat.completion',
2219
+ id: "chatcmpl-8Y44iRCRrohYbAqm8rfBbJqTUADC7",
2220
+ object: "chat.completion",
2218
2221
  created: 1703129508,
2219
- model: 'gpt-4-1106-vision-preview',
2222
+ model: "gpt-4-1106-vision-preview",
2220
2223
  usage: {
2221
2224
  prompt_tokens: 451,
2222
2225
  completion_tokens: 16,
@@ -2225,11 +2228,11 @@ class ChatLLM {
2225
2228
  choices: [
2226
2229
  {
2227
2230
  message: {
2228
- role: 'assistant',
2231
+ role: "assistant",
2229
2232
  content:
2230
- 'The chart you\'re referring to is a bar graph titled "The Number of Diamonds',
2233
+ "The chart you're referring to is a bar graph titled \"The Number of Diamonds",
2231
2234
  },
2232
- finish_reason: 'length',
2235
+ finish_reason: "length",
2233
2236
  index: 0,
2234
2237
  },
2235
2238
  ],
@@ -2240,7 +2243,7 @@ class ChatLLM {
2240
2243
  }
2241
2244
 
2242
2245
  ClaudeJson(text, img = null) {
2243
- const anthropicVersion = 'vertex-2023-10-16';
2246
+ const anthropicVersion = "vertex-2023-10-16";
2244
2247
  const maxTokens = 256;
2245
2248
 
2246
2249
  const payload = {
@@ -2251,7 +2254,7 @@ class ChatLLM {
2251
2254
 
2252
2255
  // Construct the user message object
2253
2256
  const userMessage = {
2254
- role: 'user',
2257
+ role: "user",
2255
2258
  content: [],
2256
2259
  };
2257
2260
 
@@ -2259,22 +2262,22 @@ class ChatLLM {
2259
2262
  if (img) {
2260
2263
  userMessage.content.push(
2261
2264
  {
2262
- type: 'image',
2265
+ type: "image",
2263
2266
  source: {
2264
- type: 'base64',
2265
- media_type: 'image/jpeg', // Update if other formats are supported
2267
+ type: "base64",
2268
+ media_type: "image/jpeg", // Update if other formats are supported
2266
2269
  data: img,
2267
2270
  },
2268
2271
  },
2269
2272
  {
2270
- type: 'text',
2273
+ type: "text",
2271
2274
  text: text,
2272
2275
  }
2273
2276
  );
2274
2277
  } else {
2275
2278
  // Add only the text content if no image is provided
2276
2279
  userMessage.content.push({
2277
- type: 'text',
2280
+ type: "text",
2278
2281
  text: text,
2279
2282
  });
2280
2283
  }
@@ -2286,16 +2289,16 @@ class ChatLLM {
2286
2289
  }
2287
2290
 
2288
2291
  ClaudePromptAPI(text, imgBase64 = null) {
2289
- console.log('Claude prompt API');
2292
+ console.log("Claude prompt API");
2290
2293
  let url =
2291
- 'https://maidr-service.azurewebsites.net/api/claude?code=I8Aa2PlPspjQ8Hks0QzGyszP8_i2-XJ3bq7Xh8-ykEe4AzFuYn_QWA%3D%3D';
2294
+ "https://maidr-service.azurewebsites.net/api/claude?code=I8Aa2PlPspjQ8Hks0QzGyszP8_i2-XJ3bq7Xh8-ykEe4AzFuYn_QWA%3D%3D";
2292
2295
 
2293
2296
  // Create the prompt
2294
2297
  let prompt = constants.LLMSystemMessage;
2295
2298
  if (constants.LLMPreferences) {
2296
2299
  prompt += constants.LLMPreferences;
2297
2300
  }
2298
- prompt += '\n\n' + text; // Use the text parameter as the prompt
2301
+ prompt += "\n\n" + text; // Use the text parameter as the prompt
2299
2302
 
2300
2303
  if (imgBase64 == null) {
2301
2304
  imgBase64 = constants.LLMImage;
@@ -2307,9 +2310,9 @@ class ChatLLM {
2307
2310
  let requestJson = chatLLM.ClaudeJson(prompt, imgBase64);
2308
2311
 
2309
2312
  fetch(url, {
2310
- method: 'POST',
2313
+ method: "POST",
2311
2314
  headers: {
2312
- 'Content-Type': 'application/json',
2315
+ "Content-Type": "application/json",
2313
2316
  Authentication: constants.emailAuthKey,
2314
2317
  },
2315
2318
  body: JSON.stringify(requestJson),
@@ -2319,12 +2322,12 @@ class ChatLLM {
2319
2322
  data.text = function () {
2320
2323
  return data.content[0].text;
2321
2324
  };
2322
- chatLLM.ProcessLLMResponse(data, 'claude');
2325
+ chatLLM.ProcessLLMResponse(data, "claude");
2323
2326
  })
2324
2327
  .catch((error) => {
2325
2328
  chatLLM.WaitingSound(false);
2326
- console.error('Error:', error);
2327
- chatLLM.DisplayChatMessage('Claude', 'Error processing request.', true);
2329
+ console.error("Error:", error);
2330
+ chatLLM.DisplayChatMessage("Claude", "Error processing request.", true);
2328
2331
  // also todo: handle errors somehow
2329
2332
  });
2330
2333
  }
@@ -2338,27 +2341,27 @@ class ChatLLM {
2338
2341
  */
2339
2342
  OpenAIPrompt(text, img = null) {
2340
2343
  // request init
2341
- let url = 'https://api.openai.com/v1/chat/completions';
2344
+ let url = "https://api.openai.com/v1/chat/completions";
2342
2345
  let auth = constants.openAIAuthKey;
2343
2346
  let requestJson = chatLLM.OpenAIJson(text, img);
2344
2347
  //console.log('LLM request: ', requestJson);
2345
2348
 
2346
2349
  fetch(url, {
2347
- method: 'POST',
2350
+ method: "POST",
2348
2351
  headers: {
2349
- 'Content-Type': 'application/json',
2350
- Authorization: 'Bearer ' + auth,
2352
+ "Content-Type": "application/json",
2353
+ Authorization: "Bearer " + auth,
2351
2354
  },
2352
2355
  body: JSON.stringify(requestJson),
2353
2356
  })
2354
2357
  .then((response) => response.json())
2355
2358
  .then((data) => {
2356
- chatLLM.ProcessLLMResponse(data, 'openai');
2359
+ chatLLM.ProcessLLMResponse(data, "openai");
2357
2360
  })
2358
2361
  .catch((error) => {
2359
2362
  chatLLM.WaitingSound(false);
2360
- console.error('Error:', error);
2361
- chatLLM.DisplayChatMessage('OpenAI', 'Error processing request.', true);
2363
+ console.error("Error:", error);
2364
+ chatLLM.DisplayChatMessage("OpenAI", "Error processing request.", true);
2362
2365
  // also todo: handle errors somehow
2363
2366
  });
2364
2367
  }
@@ -2366,27 +2369,27 @@ class ChatLLM {
2366
2369
  OpenAIPromptAPI(text, img = null) {
2367
2370
  // request init
2368
2371
  let url =
2369
- 'https://maidr-service.azurewebsites.net/api/openai?code=I8Aa2PlPspjQ8Hks0QzGyszP8_i2-XJ3bq7Xh8-ykEe4AzFuYn_QWA%3D%3D';
2372
+ "https://maidr-service.azurewebsites.net/api/openai?code=I8Aa2PlPspjQ8Hks0QzGyszP8_i2-XJ3bq7Xh8-ykEe4AzFuYn_QWA%3D%3D";
2370
2373
  let auth = constants.openAIAuthKey;
2371
2374
  let requestJson = chatLLM.OpenAIJson(text, img);
2372
- console.log('LLM request: ', requestJson);
2375
+ console.log("LLM request: ", requestJson);
2373
2376
 
2374
2377
  fetch(url, {
2375
- method: 'POST',
2378
+ method: "POST",
2376
2379
  headers: {
2377
- 'Content-Type': 'application/json',
2380
+ "Content-Type": "application/json",
2378
2381
  Authentication: constants.emailAuthKey,
2379
2382
  },
2380
2383
  body: JSON.stringify(requestJson),
2381
2384
  })
2382
2385
  .then((response) => response.json())
2383
2386
  .then((data) => {
2384
- chatLLM.ProcessLLMResponse(data, 'openai');
2387
+ chatLLM.ProcessLLMResponse(data, "openai");
2385
2388
  })
2386
2389
  .catch((error) => {
2387
2390
  chatLLM.WaitingSound(false);
2388
- console.error('Error:', error);
2389
- chatLLM.DisplayChatMessage('OpenAI', 'Error processing request.', true);
2391
+ console.error("Error:", error);
2392
+ chatLLM.DisplayChatMessage("OpenAI", "Error processing request.", true);
2390
2393
  // also todo: handle errors somehow
2391
2394
  });
2392
2395
  }
@@ -2394,22 +2397,22 @@ class ChatLLM {
2394
2397
  OpenAIJson(text, img = null) {
2395
2398
  let sysMessage = constants.LLMSystemMessage;
2396
2399
  let backupMessage =
2397
- 'Describe ' + singleMaidr.type + ' charts to a blind person';
2400
+ "Describe " + singleMaidr.type + " charts to a blind person";
2398
2401
  // headers and sys message
2399
2402
  if (!this.requestJson) {
2400
2403
  this.requestJson = {};
2401
2404
  //this.requestJson.model = 'gpt-4-vision-preview';
2402
- this.requestJson.model = 'gpt-4o-2024-08-06';
2405
+ this.requestJson.model = "gpt-4o-2024-11-20";
2403
2406
  this.requestJson.max_tokens = constants.LLMmaxResponseTokens; // note: if this is too short (tested with less than 200), the response gets cut off
2404
2407
 
2405
2408
  // sys message
2406
2409
  this.requestJson.messages = [];
2407
2410
  this.requestJson.messages[0] = {};
2408
- this.requestJson.messages[0].role = 'system';
2411
+ this.requestJson.messages[0].role = "system";
2409
2412
  this.requestJson.messages[0].content = sysMessage;
2410
2413
  if (constants.LLMPreferences) {
2411
2414
  this.requestJson.messages[1] = {};
2412
- this.requestJson.messages[1].role = 'system';
2415
+ this.requestJson.messages[1].role = "system";
2413
2416
  this.requestJson.messages[1].content = constants.LLMPreferences;
2414
2417
  }
2415
2418
  }
@@ -2418,16 +2421,16 @@ class ChatLLM {
2418
2421
  // if we have an image (first time only), send the image and the text, otherwise just the text
2419
2422
  let i = this.requestJson.messages.length;
2420
2423
  this.requestJson.messages[i] = {};
2421
- this.requestJson.messages[i].role = 'user';
2424
+ this.requestJson.messages[i].role = "user";
2422
2425
  if (img) {
2423
2426
  // first message, include the img
2424
2427
  this.requestJson.messages[i].content = [
2425
2428
  {
2426
- type: 'text',
2429
+ type: "text",
2427
2430
  text: text,
2428
2431
  },
2429
2432
  {
2430
- type: 'image_url',
2433
+ type: "image_url",
2431
2434
  image_url: { url: img },
2432
2435
  },
2433
2436
  ];
@@ -2442,7 +2445,7 @@ class ChatLLM {
2442
2445
  GeminiJson(text, img = null) {
2443
2446
  let sysMessage = constants.LLMSystemMessage;
2444
2447
  let backupMessage =
2445
- 'Describe ' + singleMaidr.type + ' charts to a blind person';
2448
+ "Describe " + singleMaidr.type + " charts to a blind person";
2446
2449
 
2447
2450
  let payload = {
2448
2451
  generationConfig: {},
@@ -2452,7 +2455,7 @@ class ChatLLM {
2452
2455
 
2453
2456
  // System message as the initial "role" and "text" content for context
2454
2457
  let sysContent = {
2455
- role: 'user',
2458
+ role: "user",
2456
2459
  parts: [
2457
2460
  {
2458
2461
  text: sysMessage || backupMessage, // Fallback if sysMessage is unavailable
@@ -2471,7 +2474,7 @@ class ChatLLM {
2471
2474
 
2472
2475
  // Add user input content, including image if available
2473
2476
  let userContent = {
2474
- role: 'user',
2477
+ role: "user",
2475
2478
  parts: [],
2476
2479
  };
2477
2480
 
@@ -2484,7 +2487,7 @@ class ChatLLM {
2484
2487
  {
2485
2488
  inlineData: {
2486
2489
  data: img, // Expecting base64-encoded image data
2487
- mimeType: 'image/png', // Adjust if different image formats are possible
2490
+ mimeType: "image/png", // Adjust if different image formats are possible
2488
2491
  },
2489
2492
  }
2490
2493
  );
@@ -2503,14 +2506,14 @@ class ChatLLM {
2503
2506
 
2504
2507
  async GeminiPromptAPI(text, imgBase64 = null) {
2505
2508
  let url =
2506
- 'https://maidr-service.azurewebsites.net/api/gemini?code=I8Aa2PlPspjQ8Hks0QzGyszP8_i2-XJ3bq7Xh8-ykEe4AzFuYn_QWA%3D%3D';
2509
+ "https://maidr-service.azurewebsites.net/api/gemini?code=I8Aa2PlPspjQ8Hks0QzGyszP8_i2-XJ3bq7Xh8-ykEe4AzFuYn_QWA%3D%3D";
2507
2510
 
2508
2511
  // Create the prompt
2509
2512
  let prompt = constants.LLMSystemMessage;
2510
2513
  if (constants.LLMPreferences) {
2511
2514
  prompt += constants.LLMPreferences;
2512
2515
  }
2513
- prompt += '\n\n' + text; // Use the text parameter as the prompt
2516
+ prompt += "\n\n" + text; // Use the text parameter as the prompt
2514
2517
 
2515
2518
  if (imgBase64 == null) {
2516
2519
  imgBase64 = constants.LLMImage;
@@ -2522,9 +2525,9 @@ class ChatLLM {
2522
2525
  let requestJson = chatLLM.GeminiJson(prompt, imgBase64);
2523
2526
 
2524
2527
  const response = await fetch(url, {
2525
- method: 'POST',
2528
+ method: "POST",
2526
2529
  headers: {
2527
- 'Content-Type': 'application/json',
2530
+ "Content-Type": "application/json",
2528
2531
  Authentication: constants.emailAuthKey,
2529
2532
  },
2530
2533
  body: JSON.stringify(requestJson),
@@ -2534,11 +2537,11 @@ class ChatLLM {
2534
2537
  responseJson.text = () => {
2535
2538
  return responseJson.candidates[0].content.parts[0].text;
2536
2539
  };
2537
- chatLLM.ProcessLLMResponse(responseJson, 'gemini');
2540
+ chatLLM.ProcessLLMResponse(responseJson, "gemini");
2538
2541
  } else {
2539
2542
  chatLLM.WaitingSound(false);
2540
- console.error('Error:', error);
2541
- chatLLM.DisplayChatMessage('OpenAI', 'Error processing request.', true);
2543
+ console.error("Error:", error);
2544
+ chatLLM.DisplayChatMessage("OpenAI", "Error processing request.", true);
2542
2545
  // also todo: handle errors somehow
2543
2546
  }
2544
2547
  }
@@ -2556,12 +2559,12 @@ class ChatLLM {
2556
2559
 
2557
2560
  // Import the module
2558
2561
  const { GoogleGenerativeAI } = await import(
2559
- 'https://esm.run/@google/generative-ai'
2562
+ "https://esm.run/@google/generative-ai"
2560
2563
  );
2561
2564
  const API_KEY = constants.geminiAuthKey;
2562
2565
  const genAI = new GoogleGenerativeAI(API_KEY);
2563
2566
  const model = genAI.getGenerativeModel({
2564
- model: 'gemini-1.5-pro-latest',
2567
+ model: "gemini-1.5-pro-latest",
2565
2568
  }); // old model was 'gemini-pro-vision'
2566
2569
 
2567
2570
  // Create the prompt
@@ -2569,11 +2572,11 @@ class ChatLLM {
2569
2572
  if (constants.LLMPreferences) {
2570
2573
  prompt += constants.LLMPreferences;
2571
2574
  }
2572
- prompt += '\n\n' + text; // Use the text parameter as the prompt
2575
+ prompt += "\n\n" + text; // Use the text parameter as the prompt
2573
2576
  const image = {
2574
2577
  inlineData: {
2575
2578
  data: imgBase64, // Use the base64 image string
2576
- mimeType: 'image/png', // Or the appropriate mime type of your image
2579
+ mimeType: "image/png", // Or the appropriate mime type of your image
2577
2580
  },
2578
2581
  };
2579
2582
 
@@ -2583,11 +2586,11 @@ class ChatLLM {
2583
2586
  //console.log(result.response.text());
2584
2587
 
2585
2588
  // Process the response
2586
- chatLLM.ProcessLLMResponse(result.response, 'gemini');
2589
+ chatLLM.ProcessLLMResponse(result.response, "gemini");
2587
2590
  } catch (error) {
2588
2591
  chatLLM.WaitingSound(false);
2589
- chatLLM.DisplayChatMessage('Gemini', 'Error processing request.', true);
2590
- console.error('Error in GeminiPrompt:', error);
2592
+ chatLLM.DisplayChatMessage("Gemini", "Error processing request.", true);
2593
+ console.error("Error in GeminiPrompt:", error);
2591
2594
  throw error; // Rethrow the error for further handling if necessary
2592
2595
  }
2593
2596
  }
@@ -2599,11 +2602,11 @@ class ChatLLM {
2599
2602
  * @memberof module:constants
2600
2603
  * @returns {void}
2601
2604
  */
2602
- DisplayChatMessage(user = 'User', text = '', isSystem = false) {
2603
- let hLevel = 'h3';
2604
- if (!isSystem && constants.LLMModel == 'multi' && user != 'User') {
2605
+ DisplayChatMessage(user = "User", text = "", isSystem = false) {
2606
+ let hLevel = "h3";
2607
+ if (!isSystem && constants.LLMModel == "multi" && user != "User") {
2605
2608
  if (this.firstMulti) {
2606
- let multiAIName = resources.GetString('multi');
2609
+ let multiAIName = resources.GetString("multi");
2607
2610
  let titleHtml = `
2608
2611
  <div class="chatLLM_message chatLLM_message_other">
2609
2612
  <h3 class="chatLLM_message_user">${multiAIName} Responses</h3>
@@ -2612,20 +2615,20 @@ class ChatLLM {
2612
2615
  this.RenderChatMessage(titleHtml);
2613
2616
  this.firstMulti = false;
2614
2617
  }
2615
- hLevel = 'h4';
2618
+ hLevel = "h4";
2616
2619
  }
2617
2620
  let html = `
2618
2621
  <div class="chatLLM_message ${
2619
- user == 'User' ? 'chatLLM_message_self' : 'chatLLM_message_other'
2622
+ user == "User" ? "chatLLM_message_self" : "chatLLM_message_other"
2620
2623
  }">`;
2621
- if (text != resources.GetString('processing')) {
2624
+ if (text != resources.GetString("processing")) {
2622
2625
  html += `<${hLevel} class="chatLLM_message_user">${user}</${hLevel}>`;
2623
2626
  }
2624
2627
  html += `<p class="chatLLM_message_text">${text}</p>
2625
2628
  </div>
2626
2629
  `;
2627
2630
  // add a copy button to actual messages
2628
- if (user != 'User' && text != resources.GetString('processing')) {
2631
+ if (user != "User" && text != resources.GetString("processing")) {
2629
2632
  html += `
2630
2633
  <p class="chatLLM_message_copy"><button class="chatLLM_message_copy_button">Copy</button></p>
2631
2634
  `;
@@ -2635,13 +2638,13 @@ class ChatLLM {
2635
2638
  }
2636
2639
  RenderChatMessage(html) {
2637
2640
  document
2638
- .getElementById('chatLLM_chat_history')
2639
- .insertAdjacentHTML('beforeend', html);
2640
- document.getElementById('chatLLM_input').value = '';
2641
+ .getElementById("chatLLM_chat_history")
2642
+ .insertAdjacentHTML("beforeend", html);
2643
+ document.getElementById("chatLLM_input").value = "";
2641
2644
 
2642
2645
  // scroll to bottom
2643
- document.getElementById('chatLLM_chat_history').scrollTop =
2644
- document.getElementById('chatLLM_chat_history').scrollHeight;
2646
+ document.getElementById("chatLLM_chat_history").scrollTop =
2647
+ document.getElementById("chatLLM_chat_history").scrollHeight;
2645
2648
  }
2646
2649
 
2647
2650
  /**
@@ -2649,7 +2652,7 @@ class ChatLLM {
2649
2652
  */
2650
2653
  ResetLLM() {
2651
2654
  // clear the main chat history
2652
- document.getElementById('chatLLM_chat_history').innerHTML = '';
2655
+ document.getElementById("chatLLM_chat_history").innerHTML = "";
2653
2656
 
2654
2657
  // reset the data
2655
2658
  this.requestJson = null;
@@ -2670,11 +2673,11 @@ class ChatLLM {
2670
2673
  */
2671
2674
  Destroy() {
2672
2675
  // chatLLM element destruction
2673
- let chatLLM = document.getElementById('chatLLM');
2676
+ let chatLLM = document.getElementById("chatLLM");
2674
2677
  if (chatLLM) {
2675
2678
  chatLLM.remove();
2676
2679
  }
2677
- let backdrop = document.getElementById('chatLLM_modal_backdrop');
2680
+ let backdrop = document.getElementById("chatLLM_modal_backdrop");
2678
2681
  if (backdrop) {
2679
2682
  backdrop.remove();
2680
2683
  }
@@ -2685,8 +2688,8 @@ class ChatLLM {
2685
2688
  * @param {boolean} [onoff=false] - Whether to turn the chatLLM on or off. Defaults to false (close).
2686
2689
  */
2687
2690
  Toggle(onoff) {
2688
- if (typeof onoff == 'undefined') {
2689
- if (document.getElementById('chatLLM').classList.contains('hidden')) {
2691
+ if (typeof onoff == "undefined") {
2692
+ if (document.getElementById("chatLLM").classList.contains("hidden")) {
2690
2693
  onoff = true;
2691
2694
  } else {
2692
2695
  onoff = false;
@@ -2697,19 +2700,19 @@ class ChatLLM {
2697
2700
  // open
2698
2701
  this.whereWasMyFocus = document.activeElement;
2699
2702
  constants.tabMovement = 0;
2700
- document.getElementById('chatLLM').classList.remove('hidden');
2703
+ document.getElementById("chatLLM").classList.remove("hidden");
2701
2704
  document
2702
- .getElementById('chatLLM_modal_backdrop')
2703
- .classList.remove('hidden');
2704
- document.querySelector('#chatLLM .close').focus();
2705
+ .getElementById("chatLLM_modal_backdrop")
2706
+ .classList.remove("hidden");
2707
+ document.querySelector("#chatLLM .close").focus();
2705
2708
 
2706
2709
  if (this.firstTime) {
2707
2710
  this.InitChatMessage();
2708
2711
  }
2709
2712
  } else {
2710
2713
  // close
2711
- document.getElementById('chatLLM').classList.add('hidden');
2712
- document.getElementById('chatLLM_modal_backdrop').classList.add('hidden');
2714
+ document.getElementById("chatLLM").classList.add("hidden");
2715
+ document.getElementById("chatLLM_modal_backdrop").classList.add("hidden");
2713
2716
  this.whereWasMyFocus.focus();
2714
2717
  this.whereWasMyFocus = null;
2715
2718
  this.firstOpen = true;
@@ -2723,11 +2726,11 @@ class ChatLLM {
2723
2726
  async ConvertSVGtoJPG(id, model) {
2724
2727
  let svgElement = document.getElementById(id);
2725
2728
  return new Promise((resolve, reject) => {
2726
- var canvas = document.createElement('canvas');
2727
- var ctx = canvas.getContext('2d');
2729
+ var canvas = document.createElement("canvas");
2730
+ var ctx = canvas.getContext("2d");
2728
2731
 
2729
2732
  var svgData = new XMLSerializer().serializeToString(svgElement);
2730
- if (!svgData.startsWith('<svg xmlns')) {
2733
+ if (!svgData.startsWith("<svg xmlns")) {
2731
2734
  svgData = `<svg xmlns="http://www.w3.org/2000/svg" ${svgData.slice(4)}`;
2732
2735
  }
2733
2736
 
@@ -2739,11 +2742,11 @@ class ChatLLM {
2739
2742
  var img = new Image();
2740
2743
  img.onload = function () {
2741
2744
  ctx.drawImage(img, 0, 0, svgSize.width, svgSize.height);
2742
- var jpegData = canvas.toDataURL('image/jpeg', 0.9); // 0.9 is the quality parameter
2743
- if (model == 'openai') {
2745
+ var jpegData = canvas.toDataURL("image/jpeg", 0.9); // 0.9 is the quality parameter
2746
+ if (model == "openai") {
2744
2747
  resolve(jpegData);
2745
- } else if (model == 'gemini' || model == 'claude') {
2746
- let base64Data = jpegData.split(',')[1];
2748
+ } else if (model == "gemini" || model == "claude") {
2749
+ let base64Data = jpegData.split(",")[1];
2747
2750
  resolve(base64Data);
2748
2751
  //resolve(jpegData);
2749
2752
  }
@@ -2751,11 +2754,11 @@ class ChatLLM {
2751
2754
  };
2752
2755
 
2753
2756
  img.onerror = function () {
2754
- reject(new Error('Error loading SVG'));
2757
+ reject(new Error("Error loading SVG"));
2755
2758
  };
2756
2759
 
2757
2760
  var svgBlob = new Blob([svgData], {
2758
- type: 'image/svg+xml;charset=utf-8',
2761
+ type: "image/svg+xml;charset=utf-8",
2759
2762
  });
2760
2763
  var url = URL.createObjectURL(svgBlob);
2761
2764
  img.src = url;
@@ -2768,25 +2771,25 @@ class ChatLLM {
2768
2771
  * The prompt includes information about the blind person's skill level and the chart's image and raw data, if available.
2769
2772
  */
2770
2773
  GetDefaultPrompt() {
2771
- let text = 'Describe this chart to a blind person';
2774
+ let text = "Describe this chart to a blind person";
2772
2775
  if (constants.skillLevel) {
2773
- if (constants.skillLevel == 'other' && constants.skillLevelOther) {
2776
+ if (constants.skillLevel == "other" && constants.skillLevelOther) {
2774
2777
  text +=
2775
- ' who has a ' +
2778
+ " who has a " +
2776
2779
  constants.skillLevelOther +
2777
- ' understanding of statistical charts. ';
2780
+ " understanding of statistical charts. ";
2778
2781
  } else {
2779
2782
  text +=
2780
- ' who has a ' +
2783
+ " who has a " +
2781
2784
  constants.skillLevel +
2782
- ' understanding of statistical charts. ';
2785
+ " understanding of statistical charts. ";
2783
2786
  }
2784
2787
  } else {
2785
- text += ' who has a basic understanding of statistical charts. ';
2788
+ text += " who has a basic understanding of statistical charts. ";
2786
2789
  }
2787
- text += 'Here is a chart in image format';
2790
+ text += "Here is a chart in image format";
2788
2791
  if (singleMaidr) {
2789
- text += ' and raw data in json format: \n';
2792
+ text += " and raw data in json format: \n";
2790
2793
  text += JSON.stringify(singleMaidr);
2791
2794
  }
2792
2795
 
@@ -2841,26 +2844,26 @@ class Description {
2841
2844
 
2842
2845
  `;
2843
2846
 
2844
- document.querySelector('body').insertAdjacentHTML('beforeend', html);
2847
+ document.querySelector("body").insertAdjacentHTML("beforeend", html);
2845
2848
 
2846
2849
  // close events
2847
2850
  let allClose = document.querySelectorAll(
2848
- '#close_desc, #description .close'
2851
+ "#close_desc, #description .close"
2849
2852
  );
2850
2853
  for (let i = 0; i < allClose.length; i++) {
2851
2854
  constants.events.push([
2852
2855
  allClose[i],
2853
- 'click',
2856
+ "click",
2854
2857
  function (e) {
2855
2858
  description.Toggle(false);
2856
2859
  },
2857
2860
  ]);
2858
2861
  }
2859
2862
  constants.events.push([
2860
- document.getElementById('description'),
2861
- 'keyup',
2863
+ document.getElementById("description"),
2864
+ "keyup",
2862
2865
  function (e) {
2863
- if (e.key == 'Esc') {
2866
+ if (e.key == "Esc") {
2864
2867
  // esc
2865
2868
  description.Toggle(false);
2866
2869
  }
@@ -2870,9 +2873,9 @@ class Description {
2870
2873
  // open events
2871
2874
  constants.events.push([
2872
2875
  document,
2873
- 'keyup',
2876
+ "keyup",
2874
2877
  function (e) {
2875
- if (e.key == 'd') {
2878
+ if (e.key == "d") {
2876
2879
  description.Toggle(true);
2877
2880
  }
2878
2881
  },
@@ -2884,11 +2887,11 @@ class Description {
2884
2887
  */
2885
2888
  Destroy() {
2886
2889
  // description element destruction
2887
- let description = document.getElementById('menu');
2890
+ let description = document.getElementById("menu");
2888
2891
  if (description) {
2889
2892
  description.remove();
2890
2893
  }
2891
- let backdrop = document.getElementById('desc_modal_backdrop');
2894
+ let backdrop = document.getElementById("desc_modal_backdrop");
2892
2895
  if (backdrop) {
2893
2896
  backdrop.remove();
2894
2897
  }
@@ -2899,8 +2902,8 @@ class Description {
2899
2902
  * @param {boolean} [onoff=false] - Whether to turn the description element on or off.
2900
2903
  */
2901
2904
  Toggle(onoff = false) {
2902
- if (typeof onoff == 'undefined') {
2903
- if (document.getElementById('description').classList.contains('hidden')) {
2905
+ if (typeof onoff == "undefined") {
2906
+ if (document.getElementById("description").classList.contains("hidden")) {
2904
2907
  onoff = true;
2905
2908
  } else {
2906
2909
  onoff = false;
@@ -2911,13 +2914,13 @@ class Description {
2911
2914
  this.whereWasMyFocus = document.activeElement;
2912
2915
  constants.tabMovement = 0;
2913
2916
  this.PopulateData();
2914
- document.getElementById('description').classList.remove('hidden');
2915
- document.getElementById('desc_modal_backdrop').classList.remove('hidden');
2916
- document.querySelector('#description .close').focus();
2917
+ document.getElementById("description").classList.remove("hidden");
2918
+ document.getElementById("desc_modal_backdrop").classList.remove("hidden");
2919
+ document.querySelector("#description .close").focus();
2917
2920
  } else {
2918
2921
  // close
2919
- document.getElementById('description').classList.add('hidden');
2920
- document.getElementById('desc_modal_backdrop').classList.add('hidden');
2922
+ document.getElementById("description").classList.add("hidden");
2923
+ document.getElementById("desc_modal_backdrop").classList.add("hidden");
2921
2924
  this.whereWasMyFocus.focus();
2922
2925
  this.whereWasMyFocus = null;
2923
2926
  }
@@ -2927,22 +2930,22 @@ class Description {
2927
2930
  * Populates the data for the chart and table based on the chart type and plot data.
2928
2931
  */
2929
2932
  PopulateData() {
2930
- let descHtml = '';
2933
+ let descHtml = "";
2931
2934
 
2932
2935
  // chart labels and descriptions
2933
- let descType = '';
2934
- if (constants.chartType == 'bar') {
2935
- descType = 'Bar chart';
2936
- } else if (constants.chartType == 'heat') {
2937
- descType = 'Heatmap';
2938
- } else if (constants.chartType == 'box') {
2939
- descType = 'Box plot';
2940
- } else if (constants.chartType == 'scatter') {
2941
- descType = 'Scatter plot';
2942
- } else if (constants.chartType == 'line') {
2943
- descType = 'Line chart';
2944
- } else if (constants.chartType == 'hist') {
2945
- descType = 'Histogram';
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";
2946
2949
  }
2947
2950
 
2948
2951
  if (descType) {
@@ -2959,7 +2962,7 @@ class Description {
2959
2962
  }
2960
2963
 
2961
2964
  // table of data, prep
2962
- let descTableHtml = '';
2965
+ let descTableHtml = "";
2963
2966
  let descLabelX = null;
2964
2967
  let descLabelY = null;
2965
2968
  let descTickX = null;
@@ -2969,7 +2972,7 @@ class Description {
2969
2972
  let descNumColsWithLabels = 0;
2970
2973
  let descNumRows = 0;
2971
2974
  let descNumRowsWithLabels = 0;
2972
- if (constants.chartType == 'bar') {
2975
+ if (constants.chartType == "bar") {
2973
2976
  if (plot.plotLegend.x != null) {
2974
2977
  descLabelX = plot.plotLegend.x;
2975
2978
  descNumColsWithLabels += 1;
@@ -2994,43 +2997,43 @@ class Description {
2994
2997
 
2995
2998
  // table of data, create
2996
2999
  if (descData != null) {
2997
- descTableHtml += '<table>';
3000
+ descTableHtml += "<table>";
2998
3001
 
2999
3002
  // header rows
3000
3003
  if (descLabelX != null || descTickX != null) {
3001
- descTableHtml += '<thead>';
3004
+ descTableHtml += "<thead>";
3002
3005
  if (descLabelX != null) {
3003
- descTableHtml += '<tr>';
3006
+ descTableHtml += "<tr>";
3004
3007
  if (descLabelY != null) {
3005
- descTableHtml += '<td></td>';
3008
+ descTableHtml += "<td></td>";
3006
3009
  }
3007
3010
  if (descTickY != null) {
3008
- descTableHtml += '<td></td>';
3011
+ descTableHtml += "<td></td>";
3009
3012
  }
3010
3013
  descTableHtml += `<th scope="col" colspan="${descNumCols}">${descLabelX}</th>`;
3011
- descTableHtml += '</tr>';
3014
+ descTableHtml += "</tr>";
3012
3015
  }
3013
3016
  if (descTickX != null) {
3014
- descTableHtml += '<tr>';
3017
+ descTableHtml += "<tr>";
3015
3018
  if (descLabelY != null) {
3016
- descTableHtml += '<td></td>';
3019
+ descTableHtml += "<td></td>";
3017
3020
  }
3018
3021
  if (descTickY != null) {
3019
- descTableHtml += '<td></td>';
3022
+ descTableHtml += "<td></td>";
3020
3023
  }
3021
3024
  for (let i = 0; i < descNumCols; i++) {
3022
3025
  descTableHtml += `<th scope="col">${descTickX[i]}</th>`;
3023
3026
  }
3024
- descTableHtml += '</tr>';
3027
+ descTableHtml += "</tr>";
3025
3028
  }
3026
- descTableHtml += '</thead>';
3029
+ descTableHtml += "</thead>";
3027
3030
  }
3028
3031
 
3029
3032
  // body rows
3030
3033
  if (descNumRows > 0) {
3031
- descTableHtml += '<tbody>';
3034
+ descTableHtml += "<tbody>";
3032
3035
  for (let i = 0; i < descNumRows; i++) {
3033
- descTableHtml += '<tr>';
3036
+ descTableHtml += "<tr>";
3034
3037
  if (descLabelY != null && i == 0) {
3035
3038
  descTableHtml += `<th scope="row" rowspan="${descNumRows}">${descLabelY}</th>`;
3036
3039
  }
@@ -3040,19 +3043,19 @@ class Description {
3040
3043
  for (let j = 0; j < descNumCols; j++) {
3041
3044
  descTableHtml += `<td>${descData[i][j]}</td>`;
3042
3045
  }
3043
- descTableHtml += '</tr>';
3046
+ descTableHtml += "</tr>";
3044
3047
  }
3045
- descTableHtml += '</tbody>';
3048
+ descTableHtml += "</tbody>";
3046
3049
  }
3047
3050
 
3048
- descTableHtml += '</table>';
3051
+ descTableHtml += "</table>";
3049
3052
  }
3050
3053
 
3051
3054
  // bar: don't need colspan or rowspan stuff, put legendX and Y as headers
3052
3055
 
3053
- document.getElementById('desc_title').innerHTML = descType + ' description';
3054
- document.getElementById('desc_content').innerHTML = descHtml;
3055
- document.getElementById('desc_table').innerHTML = descTableHtml;
3056
+ document.getElementById("desc_title").innerHTML = descType + " description";
3057
+ document.getElementById("desc_content").innerHTML = descHtml;
3058
+ document.getElementById("desc_table").innerHTML = descTableHtml;
3056
3059
  }
3057
3060
  }
3058
3061
 
@@ -3094,7 +3097,7 @@ class Helper {
3094
3097
  class Tracker {
3095
3098
  // URL
3096
3099
  logUrl =
3097
- 'https://maidr-service.azurewebsites.net/api/log?code=I8Aa2PlPspjQ8Hks0QzGyszP8_i2-XJ3bq7Xh8-ykEe4AzFuYn_QWA%3D%3D'; // TODO Replace
3100
+ "https://maidr-service.azurewebsites.net/api/log?code=I8Aa2PlPspjQ8Hks0QzGyszP8_i2-XJ3bq7Xh8-ykEe4AzFuYn_QWA%3D%3D"; // TODO Replace
3098
3101
  isLocal = false;
3099
3102
 
3100
3103
  constructor() {
@@ -3113,7 +3116,7 @@ class Tracker {
3113
3116
  data.language = Object.assign(navigator.language);
3114
3117
  data.platform = Object.assign(navigator.platform);
3115
3118
  data.geolocation = Object.assign(navigator.geolocation);
3116
- data.log_type = 'system_data';
3119
+ data.log_type = "system_data";
3117
3120
  data.events = [];
3118
3121
  data.settings = [];
3119
3122
 
@@ -3126,11 +3129,11 @@ class Tracker {
3126
3129
  * Downloads the tracker data as a JSON file.
3127
3130
  */
3128
3131
  DownloadTrackerData() {
3129
- let link = document.createElement('a');
3132
+ let link = document.createElement("a");
3130
3133
  let data = this.GetTrackerData();
3131
- let fileStr = new Blob([JSON.stringify(data)], { type: 'text/plain' });
3134
+ let fileStr = new Blob([JSON.stringify(data)], { type: "text/plain" });
3132
3135
  link.href = URL.createObjectURL(fileStr);
3133
- link.download = 'tracking.json';
3136
+ link.download = "tracking.json";
3134
3137
  link.click();
3135
3138
  }
3136
3139
 
@@ -3139,16 +3142,16 @@ class Tracker {
3139
3142
  * @param {Object} data - The data to be saved.
3140
3143
  */
3141
3144
  async SaveTrackerData(data) {
3142
- console.log('about to save data', data);
3145
+ console.log("about to save data", data);
3143
3146
  if (this.isLocal) {
3144
3147
  localStorage.setItem(constants.project_id, JSON.stringify(data));
3145
3148
  } else {
3146
3149
  // test this first
3147
3150
  try {
3148
3151
  const response = await fetch(this.logUrl, {
3149
- method: 'POST',
3152
+ method: "POST",
3150
3153
  headers: {
3151
- 'Content-Type': 'application/json',
3154
+ "Content-Type": "application/json",
3152
3155
  },
3153
3156
  body: JSON.stringify(data),
3154
3157
  });
@@ -3158,10 +3161,10 @@ class Tracker {
3158
3161
  }
3159
3162
 
3160
3163
  const result = await response.json();
3161
- console.log('Data saved successfully:', result);
3164
+ console.log("Data saved successfully:", result);
3162
3165
  return result;
3163
3166
  } catch (error) {
3164
- console.error('Error saving data:', error);
3167
+ console.error("Error saving data:", error);
3165
3168
  return null;
3166
3169
  }
3167
3170
  }
@@ -3184,7 +3187,7 @@ class Tracker {
3184
3187
  this.data = null;
3185
3188
 
3186
3189
  if (constants.debugLevel > 0) {
3187
- console.log('tracking data cleared');
3190
+ console.log("tracking data cleared");
3188
3191
  }
3189
3192
 
3190
3193
  this.DataSetup();
@@ -3192,16 +3195,16 @@ class Tracker {
3192
3195
 
3193
3196
  SaveSettings() {
3194
3197
  // fetch all settings, push to data.settings
3195
- let settings = JSON.parse(localStorage.getItem('settings_data'));
3198
+ let settings = JSON.parse(localStorage.getItem("settings_data"));
3196
3199
  if (settings) {
3197
3200
  // don't store their auth keys
3198
- settings.openAIAuthKey = 'hidden';
3199
- settings.geminiAuthKey = 'hidden';
3201
+ settings.openAIAuthKey = "hidden";
3202
+ settings.geminiAuthKey = "hidden";
3200
3203
  if (constants.emailAuthKey) {
3201
3204
  settings.username = constants.emailAuthKey;
3202
3205
  }
3203
3206
  settings;
3204
- this.SetData('settings', settings);
3207
+ this.SetData("settings", settings);
3205
3208
  }
3206
3209
  }
3207
3210
 
@@ -3298,7 +3301,7 @@ class Tracker {
3298
3301
  }
3299
3302
  if (!this.isUndefinedOrNull(constants.infoDiv.innerHTML)) {
3300
3303
  let textDisplay = Object.assign(constants.infoDiv.innerHTML);
3301
- textDisplay = textDisplay.replaceAll(/<[^>]*>?/gm, '');
3304
+ textDisplay = textDisplay.replaceAll(/<[^>]*>?/gm, "");
3302
3305
  eventToLog.text_display = textDisplay;
3303
3306
  }
3304
3307
  if (!this.isUndefinedOrNull(location.href)) {
@@ -3306,13 +3309,13 @@ class Tracker {
3306
3309
  }
3307
3310
 
3308
3311
  // chart specific values
3309
- let x_tickmark = '';
3310
- let y_tickmark = '';
3311
- let x_label = '';
3312
- let y_label = '';
3313
- let value = '';
3314
- let fill_value = '';
3315
- if (constants.chartType == 'bar') {
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") {
3316
3319
  if (!this.isUndefinedOrNull(plot.columnLabels[position.x])) {
3317
3320
  x_tickmark = plot.columnLabels[position.x];
3318
3321
  }
@@ -3325,7 +3328,7 @@ class Tracker {
3325
3328
  if (!this.isUndefinedOrNull(plot.plotData[position.x])) {
3326
3329
  value = plot.plotData[position.x];
3327
3330
  }
3328
- } else if (constants.chartType == 'heat') {
3331
+ } else if (constants.chartType == "heat") {
3329
3332
  if (!this.isUndefinedOrNull(plot.x_labels[position.x])) {
3330
3333
  x_tickmark = plot.x_labels[position.x].trim();
3331
3334
  }
@@ -3346,11 +3349,11 @@ class Tracker {
3346
3349
  if (!this.isUndefinedOrNull(plot.group_labels[2])) {
3347
3350
  fill_value = plot.group_labels[2];
3348
3351
  }
3349
- } else if (constants.chartType == 'box') {
3352
+ } else if (constants.chartType == "box") {
3350
3353
  let plotPos =
3351
- constants.plotOrientation == 'vert' ? position.x : position.y;
3354
+ constants.plotOrientation == "vert" ? position.x : position.y;
3352
3355
  let sectionPos =
3353
- constants.plotOrientation == 'vert' ? position.y : position.x;
3356
+ constants.plotOrientation == "vert" ? position.y : position.x;
3354
3357
  let sectionLabel = plot.sections[sectionPos];
3355
3358
 
3356
3359
  if (!this.isUndefinedOrNull(plot.x_group_label)) {
@@ -3359,7 +3362,7 @@ class Tracker {
3359
3362
  if (!this.isUndefinedOrNull(plot.y_group_label)) {
3360
3363
  y_label = plot.y_group_label;
3361
3364
  }
3362
- if (constants.plotOrientation == 'vert') {
3365
+ if (constants.plotOrientation == "vert") {
3363
3366
  if (plotPos > -1 && sectionPos > -1) {
3364
3367
  if (!this.isUndefinedOrNull(sectionLabel)) {
3365
3368
  y_tickmark = sectionLabel;
@@ -3384,7 +3387,7 @@ class Tracker {
3384
3387
  }
3385
3388
  }
3386
3389
  }
3387
- } else if (constants.chartType == 'point') {
3390
+ } else if (constants.chartType == "point") {
3388
3391
  if (!this.isUndefinedOrNull(plot.x_group_label)) {
3389
3392
  x_label = plot.x_group_label;
3390
3393
  }
@@ -3411,7 +3414,7 @@ class Tracker {
3411
3414
 
3412
3415
  //console.log("x_tickmark: '", x_tickmark, "', y_tickmark: '", y_tickmark, "', x_label: '", x_label, "', y_label: '", y_label, "', value: '", value, "', fill_value: '", fill_value);
3413
3416
 
3414
- this.SetData('events', eventToLog);
3417
+ this.SetData("events", eventToLog);
3415
3418
  //console.log('logged an event');
3416
3419
  }
3417
3420
 
@@ -3424,7 +3427,7 @@ class Tracker {
3424
3427
  SetData(key, value) {
3425
3428
  if (this.isLocal) {
3426
3429
  let data = this.GetTrackerData();
3427
- let arrayKeys = ['events', 'ChatHistory', 'settings'];
3430
+ let arrayKeys = ["events", "ChatHistory", "settings"];
3428
3431
  if (!arrayKeys.includes(key)) {
3429
3432
  data[key] = value;
3430
3433
  } else {
@@ -3435,7 +3438,7 @@ class Tracker {
3435
3438
  }
3436
3439
  this.SaveTrackerData(data);
3437
3440
  } else {
3438
- value['log_type'] = key;
3441
+ value["log_type"] = key;
3439
3442
  this.SaveTrackerData(value);
3440
3443
  }
3441
3444
  }
@@ -3469,20 +3472,20 @@ class Review {
3469
3472
  // true means on or show
3470
3473
  if (onoff) {
3471
3474
  constants.reviewSaveSpot = document.activeElement;
3472
- constants.review_container.classList.remove('hidden');
3475
+ constants.review_container.classList.remove("hidden");
3473
3476
  constants.reviewSaveBrailleMode = constants.brailleMode;
3474
3477
  constants.review.focus();
3475
3478
 
3476
- display.announceText('Review on');
3479
+ display.announceText("Review on");
3477
3480
  } else {
3478
- constants.review_container.classList.add('hidden');
3479
- if (constants.reviewSaveBrailleMode == 'on') {
3481
+ constants.review_container.classList.add("hidden");
3482
+ if (constants.reviewSaveBrailleMode == "on") {
3480
3483
  // we have to turn braille mode back on
3481
- display.toggleBrailleMode('on');
3484
+ display.toggleBrailleMode("on");
3482
3485
  } else {
3483
3486
  constants.reviewSaveSpot.focus();
3484
3487
  }
3485
- display.announceText('Review off');
3488
+ display.announceText("Review off");
3486
3489
  }
3487
3490
  }
3488
3491
  }
@@ -3499,7 +3502,7 @@ class LogError {
3499
3502
  * @param {string} a - The absent element to log.
3500
3503
  */
3501
3504
  LogAbsentElement(a) {
3502
- console.log(a, 'not found. Visual highlighting is turned off.');
3505
+ console.log(a, "not found. Visual highlighting is turned off.");
3503
3506
  }
3504
3507
 
3505
3508
  /**
@@ -3507,7 +3510,7 @@ class LogError {
3507
3510
  * @param {string} a - The critical element to log.
3508
3511
  */
3509
3512
  LogCriticalElement(a) {
3510
- consolelog(a, 'is critical. MAIDR unable to run');
3513
+ consolelog(a, "is critical. MAIDR unable to run");
3511
3514
  }
3512
3515
 
3513
3516
  /**
@@ -3518,9 +3521,9 @@ class LogError {
3518
3521
  LogDifferentLengths(a, b) {
3519
3522
  console.log(
3520
3523
  a,
3521
- 'and',
3524
+ "and",
3522
3525
  b,
3523
- 'do not have the same length. Visual highlighting is turned off.'
3526
+ "do not have the same length. Visual highlighting is turned off."
3524
3527
  );
3525
3528
  }
3526
3529
 
@@ -3531,11 +3534,11 @@ class LogError {
3531
3534
  */
3532
3535
  LogTooManyElements(a, b) {
3533
3536
  console.log(
3534
- 'Too many',
3537
+ "Too many",
3535
3538
  a,
3536
- 'elements. Only the first',
3539
+ "elements. Only the first",
3537
3540
  b,
3538
- 'will be highlighted.'
3541
+ "will be highlighted."
3539
3542
  );
3540
3543
  }
3541
3544
 
@@ -3544,7 +3547,7 @@ class LogError {
3544
3547
  * @param {*} a - The parameter that is not an array.
3545
3548
  */
3546
3549
  LogNotArray(a) {
3547
- console.log(a, 'is not an array. Visual highlighting is turned off.');
3550
+ console.log(a, "is not an array. Visual highlighting is turned off.");
3548
3551
  }
3549
3552
  }
3550
3553