maidr 2.17.2 → 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,28 +128,36 @@ 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
+
140
+ /**
141
+ * We lock the selection so we don't pick up programatic selection changes
142
+ * @type {boolean}
143
+ * @default false
144
+ */
145
+ lockSelection = false;
146
+
139
147
  /**
140
148
  * The current sonification mode. Can be 'on', 'off', 'sep' (seperated), or 'same' (all played at once).
141
149
  * @type {("on"|"off"|"sep"|"same")}
142
150
  * @memberof BTSModes
143
151
  * @default 'on'
144
152
  */
145
- sonifMode = 'on';
153
+ sonifMode = "on";
146
154
  /**
147
155
  * The current review mode. Can be 'on' or 'off'.
148
156
  * @type {("on"|"off")}
149
157
  * @memberof BTSModes
150
158
  * @default 'off'
151
159
  */
152
- reviewMode = 'off';
160
+ reviewMode = "off";
153
161
 
154
162
  // basic chart properties
155
163
  /**
@@ -190,14 +198,14 @@ class Constants {
190
198
  * @memberof HtmlIds
191
199
  * @default ''
192
200
  */
193
- plotId = ''; // update with id in chart specific js
201
+ plotId = ""; // update with id in chart specific js
194
202
  /**
195
203
  * The chart type, sort of a short name of the chart such as 'box', 'bar', 'line', etc.
196
204
  * @type {string}
197
205
  * @default ''
198
206
  * @memberof BasicChartProperties
199
207
  */
200
- chartType = '';
208
+ chartType = "";
201
209
  /**
202
210
  * The navigation orientation of the chart. 0 = row navigation (up/down), 1 = col navigation (left/right).
203
211
  * @type {number}
@@ -211,7 +219,7 @@ class Constants {
211
219
  * @default 'horz'
212
220
  * @memberof BasicChartProperties
213
221
  */
214
- plotOrientation = 'horz';
222
+ plotOrientation = "horz";
215
223
 
216
224
  /**
217
225
  * @namespace AudioProperties
@@ -321,7 +329,7 @@ class Constants {
321
329
  * @default '#03C809' (green)
322
330
  * @memberof UserSettings
323
331
  */
324
- colorSelected = '#03C809';
332
+ colorSelected = "#03C809";
325
333
  /**
326
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).
327
335
  * @type {number}
@@ -389,7 +397,7 @@ class Constants {
389
397
  * @default 50
390
398
  * @memberof AdvancedUserSettings
391
399
  */
392
- 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
393
401
  /**
394
402
  * Whether or not we're logging user data. This is off by default, but is used for research purposes.
395
403
  * @type {boolean}
@@ -417,30 +425,30 @@ class Constants {
417
425
  * @default 'assertive'
418
426
  * @memberof AdvancedUserSettings
419
427
  */
420
- ariaMode = 'assertive';
428
+ ariaMode = "assertive";
421
429
 
422
430
  /**
423
431
  * Full list of user settings, used internally to save and load settings.
424
432
  * @type {string[]}
425
433
  */
426
434
  userSettingsKeys = [
427
- 'vol',
428
- 'autoPlayRate',
429
- 'brailleDisplayLength',
430
- 'colorSelected',
431
- 'MIN_FREQUENCY',
432
- 'MAX_FREQUENCY',
433
- 'keypressInterval',
434
- 'ariaMode',
435
- 'openAIAuthKey',
436
- 'geminiAuthKey',
437
- 'skillLevel',
438
- 'skillLevelOther',
439
- 'LLMModel',
440
- 'LLMPreferences',
441
- 'LLMOpenAiMulti',
442
- 'LLMGeminiMulti',
443
- '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",
444
452
  ];
445
453
 
446
454
  // LLM settings
@@ -481,14 +489,14 @@ class Constants {
481
489
  * @default 'high'
482
490
  * @memberof LLMSettings
483
491
  */
484
- 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)
485
493
  /**
486
494
  * Current LLM model in use. Can be 'openai' (default) or 'gemini' or 'multi'. More to be added.
487
495
  * @type {("openai"|"gemini"|"multi")}
488
496
  * @default 'openai'
489
497
  * @memberof LLMSettings
490
498
  */
491
- LLMModel = 'openai';
499
+ LLMModel = "openai";
492
500
  /**
493
501
  * The default system message for the LLM. Helps the LLM understand the context of the chart and its role.
494
502
  * @type {string}
@@ -496,21 +504,21 @@ class Constants {
496
504
  * @memberof LLMSettings
497
505
  */
498
506
  LLMSystemMessage =
499
- '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. ";
500
508
  /**
501
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.
502
510
  * @type {("basic"|"intermediate"|"expert"|"other")}
503
511
  * @default 'basic'
504
512
  * @memberof LLMSettings
505
513
  */
506
- skillLevel = 'basic'; // basic / intermediate / expert
514
+ skillLevel = "basic"; // basic / intermediate / expert
507
515
  /**
508
516
  * Custom skill level, used if the user selects 'other' as their skill level.
509
517
  * @type {string}
510
518
  * @default ''
511
519
  * @memberof LLMSettings
512
520
  */
513
- skillLevelOther = ''; // custom skill level
521
+ skillLevelOther = ""; // custom skill level
514
522
  /**
515
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.
516
524
  * @type {boolean}
@@ -524,7 +532,7 @@ class Constants {
524
532
  * @default ''
525
533
  * @memberof LLMSettings
526
534
  */
527
- verboseText = '';
535
+ verboseText = "";
528
536
  /**
529
537
  * An internal variable used to turn the waiting beep on and off.
530
538
  * @type {number}
@@ -572,31 +580,31 @@ class Constants {
572
580
  * @type {boolean}
573
581
  * @memberof PlatformControls
574
582
  */
575
- isMac = navigator.userAgent.toLowerCase().includes('mac'); // true if macOS
583
+ isMac = navigator.userAgent.toLowerCase().includes("mac"); // true if macOS
576
584
  /**
577
585
  * The control key for the user's platform. Can be 'Cmd' or 'Ctrl'. Used in keyboard shortcut display in help.
578
586
  * @type {"Cmd"|"Ctrl"}
579
587
  * @memberof PlatformControls
580
588
  */
581
- control = this.isMac ? 'Cmd' : 'Ctrl';
589
+ control = this.isMac ? "Cmd" : "Ctrl";
582
590
  /**
583
591
  * The alt key for the user's platform. Can be 'option' or 'Alt'. Used in keyboard shortcut display in help.
584
592
  * @type {"option"|"Alt"}
585
593
  * @memberof PlatformControls
586
594
  */
587
- alt = this.isMac ? 'option' : 'Alt';
595
+ alt = this.isMac ? "option" : "Alt";
588
596
  /**
589
597
  * The home key for the user's platform. Can be 'fn + Left arrow' or 'Home'. Used in keyboard shortcut display in help.
590
598
  * @type {"fn + Left arrow"|"Home"}
591
599
  * @memberof PlatformControls
592
600
  */
593
- home = this.isMac ? 'fn + Left arrow' : 'Home';
601
+ home = this.isMac ? "fn + Left arrow" : "Home";
594
602
  /**
595
603
  * The end key for the user's platform. Can be 'fn + Right arrow' or 'End'. Used in keyboard shortcut display in help.
596
604
  * @type {"fn + Right arrow"|"End"}
597
605
  * @memberof PlatformControls
598
606
  */
599
- end = this.isMac ? 'fn + Right arrow' : 'End';
607
+ end = this.isMac ? "fn + Right arrow" : "End";
600
608
 
601
609
  // internal controls
602
610
  // todo: are these even used? Sean doesn't think so (May 2024)
