maidr 2.18.0 → 2.19.0

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