maidr 2.17.3 → 2.18.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,14 +128,14 @@ class Constants {
128
128
  * @memberof BTSModes
129
129
  * @default 'verbose'
130
130
  */
131
- textMode = 'verbose';
131
+ textMode = "verbose";
132
132
  /**
133
133
  * The current braille mode. Can be 'off' or 'on'.
134
134
  * @type {("off"|"on")}
135
135
  * @memberof BTSModes
136
136
  * @default 'off'
137
137
  */
138
- brailleMode = 'off';
138
+ brailleMode = "off";
139
139
 
140
140
  /**
141
141
  * We lock the selection so we don't pick up programatic selection changes
@@ -150,14 +150,14 @@ class Constants {
150
150
  * @memberof BTSModes
151
151
  * @default 'on'
152
152
  */
153
- sonifMode = 'on';
153
+ sonifMode = "on";
154
154
  /**
155
155
  * The current review mode. Can be 'on' or 'off'.
156
156
  * @type {("on"|"off")}
157
157
  * @memberof BTSModes
158
158
  * @default 'off'
159
159
  */
160
- reviewMode = 'off';
160
+ reviewMode = "off";
161
161
 
162
162
  // basic chart properties
163
163
  /**
@@ -198,14 +198,14 @@ class Constants {
198
198
  * @memberof HtmlIds
199
199
  * @default ''
200
200
  */
201
- plotId = ''; // update with id in chart specific js
201
+ plotId = ""; // update with id in chart specific js
202
202
  /**
203
203
  * The chart type, sort of a short name of the chart such as 'box', 'bar', 'line', etc.
204
204
  * @type {string}
205
205
  * @default ''
206
206
  * @memberof BasicChartProperties
207
207
  */
208
- chartType = '';
208
+ chartType = "";
209
209
  /**
210
210
  * The navigation orientation of the chart. 0 = row navigation (up/down), 1 = col navigation (left/right).
211
211
  * @type {number}
@@ -219,7 +219,7 @@ class Constants {
219
219
  * @default 'horz'
220
220
  * @memberof BasicChartProperties
221
221
  */
222
- plotOrientation = 'horz';
222
+ plotOrientation = "horz";
223
223
 
224
224
  /**
225
225
  * @namespace AudioProperties
@@ -329,7 +329,7 @@ class Constants {
329
329
  * @default '#03C809' (green)
330
330
  * @memberof UserSettings
331
331
  */
332
- colorSelected = '#03C809';
332
+ colorSelected = "#03C809";
333
333
  /**
334
334
  * 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).
335
335
  * @type {number}
@@ -397,7 +397,7 @@ class Constants {
397
397
  * @default 50
398
398
  * @memberof AdvancedUserSettings
399
399
  */
400
- colorUnselected = '#595959'; // deprecated, todo: find all instances replace with storing old color method
400
+ colorUnselected = "#595959"; // deprecated, todo: find all instances replace with storing old color method
401
401
  /**
402
402
  * Whether or not we're logging user data. This is off by default, but is used for research purposes.
403
403
  * @type {boolean}
@@ -425,30 +425,30 @@ class Constants {
425
425
  * @default 'assertive'
426
426
  * @memberof AdvancedUserSettings
427
427
  */
428
- ariaMode = 'assertive';
428
+ ariaMode = "assertive";
429
429
 
430
430
  /**
431
431
  * Full list of user settings, used internally to save and load settings.
432
432
  * @type {string[]}
433
433
  */
434
434
  userSettingsKeys = [
435
- 'vol',
436
- 'autoPlayRate',
437
- 'brailleDisplayLength',
438
- 'colorSelected',
439
- 'MIN_FREQUENCY',
440
- 'MAX_FREQUENCY',
441
- 'keypressInterval',
442
- 'ariaMode',
443
- 'openAIAuthKey',
444
- 'geminiAuthKey',
445
- 'skillLevel',
446
- 'skillLevelOther',
447
- 'LLMModel',
448
- 'LLMPreferences',
449
- 'LLMOpenAiMulti',
450
- 'LLMGeminiMulti',
451
- 'autoInitLLM',
435
+ "vol",
436
+ "autoPlayRate",
437
+ "brailleDisplayLength",
438
+ "colorSelected",
439
+ "MIN_FREQUENCY",
440
+ "MAX_FREQUENCY",
441
+ "keypressInterval",
442
+ "ariaMode",
443
+ "openAIAuthKey",
444
+ "geminiAuthKey",
445
+ "skillLevel",
446
+ "skillLevelOther",
447
+ "LLMModel",
448
+ "LLMPreferences",
449
+ "LLMOpenAiMulti",
450
+ "LLMGeminiMulti",
451
+ "autoInitLLM",
452
452
  ];
453
453
 
454
454
  // LLM settings
@@ -489,14 +489,14 @@ class Constants {
489
489
  * @default 'high'
490
490
  * @memberof LLMSettings
491
491
  */
492
- LLMDetail = 'high'; // low (default for testing, like 100 tokens) / high (default for real, like 1000 tokens)
492
+ LLMDetail = "high"; // low (default for testing, like 100 tokens) / high (default for real, like 1000 tokens)
493
493
  /**
494
494
  * Current LLM model in use. Can be 'openai' (default) or 'gemini' or 'multi'. More to be added.
495
495
  * @type {("openai"|"gemini"|"multi")}
496
496
  * @default 'openai'
497
497
  * @memberof LLMSettings
498
498
  */
499
- LLMModel = 'openai';
499
+ LLMModel = "openai";
500
500
  /**
501
501
  * The default system message for the LLM. Helps the LLM understand the context of the chart and its role.
502
502
  * @type {string}
@@ -504,21 +504,21 @@ class Constants {
504
504
  * @memberof LLMSettings
505
505
  */
506
506
  LLMSystemMessage =
507
- 'You are a helpful assistant describing the chart to a blind person. ';
507
+ "You are a helpful assistant describing the chart to a blind person. ";
508
508
  /**
509
509
  * 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.
510
510
  * @type {("basic"|"intermediate"|"expert"|"other")}
511
511
  * @default 'basic'
512
512
  * @memberof LLMSettings
513
513
  */
514
- skillLevel = 'basic'; // basic / intermediate / expert
514
+ skillLevel = "basic"; // basic / intermediate / expert
515
515
  /**
516
516
  * Custom skill level, used if the user selects 'other' as their skill level.
517
517
  * @type {string}
518
518
  * @default ''
519
519
  * @memberof LLMSettings
520
520
  */
521
- skillLevelOther = ''; // custom skill level
521
+ skillLevelOther = ""; // custom skill level
522
522
  /**
523
523
  * 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.
524
524
  * @type {boolean}
@@ -532,7 +532,7 @@ class Constants {
532
532
  * @default ''
533
533
  * @memberof LLMSettings
534
534
  */
535
- verboseText = '';
535
+ verboseText = "";
536
536
  /**
537
537
  * An internal variable used to turn the waiting beep on and off.
538
538
  * @type {number}
@@ -580,31 +580,31 @@ class Constants {
580
580
  * @type {boolean}
581
581
  * @memberof PlatformControls
582
582
  */
583
- isMac = navigator.userAgent.toLowerCase().includes('mac'); // true if macOS
583
+ isMac = navigator.userAgent.toLowerCase().includes("mac"); // true if macOS
584
584
  /**
585
585
  * The control key for the user's platform. Can be 'Cmd' or 'Ctrl'. Used in keyboard shortcut display in help.
586
586
  * @type {"Cmd"|"Ctrl"}
587
587
  * @memberof PlatformControls
588
588
  */
589
- control = this.isMac ? 'Cmd' : 'Ctrl';
589
+ control = this.isMac ? "Cmd" : "Ctrl";
590
590
  /**
591
591
  * The alt key for the user's platform. Can be 'option' or 'Alt'. Used in keyboard shortcut display in help.
592
592
  * @type {"option"|"Alt"}
593
593
  * @memberof PlatformControls
594
594
  */
595
- alt = this.isMac ? 'option' : 'Alt';
595
+ alt = this.isMac ? "option" : "Alt";
596
596
  /**
597
597
  * The home key for the user's platform. Can be 'fn + Left arrow' or 'Home'. Used in keyboard shortcut display in help.
598
598
  * @type {"fn + Left arrow"|"Home"}
599
599
  * @memberof PlatformControls
600
600
  */
601
- home = this.isMac ? 'fn + Left arrow' : 'Home';
601
+ home = this.isMac ? "fn + Left arrow" : "Home";
602
602
  /**
603
603
  * The end key for the user's platform. Can be 'fn + Right arrow' or 'End'. Used in keyboard shortcut display in help.
604
604
  * @type {"fn + Right arrow"|"End"}
605
605
  * @memberof PlatformControls
606
606
  */
607
- end = this.isMac ? 'fn + Right arrow' : 'End';
607
+ end = this.isMac ? "fn + Right arrow" : "End";
608
608
 
609
609
  // internal controls
610
610
  // todo: are these even used? Sean doesn't think so (May 2024)
@@ -697,13 +697,13 @@ class Constants {
697
697
  */
698
698
  ConvertHexToRGBString(hexColorString) {
699
699
  return (
700
- 'rgb(' +
700
+ "rgb(" +
701
701
  parseInt(hexColorString.slice(1, 3), 16) +
702
- ',' +
702
+ "," +
703
703
  parseInt(hexColorString.slice(3, 5), 16) +
704
- ',' +
704
+ "," +
705
705
  parseInt(hexColorString.slice(5, 7), 16) +
706
- ')'
706
+ ")"
707
707
  );
708
708
  }
709
709
 
@@ -713,12 +713,12 @@ class Constants {
713
713
  * @returns {string} - hexadecimal color (e.g., "#595959").
714
714
  */
715
715
  ConvertRGBStringToHex(rgbColorString) {
716
- let rgb = rgbColorString.replace(/[^\d,]/g, '').split(',');
716
+ let rgb = rgbColorString.replace(/[^\d,]/g, "").split(",");
717
717
  return (
718
- '#' +
719
- rgb[0].toString(16).padStart(2, '0') +
720
- rgb[1].toString(16).padStart(2, '0') +
721
- rgb[2].toString(16).padStart(2, '0')
718
+ "#" +
719
+ rgb[0].toString(16).padStart(2, "0") +
720
+ rgb[1].toString(16).padStart(2, "0") +
721
+ rgb[2].toString(16).padStart(2, "0")
722
722
  );
723
723
  }
724
724
 
@@ -730,11 +730,11 @@ class Constants {
730
730
  */
731
731
  ColorInvert(color) {
732
732
  // invert an rgb color
733
- let rgb = color.replace(/[^\d,]/g, '').split(',');
733
+ let rgb = color.replace(/[^\d,]/g, "").split(",");
734
734
  let r = 255 - rgb[0];
735
735
  let g = 255 - rgb[1];
736
736
  let b = 255 - rgb[2];
737
- return 'rgb(' + r + ',' + g + ',' + b + ')';
737
+ return "rgb(" + r + "," + g + "," + b + ")";
738
738
  }
739
739
 
740
740
  /**
@@ -743,11 +743,11 @@ class Constants {
743
743
  * @returns {string} The better color
744
744
  */
745
745
  GetBetterColor(oldColor) {
746
- if (oldColor.indexOf('#') !== -1) {
746
+ if (oldColor.indexOf("#") !== -1) {
747
747
  oldColor = this.ConvertHexToRGBString(oldColor);
748
748
  }
749
749
  let newColor = this.ColorInvert(oldColor);
750
- let rgb = newColor.replace(/[^\d,]/g, '').split(',');
750
+ let rgb = newColor.replace(/[^\d,]/g, "").split(",");
751
751
  if (
752
752
  rgb[1] < rgb[0] + 10 &&
753
753
  rgb[1] > rgb[0] - 10 &&
@@ -769,7 +769,7 @@ class Constants {
769
769
  */
770
770
  GetStyleArrayFromString(styleString) {
771
771
  // Get an array of CSS style attributes and values from a style string
772
- return styleString.replaceAll(' ', '').split(/[:;]/);
772
+ return styleString.replaceAll(" ", "").split(/[:;]/);
773
773
  }
774
774
 
775
775
  /**
@@ -779,16 +779,16 @@ class Constants {
779
779
  */
780
780
  GetStyleStringFromArray(styleArray) {
781
781
  // Get CSS style string from an array of style attributes and values
782
- let styleString = '';
782
+ let styleString = "";
783
783
  for (let i = 0; i < styleArray.length; i++) {
784
784
  if (i % 2 === 0) {
785
785
  if (i !== styleArray.length - 1) {
786
- styleString += styleArray[i] + ': ';
786
+ styleString += styleArray[i] + ": ";
787
787
  } else {
788
788
  styleString += styleArray[i];
789
789
  }
790
790
  } else {
791
- styleString += styleArray[i] + '; ';
791
+ styleString += styleArray[i] + "; ";
792
792
  }
793
793
  }
794
794
  return styleString;
@@ -801,35 +801,35 @@ class Constants {
801
801
  class Resources {
802
802
  constructor() {}
803
803
 
804
- language = 'en'; // Current language, 2 char lang code
805
- knowledgeLevel = 'basic'; // basic, intermediate, expert
804
+ language = "en"; // Current language, 2 char lang code
805
+ knowledgeLevel = "basic"; // basic, intermediate, expert
806
806
 
807
807
  // language strings, per 2 char language code
808
808
  strings = {
809
809
  en: {
810
810
  basic: {
811
- upper_outlier: 'Upper Outlier',
812
- lower_outlier: 'Lower Outlier',
813
- min: 'Minimum',
814
- max: 'Maximum',
815
- 25: '25%',
816
- 50: '50%',
817
- 75: '75%',
818
- q1: '25%',
819
- q2: '50%',
820
- q3: '75%',
821
- son_on: 'Sonification on',
822
- son_off: 'Sonification off',
823
- son_des: 'Sonification descrete',
824
- son_comp: 'Sonification compare',
825
- son_ch: 'Sonification chord',
826
- son_sep: 'Sonification separate',
827
- son_same: 'Sonification combined',
828
- empty: 'Empty',
829
- openai: 'OpenAI Vision',
830
- gemini: 'Gemini Pro Vision',
831
- multi: 'Multiple AI',
832
- processing: 'Processing Chart...',
811
+ upper_outlier: "Upper Outlier",
812
+ lower_outlier: "Lower Outlier",
813
+ min: "Minimum",
814
+ max: "Maximum",
815
+ 25: "25%",
816
+ 50: "50%",
817
+ 75: "75%",
818
+ q1: "25%",
819
+ q2: "50%",
820
+ q3: "75%",
821
+ son_on: "Sonification on",
822
+ son_off: "Sonification off",
823
+ son_des: "Sonification descrete",
824
+ son_comp: "Sonification compare",
825
+ son_ch: "Sonification chord",
826
+ son_sep: "Sonification separate",
827
+ son_same: "Sonification combined",
828
+ empty: "Empty",
829
+ openai: "OpenAI Vision",
830
+ gemini: "Gemini Pro Vision",
831
+ multi: "Multiple AI",
832
+ processing: "Processing Chart...",
833
833
  },
834
834
  },
835
835
  };
@@ -944,7 +944,11 @@ class Menu {
944
944
  <td>Auto-play speed down</td>
945
945
  <td>Comma (,)</td>
946
946
  </tr>
947
- <tr>
947
+ <tr>
948
+ <td>Auto-play speed reset</td>
949
+ <td>Slash (/)</td>
950
+ </tr>
951
+ <tr>
948
952
  <td>Check label for the title of current plot</td>
949
953
  <td>l t</td>
950
954
  </tr>
@@ -1000,12 +1004,12 @@ class Menu {
1000
1004
  <div><fieldset>
1001
1005
  <legend>Aria Mode</legend>
1002
1006
  <p><input type="radio" id="aria_mode_assertive" name="aria_mode" value="assertive" ${
1003
- constants.ariaMode == 'assertive'
1004
- ? 'checked'
1005
- : ''
1007
+ constants.ariaMode == "assertive"
1008
+ ? "checked"
1009
+ : ""
1006
1010
  }><label for="aria_mode_assertive">Assertive</label></p>
1007
1011
  <p><input type="radio" id="aria_mode_polite" name="aria_mode" value="polite" ${
1008
- constants.ariaMode == 'polite' ? 'checked' : ''
1012
+ constants.ariaMode == "polite" ? "checked" : ""
1009
1013
  }><label for="aria_mode_polite">Polite</label></p>
1010
1014
  </fieldset></div>
1011
1015
  <h5 class="modal-title">LLM Settings</h5>
@@ -1026,7 +1030,7 @@ class Menu {
1026
1030
  <input type="password" size="50" id="gemini_auth_key"><button aria-label="Delete Gemini key" title="Delete Gemini key" id="delete_gemini_key" class="invis_button">&times;</button><label for="gemini_auth_key">Gemini Authentication Key</label>
1027
1031
  </p>
1028
1032
  <p><input type="checkbox" ${
1029
- constants.autoInitLLM ? 'checked' : ''
1033
+ constants.autoInitLLM ? "checked" : ""
1030
1034
  } id="init_llm_on_load" name="init_llm_on_load"><label for="init_llm_on_load">Start first LLM chat chart load</label></p>
1031
1035
  <p>
1032
1036
  <select id="skill_level">
@@ -1063,33 +1067,33 @@ class Menu {
1063
1067
  CreateMenu() {
1064
1068
  // menu element creation
1065
1069
  document
1066
- .querySelector('body')
1067
- .insertAdjacentHTML('beforeend', this.menuHtml);
1070
+ .querySelector("body")
1071
+ .insertAdjacentHTML("beforeend", this.menuHtml);
1068
1072
 
1069
1073
  // menu close events
1070
- let allClose = document.querySelectorAll('#close_menu, #menu .close');
1074
+ let allClose = document.querySelectorAll("#close_menu, #menu .close");
1071
1075
  for (let i = 0; i < allClose.length; i++) {
1072
1076
  constants.events.push([
1073
1077
  allClose[i],
1074
- 'click',
1078
+ "click",
1075
1079
  function (e) {
1076
1080
  menu.Toggle(false);
1077
1081
  },
1078
1082
  ]);
1079
1083
  }
1080
1084
  constants.events.push([
1081
- document.getElementById('save_and_close_menu'),
1082
- 'click',
1085
+ document.getElementById("save_and_close_menu"),
1086
+ "click",
1083
1087
  function (e) {
1084
1088
  menu.SaveData();
1085
1089
  menu.Toggle(false);
1086
1090
  },
1087
1091
  ]);
1088
1092
  constants.events.push([
1089
- document.getElementById('menu'),
1090
- 'keyup',
1093
+ document.getElementById("menu"),
1094
+ "keyup",
1091
1095
  function (e) {
1092
- if (e.key == 'Esc') {
1096
+ if (e.key == "Esc") {
1093
1097
  // esc
1094
1098
  menu.Toggle(false);
1095
1099
  }
@@ -1099,15 +1103,15 @@ class Menu {
1099
1103
  // Menu open events
1100
1104
  constants.events.push([
1101
1105
  document,
1102
- 'keyup',
1106
+ "keyup",
1103
1107
  function (e) {
1104
1108
  // don't fire on input elements
1105
1109
  if (
1106
- e.target.tagName.toLowerCase() == 'input' ||
1107
- e.target.tagName.toLowerCase() == 'textarea'
1110
+ e.target.tagName.toLowerCase() == "input" ||
1111
+ e.target.tagName.toLowerCase() == "textarea"
1108
1112
  ) {
1109
1113
  return;
1110
- } else if (e.key == 'h') {
1114
+ } else if (e.key == "h") {
1111
1115
  menu.Toggle(true);
1112
1116
  }
1113
1117
  },
@@ -1115,71 +1119,71 @@ class Menu {
1115
1119
 
1116
1120
  // toggle auth key fields
1117
1121
  constants.events.push([
1118
- document.getElementById('LLM_model'),
1119
- 'change',
1122
+ document.getElementById("LLM_model"),
1123
+ "change",
1120
1124
  function (e) {
1121
- if (e.target.value == 'openai') {
1125
+ if (e.target.value == "openai") {
1122
1126
  document
1123
- .getElementById('openai_auth_key_container')
1124
- .classList.remove('hidden');
1127
+ .getElementById("openai_auth_key_container")
1128
+ .classList.remove("hidden");
1125
1129
  document
1126
- .getElementById('gemini_auth_key_container')
1127
- .classList.add('hidden');
1130
+ .getElementById("gemini_auth_key_container")
1131
+ .classList.add("hidden");
1128
1132
  document
1129
- .getElementById('openai_multi_container')
1130
- .classList.add('hidden');
1133
+ .getElementById("openai_multi_container")
1134
+ .classList.add("hidden");
1131
1135
  document
1132
- .getElementById('gemini_multi_container')
1133
- .classList.add('hidden');
1134
- document.getElementById('openai_multi').checked = true;
1135
- document.getElementById('gemini_multi').checked = false;
1136
- } else if (e.target.value == 'gemini') {
1136
+ .getElementById("gemini_multi_container")
1137
+ .classList.add("hidden");
1138
+ document.getElementById("openai_multi").checked = true;
1139
+ document.getElementById("gemini_multi").checked = false;
1140
+ } else if (e.target.value == "gemini") {
1137
1141
  document
1138
- .getElementById('openai_auth_key_container')
1139
- .classList.add('hidden');
1142
+ .getElementById("openai_auth_key_container")
1143
+ .classList.add("hidden");
1140
1144
  document
1141
- .getElementById('gemini_auth_key_container')
1142
- .classList.remove('hidden');
1145
+ .getElementById("gemini_auth_key_container")
1146
+ .classList.remove("hidden");
1143
1147
  document
1144
- .getElementById('openai_multi_container')
1145
- .classList.add('hidden');
1148
+ .getElementById("openai_multi_container")
1149
+ .classList.add("hidden");
1146
1150
  document
1147
- .getElementById('gemini_multi_container')
1148
- .classList.add('hidden');
1149
- document.getElementById('openai_multi').checked = false;
1150
- document.getElementById('gemini_multi').checked = true;
1151
- } else if (e.target.value == 'multi') {
1151
+ .getElementById("gemini_multi_container")
1152
+ .classList.add("hidden");
1153
+ document.getElementById("openai_multi").checked = false;
1154
+ document.getElementById("gemini_multi").checked = true;
1155
+ } else if (e.target.value == "multi") {
1152
1156
  document
1153
- .getElementById('openai_auth_key_container')
1154
- .classList.remove('hidden');
1157
+ .getElementById("openai_auth_key_container")
1158
+ .classList.remove("hidden");
1155
1159
  document
1156
- .getElementById('gemini_auth_key_container')
1157
- .classList.remove('hidden');
1160
+ .getElementById("gemini_auth_key_container")
1161
+ .classList.remove("hidden");
1158
1162
  document
1159
- .getElementById('openai_multi_container')
1160
- .classList.remove('hidden');
1163
+ .getElementById("openai_multi_container")
1164
+ .classList.remove("hidden");
1161
1165
  document
1162
- .getElementById('gemini_multi_container')
1163
- .classList.remove('hidden');
1164
- document.getElementById('openai_multi').checked = true;
1165
- document.getElementById('gemini_multi').checked = true;
1166
+ .getElementById("gemini_multi_container")
1167
+ .classList.remove("hidden");
1168
+ document.getElementById("openai_multi").checked = true;
1169
+ document.getElementById("gemini_multi").checked = true;
1166
1170
  }
1167
1171
  },
1168
1172
  ]);
1169
1173
 
1170
1174
  // Skill level other events
1171
1175
  constants.events.push([
1172
- document.getElementById('skill_level'),
1173
- 'change',
1176
+ document.getElementById("skill_level"),
1177
+ "change",
1174
1178
  function (e) {
1175
- if (e.target.value == 'other') {
1179
+ if (e.target.value == "other") {
1176
1180
  document
1177
- .getElementById('skill_level_other_container')
1178
- .classList.remove('hidden');
1181
+ .getElementById("skill_level_other_container")
1182
+ .classList.remove("hidden");
1179
1183
  } else {
1180
1184
  document
1181
- .getElementById('skill_level_other_container')
1182
- .classList.add('hidden');
1185
+ .getElementById("skill_level_other_container")
1186
+ .classList.add("hidden");
1183
1187
  }
1184
1188
  },
1185
1189
  ]);
@@ -1187,16 +1191,16 @@ class Menu {
1187
1191
  // trigger notification that LLM will be reset
1188
1192
  // this is done on change of LLM model, multi settings, or skill level
1189
1193
  let LLMResetIds = [
1190
- 'LLM_model',
1191
- 'openai_multi',
1192
- 'gemini_multi',
1193
- 'skill_level',
1194
- 'LLM_preferences',
1194
+ "LLM_model",
1195
+ "openai_multi",
1196
+ "gemini_multi",
1197
+ "skill_level",
1198
+ "LLM_preferences",
1195
1199
  ];
1196
1200
  for (let i = 0; i < LLMResetIds.length; i++) {
1197
1201
  constants.events.push([
1198
1202
  document.getElementById(LLMResetIds[i]),
1199
- 'change',
1203
+ "change",
1200
1204
  function (e) {
1201
1205
  menu.NotifyOfLLMReset();
1202
1206
  },
@@ -1210,11 +1214,11 @@ class Menu {
1210
1214
  */
1211
1215
  Destroy() {
1212
1216
  // menu element destruction
1213
- let menu = document.getElementById('menu');
1217
+ let menu = document.getElementById("menu");
1214
1218
  if (menu) {
1215
1219
  menu.remove();
1216
1220
  }
1217
- let backdrop = document.getElementById('menu_modal_backdrop');
1221
+ let backdrop = document.getElementById("menu_modal_backdrop");
1218
1222
  if (backdrop) {
1219
1223
  backdrop.remove();
1220
1224
  }
@@ -1226,16 +1230,16 @@ class Menu {
1226
1230
  * @return {void}
1227
1231
  */
1228
1232
  Toggle(onoff = false) {
1229
- if (typeof onoff == 'undefined') {
1230
- if (document.getElementById('menu').classList.contains('hidden')) {
1233
+ if (typeof onoff == "undefined") {
1234
+ if (document.getElementById("menu").classList.contains("hidden")) {
1231
1235
  onoff = true;
1232
1236
  } else {
1233
1237
  onoff = false;
1234
1238
  }
1235
1239
  }
1236
1240
  // don't open if we have another modal open already
1237
- if (onoff && document.getElementById('chatLLM')) {
1238
- if (!document.getElementById('chatLLM').classList.contains('hidden')) {
1241
+ if (onoff && document.getElementById("chatLLM")) {
1242
+ if (!document.getElementById("chatLLM").classList.contains("hidden")) {
1239
1243
  return;
1240
1244
  }
1241
1245
  }
@@ -1244,13 +1248,13 @@ class Menu {
1244
1248
  this.whereWasMyFocus = document.activeElement;
1245
1249
  this.PopulateData();
1246
1250
  constants.tabMovement = 0;
1247
- document.getElementById('menu').classList.remove('hidden');
1248
- document.getElementById('menu_modal_backdrop').classList.remove('hidden');
1249
- document.querySelector('#menu .close').focus();
1251
+ document.getElementById("menu").classList.remove("hidden");
1252
+ document.getElementById("menu_modal_backdrop").classList.remove("hidden");
1253
+ document.querySelector("#menu .close").focus();
1250
1254
  } else {
1251
1255
  // close
1252
- document.getElementById('menu').classList.add('hidden');
1253
- document.getElementById('menu_modal_backdrop').classList.add('hidden');
1256
+ document.getElementById("menu").classList.add("hidden");
1257
+ document.getElementById("menu_modal_backdrop").classList.add("hidden");
1254
1258
  this.whereWasMyFocus.focus();
1255
1259
  this.whereWasMyFocus = null;
1256
1260
  }
@@ -1261,89 +1265,89 @@ class Menu {
1261
1265
  * @return {void}
1262
1266
  */
1263
1267
  PopulateData() {
1264
- document.getElementById('vol').value = constants.vol;
1265
- document.getElementById('autoplay_rate').value = constants.autoPlayRate;
1266
- document.getElementById('braille_display_length').value =
1268
+ document.getElementById("vol").value = constants.vol;
1269
+ document.getElementById("autoplay_rate").value = constants.autoPlayRate;
1270
+ document.getElementById("braille_display_length").value =
1267
1271
  constants.brailleDisplayLength;
1268
- document.getElementById('color_selected').value = constants.colorSelected;
1269
- document.getElementById('min_freq').value = constants.MIN_FREQUENCY;
1270
- document.getElementById('max_freq').value = constants.MAX_FREQUENCY;
1271
- document.getElementById('keypress_interval').value =
1272
+ document.getElementById("color_selected").value = constants.colorSelected;
1273
+ document.getElementById("min_freq").value = constants.MIN_FREQUENCY;
1274
+ document.getElementById("max_freq").value = constants.MAX_FREQUENCY;
1275
+ document.getElementById("keypress_interval").value =
1272
1276
  constants.keypressInterval;
1273
- if (typeof constants.openAIAuthKey == 'string') {
1274
- document.getElementById('openai_auth_key').value =
1277
+ if (typeof constants.openAIAuthKey == "string") {
1278
+ document.getElementById("openai_auth_key").value =
1275
1279
  constants.openAIAuthKey;
1276
1280
  }
1277
- if (typeof constants.geminiAuthKey == 'string') {
1278
- document.getElementById('gemini_auth_key').value =
1281
+ if (typeof constants.geminiAuthKey == "string") {
1282
+ document.getElementById("gemini_auth_key").value =
1279
1283
  constants.geminiAuthKey;
1280
1284
  }
1281
- document.getElementById('skill_level').value = constants.skillLevel;
1285
+ document.getElementById("skill_level").value = constants.skillLevel;
1282
1286
  if (constants.skillLevelOther) {
1283
- document.getElementById('skill_level_other').value =
1287
+ document.getElementById("skill_level_other").value =
1284
1288
  constants.skillLevelOther;
1285
1289
  }
1286
- document.getElementById('LLM_model').value = constants.LLMModel;
1290
+ document.getElementById("LLM_model").value = constants.LLMModel;
1287
1291
 
1288
1292
  // aria mode
1289
- if (constants.ariaMode == 'assertive') {
1290
- document.getElementById('aria_mode_assertive').checked = true;
1291
- document.getElementById('aria_mode_polite').checked = false;
1293
+ if (constants.ariaMode == "assertive") {
1294
+ document.getElementById("aria_mode_assertive").checked = true;
1295
+ document.getElementById("aria_mode_polite").checked = false;
1292
1296
  } else {
1293
- document.getElementById('aria_mode_polite').checked = true;
1294
- document.getElementById('aria_mode_assertive').checked = false;
1297
+ document.getElementById("aria_mode_polite").checked = true;
1298
+ document.getElementById("aria_mode_assertive").checked = false;
1295
1299
  }
1296
1300
  // hide either openai or gemini auth key field
1297
- if (constants.LLMModel == 'openai') {
1301
+ if (constants.LLMModel == "openai") {
1298
1302
  document
1299
- .getElementById('openai_auth_key_container')
1300
- .classList.remove('hidden');
1303
+ .getElementById("openai_auth_key_container")
1304
+ .classList.remove("hidden");
1301
1305
  document
1302
- .getElementById('gemini_auth_key_container')
1303
- .classList.add('hidden');
1304
- } else if (constants.LLMModel == 'gemini') {
1306
+ .getElementById("gemini_auth_key_container")
1307
+ .classList.add("hidden");
1308
+ } else if (constants.LLMModel == "gemini") {
1305
1309
  document
1306
- .getElementById('openai_auth_key_container')
1307
- .classList.add('hidden');
1310
+ .getElementById("openai_auth_key_container")
1311
+ .classList.add("hidden");
1308
1312
  document
1309
- .getElementById('gemini_auth_key_container')
1310
- .classList.remove('hidden');
1311
- } else if (constants.LLMModel == 'multi') {
1313
+ .getElementById("gemini_auth_key_container")
1314
+ .classList.remove("hidden");
1315
+ } else if (constants.LLMModel == "multi") {
1312
1316
  // multi LLM mode
1313
1317
  document
1314
- .getElementById('openai_auth_key_container')
1315
- .classList.remove('hidden');
1318
+ .getElementById("openai_auth_key_container")
1319
+ .classList.remove("hidden");
1316
1320
  document
1317
- .getElementById('gemini_auth_key_container')
1318
- .classList.remove('hidden');
1321
+ .getElementById("gemini_auth_key_container")
1322
+ .classList.remove("hidden");
1319
1323
  document
1320
- .getElementById('openai_multi_container')
1321
- .classList.remove('hidden');
1324
+ .getElementById("openai_multi_container")
1325
+ .classList.remove("hidden");
1322
1326
  document
1323
- .getElementById('gemini_multi_container')
1324
- .classList.remove('hidden');
1325
- document.getElementById('openai_multi').checked = false;
1327
+ .getElementById("gemini_multi_container")
1328
+ .classList.remove("hidden");
1329
+ document.getElementById("openai_multi").checked = false;
1326
1330
  if (constants.LLMOpenAiMulti) {
1327
- document.getElementById('openai_multi').checked = true;
1331
+ document.getElementById("openai_multi").checked = true;
1328
1332
  }
1329
- document.getElementById('gemini_multi').checked = false;
1333
+ document.getElementById("gemini_multi").checked = false;
1330
1334
  if (constants.LLMGeminiMulti) {
1331
- document.getElementById('gemini_multi').checked = true;
1335
+ document.getElementById("gemini_multi").checked = true;
1332
1336
  }
1333
1337
  }
1334
1338
  // skill level other
1335
- if (constants.skillLevel == 'other') {
1339
+ if (constants.skillLevel == "other") {
1336
1340
  document
1337
- .getElementById('skill_level_other_container')
1338
- .classList.remove('hidden');
1341
+ .getElementById("skill_level_other_container")
1342
+ .classList.remove("hidden");
1339
1343
  }
1340
1344
  // LLM preferences
1341
1345
  if (constants.LLMPreferences) {
1342
- document.getElementById('LLM_preferences').value =
1346
+ document.getElementById("LLM_preferences").value =
1343
1347
  constants.LLMPreferences;
1344
1348
  }
1345
- if (document.getElementById('LLM_reset_notification')) {
1346
- document.getElementById('LLM_reset_notification').remove();
1349
+ if (document.getElementById("LLM_reset_notification")) {
1350
+ document.getElementById("LLM_reset_notification").remove();
1347
1351
  }
1348
1352
  }
1349
1353
 
@@ -1354,33 +1358,33 @@ class Menu {
1354
1358
  SaveData() {
1355
1359
  let shouldReset = this.ShouldLLMReset();
1356
1360
 
1357
- constants.vol = document.getElementById('vol').value;
1358
- constants.autoPlayRate = document.getElementById('autoplay_rate').value;
1361
+ constants.vol = document.getElementById("vol").value;
1362
+ constants.autoPlayRate = document.getElementById("autoplay_rate").value;
1359
1363
  constants.brailleDisplayLength = document.getElementById(
1360
- 'braille_display_length'
1364
+ "braille_display_length"
1361
1365
  ).value;
1362
- constants.colorSelected = document.getElementById('color_selected').value;
1363
- constants.MIN_FREQUENCY = document.getElementById('min_freq').value;
1364
- constants.MAX_FREQUENCY = document.getElementById('max_freq').value;
1366
+ constants.colorSelected = document.getElementById("color_selected").value;
1367
+ constants.MIN_FREQUENCY = document.getElementById("min_freq").value;
1368
+ constants.MAX_FREQUENCY = document.getElementById("max_freq").value;
1365
1369
  constants.keypressInterval =
1366
- document.getElementById('keypress_interval').value;
1370
+ document.getElementById("keypress_interval").value;
1367
1371
 
1368
- constants.openAIAuthKey = document.getElementById('openai_auth_key').value;
1369
- constants.geminiAuthKey = document.getElementById('gemini_auth_key').value;
1370
- constants.skillLevel = document.getElementById('skill_level').value;
1372
+ constants.openAIAuthKey = document.getElementById("openai_auth_key").value;
1373
+ constants.geminiAuthKey = document.getElementById("gemini_auth_key").value;
1374
+ constants.skillLevel = document.getElementById("skill_level").value;
1371
1375
  constants.skillLevelOther =
1372
- document.getElementById('skill_level_other').value;
1373
- constants.LLMModel = document.getElementById('LLM_model').value;
1374
- constants.LLMPreferences = document.getElementById('LLM_preferences').value;
1375
- constants.LLMOpenAiMulti = document.getElementById('openai_multi').checked;
1376
- constants.LLMGeminiMulti = document.getElementById('gemini_multi').checked;
1377
- constants.autoInitLLM = document.getElementById('init_llm_on_load').checked;
1376
+ document.getElementById("skill_level_other").value;
1377
+ constants.LLMModel = document.getElementById("LLM_model").value;
1378
+ constants.LLMPreferences = document.getElementById("LLM_preferences").value;
1379
+ constants.LLMOpenAiMulti = document.getElementById("openai_multi").checked;
1380
+ constants.LLMGeminiMulti = document.getElementById("gemini_multi").checked;
1381
+ constants.autoInitLLM = document.getElementById("init_llm_on_load").checked;
1378
1382
 
1379
1383
  // aria
1380
- if (document.getElementById('aria_mode_assertive').checked) {
1381
- constants.ariaMode = 'assertive';
1382
- } else if (document.getElementById('aria_mode_polite').checked) {
1383
- constants.ariaMode = 'polite';
1384
+ if (document.getElementById("aria_mode_assertive").checked) {
1385
+ constants.ariaMode = "assertive";
1386
+ } else if (document.getElementById("aria_mode_polite").checked) {
1387
+ constants.ariaMode = "polite";
1384
1388
  }
1385
1389
 
1386
1390
  this.SaveDataToLocalStorage();
@@ -1399,29 +1403,29 @@ class Menu {
1399
1403
  */
1400
1404
  UpdateHtml() {
1401
1405
  // set aria attributes
1402
- constants.infoDiv.setAttribute('aria-live', constants.ariaMode);
1406
+ constants.infoDiv.setAttribute("aria-live", constants.ariaMode);
1403
1407
  document
1404
1408
  .getElementById(constants.announcement_container_id)
1405
- .setAttribute('aria-live', constants.ariaMode);
1409
+ .setAttribute("aria-live", constants.ariaMode);
1406
1410
 
1407
- document.getElementById('init_llm_on_load').checked = constants.autoInitLLM;
1408
- const scatter = document.getElementsByClassName('highlight_point');
1409
- const heatmap = document.getElementById('highlight_rect');
1410
- const line = document.getElementById('highlight_point');
1411
+ document.getElementById("init_llm_on_load").checked = constants.autoInitLLM;
1412
+ const scatter = document.getElementsByClassName("highlight_point");
1413
+ const heatmap = document.getElementById("highlight_rect");
1414
+ const line = document.getElementById("highlight_point");
1411
1415
 
1412
1416
  if (scatter !== null && scatter.length > 0) {
1413
1417
  for (let i = 0; i < scatter.length; i++) {
1414
- scatter[i].setAttribute('stroke', constants.colorSelected);
1415
- scatter[i].setAttribute('fill', constants.colorSelected);
1418
+ scatter[i].setAttribute("stroke", constants.colorSelected);
1419
+ scatter[i].setAttribute("fill", constants.colorSelected);
1416
1420
  }
1417
1421
  }
1418
1422
 
1419
1423
  if (heatmap !== null) {
1420
- heatmap.setAttribute('stroke', constants.colorSelected);
1424
+ heatmap.setAttribute("stroke", constants.colorSelected);
1421
1425
  }
1422
1426
 
1423
1427
  if (line !== null) {
1424
- line.setAttribute('stroke', constants.colorSelected);
1428
+ line.setAttribute("stroke", constants.colorSelected);
1425
1429
  }
1426
1430
  }
1427
1431
 
@@ -1433,19 +1437,19 @@ class Menu {
1433
1437
  let html =
1434
1438
  '<p id="LLM_reset_notification">Note: Changes in LLM settings will reset any existing conversation.</p>';
1435
1439
 
1436
- if (document.getElementById('LLM_reset_notification')) {
1437
- document.getElementById('LLM_reset_notification').remove();
1440
+ if (document.getElementById("LLM_reset_notification")) {
1441
+ document.getElementById("LLM_reset_notification").remove();
1438
1442
  }
1439
1443
  document
1440
- .getElementById('save_and_close_menu')
1441
- .parentElement.insertAdjacentHTML('afterend', html);
1444
+ .getElementById("save_and_close_menu")
1445
+ .parentElement.insertAdjacentHTML("afterend", html);
1442
1446
 
1443
1447
  // add to aria button text
1444
1448
  document
1445
- .getElementById('save_and_close_menu')
1449
+ .getElementById("save_and_close_menu")
1446
1450
  .setAttribute(
1447
- 'aria-labelledby',
1448
- 'save_and_close_text LLM_reset_notification'
1451
+ "aria-labelledby",
1452
+ "save_and_close_text LLM_reset_notification"
1449
1453
  );
1450
1454
  }
1451
1455
  /**
@@ -1457,29 +1461,29 @@ class Menu {
1457
1461
  let shouldReset = false;
1458
1462
  if (
1459
1463
  !shouldReset &&
1460
- constants.skillLevel != document.getElementById('skill_level').value
1464
+ constants.skillLevel != document.getElementById("skill_level").value
1461
1465
  ) {
1462
1466
  shouldReset = true;
1463
1467
  }
1464
1468
  if (
1465
1469
  !shouldReset &&
1466
1470
  constants.LLMPreferences !=
1467
- document.getElementById('LLM_preferences').value
1471
+ document.getElementById("LLM_preferences").value
1468
1472
  ) {
1469
1473
  shouldReset = true;
1470
1474
  }
1471
1475
  if (
1472
1476
  !shouldReset &&
1473
- constants.LLMModel != document.getElementById('LLM_model').value
1477
+ constants.LLMModel != document.getElementById("LLM_model").value
1474
1478
  ) {
1475
1479
  shouldReset = true;
1476
1480
  }
1477
1481
  if (
1478
1482
  !shouldReset &&
1479
1483
  (constants.LLMOpenAiMulti !=
1480
- document.getElementById('openai_multi').checked ||
1484
+ document.getElementById("openai_multi").checked ||
1481
1485
  constants.LLMGeminiMulti !=
1482
- document.getElementById('gemini_multi').checked)
1486
+ document.getElementById("gemini_multi").checked)
1483
1487
  ) {
1484
1488
  shouldReset = true;
1485
1489
  }
@@ -1497,23 +1501,23 @@ class Menu {
1497
1501
  data[constants.userSettingsKeys[i]] =
1498
1502
  constants[constants.userSettingsKeys[i]];
1499
1503
  }
1500
- localStorage.setItem('settings_data', JSON.stringify(data));
1504
+ localStorage.setItem("settings_data", JSON.stringify(data));
1501
1505
 
1502
1506
  // also save to tracking if we're doing that
1503
1507
  if (constants.isTracking) {
1504
1508
  // but not auth keys
1505
- data.openAIAuthKey = 'hidden';
1506
- data.geminiAuthKey = 'hidden';
1509
+ data.openAIAuthKey = "hidden";
1510
+ data.geminiAuthKey = "hidden";
1507
1511
  // and need a timestamp
1508
1512
  data.timestamp = new Date().toISOString();
1509
- tracker.SetData('settings', data);
1513
+ tracker.SetData("settings", data);
1510
1514
  }
1511
1515
  }
1512
1516
  /**
1513
1517
  * Loads data from 'settings_data' localStorage, and updates contants variables
1514
1518
  */
1515
1519
  LoadDataFromLocalStorage() {
1516
- let data = JSON.parse(localStorage.getItem('settings_data'));
1520
+ let data = JSON.parse(localStorage.getItem("settings_data"));
1517
1521
  if (data) {
1518
1522
  for (let i = 0; i < constants.userSettingsKeys.length; i++) {
1519
1523
  constants[constants.userSettingsKeys[i]] =
@@ -1539,9 +1543,9 @@ class ChatLLM {
1539
1543
  if (constants.autoInitLLM) {
1540
1544
  // only run if we have API keys set
1541
1545
  if (
1542
- (constants.LLMModel == 'openai' && constants.openAIAuthKey) ||
1543
- (constants.LLMModel == 'gemini' && constants.geminiAuthKey) ||
1544
- (constants.LLMModel == 'multi' &&
1546
+ (constants.LLMModel == "openai" && constants.openAIAuthKey) ||
1547
+ (constants.LLMModel == "gemini" && constants.geminiAuthKey) ||
1548
+ (constants.LLMModel == "multi" &&
1545
1549
  constants.openAIAuthKey &&
1546
1550
  constants.geminiAuthKey)
1547
1551
  ) {
@@ -1596,7 +1600,7 @@ class ChatLLM {
1596
1600
  </div>
1597
1601
  <div id="chatLLM_modal_backdrop" class="modal-backdrop hidden"></div>
1598
1602
  `;
1599
- document.querySelector('body').insertAdjacentHTML('beforeend', html);
1603
+ document.querySelector("body").insertAdjacentHTML("beforeend", html);
1600
1604
  }
1601
1605
 
1602
1606
  /**
@@ -1605,21 +1609,21 @@ class ChatLLM {
1605
1609
  */
1606
1610
  SetEvents() {
1607
1611
  // chatLLM close events
1608
- let allClose = document.querySelectorAll('#close_chatLLM, #chatLLM .close');
1612
+ let allClose = document.querySelectorAll("#close_chatLLM, #chatLLM .close");
1609
1613
  for (let i = 0; i < allClose.length; i++) {
1610
1614
  constants.events.push([
1611
1615
  allClose[i],
1612
- 'click',
1616
+ "click",
1613
1617
  function (e) {
1614
1618
  chatLLM.Toggle(false);
1615
1619
  },
1616
1620
  ]);
1617
1621
  }
1618
1622
  constants.events.push([
1619
- document.getElementById('chatLLM'),
1620
- 'keyup',
1623
+ document.getElementById("chatLLM"),
1624
+ "keyup",
1621
1625
  function (e) {
1622
- if (e.key == 'Esc') {
1626
+ if (e.key == "Esc") {
1623
1627
  // esc
1624
1628
  chatLLM.Toggle(false);
1625
1629
  }
@@ -1629,9 +1633,9 @@ class ChatLLM {
1629
1633
  // ChatLLM open/close toggle
1630
1634
  constants.events.push([
1631
1635
  document,
1632
- 'keyup',
1636
+ "keyup",
1633
1637
  function (e) {
1634
- if ((e.key == '?' && (e.ctrlKey || e.metaKey)) || e.key == '¿') {
1638
+ if ((e.key == "?" && (e.ctrlKey || e.metaKey)) || e.key == "¿") {
1635
1639
  chatLLM.Toggle();
1636
1640
  }
1637
1641
  },
@@ -1639,21 +1643,21 @@ class ChatLLM {
1639
1643
 
1640
1644
  // ChatLLM request events
1641
1645
  constants.events.push([
1642
- document.getElementById('chatLLM_submit'),
1643
- 'click',
1646
+ document.getElementById("chatLLM_submit"),
1647
+ "click",
1644
1648
  function (e) {
1645
- let text = document.getElementById('chatLLM_input').value;
1646
- chatLLM.DisplayChatMessage('User', text);
1649
+ let text = document.getElementById("chatLLM_input").value;
1650
+ chatLLM.DisplayChatMessage("User", text);
1647
1651
  chatLLM.Submit(text);
1648
1652
  },
1649
1653
  ]);
1650
1654
  constants.events.push([
1651
- document.getElementById('chatLLM_input'),
1652
- 'keyup',
1655
+ document.getElementById("chatLLM_input"),
1656
+ "keyup",
1653
1657
  function (e) {
1654
- if (e.key == 'Enter' && !e.shiftKey) {
1655
- let text = document.getElementById('chatLLM_input').value;
1656
- chatLLM.DisplayChatMessage('User', text);
1658
+ if (e.key == "Enter" && !e.shiftKey) {
1659
+ let text = document.getElementById("chatLLM_input").value;
1660
+ chatLLM.DisplayChatMessage("User", text);
1657
1661
  chatLLM.Submit(text);
1658
1662
  }
1659
1663
  },
@@ -1662,15 +1666,15 @@ class ChatLLM {
1662
1666
  // ChatLLM suggestion events
1663
1667
  // actual suggestions:
1664
1668
  let suggestions = document.querySelectorAll(
1665
- '#chatLLM .LLM_suggestions button:not(#more_suggestions)'
1669
+ "#chatLLM .LLM_suggestions button:not(#more_suggestions)"
1666
1670
  );
1667
1671
  for (let i = 0; i < suggestions.length; i++) {
1668
1672
  constants.events.push([
1669
1673
  suggestions[i],
1670
- 'click',
1674
+ "click",
1671
1675
  function (e) {
1672
1676
  let text = e.target.innerHTML;
1673
- chatLLM.DisplayChatMessage('User', text);
1677
+ chatLLM.DisplayChatMessage("User", text);
1674
1678
  chatLLM.Submit(text);
1675
1679
  },
1676
1680
  ]);
@@ -1678,24 +1682,24 @@ class ChatLLM {
1678
1682
 
1679
1683
  // Delete OpenAI and Gemini keys
1680
1684
  constants.events.push([
1681
- document.getElementById('delete_openai_key'),
1682
- 'click',
1685
+ document.getElementById("delete_openai_key"),
1686
+ "click",
1683
1687
  function (e) {
1684
- document.getElementById('openai_auth_key').value = '';
1688
+ document.getElementById("openai_auth_key").value = "";
1685
1689
  },
1686
1690
  ]);
1687
1691
  constants.events.push([
1688
- document.getElementById('delete_gemini_key'),
1689
- 'click',
1692
+ document.getElementById("delete_gemini_key"),
1693
+ "click",
1690
1694
  function (e) {
1691
- document.getElementById('gemini_auth_key').value = '';
1695
+ document.getElementById("gemini_auth_key").value = "";
1692
1696
  },
1693
1697
  ]);
1694
1698
 
1695
1699
  // Reset chatLLM
1696
1700
  constants.events.push([
1697
- document.getElementById('reset_chatLLM'),
1698
- 'click',
1701
+ document.getElementById("reset_chatLLM"),
1702
+ "click",
1699
1703
  function (e) {
1700
1704
  chatLLM.ResetLLM();
1701
1705
  },
@@ -1703,15 +1707,15 @@ class ChatLLM {
1703
1707
 
1704
1708
  // copy to clipboard
1705
1709
  constants.events.push([
1706
- document.getElementById('chatLLM'),
1707
- 'click',
1710
+ document.getElementById("chatLLM"),
1711
+ "click",
1708
1712
  function (e) {
1709
1713
  chatLLM.CopyChatHistory(e);
1710
1714
  },
1711
1715
  ]);
1712
1716
  constants.events.push([
1713
- document.getElementById('chatLLM'),
1714
- 'keyup',
1717
+ document.getElementById("chatLLM"),
1718
+ "keyup",
1715
1719
  function (e) {
1716
1720
  chatLLM.CopyChatHistory(e);
1717
1721
  },
@@ -1729,96 +1733,96 @@ class ChatLLM {
1729
1733
  * @param {Event|undefined} e - The event that triggered the copy action. If undefined, the entire chat history is copied.
1730
1734
  */
1731
1735
  CopyChatHistory(e) {
1732
- let text = '';
1733
- if (typeof e == 'undefined') {
1736
+ let text = "";
1737
+ if (typeof e == "undefined") {
1734
1738
  // check for passthrough
1735
1739
  // get html of the full chat history
1736
- text = document.getElementById('chatLLM_chat_history').innerHTML;
1737
- } else if (e.type == 'click') {
1740
+ text = document.getElementById("chatLLM_chat_history").innerHTML;
1741
+ } else if (e.type == "click") {
1738
1742
  // check for buttons
1739
- if (e.target.id == 'chatLLM_copy_all') {
1743
+ if (e.target.id == "chatLLM_copy_all") {
1740
1744
  // get html of the full chat history
1741
- text = document.getElementById('chatLLM_chat_history').innerHTML;
1742
- } else if (e.target.classList.contains('chatLLM_message_copy_button')) {
1745
+ text = document.getElementById("chatLLM_chat_history").innerHTML;
1746
+ } else if (e.target.classList.contains("chatLLM_message_copy_button")) {
1743
1747
  // get the text of the element before the button
1744
- text = e.target.closest('p').previousElementSibling.innerHTML;
1748
+ text = e.target.closest("p").previousElementSibling.innerHTML;
1745
1749
  }
1746
- } else if (e.type == 'keyup') {
1750
+ } else if (e.type == "keyup") {
1747
1751
  // check for alt shift c or ctrl shift c
1748
- if (e.key == 'C' && (e.ctrlKey || e.metaKey || e.altKey) && e.shiftKey) {
1752
+ if (e.key == "C" && (e.ctrlKey || e.metaKey || e.altKey) && e.shiftKey) {
1749
1753
  e.preventDefault();
1750
1754
  // get the last message
1751
1755
  let elem = document.querySelector(
1752
- '#chatLLM_chat_history > .chatLLM_message_other:last-of-type'
1756
+ "#chatLLM_chat_history > .chatLLM_message_other:last-of-type"
1753
1757
  );
1754
1758
  if (elem) {
1755
1759
  text = elem.innerHTML;
1756
1760
  }
1757
1761
  } else if (
1758
- e.key == 'A' &&
1762
+ e.key == "A" &&
1759
1763
  (e.ctrlKey || e.metaKey || e.altKey) &&
1760
1764
  e.shiftKey
1761
1765
  ) {
1762
1766
  e.preventDefault();
1763
1767
  // get html of the full chat history
1764
- text = document.getElementById('chatLLM_chat_history').innerHTML;
1768
+ text = document.getElementById("chatLLM_chat_history").innerHTML;
1765
1769
  }
1766
1770
  }
1767
1771
 
1768
- if (text == '') {
1772
+ if (text == "") {
1769
1773
  return;
1770
1774
  } else {
1771
1775
  // clear the html, removing buttons etc
1772
- let cleanElems = document.createElement('div');
1776
+ let cleanElems = document.createElement("div");
1773
1777
  cleanElems.innerHTML = text;
1774
- let removeThese = cleanElems.querySelectorAll('.chatLLM_message_copy');
1778
+ let removeThese = cleanElems.querySelectorAll(".chatLLM_message_copy");
1775
1779
  removeThese.forEach((elem) => elem.remove());
1776
1780
 
1777
1781
  // convert from html to markdown
1778
1782
  let markdown = this.htmlToMarkdown(cleanElems);
1779
1783
  // this messes up a bit with spacing, so kill more than 2 newlines in a row
1780
- markdown = markdown.replace(/\n{3,}/g, '\n\n');
1784
+ markdown = markdown.replace(/\n{3,}/g, "\n\n");
1781
1785
 
1782
1786
  try {
1783
1787
  navigator.clipboard.writeText(markdown); // note: this fails if you're on the inspector. That's fine as it'll never happen to real users
1784
1788
  } catch (err) {
1785
- console.error('Failed to copy: ', err);
1789
+ console.error("Failed to copy: ", err);
1786
1790
  }
1787
1791
  return markdown;
1788
1792
  }
1789
1793
  }
1790
1794
 
1791
1795
  htmlToMarkdown(element) {
1792
- let markdown = '';
1796
+ let markdown = "";
1793
1797
 
1794
1798
  const convertElementToMarkdown = (element) => {
1795
1799
  switch (element.tagName) {
1796
- case 'H1':
1800
+ case "H1":
1797
1801
  return `# ${element.textContent}`;
1798
- case 'H2':
1802
+ case "H2":
1799
1803
  return `## ${element.textContent}`;
1800
- case 'H3':
1804
+ case "H3":
1801
1805
  return `### ${element.textContent}`;
1802
- case 'H4':
1806
+ case "H4":
1803
1807
  return `#### ${element.textContent}`;
1804
- case 'H5':
1808
+ case "H5":
1805
1809
  return `##### ${element.textContent}`;
1806
- case 'H6':
1810
+ case "H6":
1807
1811
  return `###### ${element.textContent}`;
1808
- case 'P':
1812
+ case "P":
1809
1813
  return element.textContent;
1810
- case 'DIV':
1814
+ case "DIV":
1811
1815
  // For divs, process each child and add newlines as needed
1812
1816
  return (
1813
1817
  Array.from(element.childNodes)
1814
1818
  .map((child) => convertElementToMarkdown(child))
1815
- .join('\n') + '\n\n'
1819
+ .join("\n") + "\n\n"
1816
1820
  );
1817
1821
  default:
1818
1822
  // For any other element, process its children recursively
1819
1823
  return Array.from(element.childNodes)
1820
1824
  .map((child) => convertElementToMarkdown(child))
1821
- .join('');
1825
+ .join("");
1822
1826
  }
1823
1827
  };
1824
1828
 
@@ -1826,7 +1830,7 @@ class ChatLLM {
1826
1830
  markdown += convertElementToMarkdown(element);
1827
1831
  } else if (
1828
1832
  element.nodeType === Node.TEXT_NODE &&
1829
- element.textContent.trim() !== ''
1833
+ element.textContent.trim() !== ""
1830
1834
  ) {
1831
1835
  markdown += element.textContent.trim();
1832
1836
  }
@@ -1851,14 +1855,14 @@ class ChatLLM {
1851
1855
 
1852
1856
  // 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
1853
1857
  if (
1854
- (this.firstOpen || constants.LLMModel == 'gemini') &&
1858
+ (this.firstOpen || constants.LLMModel == "gemini") &&
1855
1859
  !firsttime &&
1856
1860
  constants.verboseText.length > 0
1857
1861
  ) {
1858
1862
  text =
1859
1863
  "Here is the current position in the chart; no response necessarily needed, use this info only if it's relevant to future questions: " +
1860
1864
  constants.verboseText +
1861
- '. My question is: ' +
1865
+ ". My question is: " +
1862
1866
  text;
1863
1867
 
1864
1868
  this.firstOpen = false;
@@ -1869,15 +1873,15 @@ class ChatLLM {
1869
1873
  this.WaitingSound(true);
1870
1874
  }
1871
1875
 
1872
- if (constants.LLMOpenAiMulti || constants.LLMModel == 'openai') {
1876
+ if (constants.LLMOpenAiMulti || constants.LLMModel == "openai") {
1873
1877
  if (firsttime) {
1874
- img = await this.ConvertSVGtoJPG(singleMaidr.id, 'openai');
1878
+ img = await this.ConvertSVGtoJPG(singleMaidr.id, "openai");
1875
1879
  }
1876
1880
  chatLLM.OpenAIPrompt(text, img);
1877
1881
  }
1878
- if (constants.LLMGeminiMulti || constants.LLMModel == 'gemini') {
1882
+ if (constants.LLMGeminiMulti || constants.LLMModel == "gemini") {
1879
1883
  if (firsttime) {
1880
- img = await this.ConvertSVGtoJPG(singleMaidr.id, 'gemini');
1884
+ img = await this.ConvertSVGtoJPG(singleMaidr.id, "gemini");
1881
1885
  }
1882
1886
  chatLLM.GeminiPrompt(text, img);
1883
1887
  }
@@ -1938,7 +1942,7 @@ class ChatLLM {
1938
1942
  }, 30000);
1939
1943
 
1940
1944
  // set queue for multi
1941
- if (constants.LLMModel != 'multi') {
1945
+ if (constants.LLMModel != "multi") {
1942
1946
  constants.waitingQueue = 1;
1943
1947
  } else {
1944
1948
  constants.waitingQueue = 0;
@@ -1970,7 +1974,7 @@ class ChatLLM {
1970
1974
  // get name from resource
1971
1975
  let LLMName = resources.GetString(constants.LLMModel);
1972
1976
  this.firstTime = false;
1973
- this.DisplayChatMessage(LLMName, resources.GetString('processing'), true);
1977
+ this.DisplayChatMessage(LLMName, resources.GetString("processing"), true);
1974
1978
  let defaultPrompt = this.GetDefaultPrompt();
1975
1979
  this.Submit(defaultPrompt, true);
1976
1980
  }
@@ -1982,35 +1986,35 @@ class ChatLLM {
1982
1986
  */
1983
1987
  ProcessLLMResponse(data, model) {
1984
1988
  chatLLM.WaitingSound(false);
1985
- console.log('LLM response: ', data);
1986
- let text = '';
1989
+ console.log("LLM response: ", data);
1990
+ let text = "";
1987
1991
  let LLMName = resources.GetString(model);
1988
1992
 
1989
- if (model == 'openai') {
1993
+ if (model == "openai") {
1990
1994
  text = data.choices[0].message.content;
1991
1995
  let i = this.requestJson.messages.length;
1992
1996
  this.requestJson.messages[i] = {};
1993
- this.requestJson.messages[i].role = 'assistant';
1997
+ this.requestJson.messages[i].role = "assistant";
1994
1998
  this.requestJson.messages[i].content = text;
1995
1999
 
1996
2000
  if (data.error) {
1997
- chatLLM.DisplayChatMessage(LLMName, 'Error processing request.', true);
2001
+ chatLLM.DisplayChatMessage(LLMName, "Error processing request.", true);
1998
2002
  chatLLM.WaitingSound(false);
1999
2003
  } else {
2000
2004
  chatLLM.DisplayChatMessage(LLMName, text);
2001
2005
  }
2002
- } else if (model == 'gemini') {
2006
+ } else if (model == "gemini") {
2003
2007
  if (data.text()) {
2004
2008
  text = data.text();
2005
2009
  chatLLM.DisplayChatMessage(LLMName, text);
2006
2010
  } else {
2007
2011
  if (!data.error) {
2008
- data.error = 'Error processing request.';
2012
+ data.error = "Error processing request.";
2009
2013
  chatLLM.WaitingSound(false);
2010
2014
  }
2011
2015
  }
2012
2016
  if (data.error) {
2013
- chatLLM.DisplayChatMessage(LLMName, 'Error processing request.', true);
2017
+ chatLLM.DisplayChatMessage(LLMName, "Error processing request.", true);
2014
2018
  chatLLM.WaitingSound(false);
2015
2019
  } else {
2016
2020
  // todo: display actual response
@@ -2020,7 +2024,7 @@ class ChatLLM {
2020
2024
  // if we're tracking, log the data
2021
2025
  if (constants.isTracking) {
2022
2026
  let chatHist = chatLLM.CopyChatHistory();
2023
- tracker.SetData('ChatHistory', chatHist);
2027
+ tracker.SetData("ChatHistory", chatHist);
2024
2028
  }
2025
2029
  }
2026
2030
 
@@ -2034,11 +2038,11 @@ class ChatLLM {
2034
2038
  if (this.requestJson.messages.length > 2) {
2035
2039
  // subsequent responses
2036
2040
  responseText = {
2037
- id: 'chatcmpl-8Y44iRCRrohYbAqm8rfBbJqTUADC7',
2038
- object: 'chat.completion',
2041
+ id: "chatcmpl-8Y44iRCRrohYbAqm8rfBbJqTUADC7",
2042
+ object: "chat.completion",
2039
2043
  created: 1703129508,
2040
2044
  //model: 'gpt-4-1106-vision-preview',
2041
- model: 'gpt4-o',
2045
+ model: "gpt4-o",
2042
2046
  usage: {
2043
2047
  prompt_tokens: 451,
2044
2048
  completion_tokens: 16,
@@ -2047,10 +2051,10 @@ class ChatLLM {
2047
2051
  choices: [
2048
2052
  {
2049
2053
  message: {
2050
- role: 'assistant',
2051
- content: 'A fake response from the LLM. Nice.',
2054
+ role: "assistant",
2055
+ content: "A fake response from the LLM. Nice.",
2052
2056
  },
2053
- finish_reason: 'length',
2057
+ finish_reason: "length",
2054
2058
  index: 0,
2055
2059
  },
2056
2060
  ],
@@ -2058,10 +2062,10 @@ class ChatLLM {
2058
2062
  } else {
2059
2063
  // first response
2060
2064
  responseText = {
2061
- id: 'chatcmpl-8Y44iRCRrohYbAqm8rfBbJqTUADC7',
2062
- object: 'chat.completion',
2065
+ id: "chatcmpl-8Y44iRCRrohYbAqm8rfBbJqTUADC7",
2066
+ object: "chat.completion",
2063
2067
  created: 1703129508,
2064
- model: 'gpt-4-1106-vision-preview',
2068
+ model: "gpt-4-1106-vision-preview",
2065
2069
  usage: {
2066
2070
  prompt_tokens: 451,
2067
2071
  completion_tokens: 16,
@@ -2070,11 +2074,11 @@ class ChatLLM {
2070
2074
  choices: [
2071
2075
  {
2072
2076
  message: {
2073
- role: 'assistant',
2077
+ role: "assistant",
2074
2078
  content:
2075
- 'The chart you\'re referring to is a bar graph titled "The Number of Diamonds',
2079
+ "The chart you're referring to is a bar graph titled \"The Number of Diamonds",
2076
2080
  },
2077
- finish_reason: 'length',
2081
+ finish_reason: "length",
2078
2082
  index: 0,
2079
2083
  },
2080
2084
  ],
@@ -2093,49 +2097,49 @@ class ChatLLM {
2093
2097
  */
2094
2098
  OpenAIPrompt(text, img = null) {
2095
2099
  // request init
2096
- let url = 'https://api.openai.com/v1/chat/completions';
2100
+ let url = "https://api.openai.com/v1/chat/completions";
2097
2101
  let auth = constants.openAIAuthKey;
2098
2102
  let requestJson = chatLLM.OpenAIJson(text, img);
2099
- console.log('LLM request: ', requestJson);
2103
+ console.log("LLM request: ", requestJson);
2100
2104
 
2101
2105
  fetch(url, {
2102
- method: 'POST',
2106
+ method: "POST",
2103
2107
  headers: {
2104
- 'Content-Type': 'application/json',
2105
- Authorization: 'Bearer ' + auth,
2108
+ "Content-Type": "application/json",
2109
+ Authorization: "Bearer " + auth,
2106
2110
  },
2107
2111
  body: JSON.stringify(requestJson),
2108
2112
  })
2109
2113
  .then((response) => response.json())
2110
2114
  .then((data) => {
2111
- chatLLM.ProcessLLMResponse(data, 'openai');
2115
+ chatLLM.ProcessLLMResponse(data, "openai");
2112
2116
  })
2113
2117
  .catch((error) => {
2114
2118
  chatLLM.WaitingSound(false);
2115
- console.error('Error:', error);
2116
- chatLLM.DisplayChatMessage('OpenAI', 'Error processing request.', true);
2119
+ console.error("Error:", error);
2120
+ chatLLM.DisplayChatMessage("OpenAI", "Error processing request.", true);
2117
2121
  // also todo: handle errors somehow
2118
2122
  });
2119
2123
  }
2120
2124
  OpenAIJson(text, img = null) {
2121
2125
  let sysMessage = constants.LLMSystemMessage;
2122
2126
  let backupMessage =
2123
- 'Describe ' + singleMaidr.type + ' charts to a blind person';
2127
+ "Describe " + singleMaidr.type + " charts to a blind person";
2124
2128
  // headers and sys message
2125
2129
  if (!this.requestJson) {
2126
2130
  this.requestJson = {};
2127
2131
  //this.requestJson.model = 'gpt-4-vision-preview';
2128
- this.requestJson.model = 'gpt-4o-2024-08-06';
2132
+ this.requestJson.model = "gpt-4o-2024-08-06";
2129
2133
  this.requestJson.max_tokens = constants.LLMmaxResponseTokens; // note: if this is too short (tested with less than 200), the response gets cut off
2130
2134
 
2131
2135
  // sys message
2132
2136
  this.requestJson.messages = [];
2133
2137
  this.requestJson.messages[0] = {};
2134
- this.requestJson.messages[0].role = 'system';
2138
+ this.requestJson.messages[0].role = "system";
2135
2139
  this.requestJson.messages[0].content = sysMessage;
2136
2140
  if (constants.LLMPreferences) {
2137
2141
  this.requestJson.messages[1] = {};
2138
- this.requestJson.messages[1].role = 'system';
2142
+ this.requestJson.messages[1].role = "system";
2139
2143
  this.requestJson.messages[1].content = constants.LLMPreferences;
2140
2144
  }
2141
2145
  }
@@ -2144,16 +2148,16 @@ class ChatLLM {
2144
2148
  // if we have an image (first time only), send the image and the text, otherwise just the text
2145
2149
  let i = this.requestJson.messages.length;
2146
2150
  this.requestJson.messages[i] = {};
2147
- this.requestJson.messages[i].role = 'user';
2151
+ this.requestJson.messages[i].role = "user";
2148
2152
  if (img) {
2149
2153
  // first message, include the img
2150
2154
  this.requestJson.messages[i].content = [
2151
2155
  {
2152
- type: 'text',
2156
+ type: "text",
2153
2157
  text: text,
2154
2158
  },
2155
2159
  {
2156
- type: 'image_url',
2160
+ type: "image_url",
2157
2161
  image_url: { url: img },
2158
2162
  },
2159
2163
  ];
@@ -2178,12 +2182,12 @@ class ChatLLM {
2178
2182
 
2179
2183
  // Import the module
2180
2184
  const { GoogleGenerativeAI } = await import(
2181
- 'https://esm.run/@google/generative-ai'
2185
+ "https://esm.run/@google/generative-ai"
2182
2186
  );
2183
2187
  const API_KEY = constants.geminiAuthKey;
2184
2188
  const genAI = new GoogleGenerativeAI(API_KEY);
2185
2189
  const model = genAI.getGenerativeModel({
2186
- model: 'gemini-1.5-pro-latest',
2190
+ model: "gemini-1.5-pro-latest",
2187
2191
  }); // old model was 'gemini-pro-vision'
2188
2192
 
2189
2193
  // Create the prompt
@@ -2191,25 +2195,25 @@ class ChatLLM {
2191
2195
  if (constants.LLMPreferences) {
2192
2196
  prompt += constants.LLMPreferences;
2193
2197
  }
2194
- prompt += '\n\n' + text; // Use the text parameter as the prompt
2198
+ prompt += "\n\n" + text; // Use the text parameter as the prompt
2195
2199
  const image = {
2196
2200
  inlineData: {
2197
2201
  data: imgBase64, // Use the base64 image string
2198
- mimeType: 'image/png', // Or the appropriate mime type of your image
2202
+ mimeType: "image/png", // Or the appropriate mime type of your image
2199
2203
  },
2200
2204
  };
2201
2205
 
2202
2206
  // Generate the content
2203
- console.log('LLM request: ', prompt, image);
2207
+ console.log("LLM request: ", prompt, image);
2204
2208
  const result = await model.generateContent([prompt, image]);
2205
2209
  console.log(result.response.text());
2206
2210
 
2207
2211
  // Process the response
2208
- chatLLM.ProcessLLMResponse(result.response, 'gemini');
2212
+ chatLLM.ProcessLLMResponse(result.response, "gemini");
2209
2213
  } catch (error) {
2210
2214
  chatLLM.WaitingSound(false);
2211
- chatLLM.DisplayChatMessage('Gemini', 'Error processing request.', true);
2212
- console.error('Error in GeminiPrompt:', error);
2215
+ chatLLM.DisplayChatMessage("Gemini", "Error processing request.", true);
2216
+ console.error("Error in GeminiPrompt:", error);
2213
2217
  throw error; // Rethrow the error for further handling if necessary
2214
2218
  }
2215
2219
  }
@@ -2221,11 +2225,11 @@ class ChatLLM {
2221
2225
  * @memberof module:constants
2222
2226
  * @returns {void}
2223
2227
  */
2224
- DisplayChatMessage(user = 'User', text = '', isSystem = false) {
2225
- let hLevel = 'h3';
2226
- if (!isSystem && constants.LLMModel == 'multi' && user != 'User') {
2228
+ DisplayChatMessage(user = "User", text = "", isSystem = false) {
2229
+ let hLevel = "h3";
2230
+ if (!isSystem && constants.LLMModel == "multi" && user != "User") {
2227
2231
  if (this.firstMulti) {
2228
- let multiAIName = resources.GetString('multi');
2232
+ let multiAIName = resources.GetString("multi");
2229
2233
  let titleHtml = `
2230
2234
  <div class="chatLLM_message chatLLM_message_other">
2231
2235
  <h3 class="chatLLM_message_user">${multiAIName} Responses</h3>
@@ -2234,20 +2238,20 @@ class ChatLLM {
2234
2238
  this.RenderChatMessage(titleHtml);
2235
2239
  this.firstMulti = false;
2236
2240
  }
2237
- hLevel = 'h4';
2241
+ hLevel = "h4";
2238
2242
  }
2239
2243
  let html = `
2240
2244
  <div class="chatLLM_message ${
2241
- user == 'User' ? 'chatLLM_message_self' : 'chatLLM_message_other'
2245
+ user == "User" ? "chatLLM_message_self" : "chatLLM_message_other"
2242
2246
  }">`;
2243
- if (text != resources.GetString('processing')) {
2247
+ if (text != resources.GetString("processing")) {
2244
2248
  html += `<${hLevel} class="chatLLM_message_user">${user}</${hLevel}>`;
2245
2249
  }
2246
2250
  html += `<p class="chatLLM_message_text">${text}</p>
2247
2251
  </div>
2248
2252
  `;
2249
2253
  // add a copy button to actual messages
2250
- if (user != 'User' && text != resources.GetString('processing')) {
2254
+ if (user != "User" && text != resources.GetString("processing")) {
2251
2255
  html += `
2252
2256
  <p class="chatLLM_message_copy"><button class="chatLLM_message_copy_button">Copy</button></p>
2253
2257
  `;
@@ -2257,13 +2261,13 @@ class ChatLLM {
2257
2261
  }
2258
2262
  RenderChatMessage(html) {
2259
2263
  document
2260
- .getElementById('chatLLM_chat_history')
2261
- .insertAdjacentHTML('beforeend', html);
2262
- document.getElementById('chatLLM_input').value = '';
2264
+ .getElementById("chatLLM_chat_history")
2265
+ .insertAdjacentHTML("beforeend", html);
2266
+ document.getElementById("chatLLM_input").value = "";
2263
2267
 
2264
2268
  // scroll to bottom
2265
- document.getElementById('chatLLM_chat_history').scrollTop =
2266
- document.getElementById('chatLLM_chat_history').scrollHeight;
2269
+ document.getElementById("chatLLM_chat_history").scrollTop =
2270
+ document.getElementById("chatLLM_chat_history").scrollHeight;
2267
2271
  }
2268
2272
 
2269
2273
  /**
@@ -2271,7 +2275,7 @@ class ChatLLM {
2271
2275
  */
2272
2276
  ResetLLM() {
2273
2277
  // clear the main chat history
2274
- document.getElementById('chatLLM_chat_history').innerHTML = '';
2278
+ document.getElementById("chatLLM_chat_history").innerHTML = "";
2275
2279
 
2276
2280
  // reset the data
2277
2281
  this.requestJson = null;
@@ -2292,11 +2296,11 @@ class ChatLLM {
2292
2296
  */
2293
2297
  Destroy() {
2294
2298
  // chatLLM element destruction
2295
- let chatLLM = document.getElementById('chatLLM');
2299
+ let chatLLM = document.getElementById("chatLLM");
2296
2300
  if (chatLLM) {
2297
2301
  chatLLM.remove();
2298
2302
  }
2299
- let backdrop = document.getElementById('chatLLM_modal_backdrop');
2303
+ let backdrop = document.getElementById("chatLLM_modal_backdrop");
2300
2304
  if (backdrop) {
2301
2305
  backdrop.remove();
2302
2306
  }
@@ -2307,8 +2311,8 @@ class ChatLLM {
2307
2311
  * @param {boolean} [onoff=false] - Whether to turn the chatLLM on or off. Defaults to false (close).
2308
2312
  */
2309
2313
  Toggle(onoff) {
2310
- if (typeof onoff == 'undefined') {
2311
- if (document.getElementById('chatLLM').classList.contains('hidden')) {
2314
+ if (typeof onoff == "undefined") {
2315
+ if (document.getElementById("chatLLM").classList.contains("hidden")) {
2312
2316
  onoff = true;
2313
2317
  } else {
2314
2318
  onoff = false;
@@ -2319,19 +2323,19 @@ class ChatLLM {
2319
2323
  // open
2320
2324
  this.whereWasMyFocus = document.activeElement;
2321
2325
  constants.tabMovement = 0;
2322
- document.getElementById('chatLLM').classList.remove('hidden');
2326
+ document.getElementById("chatLLM").classList.remove("hidden");
2323
2327
  document
2324
- .getElementById('chatLLM_modal_backdrop')
2325
- .classList.remove('hidden');
2326
- document.querySelector('#chatLLM .close').focus();
2328
+ .getElementById("chatLLM_modal_backdrop")
2329
+ .classList.remove("hidden");
2330
+ document.querySelector("#chatLLM .close").focus();
2327
2331
 
2328
2332
  if (this.firstTime) {
2329
2333
  this.InitChatMessage();
2330
2334
  }
2331
2335
  } else {
2332
2336
  // close
2333
- document.getElementById('chatLLM').classList.add('hidden');
2334
- document.getElementById('chatLLM_modal_backdrop').classList.add('hidden');
2337
+ document.getElementById("chatLLM").classList.add("hidden");
2338
+ document.getElementById("chatLLM_modal_backdrop").classList.add("hidden");
2335
2339
  this.whereWasMyFocus.focus();
2336
2340
  this.whereWasMyFocus = null;
2337
2341
  this.firstOpen = true;
@@ -2345,11 +2349,11 @@ class ChatLLM {
2345
2349
  async ConvertSVGtoJPG(id, model) {
2346
2350
  let svgElement = document.getElementById(id);
2347
2351
  return new Promise((resolve, reject) => {
2348
- var canvas = document.createElement('canvas');
2349
- var ctx = canvas.getContext('2d');
2352
+ var canvas = document.createElement("canvas");
2353
+ var ctx = canvas.getContext("2d");
2350
2354
 
2351
2355
  var svgData = new XMLSerializer().serializeToString(svgElement);
2352
- if (!svgData.startsWith('<svg xmlns')) {
2356
+ if (!svgData.startsWith("<svg xmlns")) {
2353
2357
  svgData = `<svg xmlns="http://www.w3.org/2000/svg" ${svgData.slice(4)}`;
2354
2358
  }
2355
2359
 
@@ -2361,11 +2365,11 @@ class ChatLLM {
2361
2365
  var img = new Image();
2362
2366
  img.onload = function () {
2363
2367
  ctx.drawImage(img, 0, 0, svgSize.width, svgSize.height);
2364
- var jpegData = canvas.toDataURL('image/jpeg', 0.9); // 0.9 is the quality parameter
2365
- if (model == 'openai') {
2368
+ var jpegData = canvas.toDataURL("image/jpeg", 0.9); // 0.9 is the quality parameter
2369
+ if (model == "openai") {
2366
2370
  resolve(jpegData);
2367
- } else if (model == 'gemini') {
2368
- let base64Data = jpegData.split(',')[1];
2371
+ } else if (model == "gemini") {
2372
+ let base64Data = jpegData.split(",")[1];
2369
2373
  resolve(base64Data);
2370
2374
  //resolve(jpegData);
2371
2375
  }
@@ -2373,11 +2377,11 @@ class ChatLLM {
2373
2377
  };
2374
2378
 
2375
2379
  img.onerror = function () {
2376
- reject(new Error('Error loading SVG'));
2380
+ reject(new Error("Error loading SVG"));
2377
2381
  };
2378
2382
 
2379
2383
  var svgBlob = new Blob([svgData], {
2380
- type: 'image/svg+xml;charset=utf-8',
2384
+ type: "image/svg+xml;charset=utf-8",
2381
2385
  });
2382
2386
  var url = URL.createObjectURL(svgBlob);
2383
2387
  img.src = url;
@@ -2390,25 +2394,25 @@ class ChatLLM {
2390
2394
  * The prompt includes information about the blind person's skill level and the chart's image and raw data, if available.
2391
2395
  */
2392
2396
  GetDefaultPrompt() {
2393
- let text = 'Describe this chart to a blind person';
2397
+ let text = "Describe this chart to a blind person";
2394
2398
  if (constants.skillLevel) {
2395
- if (constants.skillLevel == 'other' && constants.skillLevelOther) {
2399
+ if (constants.skillLevel == "other" && constants.skillLevelOther) {
2396
2400
  text +=
2397
- ' who has a ' +
2401
+ " who has a " +
2398
2402
  constants.skillLevelOther +
2399
- ' understanding of statistical charts. ';
2403
+ " understanding of statistical charts. ";
2400
2404
  } else {
2401
2405
  text +=
2402
- ' who has a ' +
2406
+ " who has a " +
2403
2407
  constants.skillLevel +
2404
- ' understanding of statistical charts. ';
2408
+ " understanding of statistical charts. ";
2405
2409
  }
2406
2410
  } else {
2407
- text += ' who has a basic understanding of statistical charts. ';
2411
+ text += " who has a basic understanding of statistical charts. ";
2408
2412
  }
2409
- text += 'Here is a chart in image format';
2413
+ text += "Here is a chart in image format";
2410
2414
  if (singleMaidr) {
2411
- text += ' and raw data in json format: \n';
2415
+ text += " and raw data in json format: \n";
2412
2416
  text += JSON.stringify(singleMaidr);
2413
2417
  }
2414
2418
 
@@ -2463,26 +2467,26 @@ class Description {
2463
2467
 
2464
2468
  `;
2465
2469
 
2466
- document.querySelector('body').insertAdjacentHTML('beforeend', html);
2470
+ document.querySelector("body").insertAdjacentHTML("beforeend", html);
2467
2471
 
2468
2472
  // close events
2469
2473
  let allClose = document.querySelectorAll(
2470
- '#close_desc, #description .close'
2474
+ "#close_desc, #description .close"
2471
2475
  );
2472
2476
  for (let i = 0; i < allClose.length; i++) {
2473
2477
  constants.events.push([
2474
2478
  allClose[i],
2475
- 'click',
2479
+ "click",
2476
2480
  function (e) {
2477
2481
  description.Toggle(false);
2478
2482
  },
2479
2483
  ]);
2480
2484
  }
2481
2485
  constants.events.push([
2482
- document.getElementById('description'),
2483
- 'keyup',
2486
+ document.getElementById("description"),
2487
+ "keyup",
2484
2488
  function (e) {
2485
- if (e.key == 'Esc') {
2489
+ if (e.key == "Esc") {
2486
2490
  // esc
2487
2491
  description.Toggle(false);
2488
2492
  }
@@ -2492,9 +2496,9 @@ class Description {
2492
2496
  // open events
2493
2497
  constants.events.push([
2494
2498
  document,
2495
- 'keyup',
2499
+ "keyup",
2496
2500
  function (e) {
2497
- if (e.key == 'd') {
2501
+ if (e.key == "d") {
2498
2502
  description.Toggle(true);
2499
2503
  }
2500
2504
  },
@@ -2506,11 +2510,11 @@ class Description {
2506
2510
  */
2507
2511
  Destroy() {
2508
2512
  // description element destruction
2509
- let description = document.getElementById('menu');
2513
+ let description = document.getElementById("menu");
2510
2514
  if (description) {
2511
2515
  description.remove();
2512
2516
  }
2513
- let backdrop = document.getElementById('desc_modal_backdrop');
2517
+ let backdrop = document.getElementById("desc_modal_backdrop");
2514
2518
  if (backdrop) {
2515
2519
  backdrop.remove();
2516
2520
  }
@@ -2521,8 +2525,8 @@ class Description {
2521
2525
  * @param {boolean} [onoff=false] - Whether to turn the description element on or off.
2522
2526
  */
2523
2527
  Toggle(onoff = false) {
2524
- if (typeof onoff == 'undefined') {
2525
- if (document.getElementById('description').classList.contains('hidden')) {
2528
+ if (typeof onoff == "undefined") {
2529
+ if (document.getElementById("description").classList.contains("hidden")) {
2526
2530
  onoff = true;
2527
2531
  } else {
2528
2532
  onoff = false;
@@ -2533,13 +2537,13 @@ class Description {
2533
2537
  this.whereWasMyFocus = document.activeElement;
2534
2538
  constants.tabMovement = 0;
2535
2539
  this.PopulateData();
2536
- document.getElementById('description').classList.remove('hidden');
2537
- document.getElementById('desc_modal_backdrop').classList.remove('hidden');
2538
- document.querySelector('#description .close').focus();
2540
+ document.getElementById("description").classList.remove("hidden");
2541
+ document.getElementById("desc_modal_backdrop").classList.remove("hidden");
2542
+ document.querySelector("#description .close").focus();
2539
2543
  } else {
2540
2544
  // close
2541
- document.getElementById('description').classList.add('hidden');
2542
- document.getElementById('desc_modal_backdrop').classList.add('hidden');
2545
+ document.getElementById("description").classList.add("hidden");
2546
+ document.getElementById("desc_modal_backdrop").classList.add("hidden");
2543
2547
  this.whereWasMyFocus.focus();
2544
2548
  this.whereWasMyFocus = null;
2545
2549
  }
@@ -2549,22 +2553,22 @@ class Description {
2549
2553
  * Populates the data for the chart and table based on the chart type and plot data.
2550
2554
  */
2551
2555
  PopulateData() {
2552
- let descHtml = '';
2556
+ let descHtml = "";
2553
2557
 
2554
2558
  // chart labels and descriptions
2555
- let descType = '';
2556
- if (constants.chartType == 'bar') {
2557
- descType = 'Bar chart';
2558
- } else if (constants.chartType == 'heat') {
2559
- descType = 'Heatmap';
2560
- } else if (constants.chartType == 'box') {
2561
- descType = 'Box plot';
2562
- } else if (constants.chartType == 'scatter') {
2563
- descType = 'Scatter plot';
2564
- } else if (constants.chartType == 'line') {
2565
- descType = 'Line chart';
2566
- } else if (constants.chartType == 'hist') {
2567
- descType = 'Histogram';
2559
+ let descType = "";
2560
+ if (constants.chartType == "bar") {
2561
+ descType = "Bar chart";
2562
+ } else if (constants.chartType == "heat") {
2563
+ descType = "Heatmap";
2564
+ } else if (constants.chartType == "box") {
2565
+ descType = "Box plot";
2566
+ } else if (constants.chartType == "scatter") {
2567
+ descType = "Scatter plot";
2568
+ } else if (constants.chartType == "line") {
2569
+ descType = "Line chart";
2570
+ } else if (constants.chartType == "hist") {
2571
+ descType = "Histogram";
2568
2572
  }
2569
2573
 
2570
2574
  if (descType) {
@@ -2581,7 +2585,7 @@ class Description {
2581
2585
  }
2582
2586
 
2583
2587
  // table of data, prep
2584
- let descTableHtml = '';
2588
+ let descTableHtml = "";
2585
2589
  let descLabelX = null;
2586
2590
  let descLabelY = null;
2587
2591
  let descTickX = null;
@@ -2591,7 +2595,7 @@ class Description {
2591
2595
  let descNumColsWithLabels = 0;
2592
2596
  let descNumRows = 0;
2593
2597
  let descNumRowsWithLabels = 0;
2594
- if (constants.chartType == 'bar') {
2598
+ if (constants.chartType == "bar") {
2595
2599
  if (plot.plotLegend.x != null) {
2596
2600
  descLabelX = plot.plotLegend.x;
2597
2601
  descNumColsWithLabels += 1;
@@ -2616,43 +2620,43 @@ class Description {
2616
2620
 
2617
2621
  // table of data, create
2618
2622
  if (descData != null) {
2619
- descTableHtml += '<table>';
2623
+ descTableHtml += "<table>";
2620
2624
 
2621
2625
  // header rows
2622
2626
  if (descLabelX != null || descTickX != null) {
2623
- descTableHtml += '<thead>';
2627
+ descTableHtml += "<thead>";
2624
2628
  if (descLabelX != null) {
2625
- descTableHtml += '<tr>';
2629
+ descTableHtml += "<tr>";
2626
2630
  if (descLabelY != null) {
2627
- descTableHtml += '<td></td>';
2631
+ descTableHtml += "<td></td>";
2628
2632
  }
2629
2633
  if (descTickY != null) {
2630
- descTableHtml += '<td></td>';
2634
+ descTableHtml += "<td></td>";
2631
2635
  }
2632
2636
  descTableHtml += `<th scope="col" colspan="${descNumCols}">${descLabelX}</th>`;
2633
- descTableHtml += '</tr>';
2637
+ descTableHtml += "</tr>";
2634
2638
  }
2635
2639
  if (descTickX != null) {
2636
- descTableHtml += '<tr>';
2640
+ descTableHtml += "<tr>";
2637
2641
  if (descLabelY != null) {
2638
- descTableHtml += '<td></td>';
2642
+ descTableHtml += "<td></td>";
2639
2643
  }
2640
2644
  if (descTickY != null) {
2641
- descTableHtml += '<td></td>';
2645
+ descTableHtml += "<td></td>";
2642
2646
  }
2643
2647
  for (let i = 0; i < descNumCols; i++) {
2644
2648
  descTableHtml += `<th scope="col">${descTickX[i]}</th>`;
2645
2649
  }
2646
- descTableHtml += '</tr>';
2650
+ descTableHtml += "</tr>";
2647
2651
  }
2648
- descTableHtml += '</thead>';
2652
+ descTableHtml += "</thead>";
2649
2653
  }
2650
2654
 
2651
2655
  // body rows
2652
2656
  if (descNumRows > 0) {
2653
- descTableHtml += '<tbody>';
2657
+ descTableHtml += "<tbody>";
2654
2658
  for (let i = 0; i < descNumRows; i++) {
2655
- descTableHtml += '<tr>';
2659
+ descTableHtml += "<tr>";
2656
2660
  if (descLabelY != null && i == 0) {
2657
2661
  descTableHtml += `<th scope="row" rowspan="${descNumRows}">${descLabelY}</th>`;
2658
2662
  }
@@ -2662,19 +2666,19 @@ class Description {
2662
2666
  for (let j = 0; j < descNumCols; j++) {
2663
2667
  descTableHtml += `<td>${descData[i][j]}</td>`;
2664
2668
  }
2665
- descTableHtml += '</tr>';
2669
+ descTableHtml += "</tr>";
2666
2670
  }
2667
- descTableHtml += '</tbody>';
2671
+ descTableHtml += "</tbody>";
2668
2672
  }
2669
2673
 
2670
- descTableHtml += '</table>';
2674
+ descTableHtml += "</table>";
2671
2675
  }
2672
2676
 
2673
2677
  // bar: don't need colspan or rowspan stuff, put legendX and Y as headers
2674
2678
 
2675
- document.getElementById('desc_title').innerHTML = descType + ' description';
2676
- document.getElementById('desc_content').innerHTML = descHtml;
2677
- document.getElementById('desc_table').innerHTML = descTableHtml;
2679
+ document.getElementById("desc_title").innerHTML = descType + " description";
2680
+ document.getElementById("desc_content").innerHTML = descHtml;
2681
+ document.getElementById("desc_table").innerHTML = descTableHtml;
2678
2682
  }
2679
2683
  }
2680
2684
 
@@ -2744,11 +2748,11 @@ class Tracker {
2744
2748
  * Downloads the tracker data as a JSON file.
2745
2749
  */
2746
2750
  DownloadTrackerData() {
2747
- let link = document.createElement('a');
2751
+ let link = document.createElement("a");
2748
2752
  let data = this.GetTrackerData();
2749
- let fileStr = new Blob([JSON.stringify(data)], { type: 'text/plain' });
2753
+ let fileStr = new Blob([JSON.stringify(data)], { type: "text/plain" });
2750
2754
  link.href = URL.createObjectURL(fileStr);
2751
- link.download = 'tracking.json';
2755
+ link.download = "tracking.json";
2752
2756
  link.click();
2753
2757
  }
2754
2758
 
@@ -2777,7 +2781,7 @@ class Tracker {
2777
2781
  this.data = null;
2778
2782
 
2779
2783
  if (constants.debugLevel > 0) {
2780
- console.log('tracking data cleared');
2784
+ console.log("tracking data cleared");
2781
2785
  }
2782
2786
 
2783
2787
  this.DataSetup();
@@ -2785,12 +2789,12 @@ class Tracker {
2785
2789
 
2786
2790
  SaveSettings() {
2787
2791
  // fetch all settings, push to data.settings
2788
- let settings = JSON.parse(localStorage.getItem('settings_data'));
2792
+ let settings = JSON.parse(localStorage.getItem("settings_data"));
2789
2793
  if (settings) {
2790
2794
  // don't store their auth keys
2791
- settings.openAIAuthKey = 'hidden';
2792
- settings.geminiAuthKey = 'hidden';
2793
- this.SetData('settings', settings);
2795
+ settings.openAIAuthKey = "hidden";
2796
+ settings.geminiAuthKey = "hidden";
2797
+ this.SetData("settings", settings);
2794
2798
  }
2795
2799
  }
2796
2800
 
@@ -2884,7 +2888,7 @@ class Tracker {
2884
2888
  }
2885
2889
  if (!this.isUndefinedOrNull(constants.infoDiv.innerHTML)) {
2886
2890
  let textDisplay = Object.assign(constants.infoDiv.innerHTML);
2887
- textDisplay = textDisplay.replaceAll(/<[^>]*>?/gm, '');
2891
+ textDisplay = textDisplay.replaceAll(/<[^>]*>?/gm, "");
2888
2892
  eventToLog.text_display = textDisplay;
2889
2893
  }
2890
2894
  if (!this.isUndefinedOrNull(location.href)) {
@@ -2892,13 +2896,13 @@ class Tracker {
2892
2896
  }
2893
2897
 
2894
2898
  // chart specific values
2895
- let x_tickmark = '';
2896
- let y_tickmark = '';
2897
- let x_label = '';
2898
- let y_label = '';
2899
- let value = '';
2900
- let fill_value = '';
2901
- if (constants.chartType == 'bar') {
2899
+ let x_tickmark = "";
2900
+ let y_tickmark = "";
2901
+ let x_label = "";
2902
+ let y_label = "";
2903
+ let value = "";
2904
+ let fill_value = "";
2905
+ if (constants.chartType == "bar") {
2902
2906
  if (!this.isUndefinedOrNull(plot.columnLabels[position.x])) {
2903
2907
  x_tickmark = plot.columnLabels[position.x];
2904
2908
  }
@@ -2911,7 +2915,7 @@ class Tracker {
2911
2915
  if (!this.isUndefinedOrNull(plot.plotData[position.x])) {
2912
2916
  value = plot.plotData[position.x];
2913
2917
  }
2914
- } else if (constants.chartType == 'heat') {
2918
+ } else if (constants.chartType == "heat") {
2915
2919
  if (!this.isUndefinedOrNull(plot.x_labels[position.x])) {
2916
2920
  x_tickmark = plot.x_labels[position.x].trim();
2917
2921
  }
@@ -2932,11 +2936,11 @@ class Tracker {
2932
2936
  if (!this.isUndefinedOrNull(plot.group_labels[2])) {
2933
2937
  fill_value = plot.group_labels[2];
2934
2938
  }
2935
- } else if (constants.chartType == 'box') {
2939
+ } else if (constants.chartType == "box") {
2936
2940
  let plotPos =
2937
- constants.plotOrientation == 'vert' ? position.x : position.y;
2941
+ constants.plotOrientation == "vert" ? position.x : position.y;
2938
2942
  let sectionPos =
2939
- constants.plotOrientation == 'vert' ? position.y : position.x;
2943
+ constants.plotOrientation == "vert" ? position.y : position.x;
2940
2944
  let sectionLabel = plot.sections[sectionPos];
2941
2945
 
2942
2946
  if (!this.isUndefinedOrNull(plot.x_group_label)) {
@@ -2945,7 +2949,7 @@ class Tracker {
2945
2949
  if (!this.isUndefinedOrNull(plot.y_group_label)) {
2946
2950
  y_label = plot.y_group_label;
2947
2951
  }
2948
- if (constants.plotOrientation == 'vert') {
2952
+ if (constants.plotOrientation == "vert") {
2949
2953
  if (plotPos > -1 && sectionPos > -1) {
2950
2954
  if (!this.isUndefinedOrNull(sectionLabel)) {
2951
2955
  y_tickmark = sectionLabel;
@@ -2970,7 +2974,7 @@ class Tracker {
2970
2974
  }
2971
2975
  }
2972
2976
  }
2973
- } else if (constants.chartType == 'point') {
2977
+ } else if (constants.chartType == "point") {
2974
2978
  if (!this.isUndefinedOrNull(plot.x_group_label)) {
2975
2979
  x_label = plot.x_group_label;
2976
2980
  }
@@ -2997,13 +3001,13 @@ class Tracker {
2997
3001
 
2998
3002
  //console.log("x_tickmark: '", x_tickmark, "', y_tickmark: '", y_tickmark, "', x_label: '", x_label, "', y_label: '", y_label, "', value: '", value, "', fill_value: '", fill_value);
2999
3003
 
3000
- this.SetData('events', eventToLog);
3004
+ this.SetData("events", eventToLog);
3001
3005
  //console.log('logged an event');
3002
3006
  }
3003
3007
 
3004
3008
  SetData(key, value) {
3005
3009
  let data = this.GetTrackerData();
3006
- let arrayKeys = ['events', 'ChatHistory', 'settings'];
3010
+ let arrayKeys = ["events", "ChatHistory", "settings"];
3007
3011
  if (!arrayKeys.includes(key)) {
3008
3012
  data[key] = value;
3009
3013
  } else {
@@ -3044,20 +3048,20 @@ class Review {
3044
3048
  // true means on or show
3045
3049
  if (onoff) {
3046
3050
  constants.reviewSaveSpot = document.activeElement;
3047
- constants.review_container.classList.remove('hidden');
3051
+ constants.review_container.classList.remove("hidden");
3048
3052
  constants.reviewSaveBrailleMode = constants.brailleMode;
3049
3053
  constants.review.focus();
3050
3054
 
3051
- display.announceText('Review on');
3055
+ display.announceText("Review on");
3052
3056
  } else {
3053
- constants.review_container.classList.add('hidden');
3054
- if (constants.reviewSaveBrailleMode == 'on') {
3057
+ constants.review_container.classList.add("hidden");
3058
+ if (constants.reviewSaveBrailleMode == "on") {
3055
3059
  // we have to turn braille mode back on
3056
- display.toggleBrailleMode('on');
3060
+ display.toggleBrailleMode("on");
3057
3061
  } else {
3058
3062
  constants.reviewSaveSpot.focus();
3059
3063
  }
3060
- display.announceText('Review off');
3064
+ display.announceText("Review off");
3061
3065
  }
3062
3066
  }
3063
3067
  }
@@ -3074,7 +3078,7 @@ class LogError {
3074
3078
  * @param {string} a - The absent element to log.
3075
3079
  */
3076
3080
  LogAbsentElement(a) {
3077
- console.log(a, 'not found. Visual highlighting is turned off.');
3081
+ console.log(a, "not found. Visual highlighting is turned off.");
3078
3082
  }
3079
3083
 
3080
3084
  /**
@@ -3082,7 +3086,7 @@ class LogError {
3082
3086
  * @param {string} a - The critical element to log.
3083
3087
  */
3084
3088
  LogCriticalElement(a) {
3085
- consolelog(a, 'is critical. MAIDR unable to run');
3089
+ consolelog(a, "is critical. MAIDR unable to run");
3086
3090
  }
3087
3091
 
3088
3092
  /**
@@ -3093,9 +3097,9 @@ class LogError {
3093
3097
  LogDifferentLengths(a, b) {
3094
3098
  console.log(
3095
3099
  a,
3096
- 'and',
3100
+ "and",
3097
3101
  b,
3098
- 'do not have the same length. Visual highlighting is turned off.'
3102
+ "do not have the same length. Visual highlighting is turned off."
3099
3103
  );
3100
3104
  }
3101
3105
 
@@ -3106,11 +3110,11 @@ class LogError {
3106
3110
  */
3107
3111
  LogTooManyElements(a, b) {
3108
3112
  console.log(
3109
- 'Too many',
3113
+ "Too many",
3110
3114
  a,
3111
- 'elements. Only the first',
3115
+ "elements. Only the first",
3112
3116
  b,
3113
- 'will be highlighted.'
3117
+ "will be highlighted."
3114
3118
  );
3115
3119
  }
3116
3120
 
@@ -3119,7 +3123,7 @@ class LogError {
3119
3123
  * @param {*} a - The parameter that is not an array.
3120
3124
  */
3121
3125
  LogNotArray(a) {
3122
- console.log(a, 'is not an array. Visual highlighting is turned off.');
3126
+ console.log(a, "is not an array. Visual highlighting is turned off.");
3123
3127
  }
3124
3128
  }
3125
3129
 
@@ -11201,10 +11205,18 @@ function InitMaidr(thisMaidr) {
11201
11205
  // actually do eventlisteners for all events
11202
11206
  this.SetEvents();
11203
11207
 
11208
+ // Set img role for chart
11209
+ constants.chart.setAttribute('role', 'img');
11210
+
11204
11211
  // once everything is set up, announce the chart name (or title as a backup) to the user
11205
- setTimeout(function () {
11206
- if ('name' in singleMaidr) {
11207
- display.announceText(singleMaidr.name);
11212
+ // setTimeout(function () {
11213
+ if ('name' in singleMaidr) {
11214
+
11215
+ // Add the aria-label and title attributes to the chart
11216
+ constants.chart.setAttribute('aria-label', announceText);
11217
+ constants.chart.setAttribute('title', announceText);
11218
+
11219
+ // display.announceText(singleMaidr.name);
11208
11220
  } else if (
11209
11221
  'title' in singleMaidr ||
11210
11222
  ('labels' in singleMaidr && 'title' in singleMaidr.labels)
@@ -11232,10 +11244,16 @@ function InitMaidr(thisMaidr) {
11232
11244
  isMultiLayered ? multiLayerInstruction : ' '
11233
11245
  }Toggle B for Braille, T for Text, S for Sonification, and R for Review mode. Use H for Help.`;
11234
11246
 
11247
+ // Add the aria-label and title attributes to the chart
11248
+ constants.chart.setAttribute('aria-label', announceText);
11249
+ constants.chart.setAttribute('title', announceText);
11250
+
11251
+
11252
+
11235
11253
  // Display the announcement text
11236
- display.announceText(announceText);
11254
+ // display.announceText(announceText);
11237
11255
  }
11238
- }, 100);
11256
+ // }, 100);
11239
11257
  }
11240
11258
  }
11241
11259