@@ -689,13 +697,13 @@ class Constants {
689
697
  */
690
698
  ConvertHexToRGBString(hexColorString) {
691
699
  return (
692
- 'rgb(' +
700
+ "rgb(" +
693
701
  parseInt(hexColorString.slice(1, 3), 16) +
694
- ',' +
702
+ "," +
695
703
  parseInt(hexColorString.slice(3, 5), 16) +
696
- ',' +
704
+ "," +
697
705
  parseInt(hexColorString.slice(5, 7), 16) +
698
- ')'
706
+ ")"
699
707
  );
700
708
  }
701
709
 
@@ -705,12 +713,12 @@ class Constants {
705
713
  * @returns {string} - hexadecimal color (e.g., "#595959").
706
714
  */
707
715
  ConvertRGBStringToHex(rgbColorString) {
708
- let rgb = rgbColorString.replace(/[^\d,]/g, '').split(',');
716
+ let rgb = rgbColorString.replace(/[^\d,]/g, "").split(",");
709
717
  return (
710
- '#' +
711
- rgb[0].toString(16).padStart(2, '0') +
712
- rgb[1].toString(16).padStart(2, '0') +
713
- 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")
714
722
  );
715
723
  }
716
724
 
@@ -722,11 +730,11 @@ class Constants {
722
730
  */
723
731
  ColorInvert(color) {
724
732
  // invert an rgb color
725
- let rgb = color.replace(/[^\d,]/g, '').split(',');
733
+ let rgb = color.replace(/[^\d,]/g, "").split(",");
726
734
  let r = 255 - rgb[0];
727
735
  let g = 255 - rgb[1];
728
736
  let b = 255 - rgb[2];
729
- return 'rgb(' + r + ',' + g + ',' + b + ')';
737
+ return "rgb(" + r + "," + g + "," + b + ")";
730
738
  }
731
739
 
732
740
  /**
@@ -735,11 +743,11 @@ class Constants {
735
743
  * @returns {string} The better color
736
744
  */
737
745
  GetBetterColor(oldColor) {
738
- if (oldColor.indexOf('#') !== -1) {
746
+ if (oldColor.indexOf("#") !== -1) {
739
747
  oldColor = this.ConvertHexToRGBString(oldColor);
740
748
  }
741
749
  let newColor = this.ColorInvert(oldColor);
742
- let rgb = newColor.replace(/[^\d,]/g, '').split(',');
750
+ let rgb = newColor.replace(/[^\d,]/g, "").split(",");
743
751
  if (
744
752
  rgb[1] < rgb[0] + 10 &&
745
753
  rgb[1] > rgb[0] - 10 &&
@@ -761,7 +769,7 @@ class Constants {
761
769
  */
762
770
  GetStyleArrayFromString(styleString) {
763
771
  // Get an array of CSS style attributes and values from a style string
764
- return styleString.replaceAll(' ', '').split(/[:;]/);
772
+ return styleString.replaceAll(" ", "").split(/[:;]/);
765
773
  }
766
774
 
767
775
  /**
@@ -771,16 +779,16 @@ class Constants {
771
779
  */
772
780
  GetStyleStringFromArray(styleArray) {
773
781
  // Get CSS style string from an array of style attributes and values
774
- let styleString = '';
782
+ let styleString = "";
775
783
  for (let i = 0; i < styleArray.length; i++) {
776
784
  if (i % 2 === 0) {
777
785
  if (i !== styleArray.length - 1) {
778
- styleString += styleArray[i] + ': ';
786
+ styleString += styleArray[i] + ": ";
779
787
  } else {
780
788
  styleString += styleArray[i];
781
789
  }
782
790
  } else {
783
- styleString += styleArray[i] + '; ';
791
+ styleString += styleArray[i] + "; ";
784
792
  }
785
793
  }
786
794
  return styleString;
@@ -793,35 +801,35 @@ class Constants {
793
801
  class Resources {
794
802
  constructor() {}
795
803
 
796
- language = 'en'; // Current language, 2 char lang code
797
- knowledgeLevel = 'basic'; // basic, intermediate, expert
804
+ language = "en"; // Current language, 2 char lang code
805
+ knowledgeLevel = "basic"; // basic, intermediate, expert
798
806
 
799
807
  // language strings, per 2 char language code
800
808
  strings = {
801
809
  en: {
802
810
  basic: {
803
- upper_outlier: 'Upper Outlier',
804
- lower_outlier: 'Lower Outlier',
805
- min: 'Minimum',
806
- max: 'Maximum',
807
- 25: '25%',
808
- 50: '50%',
809
- 75: '75%',
810
- q1: '25%',
811
- q2: '50%',
812
- q3: '75%',
813
- son_on: 'Sonification on',
814
- son_off: 'Sonification off',
815
- son_des: 'Sonification descrete',
816
- son_comp: 'Sonification compare',
817
- son_ch: 'Sonification chord',
818
- son_sep: 'Sonification separate',
819
- son_same: 'Sonification combined',
820
- empty: 'Empty',
821
- openai: 'OpenAI Vision',
822
- gemini: 'Gemini Pro Vision',
823
- multi: 'Multiple AI',
824
- 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...",
825
833
  },
826
834
  },
827
835
  };
@@ -936,7 +944,11 @@ class Menu {
936
944
  <td>Auto-play speed down</td>
937
945
  <td>Comma (,)</td>
938
946
  </tr>
939
- <tr>
947
+ <tr>
948
+ <td>Auto-play speed reset</td>
949
+ <td>Slash (/)</td>
950
+ </tr>
951
+ <tr>
940
952
  <td>Check label for the title of current plot</td>
941
953
  <td>l t</td>
942
954
  </tr>
@@ -992,12 +1004,12 @@ class Menu {
992
1004
  <div><fieldset>
993
1005
  <legend>Aria Mode</legend>
994
1006
  <p><input type="radio" id="aria_mode_assertive" name="aria_mode" value="assertive" ${
995
- constants.ariaMode == 'assertive'
996
- ? 'checked'
997
- : ''
1007
+ constants.ariaMode == "assertive"
1008
+ ? "checked"
1009
+ : ""
998
1010
  }><label for="aria_mode_assertive">Assertive</label></p>
999
1011
  <p><input type="radio" id="aria_mode_polite" name="aria_mode" value="polite" ${
1000
- constants.ariaMode == 'polite' ? 'checked' : ''
1012
+ constants.ariaMode == "polite" ? "checked" : ""
1001
1013
  }><label for="aria_mode_polite">Polite</label></p>
1002
1014
  </fieldset></div>
1003
1015
  <h5 class="modal-title">LLM Settings</h5>
@@ -1018,7 +1030,7 @@ class Menu {
1018
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>
1019
1031
  </p>
1020
1032
  <p><input type="checkbox" ${
1021
- constants.autoInitLLM ? 'checked' : ''
1033
+ constants.autoInitLLM ? "checked" : ""
1022
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>
1023
1035
  <p>
1024
1036
  <select id="skill_level">
@@ -1055,33 +1067,33 @@ class Menu {
1055
1067
  CreateMenu() {
1056
1068
  // menu element creation
1057
1069
  document
1058
- .querySelector('body')
1059
- .insertAdjacentHTML('beforeend', this.menuHtml);
1070
+ .querySelector("body")
1071
+ .insertAdjacentHTML("beforeend", this.menuHtml);
1060
1072
 
1061
1073
  // menu close events
1062
- let allClose = document.querySelectorAll('#close_menu, #menu .close');
1074
+ let allClose = document.querySelectorAll("#close_menu, #menu .close");
1063
1075
  for (let i = 0; i < allClose.length; i++) {
1064
1076
  constants.events.push([
1065
1077
  allClose[i],
1066
- 'click',
1078
+ "click",
1067
1079
  function (e) {
1068
1080
  menu.Toggle(false);
1069
1081
  },
1070
1082
  ]);
1071
1083
  }
1072
1084
  constants.events.push([
1073
- document.getElementById('save_and_close_menu'),
1074
- 'click',
1085
+ document.getElementById("save_and_close_menu"),
1086
+ "click",
1075
1087
  function (e) {
1076
1088
  menu.SaveData();
1077
1089
  menu.Toggle(false);
1078
1090
  },
1079
1091
  ]);
1080
1092
  constants.events.push([
1081
- document.getElementById('menu'),
1082
- 'keyup',
1093
+ document.getElementById("menu"),
1094
+ "keyup",
1083
1095
  function (e) {
1084
- if (e.key == 'Esc') {
1096
+ if (e.key == "Esc") {
1085
1097
  // esc
1086
1098
  menu.Toggle(false);
1087
1099
  }
@@ -1091,15 +1103,15 @@ class Menu {
1091
1103
  // Menu open events
1092
1104
  constants.events.push([
1093
1105
  document,
1094
- 'keyup',
1106
+ "keyup",
1095
1107
  function (e) {
1096
1108
  // don't fire on input elements
1097
1109
  if (
1098
- e.target.tagName.toLowerCase() == 'input' ||
1099
- e.target.tagName.toLowerCase() == 'textarea'
1110
+ e.target.tagName.toLowerCase() == "input" ||
1111
+ e.target.tagName.toLowerCase() == "textarea"
1100
1112
  ) {
1101
1113
  return;
1102
- } else if (e.key == 'h') {
1114
+ } else if (e.key == "h") {
1103
1115
  menu.Toggle(true);
1104
1116
  }
1105
1117
  },
@@ -1107,71 +1119,71 @@ class Menu {
1107
1119
 
1108
1120
  // toggle auth key fields
1109
1121
  constants.events.push([
1110
- document.getElementById('LLM_model'),
1111
- 'change',
1122
+ document.getElementById("LLM_model"),
1123
+ "change",
1112
1124
  function (e) {
1113
- if (e.target.value == 'openai') {
1125
+ if (e.target.value == "openai") {
1114
1126
  document
1115
- .getElementById('openai_auth_key_container')
1116
- .classList.remove('hidden');
1127
+ .getElementById("openai_auth_key_container")
1128
+ .classList.remove("hidden");
1117
1129
  document
1118
- .getElementById('gemini_auth_key_container')
1119
- .classList.add('hidden');
1130
+ .getElementById("gemini_auth_key_container")
1131
+ .classList.add("hidden");
1120
1132
  document
1121
- .getElementById('openai_multi_container')
1122
- .classList.add('hidden');
1133
+ .getElementById("openai_multi_container")
1134
+ .classList.add("hidden");
1123
1135
  document
1124
- .getElementById('gemini_multi_container')
1125
- .classList.add('hidden');
1126
- document.getElementById('openai_multi').checked = true;
1127
- document.getElementById('gemini_multi').checked = false;
1128
- } 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") {
1129
1141
  document
1130
- .getElementById('openai_auth_key_container')
1131
- .classList.add('hidden');
1142
+ .getElementById("openai_auth_key_container")
1143
+ .classList.add("hidden");
1132
1144
  document
1133
- .getElementById('gemini_auth_key_container')
1134
- .classList.remove('hidden');
1145
+ .getElementById("gemini_auth_key_container")
1146
+ .classList.remove("hidden");
1135
1147
  document
1136
- .getElementById('openai_multi_container')
1137
- .classList.add('hidden');
1148
+ .getElementById("openai_multi_container")
1149
+ .classList.add("hidden");
1138
1150
  document
1139
- .getElementById('gemini_multi_container')
1140
- .classList.add('hidden');
1141
- document.getElementById('openai_multi').checked = false;
1142
- document.getElementById('gemini_multi').checked = true;
1143
- } 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") {
1144
1156
  document
1145
- .getElementById('openai_auth_key_container')
1146
- .classList.remove('hidden');
1157
+ .getElementById("openai_auth_key_container")
1158
+ .classList.remove("hidden");
1147
1159
  document
1148
- .getElementById('gemini_auth_key_container')
1149
- .classList.remove('hidden');
1160
+ .getElementById("gemini_auth_key_container")
1161
+ .classList.remove("hidden");
1150
1162
  document
1151
- .getElementById('openai_multi_container')
1152
- .classList.remove('hidden');
1163
+ .getElementById("openai_multi_container")
1164
+ .classList.remove("hidden");
1153
1165
  document
1154
- .getElementById('gemini_multi_container')
1155
- .classList.remove('hidden');
1156
- document.getElementById('openai_multi').checked = true;
1157
- 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;
1158
1170
  }
1159
1171
  },
1160
1172
  ]);
1161
1173
 
1162
1174
  // Skill level other events
1163
1175
  constants.events.push([
1164
- document.getElementById('skill_level'),
1165
- 'change',
1176
+ document.getElementById("skill_level"),
1177
+ "change",
1166
1178
  function (e) {
1167
- if (e.target.value == 'other') {
1179
+ if (e.target.value == "other") {
1168
1180
  document
1169
- .getElementById('skill_level_other_container')
1170
- .classList.remove('hidden');
1181
+ .getElementById("skill_level_other_container")
1182
+ .classList.remove("hidden");
1171
1183
  } else {
1172
1184
  document
1173
- .getElementById('skill_level_other_container')
1174
- .classList.add('hidden');
1185
+ .getElementById("skill_level_other_container")
1186
+ .classList.add("hidden");
1175
1187
  }
1176
1188
  },
1177
1189
  ]);
@@ -1179,16 +1191,16 @@ class Menu {
1179
1191
  // trigger notification that LLM will be reset
1180
1192
  // this is done on change of LLM model, multi settings, or skill level
1181
1193
  let LLMResetIds = [
1182
- 'LLM_model',
1183
- 'openai_multi',
1184
- 'gemini_multi',
1185
- 'skill_level',
1186
- 'LLM_preferences',
1194
+ "LLM_model",
1195
+ "openai_multi",
1196
+ "gemini_multi",
1197
+ "skill_level",
1198
+ "LLM_preferences",
1187
1199
  ];
1188
1200
  for (let i = 0; i < LLMResetIds.length; i++) {
1189
1201
  constants.events.push([
1190
1202
  document.getElementById(LLMResetIds[i]),
1191
- 'change',
1203
+ "change",
1192
1204
  function (e) {
1193
1205
  menu.NotifyOfLLMReset();
1194
1206
  },
@@ -1202,11 +1214,11 @@ class Menu {
1202
1214
  */
1203
1215
  Destroy() {
1204
1216
  // menu element destruction
1205
- let menu = document.getElementById('menu');
1217
+ let menu = document.getElementById("menu");
1206
1218
  if (menu) {
1207
1219
  menu.remove();
1208
1220
  }
1209
- let backdrop = document.getElementById('menu_modal_backdrop');
1221
+ let backdrop = document.getElementById("menu_modal_backdrop");
1210
1222
  if (backdrop) {
1211
1223
  backdrop.remove();
1212
1224
  }
@@ -1218,16 +1230,16 @@ class Menu {
1218
1230
  * @return {void}
1219
1231
  */
1220
1232
  Toggle(onoff = false) {
1221
- if (typeof onoff == 'undefined') {
1222
- if (document.getElementById('menu').classList.contains('hidden')) {
1233
+ if (typeof onoff == "undefined") {
1234
+ if (document.getElementById("menu").classList.contains("hidden")) {
1223
1235
  onoff = true;
1224
1236
  } else {
1225
1237
  onoff = false;
1226
1238
  }
1227
1239
  }
1228
1240
  // don't open if we have another modal open already
1229
- if (onoff && document.getElementById('chatLLM')) {
1230
- if (!document.getElementById('chatLLM').classList.contains('hidden')) {
1241
+ if (onoff && document.getElementById("chatLLM")) {
1242
+ if (!document.getElementById("chatLLM").classList.contains("hidden")) {
1231
1243
  return;
1232
1244
  }
1233
1245
  }
@@ -1236,13 +1248,13 @@ class Menu {
1236
1248
  this.whereWasMyFocus = document.activeElement;
1237
1249
  this.PopulateData();
1238
1250
  constants.tabMovement = 0;
1239
- document.getElementById('menu').classList.remove('hidden');
1240
- document.getElementById('menu_modal_backdrop').classList.remove('hidden');
1241
- 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();
1242
1254
  } else {
1243
1255
  // close
1244
- document.getElementById('menu').classList.add('hidden');
1245
- document.getElementById('menu_modal_backdrop').classList.add('hidden');
1256
+ document.getElementById("menu").classList.add("hidden");
1257
+ document.getElementById("menu_modal_backdrop").classList.add("hidden");
1246
1258
  this.whereWasMyFocus.focus();
1247
1259
  this.whereWasMyFocus = null;
1248
1260
  }
@@ -1253,89 +1265,89 @@ class Menu {
1253
1265
  * @return {void}
1254
1266
  */
1255
1267
  PopulateData() {
1256
- document.getElementById('vol').value = constants.vol;
1257
- document.getElementById('autoplay_rate').value = constants.autoPlayRate;
1258
- 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 =
1259
1271
  constants.brailleDisplayLength;
1260
- document.getElementById('color_selected').value = constants.colorSelected;
1261
- document.getElementById('min_freq').value = constants.MIN_FREQUENCY;
1262
- document.getElementById('max_freq').value = constants.MAX_FREQUENCY;
1263
- 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 =
1264
1276
  constants.keypressInterval;
1265
- if (typeof constants.openAIAuthKey == 'string') {
1266
- document.getElementById('openai_auth_key').value =
1277
+ if (typeof constants.openAIAuthKey == "string") {
1278
+ document.getElementById("openai_auth_key").value =
1267
1279
  constants.openAIAuthKey;
1268
1280
  }
1269
- if (typeof constants.geminiAuthKey == 'string') {
1270
- document.getElementById('gemini_auth_key').value =
1281
+ if (typeof constants.geminiAuthKey == "string") {
1282
+ document.getElementById("gemini_auth_key").value =
1271
1283
  constants.geminiAuthKey;
1272
1284
  }
1273
- document.getElementById('skill_level').value = constants.skillLevel;
1285
+ document.getElementById("skill_level").value = constants.skillLevel;
1274
1286
  if (constants.skillLevelOther) {
1275
- document.getElementById('skill_level_other').value =
1287
+ document.getElementById("skill_level_other").value =
1276
1288
  constants.skillLevelOther;
1277
1289
  }
1278
- document.getElementById('LLM_model').value = constants.LLMModel;
1290
+ document.getElementById("LLM_model").value = constants.LLMModel;
1279
1291
 
1280
1292
  // aria mode
1281
- if (constants.ariaMode == 'assertive') {
1282
- document.getElementById('aria_mode_assertive').checked = true;
1283
- 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;
1284
1296
  } else {
1285
- document.getElementById('aria_mode_polite').checked = true;
1286
- document.getElementById('aria_mode_assertive').checked = false;
1297
+ document.getElementById("aria_mode_polite").checked = true;
1298
+ document.getElementById("aria_mode_assertive").checked = false;
1287
1299
  }
1288
1300
  // hide either openai or gemini auth key field
1289
- if (constants.LLMModel == 'openai') {
1301
+ if (constants.LLMModel == "openai") {
1290
1302
  document
1291
- .getElementById('openai_auth_key_container')
1292
- .classList.remove('hidden');
1303
+ .getElementById("openai_auth_key_container")
1304
+ .classList.remove("hidden");
1293
1305
  document
1294
- .getElementById('gemini_auth_key_container')
1295
- .classList.add('hidden');
1296
- } else if (constants.LLMModel == 'gemini') {
1306
+ .getElementById("gemini_auth_key_container")
1307
+ .classList.add("hidden");
1308
+ } else if (constants.LLMModel == "gemini") {
1297
1309
  document
1298
- .getElementById('openai_auth_key_container')
1299
- .classList.add('hidden');
1310
+ .getElementById("openai_auth_key_container")
1311
+ .classList.add("hidden");
1300
1312
  document
1301
- .getElementById('gemini_auth_key_container')
1302
- .classList.remove('hidden');
1303
- } else if (constants.LLMModel == 'multi') {
1313
+ .getElementById("gemini_auth_key_container")
1314
+ .classList.remove("hidden");
1315
+ } else if (constants.LLMModel == "multi") {
1304
1316
  // multi LLM mode
1305
1317
  document
1306
- .getElementById('openai_auth_key_container')
1307
- .classList.remove('hidden');
1318
+ .getElementById("openai_auth_key_container")
1319
+ .classList.remove("hidden");
1308
1320
  document
1309
- .getElementById('gemini_auth_key_container')
1310
- .classList.remove('hidden');
1321
+ .getElementById("gemini_auth_key_container")
1322
+ .classList.remove("hidden");
1311
1323
  document
1312
- .getElementById('openai_multi_container')
1313
- .classList.remove('hidden');
1324
+ .getElementById("openai_multi_container")
1325
+ .classList.remove("hidden");
1314
1326
  document
1315
- .getElementById('gemini_multi_container')
1316
- .classList.remove('hidden');
1317
- document.getElementById('openai_multi').checked = false;
1327
+ .getElementById("gemini_multi_container")
1328
+ .classList.remove("hidden");
1329
+ document.getElementById("openai_multi").checked = false;
1318
1330
  if (constants.LLMOpenAiMulti) {
1319
- document.getElementById('openai_multi').checked = true;
1331
+ document.getElementById("openai_multi").checked = true;
1320
1332
  }
1321
- document.getElementById('gemini_multi').checked = false;
1333
+ document.getElementById("gemini_multi").checked = false;
1322
1334
  if (constants.LLMGeminiMulti) {
1323
- document.getElementById('gemini_multi').checked = true;
1335
+ document.getElementById("gemini_multi").checked = true;
1324
1336
  }
1325
1337
  }
1326
1338
  // skill level other
1327
- if (constants.skillLevel == 'other') {
1339
+ if (constants.skillLevel == "other") {
1328
1340
  document
1329
- .getElementById('skill_level_other_container')
1330
- .classList.remove('hidden');
1341
+ .getElementById("skill_level_other_container")
1342
+ .classList.remove("hidden");
1331
1343
  }
1332
1344
  // LLM preferences
1333
1345
  if (constants.LLMPreferences) {
1334
- document.getElementById('LLM_preferences').value =
1346
+ document.getElementById("LLM_preferences").value =
1335
1347
  constants.LLMPreferences;
1336
1348
  }
1337
- if (document.getElementById('LLM_reset_notification')) {
1338
- document.getElementById('LLM_reset_notification').remove();
1349
+ if (document.getElementById("LLM_reset_notification")) {
1350
+ document.getElementById("LLM_reset_notification").remove();
1339
1351
  }
1340
1352
  }
1341
1353
 
@@ -1346,33 +1358,33 @@ class Menu {
1346
1358
  SaveData() {
1347
1359
  let shouldReset = this.ShouldLLMReset();
1348
1360
 
1349
- constants.vol = document.getElementById('vol').value;
1350
- constants.autoPlayRate = document.getElementById('autoplay_rate').value;
1361
+ constants.vol = document.getElementById("vol").value;
1362
+ constants.autoPlayRate = document.getElementById("autoplay_rate").value;
1351
1363
  constants.brailleDisplayLength = document.getElementById(
1352
- 'braille_display_length'
1364
+ "braille_display_length"
1353
1365
  ).value;
1354
- constants.colorSelected = document.getElementById('color_selected').value;
1355
- constants.MIN_FREQUENCY = document.getElementById('min_freq').value;
1356
- 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;
1357
1369
  constants.keypressInterval =
1358
- document.getElementById('keypress_interval').value;
1370
+ document.getElementById("keypress_interval").value;
1359
1371
 
1360
- constants.openAIAuthKey = document.getElementById('openai_auth_key').value;
1361
- constants.geminiAuthKey = document.getElementById('gemini_auth_key').value;
1362
- 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;
1363
1375
  constants.skillLevelOther =
1364
- document.getElementById('skill_level_other').value;
1365
- constants.LLMModel = document.getElementById('LLM_model').value;
1366
- constants.LLMPreferences = document.getElementById('LLM_preferences').value;
1367
- constants.LLMOpenAiMulti = document.getElementById('openai_multi').checked;
1368
- constants.LLMGeminiMulti = document.getElementById('gemini_multi').checked;
1369
- 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;
1370
1382
 
1371
1383
  // aria
1372
- if (document.getElementById('aria_mode_assertive').checked) {
1373
- constants.ariaMode = 'assertive';
1374
- } else if (document.getElementById('aria_mode_polite').checked) {
1375
- 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";
1376
1388
  }
1377
1389
 
1378
1390
  this.SaveDataToLocalStorage();
@@ -1391,29 +1403,29 @@ class Menu {
1391
1403
  */
1392
1404
  UpdateHtml() {
1393
1405
  // set aria attributes
1394
- constants.infoDiv.setAttribute('aria-live', constants.ariaMode);
1406
+ constants.infoDiv.setAttribute("aria-live", constants.ariaMode);
1395
1407
  document
1396
1408
  .getElementById(constants.announcement_container_id)
1397
- .setAttribute('aria-live', constants.ariaMode);
1409
+ .setAttribute("aria-live", constants.ariaMode);
1398
1410
 
1399
- document.getElementById('init_llm_on_load').checked = constants.autoInitLLM;
1400
- const scatter = document.getElementsByClassName('highlight_point');
1401
- const heatmap = document.getElementById('highlight_rect');
1402
- 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");
1403
1415
 
1404
1416
  if (scatter !== null && scatter.length > 0) {
1405
1417
  for (let i = 0; i < scatter.length; i++) {
1406
- scatter[i].setAttribute('stroke', constants.colorSelected);
1407
- scatter[i].setAttribute('fill', constants.colorSelected);
1418
+ scatter[i].setAttribute("stroke", constants.colorSelected);
1419
+ scatter[i].setAttribute("fill", constants.colorSelected);
1408
1420
  }
1409
1421
  }
1410
1422
 
1411
1423
  if (heatmap !== null) {
1412
- heatmap.setAttribute('stroke', constants.colorSelected);
1424
+ heatmap.setAttribute("stroke", constants.colorSelected);
1413
1425
  }
1414
1426
 
1415
1427
  if (line !== null) {
1416
- line.setAttribute('stroke', constants.colorSelected);
1428
+ line.setAttribute("stroke", constants.colorSelected);
1417
1429
  }
1418
1430
  }
1419
1431
 
@@ -1425,19 +1437,19 @@ class Menu {
1425
1437
  let html =
1426
1438
  '<p id="LLM_reset_notification">Note: Changes in LLM settings will reset any existing conversation.</p>';
1427
1439
 
1428
- if (document.getElementById('LLM_reset_notification')) {
1429
- document.getElementById('LLM_reset_notification').remove();
1440
+ if (document.getElementById("LLM_reset_notification")) {
1441
+ document.getElementById("LLM_reset_notification").remove();
1430
1442
  }
1431
1443
  document
1432
- .getElementById('save_and_close_menu')
1433
- .parentElement.insertAdjacentHTML('afterend', html);
1444
+ .getElementById("save_and_close_menu")
1445
+ .parentElement.insertAdjacentHTML("afterend", html);
1434
1446
 
1435
1447
  // add to aria button text
1436
1448
  document
1437
- .getElementById('save_and_close_menu')
1449
+ .getElementById("save_and_close_menu")
1438
1450
  .setAttribute(
1439
- 'aria-labelledby',
1440
- 'save_and_close_text LLM_reset_notification'
1451
+ "aria-labelledby",
1452
+ "save_and_close_text LLM_reset_notification"
1441
1453
  );
1442
1454
  }
1443
1455
  /**
@@ -1449,29 +1461,29 @@ class Menu {
1449
1461
  let shouldReset = false;
1450
1462
  if (
1451
1463
  !shouldReset &&
1452
- constants.skillLevel != document.getElementById('skill_level').value
1464
+ constants.skillLevel != document.getElementById("skill_level").value
1453
1465
  ) {
1454
1466
  shouldReset = true;
1455
1467
  }
1456
1468
  if (
1457
1469
  !shouldReset &&
1458
1470
  constants.LLMPreferences !=
1459
- document.getElementById('LLM_preferences').value
1471
+ document.getElementById("LLM_preferences").value
1460
1472
  ) {
1461
1473
  shouldReset = true;
1462
1474
  }
1463
1475
  if (
1464
1476
  !shouldReset &&
1465
- constants.LLMModel != document.getElementById('LLM_model').value
1477
+ constants.LLMModel != document.getElementById("LLM_model").value
1466
1478
  ) {
1467
1479
  shouldReset = true;
1468
1480
  }
1469
1481
  if (
1470
1482
  !shouldReset &&
1471
1483
  (constants.LLMOpenAiMulti !=
1472
- document.getElementById('openai_multi').checked ||
1484
+ document.getElementById("openai_multi").checked ||
1473
1485
  constants.LLMGeminiMulti !=
1474
- document.getElementById('gemini_multi').checked)
1486
+ document.getElementById("gemini_multi").checked)
1475
1487
  ) {
1476
1488
  shouldReset = true;
1477
1489
  }
@@ -1489,23 +1501,23 @@ class Menu {
1489
1501
  data[constants.userSettingsKeys[i]] =
1490
1502
  constants[constants.userSettingsKeys[i]];
1491
1503
  }
1492
- localStorage.setItem('settings_data', JSON.stringify(data));
1504
+ localStorage.setItem("settings_data", JSON.stringify(data));
1493
1505
 
1494
1506
  // also save to tracking if we're doing that
1495
1507
  if (constants.isTracking) {
1496
1508
  // but not auth keys
1497
- data.openAIAuthKey = 'hidden';
1498
- data.geminiAuthKey = 'hidden';
1509
+ data.openAIAuthKey = "hidden";
1510
+ data.geminiAuthKey = "hidden";
1499
1511
  // and need a timestamp
1500
1512
  data.timestamp = new Date().toISOString();
1501
- tracker.SetData('settings', data);
1513
+ tracker.SetData("settings", data);
1502
1514
  }
1503
1515
  }
1504
1516
  /**
1505
1517
  * Loads data from 'settings_data' localStorage, and updates contants variables
1506
1518
  */
1507
1519
  LoadDataFromLocalStorage() {
1508
- let data = JSON.parse(localStorage.getItem('settings_data'));
1520
+ let data = JSON.parse(localStorage.getItem("settings_data"));
1509
1521
  if (data) {
1510
1522
  for (let i = 0; i < constants.userSettingsKeys.length; i++) {
1511
1523
  constants[constants.userSettingsKeys[i]] =
@@ -1531,9 +1543,9 @@ class ChatLLM {
1531
1543
  if (constants.autoInitLLM) {
1532
1544
  // only run if we have API keys set
1533
1545
  if (
1534
- (constants.LLMModel == 'openai' && constants.openAIAuthKey) ||
1535
- (constants.LLMModel == 'gemini' && constants.geminiAuthKey) ||
1536
- (constants.LLMModel == 'multi' &&
1546
+ (constants.LLMModel == "openai" && constants.openAIAuthKey) ||
1547
+ (constants.LLMModel == "gemini" && constants.geminiAuthKey) ||
1548
+ (constants.LLMModel == "multi" &&
1537
1549
  constants.openAIAuthKey &&
1538
1550
  constants.geminiAuthKey)
1539
1551
  ) {
@@ -1588,7 +1600,7 @@ class ChatLLM {
1588
1600
  </div>
1589
1601
  <div id="chatLLM_modal_backdrop" class="modal-backdrop hidden"></div>
1590
1602
  `;
1591
- document.querySelector('body').insertAdjacentHTML('beforeend', html);
1603
+ document.querySelector("body").insertAdjacentHTML("beforeend", html);
1592
1604
  }
1593
1605
 
1594
1606
  /**
@@ -1597,21 +1609,21 @@ class ChatLLM {
1597
1609
  */
1598
1610
  SetEvents() {
1599
1611
  // chatLLM close events
1600
- let allClose = document.querySelectorAll('#close_chatLLM, #chatLLM .close');
1612
+ let allClose = document.querySelectorAll("#close_chatLLM, #chatLLM .close");
1601
1613
  for (let i = 0; i < allClose.length; i++) {
1602
1614
  constants.events.push([
1603
1615
  allClose[i],
1604
- 'click',
1616
+ "click",
1605
1617
  function (e) {
1606
1618
  chatLLM.Toggle(false);
1607
1619
  },
1608
1620
  ]);
1609
1621
  }
1610
1622
  constants.events.push([
1611
- document.getElementById('chatLLM'),
1612
- 'keyup',
1623
+ document.getElementById("chatLLM"),
1624
+ "keyup",
1613
1625
  function (e) {
1614
- if (e.key == 'Esc') {
1626
+ if (e.key == "Esc") {
1615
1627
  // esc
1616
1628
  chatLLM.Toggle(false);
1617
1629
  }
@@ -1621,9 +1633,9 @@ class ChatLLM {
1621
1633
  // ChatLLM open/close toggle
1622
1634
  constants.events.push([
1623
1635
  document,
1624
- 'keyup',
1636
+ "keyup",
1625
1637
  function (e) {
1626
- if ((e.key == '?' && (e.ctrlKey || e.metaKey)) || e.key == '¿') {
1638
+ if ((e.key == "?" && (e.ctrlKey || e.metaKey)) || e.key == "¿") {
1627
1639
  chatLLM.Toggle();
1628
1640
  }
1629
1641
  },
@@ -1631,21 +1643,21 @@ class ChatLLM {
1631
1643
 
1632
1644
  // ChatLLM request events
1633
1645
  constants.events.push([
1634
- document.getElementById('chatLLM_submit'),
1635
- 'click',
1646
+ document.getElementById("chatLLM_submit"),
1647
+ "click",
1636
1648
  function (e) {
1637
- let text = document.getElementById('chatLLM_input').value;
1638
- chatLLM.DisplayChatMessage('User', text);
1649
+ let text = document.getElementById("chatLLM_input").value;
1650
+ chatLLM.DisplayChatMessage("User", text);
1639
1651
  chatLLM.Submit(text);
1640
1652
  },
1641
1653
  ]);
1642
1654
  constants.events.push([
1643
- document.getElementById('chatLLM_input'),
1644
- 'keyup',
1655
+ document.getElementById("chatLLM_input"),
1656
+ "keyup",
1645
1657
  function (e) {
1646
- if (e.key == 'Enter' && !e.shiftKey) {
1647
- let text = document.getElementById('chatLLM_input').value;
1648
- chatLLM.DisplayChatMessage('User', text);
1658
+ if (e.key == "Enter" && !e.shiftKey) {
1659
+ let text = document.getElementById("chatLLM_input").value;
1660
+ chatLLM.DisplayChatMessage("User", text);
1649
1661
  chatLLM.Submit(text);
1650
1662
  }
1651
1663
  },
@@ -1654,15 +1666,15 @@ class ChatLLM {
1654
1666
  // ChatLLM suggestion events
1655
1667
  // actual suggestions:
1656
1668
  let suggestions = document.querySelectorAll(
1657
- '#chatLLM .LLM_suggestions button:not(#more_suggestions)'
1669
+ "#chatLLM .LLM_suggestions button:not(#more_suggestions)"
1658
1670
  );
1659
1671
  for (let i = 0; i < suggestions.length; i++) {
1660
1672
  constants.events.push([
1661
1673
  suggestions[i],
1662
- 'click',
1674
+ "click",
1663
1675
  function (e) {
1664
1676
  let text = e.target.innerHTML;
1665
- chatLLM.DisplayChatMessage('User', text);
1677
+ chatLLM.DisplayChatMessage("User", text);
1666
1678
  chatLLM.Submit(text);
1667
1679
  },
1668
1680
  ]);
@@ -1670,24 +1682,24 @@ class ChatLLM {
1670
1682
 
1671
1683
  // Delete OpenAI and Gemini keys
1672
1684
  constants.events.push([
1673
- document.getElementById('delete_openai_key'),
1674
- 'click',
1685
+ document.getElementById("delete_openai_key"),
1686
+ "click",
1675
1687
  function (e) {
1676
- document.getElementById('openai_auth_key').value = '';
1688
+ document.getElementById("openai_auth_key").value = "";
1677
1689
  },
1678
1690
  ]);
1679
1691
  constants.events.push([
1680
- document.getElementById('delete_gemini_key'),
1681
- 'click',
1692
+ document.getElementById("delete_gemini_key"),
1693
+ "click",
1682
1694
  function (e) {
1683
- document.getElementById('gemini_auth_key').value = '';
1695
+ document.getElementById("gemini_auth_key").value = "";
1684
1696
  },
1685
1697
  ]);
1686
1698
 
1687
1699
  // Reset chatLLM
1688
1700
  constants.events.push([
1689
- document.getElementById('reset_chatLLM'),
1690
- 'click',
1701
+ document.getElementById("reset_chatLLM"),
1702
+ "click",
1691
1703
  function (e) {
1692
1704
  chatLLM.ResetLLM();
1693
1705
  },
@@ -1695,15 +1707,15 @@ class ChatLLM {
1695
1707
 
1696
1708
  // copy to clipboard
1697
1709
  constants.events.push([
1698
- document.getElementById('chatLLM'),
1699
- 'click',
1710
+ document.getElementById("chatLLM"),
1711
+ "click",
1700
1712
  function (e) {
1701
1713
  chatLLM.CopyChatHistory(e);
1702
1714
  },
1703
1715
  ]);
1704
1716
  constants.events.push([
1705
- document.getElementById('chatLLM'),
1706
- 'keyup',
1717
+ document.getElementById("chatLLM"),
1718
+ "keyup",
1707
1719
  function (e) {
1708
1720
  chatLLM.CopyChatHistory(e);
1709
1721
  },
@@ -1721,96 +1733,96 @@ class ChatLLM {
1721
1733
  * @param {Event|undefined} e - The event that triggered the copy action. If undefined, the entire chat history is copied.
1722
1734
  */
1723
1735
  CopyChatHistory(e) {
1724
- let text = '';
1725
- if (typeof e == 'undefined') {
1736
+ let text = "";
1737
+ if (typeof e == "undefined") {
1726
1738
  // check for passthrough
1727
1739
  // get html of the full chat history
1728
- text = document.getElementById('chatLLM_chat_history').innerHTML;
1729
- } else if (e.type == 'click') {
1740
+ text = document.getElementById("chatLLM_chat_history").innerHTML;
1741
+ } else if (e.type == "click") {
1730
1742
  // check for buttons
1731
- if (e.target.id == 'chatLLM_copy_all') {
1743
+ if (e.target.id == "chatLLM_copy_all") {
1732
1744
  // get html of the full chat history
1733
- text = document.getElementById('chatLLM_chat_history').innerHTML;
1734
- } 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")) {
1735
1747
  // get the text of the element before the button
1736
- text = e.target.closest('p').previousElementSibling.innerHTML;
1748
+ text = e.target.closest("p").previousElementSibling.innerHTML;
1737
1749
  }
1738
- } else if (e.type == 'keyup') {
1750
+ } else if (e.type == "keyup") {
1739
1751
  // check for alt shift c or ctrl shift c
1740
- 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) {
1741
1753
  e.preventDefault();
1742
1754
  // get the last message
1743
1755
  let elem = document.querySelector(
1744
- '#chatLLM_chat_history > .chatLLM_message_other:last-of-type'
1756
+ "#chatLLM_chat_history > .chatLLM_message_other:last-of-type"
1745
1757
  );
1746
1758
  if (elem) {
1747
1759
  text = elem.innerHTML;
1748
1760
  }
1749
1761
  } else if (
1750
- e.key == 'A' &&
1762
+ e.key == "A" &&
1751
1763
  (e.ctrlKey || e.metaKey || e.altKey) &&
1752
1764
  e.shiftKey
1753
1765
  ) {
1754
1766
  e.preventDefault();
1755
1767
  // get html of the full chat history
1756
- text = document.getElementById('chatLLM_chat_history').innerHTML;
1768
+ text = document.getElementById("chatLLM_chat_history").innerHTML;
1757
1769
  }
1758
1770
  }
1759
1771
 
1760
- if (text == '') {
1772
+ if (text == "") {
1761
1773
  return;
1762
1774
  } else {
1763
1775
  // clear the html, removing buttons etc
1764
- let cleanElems = document.createElement('div');
1776
+ let cleanElems = document.createElement("div");
1765
1777
  cleanElems.innerHTML = text;
1766
- let removeThese = cleanElems.querySelectorAll('.chatLLM_message_copy');
1778
+ let removeThese = cleanElems.querySelectorAll(".chatLLM_message_copy");
1767
1779
  removeThese.forEach((elem) => elem.remove());
1768
1780
 
1769
1781
  // convert from html to markdown
1770
1782
  let markdown = this.htmlToMarkdown(cleanElems);
1771
1783
  // this messes up a bit with spacing, so kill more than 2 newlines in a row
1772
- markdown = markdown.replace(/\n{3,}/g, '\n\n');
1784
+ markdown = markdown.replace(/\n{3,}/g, "\n\n");
1773
1785
 
1774
1786
  try {
1775
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
1776
1788
  } catch (err) {
1777
- console.error('Failed to copy: ', err);
1789
+ console.error("Failed to copy: ", err);
1778
1790
  }
1779
1791
  return markdown;
1780
1792
  }
1781
1793
  }
1782
1794
 
1783
1795
  htmlToMarkdown(element) {
1784
- let markdown = '';
1796
+ let markdown = "";
1785
1797
 
1786
1798
  const convertElementToMarkdown = (element) => {
1787
1799
  switch (element.tagName) {
1788
- case 'H1':
1800
+ case "H1":
1789
1801
  return `# ${element.textContent}`;
1790
- case 'H2':
1802
+ case "H2":
1791
1803
  return `## ${element.textContent}`;
1792
- case 'H3':
1804
+ case "H3":
1793
1805
  return `### ${element.textContent}`;
1794
- case 'H4':
1806
+ case "H4":
1795
1807
  return `#### ${element.textContent}`;
1796
- case 'H5':
1808
+ case "H5":
1797
1809
  return `##### ${element.textContent}`;
1798
- case 'H6':
1810
+ case "H6":
1799
1811
  return `###### ${element.textContent}`;
1800
- case 'P':
1812
+ case "P":
1801
1813
  return element.textContent;
1802
- case 'DIV':
1814
+ case "DIV":
1803
1815
  // For divs, process each child and add newlines as needed
1804
1816
  return (
1805
1817
  Array.from(element.childNodes)
1806
1818
  .map((child) => convertElementToMarkdown(child))
1807
- .join('\n') + '\n\n'
1819
+ .join("\n") + "\n\n"
1808
1820
  );
1809
1821
  default:
1810
1822
  // For any other element, process its children recursively
1811
1823
  return Array.from(element.childNodes)
1812
1824
  .map((child) => convertElementToMarkdown(child))
1813
- .join('');
1825
+ .join("");
1814
1826
  }
1815
1827
  };
1816
1828
 
@@ -1818,7 +1830,7 @@ class ChatLLM {
1818
1830
  markdown += convertElementToMarkdown(element);
1819
1831
  } else if (
1820
1832
  element.nodeType === Node.TEXT_NODE &&
1821
- element.textContent.trim() !== ''
1833
+ element.textContent.trim() !== ""
1822
1834
  ) {
1823
1835
  markdown += element.textContent.trim();
1824
1836
  }
@@ -1843,14 +1855,14 @@ class ChatLLM {
1843
1855
 
1844
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
1845
1857
  if (
1846
- (this.firstOpen || constants.LLMModel == 'gemini') &&
1858
+ (this.firstOpen || constants.LLMModel == "gemini") &&
1847
1859
  !firsttime &&
1848
1860
  constants.verboseText.length > 0
1849
1861
  ) {
1850
1862
  text =
1851
1863
  "Here is the current position in the chart; no response necessarily needed, use this info only if it's relevant to future questions: " +
1852
1864
  constants.verboseText +
1853
- '. My question is: ' +
1865
+ ". My question is: " +
1854
1866
  text;
1855
1867
 
1856
1868
  this.firstOpen = false;
@@ -1861,15 +1873,15 @@ class ChatLLM {
1861
1873
  this.WaitingSound(true);
1862
1874
  }
1863
1875
 
1864
- if (constants.LLMOpenAiMulti || constants.LLMModel == 'openai') {
1876
+ if (constants.LLMOpenAiMulti || constants.LLMModel == "openai") {
1865
1877
  if (firsttime) {
1866
- img = await this.ConvertSVGtoJPG(singleMaidr.id, 'openai');
1878
+ img = await this.ConvertSVGtoJPG(singleMaidr.id, "openai");
1867
1879
  }
1868
1880
  chatLLM.OpenAIPrompt(text, img);
1869
1881
  }
1870
- if (constants.LLMGeminiMulti || constants.LLMModel == 'gemini') {
1882
+ if (constants.LLMGeminiMulti || constants.LLMModel == "gemini") {
1871
1883
  if (firsttime) {
1872
- img = await this.ConvertSVGtoJPG(singleMaidr.id, 'gemini');
1884
+ img = await this.ConvertSVGtoJPG(singleMaidr.id, "gemini");
1873
1885
  }
1874
1886
  chatLLM.GeminiPrompt(text, img);
1875
1887
  }
@@ -1930,7 +1942,7 @@ class ChatLLM {
1930
1942
  }, 30000);
1931
1943
 
1932
1944
  // set queue for multi
1933
- if (constants.LLMModel != 'multi') {
1945
+ if (constants.LLMModel != "multi") {
1934
1946
  constants.waitingQueue = 1;
1935
1947
  } else {
1936
1948
  constants.waitingQueue = 0;
@@ -1962,7 +1974,7 @@ class ChatLLM {
1962
1974
  // get name from resource
1963
1975
  let LLMName = resources.GetString(constants.LLMModel);
1964
1976
  this.firstTime = false;
1965
- this.DisplayChatMessage(LLMName, resources.GetString('processing'), true);
1977
+ this.DisplayChatMessage(LLMName, resources.GetString("processing"), true);
1966
1978
  let defaultPrompt = this.GetDefaultPrompt();
1967
1979
  this.Submit(defaultPrompt, true);
1968
1980
  }
@@ -1974,35 +1986,35 @@ class ChatLLM {
1974
1986
  */
1975
1987
  ProcessLLMResponse(data, model) {
1976
1988
  chatLLM.WaitingSound(false);
1977
- console.log('LLM response: ', data);
1978
- let text = '';
1989
+ console.log("LLM response: ", data);
1990
+ let text = "";
1979
1991
  let LLMName = resources.GetString(model);
1980
1992
 
1981
- if (model == 'openai') {
1993
+ if (model == "openai") {
1982
1994
  text = data.choices[0].message.content;
1983
1995
  let i = this.requestJson.messages.length;
1984
1996
  this.requestJson.messages[i] = {};
1985
- this.requestJson.messages[i].role = 'assistant';
1997
+ this.requestJson.messages[i].role = "assistant";
1986
1998
  this.requestJson.messages[i].content = text;
1987
1999
 
1988
2000
  if (data.error) {
1989
- chatLLM.DisplayChatMessage(LLMName, 'Error processing request.', true);
2001
+ chatLLM.DisplayChatMessage(LLMName, "Error processing request.", true);
1990
2002
  chatLLM.WaitingSound(false);
1991
2003
  } else {
1992
2004
  chatLLM.DisplayChatMessage(LLMName, text);
1993
2005
  }
1994
- } else if (model == 'gemini') {
2006
+ } else if (model == "gemini") {
1995
2007
  if (data.text()) {
1996
2008
  text = data.text();
1997
2009
  chatLLM.DisplayChatMessage(LLMName, text);
1998
2010
  } else {
1999
2011
  if (!data.error) {
2000
- data.error = 'Error processing request.';
2012
+ data.error = "Error processing request.";
2001
2013
  chatLLM.WaitingSound(false);
2002
2014
  }
2003
2015
  }
2004
2016
  if (data.error) {
2005
- chatLLM.DisplayChatMessage(LLMName, 'Error processing request.', true);
2017
+ chatLLM.DisplayChatMessage(LLMName, "Error processing request.", true);
2006
2018
  chatLLM.WaitingSound(false);
2007
2019
  } else {
2008
2020
  // todo: display actual response
@@ -2012,7 +2024,7 @@ class ChatLLM {
2012
2024
  // if we're tracking, log the data
2013
2025
  if (constants.isTracking) {
2014
2026
  let chatHist = chatLLM.CopyChatHistory();
2015
- tracker.SetData('ChatHistory', chatHist);
2027
+ tracker.SetData("ChatHistory", chatHist);
2016
2028
  }
2017
2029
  }
2018
2030
 
@@ -2026,11 +2038,11 @@ class ChatLLM {
2026
2038
  if (this.requestJson.messages.length > 2) {
2027
2039
  // subsequent responses
2028
2040
  responseText = {
2029
- id: 'chatcmpl-8Y44iRCRrohYbAqm8rfBbJqTUADC7',
2030
- object: 'chat.completion',
2041
+ id: "chatcmpl-8Y44iRCRrohYbAqm8rfBbJqTUADC7",
2042
+ object: "chat.completion",
2031
2043
  created: 1703129508,
2032
2044
  //model: 'gpt-4-1106-vision-preview',
2033
- model: 'gpt4-o',
2045
+ model: "gpt4-o",
2034
2046
  usage: {
2035
2047
  prompt_tokens: 451,
2036
2048
  completion_tokens: 16,
@@ -2039,10 +2051,10 @@ class ChatLLM {
2039
2051
  choices: [
2040
2052
  {
2041
2053
  message: {
2042
- role: 'assistant',
2043
- content: 'A fake response from the LLM. Nice.',
2054
+ role: "assistant",
2055
+ content: "A fake response from the LLM. Nice.",
2044
2056
  },
2045
- finish_reason: 'length',
2057
+ finish_reason: "length",
2046
2058
  index: 0,
2047
2059
  },
2048
2060
  ],
@@ -2050,10 +2062,10 @@ class ChatLLM {
2050
2062
  } else {
2051
2063
  // first response
2052
2064
  responseText = {
2053
- id: 'chatcmpl-8Y44iRCRrohYbAqm8rfBbJqTUADC7',
2054
- object: 'chat.completion',
2065
+ id: "chatcmpl-8Y44iRCRrohYbAqm8rfBbJqTUADC7",
2066
+ object: "chat.completion",
2055
2067
  created: 1703129508,
2056
- model: 'gpt-4-1106-vision-preview',
2068
+ model: "gpt-4-1106-vision-preview",
2057
2069
  usage: {
2058
2070
  prompt_tokens: 451,
2059
2071
  completion_tokens: 16,
@@ -2062,11 +2074,11 @@ class ChatLLM {
2062
2074
  choices: [
2063
2075
  {
2064
2076
  message: {
2065
- role: 'assistant',
2077
+ role: "assistant",
2066
2078
  content:
2067
- '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",
2068
2080
  },
2069
- finish_reason: 'length',
2081
+ finish_reason: "length",
2070
2082
  index: 0,
2071
2083
  },
2072
2084
  ],
@@ -2085,49 +2097,49 @@ class ChatLLM {
2085
2097
  */
2086
2098
  OpenAIPrompt(text, img = null) {
2087
2099
  // request init
2088
- let url = 'https://api.openai.com/v1/chat/completions';
2100
+ let url = "https://api.openai.com/v1/chat/completions";
2089
2101
  let auth = constants.openAIAuthKey;
2090
2102
  let requestJson = chatLLM.OpenAIJson(text, img);
2091
- console.log('LLM request: ', requestJson);
2103
+ console.log("LLM request: ", requestJson);
2092
2104
 
2093
2105
  fetch(url, {
2094
- method: 'POST',
2106
+ method: "POST",
2095
2107
  headers: {
2096
- 'Content-Type': 'application/json',
2097
- Authorization: 'Bearer ' + auth,
2108
+ "Content-Type": "application/json",
2109
+ Authorization: "Bearer " + auth,
2098
2110
  },
2099
2111
  body: JSON.stringify(requestJson),
2100
2112
  })
2101
2113
  .then((response) => response.json())
2102
2114
  .then((data) => {
2103
- chatLLM.ProcessLLMResponse(data, 'openai');
2115
+ chatLLM.ProcessLLMResponse(data, "openai");
2104
2116
  })
2105
2117
  .catch((error) => {
2106
2118
  chatLLM.WaitingSound(false);
2107
- console.error('Error:', error);
2108
- chatLLM.DisplayChatMessage('OpenAI', 'Error processing request.', true);
2119
+ console.error("Error:", error);
2120
+ chatLLM.DisplayChatMessage("OpenAI", "Error processing request.", true);
2109
2121
  // also todo: handle errors somehow
2110
2122
  });
2111
2123
  }
2112
2124
  OpenAIJson(text, img = null) {
2113
2125
  let sysMessage = constants.LLMSystemMessage;
2114
2126
  let backupMessage =
2115
- 'Describe ' + singleMaidr.type + ' charts to a blind person';
2127
+ "Describe " + singleMaidr.type + " charts to a blind person";
2116
2128
  // headers and sys message
2117
2129
  if (!this.requestJson) {
2118
2130
  this.requestJson = {};
2119
2131
  //this.requestJson.model = 'gpt-4-vision-preview';
2120
- this.requestJson.model = 'gpt-4o-2024-08-06';
2132
+ this.requestJson.model = "gpt-4o-2024-08-06";
2121
2133
  this.requestJson.max_tokens = constants.LLMmaxResponseTokens; // note: if this is too short (tested with less than 200), the response gets cut off
2122
2134
 
2123
2135
  // sys message
2124
2136
  this.requestJson.messages = [];
2125
2137
  this.requestJson.messages[0] = {};
2126
- this.requestJson.messages[0].role = 'system';
2138
+ this.requestJson.messages[0].role = "system";
2127
2139
  this.requestJson.messages[0].content = sysMessage;
2128
2140
  if (constants.LLMPreferences) {
2129
2141
  this.requestJson.messages[1] = {};
2130
- this.requestJson.messages[1].role = 'system';
2142
+ this.requestJson.messages[1].role = "system";
2131
2143
  this.requestJson.messages[1].content = constants.LLMPreferences;
2132
2144
  }
2133
2145
  }
@@ -2136,16 +2148,16 @@ class ChatLLM {
2136
2148
  // if we have an image (first time only), send the image and the text, otherwise just the text
2137
2149
  let i = this.requestJson.messages.length;
2138
2150
  this.requestJson.messages[i] = {};
2139
- this.requestJson.messages[i].role = 'user';
2151
+ this.requestJson.messages[i].role = "user";
2140
2152
  if (img) {
2141
2153
  // first message, include the img
2142
2154
  this.requestJson.messages[i].content = [
2143
2155
  {
2144
- type: 'text',
2156
+ type: "text",
2145
2157
  text: text,
2146
2158
  },
2147
2159
  {
2148
- type: 'image_url',
2160
+ type: "image_url",
2149
2161
  image_url: { url: img },
2150
2162
  },
2151
2163
  ];
@@ -2170,12 +2182,12 @@ class ChatLLM {
2170
2182
 
2171
2183
  // Import the module
2172
2184
  const { GoogleGenerativeAI } = await import(
2173
- 'https://esm.run/@google/generative-ai'
2185
+ "https://esm.run/@google/generative-ai"
2174
2186
  );
2175
2187
  const API_KEY = constants.geminiAuthKey;
2176
2188
  const genAI = new GoogleGenerativeAI(API_KEY);
2177
2189
  const model = genAI.getGenerativeModel({
2178
- model: 'gemini-1.5-pro-latest',
2190
+ model: "gemini-1.5-pro-latest",
2179
2191
  }); // old model was 'gemini-pro-vision'
2180
2192
 
2181
2193
  // Create the prompt
@@ -2183,25 +2195,25 @@ class ChatLLM {
2183
2195
  if (constants.LLMPreferences) {
2184
2196
  prompt += constants.LLMPreferences;
2185
2197
  }
2186
- prompt += '\n\n' + text; // Use the text parameter as the prompt
2198
+ prompt += "\n\n" + text; // Use the text parameter as the prompt
2187
2199
  const image = {
2188
2200
  inlineData: {
2189
2201
  data: imgBase64, // Use the base64 image string
2190
- mimeType: 'image/png', // Or the appropriate mime type of your image
2202
+ mimeType: "image/png", // Or the appropriate mime type of your image
2191
2203
  },
2192
2204
  };
2193
2205
 
2194
2206
  // Generate the content
2195
- console.log('LLM request: ', prompt, image);
2207
+ console.log("LLM request: ", prompt, image);
2196
2208
  const result = await model.generateContent([prompt, image]);
2197
2209
  console.log(result.response.text());
2198
2210
 
2199
2211
  // Process the response
2200
- chatLLM.ProcessLLMResponse(result.response, 'gemini');
2212
+ chatLLM.ProcessLLMResponse(result.response, "gemini");
2201
2213
  } catch (error) {
2202
2214
  chatLLM.WaitingSound(false);
2203
- chatLLM.DisplayChatMessage('Gemini', 'Error processing request.', true);
2204
- console.error('Error in GeminiPrompt:', error);
2215
+ chatLLM.DisplayChatMessage("Gemini", "Error processing request.", true);
2216
+ console.error("Error in GeminiPrompt:", error);
2205
2217
  throw error; // Rethrow the error for further handling if necessary
2206
2218
  }
2207
2219
  }
@@ -2213,11 +2225,11 @@ class ChatLLM {
2213
2225
  * @memberof module:constants
2214
2226
  * @returns {void}
2215
2227
  */
2216
- DisplayChatMessage(user = 'User', text = '', isSystem = false) {
2217
- let hLevel = 'h3';
2218
- 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") {
2219
2231
  if (this.firstMulti) {
2220
- let multiAIName = resources.GetString('multi');
2232
+ let multiAIName = resources.GetString("multi");
2221
2233
  let titleHtml = `
2222
2234
  <div class="chatLLM_message chatLLM_message_other">
2223
2235
  <h3 class="chatLLM_message_user">${multiAIName} Responses</h3>
@@ -2226,20 +2238,20 @@ class ChatLLM {
2226
2238
  this.RenderChatMessage(titleHtml);
2227
2239
  this.firstMulti = false;
2228
2240
  }
2229
- hLevel = 'h4';
2241
+ hLevel = "h4";
2230
2242
  }
2231
2243
  let html = `
2232
2244
  <div class="chatLLM_message ${
2233
- user == 'User' ? 'chatLLM_message_self' : 'chatLLM_message_other'
2245
+ user == "User" ? "chatLLM_message_self" : "chatLLM_message_other"
2234
2246
  }">`;
2235
- if (text != resources.GetString('processing')) {
2247
+ if (text != resources.GetString("processing")) {
2236
2248
  html += `<${hLevel} class="chatLLM_message_user">${user}</${hLevel}>`;
2237
2249
  }
2238
2250
  html += `<p class="chatLLM_message_text">${text}</p>
2239
2251
  </div>
2240
2252
  `;
2241
2253
  // add a copy button to actual messages
2242
- if (user != 'User' && text != resources.GetString('processing')) {
2254
+ if (user != "User" && text != resources.GetString("processing")) {
2243
2255
  html += `
2244
2256
  <p class="chatLLM_message_copy"><button class="chatLLM_message_copy_button">Copy</button></p>
2245
2257
  `;
@@ -2249,13 +2261,13 @@ class ChatLLM {
2249
2261
  }
2250
2262
  RenderChatMessage(html) {
2251
2263
  document
2252
- .getElementById('chatLLM_chat_history')
2253
- .insertAdjacentHTML('beforeend', html);
2254
- document.getElementById('chatLLM_input').value = '';
2264
+ .getElementById("chatLLM_chat_history")
2265
+ .insertAdjacentHTML("beforeend", html);
2266
+ document.getElementById("chatLLM_input").value = "";
2255
2267
 
2256
2268
  // scroll to bottom
2257
- document.getElementById('chatLLM_chat_history').scrollTop =
2258
- document.getElementById('chatLLM_chat_history').scrollHeight;
2269
+ document.getElementById("chatLLM_chat_history").scrollTop =
2270
+ document.getElementById("chatLLM_chat_history").scrollHeight;
2259
2271
  }
2260
2272
 
2261
2273
  /**
@@ -2263,7 +2275,7 @@ class ChatLLM {
2263
2275
  */
2264
2276
  ResetLLM() {
2265
2277
  // clear the main chat history
2266
- document.getElementById('chatLLM_chat_history').innerHTML = '';
2278
+ document.getElementById("chatLLM_chat_history").innerHTML = "";
2267
2279
 
2268
2280
  // reset the data
2269
2281
  this.requestJson = null;
@@ -2284,11 +2296,11 @@ class ChatLLM {
2284
2296
  */
2285
2297
  Destroy() {
2286
2298
  // chatLLM element destruction
2287
- let chatLLM = document.getElementById('chatLLM');
2299
+ let chatLLM = document.getElementById("chatLLM");
2288
2300
  if (chatLLM) {
2289
2301
  chatLLM.remove();
2290
2302
  }
2291
- let backdrop = document.getElementById('chatLLM_modal_backdrop');
2303
+ let backdrop = document.getElementById("chatLLM_modal_backdrop");
2292
2304
  if (backdrop) {
2293
2305
  backdrop.remove();
2294
2306
  }
@@ -2299,8 +2311,8 @@ class ChatLLM {
2299
2311
  * @param {boolean} [onoff=false] - Whether to turn the chatLLM on or off. Defaults to false (close).
2300
2312
  */
2301
2313
  Toggle(onoff) {
2302
- if (typeof onoff == 'undefined') {
2303
- if (document.getElementById('chatLLM').classList.contains('hidden')) {
2314
+ if (typeof onoff == "undefined") {
2315
+ if (document.getElementById("chatLLM").classList.contains("hidden")) {
2304
2316
  onoff = true;
2305
2317
  } else {
2306
2318
  onoff = false;
@@ -2311,19 +2323,19 @@ class ChatLLM {
2311
2323
  // open
2312
2324
  this.whereWasMyFocus = document.activeElement;
2313
2325
  constants.tabMovement = 0;
2314
- document.getElementById('chatLLM').classList.remove('hidden');
2326
+ document.getElementById("chatLLM").classList.remove("hidden");
2315
2327
  document
2316
- .getElementById('chatLLM_modal_backdrop')
2317
- .classList.remove('hidden');
2318
- document.querySelector('#chatLLM .close').focus();
2328
+ .getElementById("chatLLM_modal_backdrop")
2329
+ .classList.remove("hidden");
2330
+ document.querySelector("#chatLLM .close").focus();
2319
2331
 
2320
2332
  if (this.firstTime) {
2321
2333
  this.InitChatMessage();
2322
2334
  }
2323
2335
  } else {
2324
2336
  // close
2325
- document.getElementById('chatLLM').classList.add('hidden');
2326
- document.getElementById('chatLLM_modal_backdrop').classList.add('hidden');
2337
+ document.getElementById("chatLLM").classList.add("hidden");
2338
+ document.getElementById("chatLLM_modal_backdrop").classList.add("hidden");
2327
2339
  this.whereWasMyFocus.focus();
2328
2340
  this.whereWasMyFocus = null;
2329
2341
  this.firstOpen = true;
@@ -2337,11 +2349,11 @@ class ChatLLM {
2337
2349
  async ConvertSVGtoJPG(id, model) {
2338
2350
  let svgElement = document.getElementById(id);
2339
2351
  return new Promise((resolve, reject) => {
2340
- var canvas = document.createElement('canvas');
2341
- var ctx = canvas.getContext('2d');
2352
+ var canvas = document.createElement("canvas");
2353
+ var ctx = canvas.getContext("2d");
2342
2354
 
2343
2355
  var svgData = new XMLSerializer().serializeToString(svgElement);
2344
- if (!svgData.startsWith('<svg xmlns')) {
2356
+ if (!svgData.startsWith("<svg xmlns")) {
2345
2357
  svgData = `<svg xmlns="http://www.w3.org/2000/svg" ${svgData.slice(4)}`;
2346
2358
  }
2347
2359
 
@@ -2353,11 +2365,11 @@ class ChatLLM {
2353
2365
  var img = new Image();
2354
2366
  img.onload = function () {
2355
2367
  ctx.drawImage(img, 0, 0, svgSize.width, svgSize.height);
2356
- var jpegData = canvas.toDataURL('image/jpeg', 0.9); // 0.9 is the quality parameter
2357
- if (model == 'openai') {
2368
+ var jpegData = canvas.toDataURL("image/jpeg", 0.9); // 0.9 is the quality parameter
2369
+ if (model == "openai") {
2358
2370
  resolve(jpegData);
2359
- } else if (model == 'gemini') {
2360
- let base64Data = jpegData.split(',')[1];
2371
+ } else if (model == "gemini") {
2372
+ let base64Data = jpegData.split(",")[1];
2361
2373
  resolve(base64Data);
2362
2374
  //resolve(jpegData);
2363
2375
  }
@@ -2365,11 +2377,11 @@ class ChatLLM {
2365
2377
  };
2366
2378
 
2367
2379
  img.onerror = function () {
2368
- reject(new Error('Error loading SVG'));
2380
+ reject(new Error("Error loading SVG"));
2369
2381
  };
2370
2382
 
2371
2383
  var svgBlob = new Blob([svgData], {
2372
- type: 'image/svg+xml;charset=utf-8',
2384
+ type: "image/svg+xml;charset=utf-8",
2373
2385
  });
2374
2386
  var url = URL.createObjectURL(svgBlob);
2375
2387
  img.src = url;
@@ -2382,25 +2394,25 @@ class ChatLLM {
2382
2394
  * The prompt includes information about the blind person's skill level and the chart's image and raw data, if available.
2383
2395
  */
2384
2396
  GetDefaultPrompt() {
2385
- let text = 'Describe this chart to a blind person';
2397
+ let text = "Describe this chart to a blind person";
2386
2398
  if (constants.skillLevel) {
2387
- if (constants.skillLevel == 'other' && constants.skillLevelOther) {
2399
+ if (constants.skillLevel == "other" && constants.skillLevelOther) {
2388
2400
  text +=
2389
- ' who has a ' +
2401
+ " who has a " +
2390
2402
  constants.skillLevelOther +
2391
- ' understanding of statistical charts. ';
2403
+ " understanding of statistical charts. ";
2392
2404
  } else {
2393
2405
  text +=
2394
- ' who has a ' +
2406
+ " who has a " +
2395
2407
  constants.skillLevel +
2396
- ' understanding of statistical charts. ';
2408
+ " understanding of statistical charts. ";
2397
2409
  }
2398
2410
  } else {
2399
- text += ' who has a basic understanding of statistical charts. ';
2411
+ text += " who has a basic understanding of statistical charts. ";
2400
2412
  }
2401
- text += 'Here is a chart in image format';
2413
+ text += "Here is a chart in image format";
2402
2414
  if (singleMaidr) {
2403
- text += ' and raw data in json format: \n';
2415
+ text += " and raw data in json format: \n";
2404
2416
  text += JSON.stringify(singleMaidr);
2405
2417
  }
2406
2418
 
@@ -2455,26 +2467,26 @@ class Description {
2455
2467
 
2456
2468
  `;
2457
2469
 
2458
- document.querySelector('body').insertAdjacentHTML('beforeend', html);
2470
+ document.querySelector("body").insertAdjacentHTML("beforeend", html);
2459
2471
 
2460
2472
  // close events
2461
2473
  let allClose = document.querySelectorAll(
2462
- '#close_desc, #description .close'
2474
+ "#close_desc, #description .close"
2463
2475
  );
2464
2476
  for (let i = 0; i < allClose.length; i++) {
2465
2477
  constants.events.push([
2466
2478
  allClose[i],
2467
- 'click',
2479
+ "click",
2468
2480
  function (e) {
2469
2481
  description.Toggle(false);
2470
2482
  },
2471
2483
  ]);
2472
2484
  }
2473
2485
  constants.events.push([
2474
- document.getElementById('description'),
2475
- 'keyup',
2486
+ document.getElementById("description"),
2487
+ "keyup",
2476
2488
  function (e) {
2477
- if (e.key == 'Esc') {
2489
+ if (e.key == "Esc") {
2478
2490
  // esc
2479
2491
  description.Toggle(false);
2480
2492
  }
@@ -2484,9 +2496,9 @@ class Description {
2484
2496
  // open events
2485
2497
  constants.events.push([
2486
2498
  document,
2487
- 'keyup',
2499
+ "keyup",
2488
2500
  function (e) {
2489
- if (e.key == 'd') {
2501
+ if (e.key == "d") {
2490
2502
  description.Toggle(true);
2491
2503
  }
2492
2504
  },
@@ -2498,11 +2510,11 @@ class Description {
2498
2510
  */
2499
2511
  Destroy() {
2500
2512
  // description element destruction
2501
- let description = document.getElementById('menu');
2513
+ let description = document.getElementById("menu");
2502
2514
  if (description) {
2503
2515
  description.remove();
2504
2516
  }
2505
- let backdrop = document.getElementById('desc_modal_backdrop');
2517
+ let backdrop = document.getElementById("desc_modal_backdrop");
2506
2518
  if (backdrop) {
2507
2519
  backdrop.remove();
2508
2520
  }
@@ -2513,8 +2525,8 @@ class Description {
2513
2525
  * @param {boolean} [onoff=false] - Whether to turn the description element on or off.
2514
2526
  */
2515
2527
  Toggle(onoff = false) {
2516
- if (typeof onoff == 'undefined') {
2517
- if (document.getElementById('description').classList.contains('hidden')) {
2528
+ if (typeof onoff == "undefined") {
2529
+ if (document.getElementById("description").classList.contains("hidden")) {
2518
2530
  onoff = true;
2519
2531
  } else {
2520
2532
  onoff = false;
@@ -2525,13 +2537,13 @@ class Description {
2525
2537
  this.whereWasMyFocus = document.activeElement;
2526
2538
  constants.tabMovement = 0;
2527
2539
  this.PopulateData();
2528
- document.getElementById('description').classList.remove('hidden');
2529
- document.getElementById('desc_modal_backdrop').classList.remove('hidden');
2530
- 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();
2531
2543
  } else {
2532
2544
  // close
2533
- document.getElementById('description').classList.add('hidden');
2534
- document.getElementById('desc_modal_backdrop').classList.add('hidden');
2545
+ document.getElementById("description").classList.add("hidden");
2546
+ document.getElementById("desc_modal_backdrop").classList.add("hidden");
2535
2547
  this.whereWasMyFocus.focus();
2536
2548
  this.whereWasMyFocus = null;
2537
2549
  }
@@ -2541,22 +2553,22 @@ class Description {
2541
2553
  * Populates the data for the chart and table based on the chart type and plot data.
2542
2554
  */
2543
2555
  PopulateData() {
2544
- let descHtml = '';
2556
+ let descHtml = "";
2545
2557
 
2546
2558
  // chart labels and descriptions
2547
- let descType = '';
2548
- if (constants.chartType == 'bar') {
2549
- descType = 'Bar chart';
2550
- } else if (constants.chartType == 'heat') {
2551
- descType = 'Heatmap';
2552
- } else if (constants.chartType == 'box') {
2553
- descType = 'Box plot';
2554
- } else if (constants.chartType == 'scatter') {
2555
- descType = 'Scatter plot';
2556
- } else if (constants.chartType == 'line') {
2557
- descType = 'Line chart';
2558
- } else if (constants.chartType == 'hist') {
2559
- 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";
2560
2572
  }
2561
2573
 
2562
2574
  if (descType) {
@@ -2573,7 +2585,7 @@ class Description {
2573
2585
  }
2574
2586
 
2575
2587
  // table of data, prep
2576
- let descTableHtml = '';
2588
+ let descTableHtml = "";
2577
2589
  let descLabelX = null;
2578
2590
  let descLabelY = null;
2579
2591
  let descTickX = null;
@@ -2583,7 +2595,7 @@ class Description {
2583
2595
  let descNumColsWithLabels = 0;
2584
2596
  let descNumRows = 0;
2585
2597
  let descNumRowsWithLabels = 0;
2586
- if (constants.chartType == 'bar') {
2598
+ if (constants.chartType == "bar") {
2587
2599
  if (plot.plotLegend.x != null) {
2588
2600
  descLabelX = plot.plotLegend.x;
2589
2601
  descNumColsWithLabels += 1;
@@ -2608,43 +2620,43 @@ class Description {
2608
2620
 
2609
2621
  // table of data, create
2610
2622
  if (descData != null) {
2611
- descTableHtml += '<table>';
2623
+ descTableHtml += "<table>";
2612
2624
 
2613
2625
  // header rows
2614
2626
  if (descLabelX != null || descTickX != null) {
2615
- descTableHtml += '<thead>';
2627
+ descTableHtml += "<thead>";
2616
2628
  if (descLabelX != null) {
2617
- descTableHtml += '<tr>';
2629
+ descTableHtml += "<tr>";
2618
2630
  if (descLabelY != null) {
2619
- descTableHtml += '<td></td>';
2631
+ descTableHtml += "<td></td>";
2620
2632
  }
2621
2633
  if (descTickY != null) {
2622
- descTableHtml += '<td></td>';
2634
+ descTableHtml += "<td></td>";
2623
2635
  }
2624
2636
  descTableHtml += `<th scope="col" colspan="${descNumCols}">${descLabelX}</th>`;
2625
- descTableHtml += '</tr>';
2637
+ descTableHtml += "</tr>";
2626
2638
  }
2627
2639
  if (descTickX != null) {
2628
- descTableHtml += '<tr>';
2640
+ descTableHtml += "<tr>";
2629
2641
  if (descLabelY != null) {
2630
- descTableHtml += '<td></td>';
2642
+ descTableHtml += "<td></td>";
2631
2643
  }
2632
2644
  if (descTickY != null) {
2633
- descTableHtml += '<td></td>';
2645
+ descTableHtml += "<td></td>";
2634
2646
  }
2635
2647
  for (let i = 0; i < descNumCols; i++) {
2636
2648
  descTableHtml += `<th scope="col">${descTickX[i]}</th>`;
2637
2649
  }
2638
- descTableHtml += '</tr>';
2650
+ descTableHtml += "</tr>";
2639
2651
  }
2640
- descTableHtml += '</thead>';
2652
+ descTableHtml += "</thead>";
2641
2653
  }
2642
2654
 
2643
2655
  // body rows
2644
2656
  if (descNumRows > 0) {
2645
- descTableHtml += '<tbody>';
2657
+ descTableHtml += "<tbody>";
2646
2658
  for (let i = 0; i < descNumRows; i++) {
2647
- descTableHtml += '<tr>';
2659
+ descTableHtml += "<tr>";
2648
2660
  if (descLabelY != null && i == 0) {
2649
2661
  descTableHtml += `<th scope="row" rowspan="${descNumRows}">${descLabelY}</th>`;
2650
2662
  }
@@ -2654,19 +2666,19 @@ class Description {
2654
2666
  for (let j = 0; j < descNumCols; j++) {
2655
2667
  descTableHtml += `<td>${descData[i][j]}</td>`;
2656
2668
  }
2657
- descTableHtml += '</tr>';
2669
+ descTableHtml += "</tr>";
2658
2670
  }
2659
- descTableHtml += '</tbody>';
2671
+ descTableHtml += "</tbody>";
2660
2672
  }
2661
2673
 
2662
- descTableHtml += '</table>';
2674
+ descTableHtml += "</table>";
2663
2675
  }
2664
2676
 
2665
2677
  // bar: don't need colspan or rowspan stuff, put legendX and Y as headers
2666
2678
 
2667
- document.getElementById('desc_title').innerHTML = descType + ' description';
2668
- document.getElementById('desc_content').innerHTML = descHtml;
2669
- 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;
2670
2682
  }
2671
2683
  }
2672
2684
 
@@ -2736,11 +2748,11 @@ class Tracker {
2736
2748
  * Downloads the tracker data as a JSON file.
2737
2749
  */
2738
2750
  DownloadTrackerData() {
2739
- let link = document.createElement('a');
2751
+ let link = document.createElement("a");
2740
2752
  let data = this.GetTrackerData();
2741
- let fileStr = new Blob([JSON.stringify(data)], { type: 'text/plain' });
2753
+ let fileStr = new Blob([JSON.stringify(data)], { type: "text/plain" });
2742
2754
  link.href = URL.createObjectURL(fileStr);
2743
- link.download = 'tracking.json';
2755
+ link.download = "tracking.json";
2744
2756
  link.click();
2745
2757
  }
2746
2758
 
@@ -2769,7 +2781,7 @@ class Tracker {
2769
2781
  this.data = null;
2770
2782
 
2771
2783
  if (constants.debugLevel > 0) {
2772
- console.log('tracking data cleared');
2784
+ console.log("tracking data cleared");
2773
2785
  }
2774
2786
 
2775
2787
  this.DataSetup();
@@ -2777,12 +2789,12 @@ class Tracker {
2777
2789
 
2778
2790
  SaveSettings() {
2779
2791
  // fetch all settings, push to data.settings
2780
- let settings = JSON.parse(localStorage.getItem('settings_data'));
2792
+ let settings = JSON.parse(localStorage.getItem("settings_data"));
2781
2793
  if (settings) {
2782
2794
  // don't store their auth keys
2783
- settings.openAIAuthKey = 'hidden';
2784
- settings.geminiAuthKey = 'hidden';
2785
- this.SetData('settings', settings);
2795
+ settings.openAIAuthKey = "hidden";
2796
+ settings.geminiAuthKey = "hidden";
2797
+ this.SetData("settings", settings);
2786
2798
  }
2787
2799
  }
2788
2800
 
@@ -2876,7 +2888,7 @@ class Tracker {
2876
2888
  }
2877
2889
  if (!this.isUndefinedOrNull(constants.infoDiv.innerHTML)) {
2878
2890
  let textDisplay = Object.assign(constants.infoDiv.innerHTML);
2879
- textDisplay = textDisplay.replaceAll(/<[^>]*>?/gm, '');
2891
+ textDisplay = textDisplay.replaceAll(/<[^>]*>?/gm, "");
2880
2892
  eventToLog.text_display = textDisplay;
2881
2893
  }
2882
2894
  if (!this.isUndefinedOrNull(location.href)) {
@@ -2884,13 +2896,13 @@ class Tracker {
2884
2896
  }
2885
2897
 
2886
2898
  // chart specific values
2887
- let x_tickmark = '';
2888
- let y_tickmark = '';
2889
- let x_label = '';
2890
- let y_label = '';
2891
- let value = '';
2892
- let fill_value = '';
2893
- 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") {
2894
2906
  if (!this.isUndefinedOrNull(plot.columnLabels[position.x])) {
2895
2907
  x_tickmark = plot.columnLabels[position.x];
2896
2908
  }
@@ -2903,7 +2915,7 @@ class Tracker {
2903
2915
  if (!this.isUndefinedOrNull(plot.plotData[position.x])) {
2904
2916
  value = plot.plotData[position.x];
2905
2917
  }
2906
- } else if (constants.chartType == 'heat') {
2918
+ } else if (constants.chartType == "heat") {
2907
2919
  if (!this.isUndefinedOrNull(plot.x_labels[position.x])) {
2908
2920
  x_tickmark = plot.x_labels[position.x].trim();
2909
2921
  }
@@ -2924,11 +2936,11 @@ class Tracker {
2924
2936
  if (!this.isUndefinedOrNull(plot.group_labels[2])) {
2925
2937
  fill_value = plot.group_labels[2];
2926
2938
  }
2927
- } else if (constants.chartType == 'box') {
2939
+ } else if (constants.chartType == "box") {
2928
2940
  let plotPos =
2929
- constants.plotOrientation == 'vert' ? position.x : position.y;
2941
+ constants.plotOrientation == "vert" ? position.x : position.y;
2930
2942
  let sectionPos =
2931
- constants.plotOrientation == 'vert' ? position.y : position.x;
2943
+ constants.plotOrientation == "vert" ? position.y : position.x;
2932
2944
  let sectionLabel = plot.sections[sectionPos];
2933
2945
 
2934
2946
  if (!this.isUndefinedOrNull(plot.x_group_label)) {
@@ -2937,7 +2949,7 @@ class Tracker {
2937
2949
  if (!this.isUndefinedOrNull(plot.y_group_label)) {
2938
2950
  y_label = plot.y_group_label;
2939
2951
  }
2940
- if (constants.plotOrientation == 'vert') {
2952
+ if (constants.plotOrientation == "vert") {
2941
2953
  if (plotPos > -1 && sectionPos > -1) {
2942
2954
  if (!this.isUndefinedOrNull(sectionLabel)) {
2943
2955
  y_tickmark = sectionLabel;
@@ -2962,7 +2974,7 @@ class Tracker {
2962
2974
  }
2963
2975
  }
2964
2976
  }
2965
- } else if (constants.chartType == 'point') {
2977
+ } else if (constants.chartType == "point") {
2966
2978
  if (!this.isUndefinedOrNull(plot.x_group_label)) {
2967
2979
  x_label = plot.x_group_label;
2968
2980
  }
@@ -2989,13 +3001,13 @@ class Tracker {
2989
3001
 
2990
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);
2991
3003
 
2992
- this.SetData('events', eventToLog);
3004
+ this.SetData("events", eventToLog);
2993
3005
  //console.log('logged an event');
2994
3006
  }
2995
3007
 
2996
3008
  SetData(key, value) {
2997
3009
  let data = this.GetTrackerData();
2998
- let arrayKeys = ['events', 'ChatHistory', 'settings'];
3010
+ let arrayKeys = ["events", "ChatHistory", "settings"];
2999
3011
  if (!arrayKeys.includes(key)) {
3000
3012
  data[key] = value;
3001
3013
  } else {
@@ -3036,20 +3048,20 @@ class Review {
3036
3048
  // true means on or show
3037
3049
  if (onoff) {
3038
3050
  constants.reviewSaveSpot = document.activeElement;
3039
- constants.review_container.classList.remove('hidden');
3051
+ constants.review_container.classList.remove("hidden");
3040
3052
  constants.reviewSaveBrailleMode = constants.brailleMode;
3041
3053
  constants.review.focus();
3042
3054
 
3043
- display.announceText('Review on');
3055
+ display.announceText("Review on");
3044
3056
  } else {
3045
- constants.review_container.classList.add('hidden');
3046
- if (constants.reviewSaveBrailleMode == 'on') {
3057
+ constants.review_container.classList.add("hidden");
3058
+ if (constants.reviewSaveBrailleMode == "on") {
3047
3059
  // we have to turn braille mode back on
3048
- display.toggleBrailleMode('on');
3060
+ display.toggleBrailleMode("on");
3049
3061
  } else {
3050
3062
  constants.reviewSaveSpot.focus();
3051
3063
  }
3052
- display.announceText('Review off');
3064
+ display.announceText("Review off");
3053
3065
  }
3054
3066
  }
3055
3067
  }
@@ -3066,7 +3078,7 @@ class LogError {
3066
3078
  * @param {string} a - The absent element to log.
3067
3079
  */
3068
3080
  LogAbsentElement(a) {
3069
- console.log(a, 'not found. Visual highlighting is turned off.');
3081
+ console.log(a, "not found. Visual highlighting is turned off.");
3070
3082
  }
3071
3083
 
3072
3084
  /**
@@ -3074,7 +3086,7 @@ class LogError {
3074
3086
  * @param {string} a - The critical element to log.
3075
3087
  */
3076
3088
  LogCriticalElement(a) {
3077
- consolelog(a, 'is critical. MAIDR unable to run');
3089
+ consolelog(a, "is critical. MAIDR unable to run");
3078
3090
  }
3079
3091
 
3080
3092
  /**
@@ -3085,9 +3097,9 @@ class LogError {
3085
3097
  LogDifferentLengths(a, b) {
3086
3098
  console.log(
3087
3099
  a,
3088
- 'and',
3100
+ "and",
3089
3101
  b,
3090
- 'do not have the same length. Visual highlighting is turned off.'
3102
+ "do not have the same length. Visual highlighting is turned off."
3091
3103
  );
3092
3104
  }
3093
3105
 
@@ -3098,11 +3110,11 @@ class LogError {
3098
3110
  */
3099
3111
  LogTooManyElements(a, b) {
3100
3112
  console.log(
3101
- 'Too many',
3113
+ "Too many",
3102
3114
  a,
3103
- 'elements. Only the first',
3115
+ "elements. Only the first",
3104
3116
  b,
3105
- 'will be highlighted.'
3117
+ "will be highlighted."
3106
3118
  );
3107
3119
  }
3108
3120
 
@@ -3111,7 +3123,7 @@ class LogError {
3111
3123
  * @param {*} a - The parameter that is not an array.
3112
3124
  */
3113
3125
  LogNotArray(a) {
3114
- 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.");
3115
3127
  }
3116
3128
  }
3117
3129
 
@@ -3782,6 +3794,7 @@ class Display {
3782
3794
  }
3783
3795
  }
3784
3796
  if (onoff == 'on') {
3797
+ constants.lockSelection = true;
3785
3798
  if (constants.chartType == 'box') {
3786
3799
  // braille mode is on before any plot is selected
3787
3800
  if (
@@ -3819,6 +3832,9 @@ class Display {
3819
3832
  if (position.x == -1 && position.y == -1) {
3820
3833
  constants.brailleInput.setSelectionRange(0, 0);
3821
3834
  }
3835
+ setTimeout(function () {
3836
+ constants.lockSelection = false;
3837
+ }, 50);
3822
3838
  } else {
3823
3839
  constants.brailleMode = 'off';
3824
3840
  document
@@ -3925,6 +3941,7 @@ class Display {
3925
3941
  * Updates the position of the cursor in the braille display based on the current chart type and position.
3926
3942
  */
3927
3943
  UpdateBraillePos() {
3944
+ constants.lockSelection = true;
3928
3945
  if (
3929
3946
  constants.chartType == 'bar' ||
3930
3947
  constants.chartType == 'hist' ||
@@ -3954,6 +3971,11 @@ class Display {
3954
3971
  let targetLabel = this.boxplotGridPlaceholders[sectionPos];
3955
3972
  let haveTargetLabel = false;
3956
3973
  let adjustedPos = 0;
3974
+ // bookmark: shiny issue: this is being called twice??
3975
+ // and the issue happens on 2nd call, sometimes it skips like 75% or whatever
3976
+ //
3977
+ // on first call, we might call it multiple as we're setting up, I care but let's check that later
3978
+
3957
3979
  if (constants.brailleData) {
3958
3980
  for (let i = 0; i < constants.brailleData.length; i++) {
3959
3981
  if (constants.brailleData[i].type != 'blank') {
@@ -3982,6 +4004,9 @@ class Display {
3982
4004
  ) {
3983
4005
  constants.brailleInput.setSelectionRange(positionL1.x, positionL1.x);
3984
4006
  }
4007
+ setTimeout(function () {
4008
+ constants.lockSelection = false;
4009
+ }, 50);
3985
4010
  }
3986
4011
 
3987
4012
  /**
@@ -4286,16 +4311,20 @@ class Display {
4286
4311
  constants.verboseText = verboseText;
4287
4312
  // aria live hack. If we're repeating (Space), aria won't detect if text is the same, so we modify vey slightly by adding / removing period at the end
4288
4313
  if (output == constants.infoDiv.innerHTML) {
4289
- if (constants.infoDiv.innerHTML.endsWith('.')) {
4290
- if (output.endsWith('.')) {
4291
- output = output.slice(0, -1);
4314
+ if (constants.infoDiv.textContent.endsWith('.')) {
4315
+ if (output.endsWith('.</p>')) {
4316
+ output = output.replace('.</p>', '</p>');
4292
4317
  }
4293
4318
  } else {
4294
- output = output + '.';
4319
+ output = output.replace('</p>', '.</p>');
4295
4320
  }
4296
4321
  }
4297
4322
 
4298
- if (constants.infoDiv) constants.infoDiv.innerHTML = output;
4323
+ // could also try this hack, but we'll need a time gap
4324
+ if (constants.infoDiv) {
4325
+ constants.infoDiv.innerHTML = '';
4326
+ constants.infoDiv.innerHTML = output;
4327
+ }
4299
4328
  if (constants.review) {
4300
4329
  if (output.length > 0) {
4301
4330
  constants.review.value = output.replace(/<[^>]*>?/gm, '');
@@ -8307,7 +8336,7 @@ class Control {
8307
8336
  *
8308
8337
  * @returns {void}
8309
8338
  */
8310
- SetControls() {
8339
+ async SetControls() {
8311
8340
  constants.events.push([
8312
8341
  document,
8313
8342
  'keydown',
@@ -8406,6 +8435,12 @@ class Control {
8406
8435
  constants.brailleMode == 'on' &&
8407
8436
  constants.brailleInput.selectionStart
8408
8437
  ) {
8438
+ if (constants.lockSelection) {
8439
+ return;
8440
+ }
8441
+ // we lock the selection while we're changing stuff so it doesn't loop
8442
+ constants.lockSelection = true;
8443
+
8409
8444
  let cursorPos = constants.brailleInput.selectionStart;
8410
8445
  // we're using braille cursor, update the selection from what was clicked
8411
8446
  cursorPos = constants.brailleInput.selectionStart;
@@ -8447,18 +8482,29 @@ class Control {
8447
8482
 
8448
8483
  // update display / text / audio
8449
8484
  if (testEnd) {
8485
+ this.lockPosition = true;
8450
8486
  control.UpdateAll();
8487
+ this.lockPosition = false;
8451
8488
  }
8452
8489
  if (testEnd) {
8453
8490
  audio.playEnd();
8454
8491
  }
8455
8492
  }
8493
+ setTimeout(function () {
8494
+ constants.lockSelection = false;
8495
+ }, 50);
8456
8496
  }
8457
8497
  });
8458
8498
  } else if ([].concat(singleMaidr.type).includes('heat')) {
8459
8499
  document.addEventListener('selectionchange', function (e) {
8460
8500
  if (constants.brailleMode == 'on') {
8501
+ if (constants.lockSelection) {
8502
+ return;
8503
+ }
8504
+
8461
8505
  let pos = constants.brailleInput.selectionStart;
8506
+ // we lock the selection while we're changing stuff so it doesn't loop
8507
+ constants.lockSelection = true;
8462
8508
 
8463
8509
  // exception: don't let users click the seperator char
8464
8510
  let seperatorPositions = constants.brailleInput.value
@@ -8490,6 +8536,9 @@ class Control {
8490
8536
  if (testEnd) {
8491
8537
  audio.playEnd();
8492
8538
  }
8539
+ setTimeout(function () {
8540
+ constants.lockSelection = false;
8541
+ }, 50);
8493
8542
  } else {
8494
8543
  // we're using normal cursor, let the default handle it
8495
8544
  }
@@ -8505,23 +8554,35 @@ class Control {
8505
8554
  ) {
8506
8555
  document.addEventListener('selectionchange', function (e) {
8507
8556
  if (constants.brailleMode == 'on') {
8508
- let pos = constants.brailleInput.selectionStart;
8509
- // we're using braille cursor, update the selection from what was clicked
8510
- pos = constants.brailleInput.selectionStart;
8511
- if (pos < 0) {
8512
- pos = 0;
8557
+ if (constants.lockSelection) {
8558
+ return;
8513
8559
  }
8514
- position.x = pos;
8515
- control.lockPosition(); // bar etc is default, no need to supply values
8516
- let testEnd = true;
8517
8560
 
8518
- // update display / text / audio
8519
- if (testEnd) {
8520
- control.UpdateAll();
8521
- }
8522
- if (testEnd) {
8523
- audio.playEnd();
8561
+ // we lock the selection while we're changing stuff so it doesn't loop
8562
+ constants.lockSelection = true;
8563
+
8564
+ if (constants.brailleInput) {
8565
+ let pos = constants.brailleInput.selectionStart;
8566
+ // we're using braille cursor, update the selection from what was clicked
8567
+ pos = constants.brailleInput.selectionStart;
8568
+ if (pos < 0) {
8569
+ pos = 0;
8570
+ }
8571
+ position.x = pos;
8572
+ control.lockPosition(); // bar etc is default, no need to supply values
8573
+ let testEnd = true;
8574
+
8575
+ // update display / text / audio
8576
+ if (testEnd) {
8577
+ control.UpdateAll();
8578
+ }
8579
+ if (testEnd) {
8580
+ audio.playEnd();
8581
+ }
8524
8582
  }
8583
+ setTimeout(function () {
8584
+ constants.lockSelection = false;
8585
+ }, 50);
8525
8586
  } else {
8526
8587
  // we're using normal cursor, let the default handle it
8527
8588
  }
@@ -10081,8 +10142,14 @@ class Control {
10081
10142
  // braille cursor routing
10082
10143
  document.addEventListener('selectionchange', function (e) {
10083
10144
  if (constants.brailleMode == 'on') {
10084
- let pos = constants.brailleInput.selectionStart;
10145
+ if (constants.lockSelection) {
10146
+ return;
10147
+ }
10148
+ // we lock the selection while we're changing stuff so it doesn't loop
10149
+ constants.lockSelection = true;
10150
+
10085
10151
  // we're using braille cursor, update the selection from what was clicked
10152
+ let pos = constants.brailleInput.selectionStart;
10086
10153
  pos = constants.brailleInput.selectionStart;
10087
10154
  if (pos < 0) {
10088
10155
  pos = 0;
@@ -10098,6 +10165,9 @@ class Control {
10098
10165
  if (testEnd) {
10099
10166
  audio.playEnd();
10100
10167
  }
10168
+ setTimeout(function () {
10169
+ constants.lockSelection = false;
10170
+ }, 50);
10101
10171
  }
10102
10172
  });
10103
10173
 
@@ -11135,10 +11205,18 @@ function InitMaidr(thisMaidr) {
11135
11205
  // actually do eventlisteners for all events
11136
11206
  this.SetEvents();
11137
11207
 
11208
+ // Set img role for chart
11209
+ constants.chart.setAttribute('role', 'img');
11210
+
11138
11211
  // once everything is set up, announce the chart name (or title as a backup) to the user
11139
- setTimeout(function () {
11140
- if ('name' in singleMaidr) {
11141
- 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);
11142
11220
  } else if (
11143
11221
  'title' in singleMaidr ||
11144
11222
  ('labels' in singleMaidr && 'title' in singleMaidr.labels)
@@ -11166,10 +11244,16 @@ function InitMaidr(thisMaidr) {
11166
11244
  isMultiLayered ? multiLayerInstruction : ' '
11167
11245
  }Toggle B for Braille, T for Text, S for Sonification, and R for Review mode. Use H for Help.`;
11168
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
+
11169
11253
  // Display the announcement text
11170
- display.announceText(announceText);
11254
+ // display.announceText(announceText);
11171
11255
  }
11172
- }, 100);
11256
+ // }, 100);
11173
11257
  }
11174
11258
  }
11175
11259