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