maidr 2.20.0 → 2.22.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
@@ -406,13 +406,6 @@ class Constants {
406
406
  * @memberof AdvancedUserSettings
407
407
  */
408
408
  canTrack = 0; // 0 / 1, can we track user data
409
- /**
410
- * Whether or not we're currently tracking user data.
411
- * @type {boolean}
412
- * @default 1
413
- * @memberof AdvancedUserSettings
414
- */
415
- isTracking = 0;
416
409
  /**
417
410
  * How are we representing braille? like, is it 1:1 with the chart, or do we do some compression and try to represent as accuratly as we can? Not currently in use.
418
411
  * @type {boolean}
@@ -443,12 +436,15 @@ class Constants {
443
436
  'ariaMode',
444
437
  'openAIAuthKey',
445
438
  'geminiAuthKey',
439
+ 'claudeAuthKey',
440
+ 'emailAuthKey',
446
441
  'skillLevel',
447
442
  'skillLevelOther',
448
443
  'LLMModel',
449
444
  'LLMPreferences',
450
445
  'LLMOpenAiMulti',
451
446
  'LLMGeminiMulti',
447
+ 'LLMModels',
452
448
  'autoInitLLM',
453
449
  ];
454
450
 
@@ -498,6 +494,13 @@ class Constants {
498
494
  * @memberof LLMSettings
499
495
  */
500
496
  LLMModel = 'openai';
497
+ /**
498
+ * Current LLM model in use. Can be 'openai' (default) or 'gemini' or 'claude'. More to be added.
499
+ * @type {("openai"|"gemini"|"claude")}
500
+ * @default 'openai'
501
+ * @memberof LLMSettings
502
+ */
503
+ LLMModels = { openai: true };
501
504
  /**
502
505
  * The default system message for the LLM. Helps the LLM understand the context of the chart and its role.
503
506
  * @type {string}
@@ -832,6 +835,7 @@ class Resources {
832
835
  empty: 'Empty',
833
836
  openai: 'OpenAI Vision',
834
837
  gemini: 'Gemini Pro Vision',
838
+ claude: 'Claude',
835
839
  multi: 'Multiple AI',
836
840
  processing: 'Processing Chart...',
837
841
  },
@@ -1011,8 +1015,7 @@ class Menu {
1011
1015
  constants.ariaMode == 'polite' ? 'checked' : ''
1012
1016
  }><label for="aria_mode_polite">Polite</label></p>
1013
1017
  </fieldset></div>
1014
- <h5 class="modal-title">LLM Settings</h5>
1015
- <p>
1018
+ <p class="hidden">
1016
1019
  <select id="LLM_model">
1017
1020
  <option value="openai">OpenAI Vision</option>
1018
1021
  <option value="gemini">Gemini Pro Vision</option>
@@ -1020,6 +1023,18 @@ class Menu {
1020
1023
  </select>
1021
1024
  <label for="LLM_model">LLM Model</label>
1022
1025
  </p>
1026
+ <h5 class="modal-title">LLM Settings</h5>
1027
+ <p>
1028
+ <fieldset>
1029
+ <legend>LLM Models (select up to 2)</legend>
1030
+ <p><input type="checkbox" id="LLM_model_openai" name="LLM_model" value="openai"><label for="LLM_model_openai">OpenAI Vision</label></p>
1031
+ <p><input type="checkbox" id="LLM_model_gemini" name="LLM_model" value="gemini"><label for="LLM_model_gemini">Gemini Pro Vision</label></p>
1032
+ <p><input type="checkbox" id="LLM_model_claude" name="LLM_model" value="claude"><label for="LLM_model_claude">Claude</label></p>
1033
+ </fieldset>
1034
+ </p>
1035
+ <p id="email_auth_key_container" class="multi_container">
1036
+ <input type="email" size="50" id="email_auth_key"><button aria-label="Delete Email Address" title="Delete Email Address" id="delete_email_key" class="invis_button">&times;</button><label for="gemini_auth_key">Email Authentication</label><button type="button" id="verify">Verify</button>
1037
+ </p>
1023
1038
  <p id="openai_auth_key_container" class="multi_container hidden">
1024
1039
  <span id="openai_multi_container" class="hidden"><input type="checkbox" id="openai_multi" name="openai_multi" aria-label="Use OpenAI in Multi modal mode"></span>
1025
1040
  <input type="password" size="50" id="openai_auth_key"><button aria-label="Delete OpenAI key" title="Delete OpenAI key" id="delete_openai_key" class="invis_button">&times;</button><label for="openai_auth_key">OpenAI Authentication Key</label>
@@ -1027,10 +1042,14 @@ class Menu {
1027
1042
  <p id="gemini_auth_key_container" class="multi_container hidden">
1028
1043
  <span id="gemini_multi_container" class="hidden"><input type="checkbox" id="gemini_multi" name="gemini_multi" aria-label="Use Gemini in Multi modal mode"></span>
1029
1044
  <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>
1045
+ </p>
1046
+ <p id="claude_auth_key_container" class="multi_container hidden">
1047
+ <span id="claude_multi_container" class="hidden"><input type="checkbox" id="claude_multi" name="claude_multi" aria-label="Use Claude in Multi modal mode"></span>
1048
+ <input type="password" size="50" id="claude_auth_key"><button aria-label="Delete Claude key" title="Delete Claude key" id="delete_claude_key" class="invis_button">&times;</button><label for="claude_auth_key">Claude Authentication Key</label>
1030
1049
  </p>
1031
1050
  <p><input type="checkbox" ${
1032
1051
  constants.autoInitLLM ? 'checked' : ''
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>
1052
+ } id="init_llm_on_load" name="init_llm_on_load"><label for="init_llm_on_load">Start LLM right away</label></p>
1034
1053
  <p>
1035
1054
  <select id="skill_level">
1036
1055
  <option value="basic">Basic</option>
@@ -1088,6 +1107,13 @@ class Menu {
1088
1107
  menu.Toggle(false);
1089
1108
  },
1090
1109
  ]);
1110
+ constants.events.push([
1111
+ document.getElementById('verify'),
1112
+ 'click',
1113
+ function (e) {
1114
+ menu.VerifyEmail();
1115
+ },
1116
+ ]);
1091
1117
  constants.events.push([
1092
1118
  document.getElementById('menu'),
1093
1119
  'keyup',
@@ -1170,6 +1196,54 @@ class Menu {
1170
1196
  },
1171
1197
  ]);
1172
1198
 
1199
+ constants.events.push([
1200
+ document.getElementById('LLM_model_openai'),
1201
+ 'change',
1202
+ function (e) {
1203
+ if (e.target.checked) {
1204
+ document
1205
+ .getElementById('openai_auth_key_container')
1206
+ .classList.remove('hidden');
1207
+ } else {
1208
+ document
1209
+ .getElementById('openai_auth_key_container')
1210
+ .classList.add('hidden');
1211
+ }
1212
+ },
1213
+ ]);
1214
+
1215
+ constants.events.push([
1216
+ document.getElementById('LLM_model_gemini'),
1217
+ 'change',
1218
+ function (e) {
1219
+ if (e.target.checked) {
1220
+ document
1221
+ .getElementById('gemini_auth_key_container')
1222
+ .classList.remove('hidden');
1223
+ } else {
1224
+ document
1225
+ .getElementById('gemini_auth_key_container')
1226
+ .classList.add('hidden');
1227
+ }
1228
+ },
1229
+ ]);
1230
+
1231
+ constants.events.push([
1232
+ document.getElementById('LLM_model_claude'),
1233
+ 'change',
1234
+ function (e) {
1235
+ // if (e.target.checked) {
1236
+ document
1237
+ .getElementById('claude_auth_key_container')
1238
+ .classList.add('hidden');
1239
+ // } else {
1240
+ // document
1241
+ // .getElementById('claude_auth_key_container')
1242
+ // .classList.add('hidden');
1243
+ // }
1244
+ },
1245
+ ]);
1246
+
1173
1247
  // Skill level other events
1174
1248
  constants.events.push([
1175
1249
  document.getElementById('skill_level'),
@@ -1205,6 +1279,20 @@ class Menu {
1205
1279
  },
1206
1280
  ]);
1207
1281
  }
1282
+
1283
+ // Limit selections to 2 AI models
1284
+ const llmCheckboxes = document.querySelectorAll('input[name="LLM_model"]');
1285
+ llmCheckboxes.forEach((checkbox) => {
1286
+ checkbox.addEventListener('change', () => {
1287
+ const checked = document.querySelectorAll(
1288
+ 'input[name="LLM_model"]:checked'
1289
+ );
1290
+ if (checked.length > 2) {
1291
+ checkbox.checked = false;
1292
+ alert('You can select up to 2 AI models.');
1293
+ }
1294
+ });
1295
+ });
1208
1296
  }
1209
1297
 
1210
1298
  /**
@@ -1276,16 +1364,22 @@ class Menu {
1276
1364
  document.getElementById('openai_auth_key').value =
1277
1365
  constants.openAIAuthKey;
1278
1366
  }
1367
+ if (typeof constants.emailAuthKey == 'string') {
1368
+ document.getElementById('email_auth_key').value = constants.emailAuthKey;
1369
+ }
1279
1370
  if (typeof constants.geminiAuthKey == 'string') {
1280
1371
  document.getElementById('gemini_auth_key').value =
1281
1372
  constants.geminiAuthKey;
1282
1373
  }
1374
+ if (typeof constants.claudeAuthKey == 'string') {
1375
+ document.getElementById('claude_auth_key').value =
1376
+ constants.claudeAuthKey;
1377
+ }
1283
1378
  document.getElementById('skill_level').value = constants.skillLevel;
1284
1379
  if (constants.skillLevelOther) {
1285
1380
  document.getElementById('skill_level_other').value =
1286
1381
  constants.skillLevelOther;
1287
1382
  }
1288
- document.getElementById('LLM_model').value = constants.LLMModel;
1289
1383
 
1290
1384
  // aria mode
1291
1385
  if (constants.ariaMode == 'assertive') {
@@ -1295,44 +1389,18 @@ class Menu {
1295
1389
  document.getElementById('aria_mode_polite').checked = true;
1296
1390
  document.getElementById('aria_mode_assertive').checked = false;
1297
1391
  }
1298
- // hide either openai or gemini auth key field
1299
- if (constants.LLMModel == 'openai') {
1300
- document
1301
- .getElementById('openai_auth_key_container')
1302
- .classList.remove('hidden');
1303
- document
1304
- .getElementById('gemini_auth_key_container')
1305
- .classList.add('hidden');
1306
- } else if (constants.LLMModel == 'gemini') {
1307
- document
1308
- .getElementById('openai_auth_key_container')
1309
- .classList.add('hidden');
1310
- document
1311
- .getElementById('gemini_auth_key_container')
1312
- .classList.remove('hidden');
1313
- } else if (constants.LLMModel == 'multi') {
1314
- // multi LLM mode
1315
- document
1316
- .getElementById('openai_auth_key_container')
1317
- .classList.remove('hidden');
1318
- document
1319
- .getElementById('gemini_auth_key_container')
1320
- .classList.remove('hidden');
1321
- document
1322
- .getElementById('openai_multi_container')
1323
- .classList.remove('hidden');
1392
+
1393
+ for (let model in constants.LLMModels) {
1394
+ document.getElementById(`LLM_model_${model}`).checked = true;
1395
+
1324
1396
  document
1325
- .getElementById('gemini_multi_container')
1397
+ .getElementById(`${model}_auth_key_container`)
1326
1398
  .classList.remove('hidden');
1327
- document.getElementById('openai_multi').checked = false;
1328
- if (constants.LLMOpenAiMulti) {
1329
- document.getElementById('openai_multi').checked = true;
1330
- }
1331
- document.getElementById('gemini_multi').checked = false;
1332
- if (constants.LLMGeminiMulti) {
1333
- document.getElementById('gemini_multi').checked = true;
1334
- }
1335
1399
  }
1400
+ document
1401
+ .getElementById(`claude_auth_key_container`)
1402
+ .classList.add('hidden');
1403
+
1336
1404
  // skill level other
1337
1405
  if (constants.skillLevel == 'other') {
1338
1406
  document
@@ -1368,10 +1436,22 @@ class Menu {
1368
1436
 
1369
1437
  constants.openAIAuthKey = document.getElementById('openai_auth_key').value;
1370
1438
  constants.geminiAuthKey = document.getElementById('gemini_auth_key').value;
1439
+ constants.claudeAuthKey = document.getElementById('claude_auth_key').value;
1440
+ constants.emailAuthKey = document.getElementById('email_auth_key').value;
1371
1441
  constants.skillLevel = document.getElementById('skill_level').value;
1372
1442
  constants.skillLevelOther =
1373
1443
  document.getElementById('skill_level_other').value;
1374
- constants.LLMModel = document.getElementById('LLM_model').value;
1444
+ // constants.LLMModel = document.getElementById('LLM_model').value;
1445
+
1446
+ const llmCheckboxes = document.querySelectorAll('input[name="LLM_model"]');
1447
+ llmCheckboxes.forEach((checkbox) => {
1448
+ if (checkbox.checked) {
1449
+ constants.LLMModels[checkbox.value] = true;
1450
+ } else {
1451
+ delete constants.LLMModels[checkbox.value];
1452
+ }
1453
+ });
1454
+
1375
1455
  constants.LLMPreferences = document.getElementById('LLM_preferences').value;
1376
1456
  constants.LLMOpenAiMulti = document.getElementById('openai_multi').checked;
1377
1457
  constants.LLMGeminiMulti = document.getElementById('gemini_multi').checked;
@@ -1394,6 +1474,41 @@ class Menu {
1394
1474
  }
1395
1475
  }
1396
1476
 
1477
+ VerifyEmail() {
1478
+ let email = document.getElementById('email_auth_key').value;
1479
+ if (email && email.indexOf('@') !== -1) {
1480
+ let url = `https://maidr-service.azurewebsites.net/api/send_email?code=I8Aa2PlPspjQ8Hks0QzGyszP8_i2-XJ3bq7Xh8-ykEe4AzFuYn_QWA%3D%3D`;
1481
+
1482
+ let requestJson = {
1483
+ email: email,
1484
+ };
1485
+
1486
+ fetch(url, {
1487
+ method: 'POST',
1488
+ headers: {
1489
+ 'Content-Type': 'application/json',
1490
+ Authentication: constants.emailAuthKey,
1491
+ },
1492
+ body: JSON.stringify(requestJson),
1493
+ })
1494
+ .then((response) => response.json())
1495
+ .then((data) => {
1496
+ if (data && data.success) {
1497
+ alert('Link sent to email address: ' + email);
1498
+ } else {
1499
+ console.log(data);
1500
+ alert(data.data);
1501
+ }
1502
+ })
1503
+ .catch((error) => {
1504
+ console.log(error);
1505
+ alert(error.data);
1506
+ });
1507
+ } else {
1508
+ alert('Please enter a valid email address.');
1509
+ }
1510
+ }
1511
+
1397
1512
  /**
1398
1513
  * Sets the aria attributes on the HTML elements in the menu
1399
1514
  * @returns {void}
@@ -1475,14 +1590,16 @@ class Menu {
1475
1590
  ) {
1476
1591
  shouldReset = true;
1477
1592
  }
1478
- if (
1479
- !shouldReset &&
1480
- (constants.LLMOpenAiMulti !=
1481
- document.getElementById('openai_multi').checked ||
1482
- constants.LLMGeminiMulti !=
1483
- document.getElementById('gemini_multi').checked)
1484
- ) {
1485
- shouldReset = true;
1593
+
1594
+ // check if LLMModels have changed
1595
+ let llmCheckboxes = document.querySelectorAll('input[name="LLM_model"]');
1596
+ for (let i = 0; i < llmCheckboxes.length; i++) {
1597
+ if (
1598
+ !shouldReset &&
1599
+ constants.LLMModels[llmCheckboxes[i].value] != llmCheckboxes[i].checked
1600
+ ) {
1601
+ shouldReset = true;
1602
+ }
1486
1603
  }
1487
1604
 
1488
1605
  return shouldReset;
@@ -1501,10 +1618,11 @@ class Menu {
1501
1618
  localStorage.setItem('settings_data', JSON.stringify(data));
1502
1619
 
1503
1620
  // also save to tracking if we're doing that
1504
- if (constants.isTracking) {
1621
+ if (constants.canTrack) {
1505
1622
  // but not auth keys
1506
1623
  data.openAIAuthKey = 'hidden';
1507
1624
  data.geminiAuthKey = 'hidden';
1625
+ data.claudeAuthKey = 'hidden';
1508
1626
  // and need a timestamp
1509
1627
  data.timestamp = new Date().toISOString();
1510
1628
  tracker.SetData('settings', data);
@@ -1517,9 +1635,10 @@ class Menu {
1517
1635
  let data = JSON.parse(localStorage.getItem('settings_data'));
1518
1636
  if (data) {
1519
1637
  for (let i = 0; i < constants.userSettingsKeys.length; i++) {
1520
- constants[constants.userSettingsKeys[i]] =
1521
- data[constants.userSettingsKeys[i]] ||
1522
- constants[constants.userSettingsKeys[i]];
1638
+ const key = constants.userSettingsKeys[i];
1639
+ if (key in data) {
1640
+ constants[key] = data[key];
1641
+ }
1523
1642
  }
1524
1643
  }
1525
1644
  this.PopulateData();
@@ -1541,11 +1660,9 @@ class ChatLLM {
1541
1660
  if (constants.autoInitLLM) {
1542
1661
  // only run if we have API keys set
1543
1662
  if (
1544
- (constants.LLMModel == 'openai' && constants.openAIAuthKey) ||
1545
- (constants.LLMModel == 'gemini' && constants.geminiAuthKey) ||
1546
- (constants.LLMModel == 'multi' &&
1547
- constants.openAIAuthKey &&
1548
- constants.geminiAuthKey)
1663
+ ('gemini' in constants.LLMModels && constants.geminiAuthKey) ||
1664
+ ('openai' in constants.LLMModels && constants.openAIAuthKey) ||
1665
+ ('claude' in constants.LLMModels && constants.claudeAuthKey)
1549
1666
  ) {
1550
1667
  this.InitChatMessage();
1551
1668
  }
@@ -1686,6 +1803,13 @@ class ChatLLM {
1686
1803
  document.getElementById('openai_auth_key').value = '';
1687
1804
  },
1688
1805
  ]);
1806
+ constants.events.push([
1807
+ document.getElementById('delete_email_key'),
1808
+ 'click',
1809
+ function (e) {
1810
+ document.getElementById('email_auth_key').value = '';
1811
+ },
1812
+ ]);
1689
1813
  constants.events.push([
1690
1814
  document.getElementById('delete_gemini_key'),
1691
1815
  'click',
@@ -1853,7 +1977,7 @@ class ChatLLM {
1853
1977
 
1854
1978
  // 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
1855
1979
  if (
1856
- (this.firstOpen || constants.LLMModel == 'gemini') &&
1980
+ (this.firstOpen || 'gemini' in constants.LLMModels) &&
1857
1981
  !firsttime &&
1858
1982
  constants.verboseText.length > 0
1859
1983
  ) {
@@ -1871,17 +1995,36 @@ class ChatLLM {
1871
1995
  this.WaitingSound(true);
1872
1996
  }
1873
1997
 
1874
- if (constants.LLMOpenAiMulti || constants.LLMModel == 'openai') {
1998
+ if ('openai' in constants.LLMModels) {
1875
1999
  if (firsttime) {
1876
2000
  img = await this.ConvertSVGtoJPG(singleMaidr.id, 'openai');
1877
2001
  }
1878
- chatLLM.OpenAIPrompt(text, img);
2002
+ if (constants.openAIAuthKey) {
2003
+ chatLLM.OpenAIPrompt(text, img);
2004
+ } else {
2005
+ chatLLM.OpenAIPromptAPI(text, img);
2006
+ }
1879
2007
  }
1880
- if (constants.LLMGeminiMulti || constants.LLMModel == 'gemini') {
2008
+ if ('gemini' in constants.LLMModels) {
1881
2009
  if (firsttime) {
1882
2010
  img = await this.ConvertSVGtoJPG(singleMaidr.id, 'gemini');
1883
2011
  }
1884
- chatLLM.GeminiPrompt(text, img);
2012
+ if (constants.geminiAuthKey) {
2013
+ chatLLM.GeminiPrompt(text, img);
2014
+ } else {
2015
+ chatLLM.GeminiPromptAPI(text, img);
2016
+ }
2017
+ }
2018
+
2019
+ if ('claude' in constants.LLMModels) {
2020
+ if (firsttime) {
2021
+ img = await this.ConvertSVGtoJPG(singleMaidr.id, 'claude');
2022
+ }
2023
+ if (constants.claudeAuthKey) {
2024
+ chatLLM.ClaudePrompt(text, img);
2025
+ } else {
2026
+ chatLLM.ClaudePromptAPI(text, img);
2027
+ }
1885
2028
  }
1886
2029
  }
1887
2030
 
@@ -1969,7 +2112,7 @@ class ChatLLM {
1969
2112
  }
1970
2113
 
1971
2114
  InitChatMessage() {
1972
- // get name from resource
2115
+ // get name from resource]
1973
2116
  let LLMName = resources.GetString(constants.LLMModel);
1974
2117
  this.firstTime = false;
1975
2118
  this.DisplayChatMessage(LLMName, resources.GetString('processing'), true);
@@ -1984,7 +2127,6 @@ class ChatLLM {
1984
2127
  */
1985
2128
  ProcessLLMResponse(data, model) {
1986
2129
  chatLLM.WaitingSound(false);
1987
- //console.log('LLM response: ', data);
1988
2130
  let text = '';
1989
2131
  let LLMName = resources.GetString(model);
1990
2132
 
@@ -2018,9 +2160,20 @@ class ChatLLM {
2018
2160
  // todo: display actual response
2019
2161
  }
2020
2162
  }
2163
+ if (model == 'claude') {
2164
+ console.log('Claude response: ', data);
2165
+ if (data.text()) {
2166
+ text = data.text();
2167
+ chatLLM.DisplayChatMessage(LLMName, text);
2168
+ }
2169
+ if (data.error) {
2170
+ chatLLM.DisplayChatMessage(LLMName, 'Error processing request.', true);
2171
+ chatLLM.WaitingSound(false);
2172
+ }
2173
+ }
2021
2174
 
2022
2175
  // if we're tracking, log the data
2023
- if (constants.isTracking) {
2176
+ if (constants.canTrack) {
2024
2177
  let chatHist = chatLLM.CopyChatHistory();
2025
2178
  tracker.SetData('ChatHistory', chatHist);
2026
2179
  }
@@ -2086,6 +2239,96 @@ class ChatLLM {
2086
2239
  return responseText;
2087
2240
  }
2088
2241
 
2242
+ ClaudeJson(text, img = null) {
2243
+ const anthropicVersion = 'vertex-2023-10-16';
2244
+ const maxTokens = 256;
2245
+
2246
+ const payload = {
2247
+ anthropic_version: anthropicVersion,
2248
+ max_tokens: maxTokens,
2249
+ messages: [],
2250
+ };
2251
+
2252
+ // Construct the user message object
2253
+ const userMessage = {
2254
+ role: 'user',
2255
+ content: [],
2256
+ };
2257
+
2258
+ // Add the image content if provided
2259
+ if (img) {
2260
+ userMessage.content.push(
2261
+ {
2262
+ type: 'image',
2263
+ source: {
2264
+ type: 'base64',
2265
+ media_type: 'image/jpeg', // Update if other formats are supported
2266
+ data: img,
2267
+ },
2268
+ },
2269
+ {
2270
+ type: 'text',
2271
+ text: text,
2272
+ }
2273
+ );
2274
+ } else {
2275
+ // Add only the text content if no image is provided
2276
+ userMessage.content.push({
2277
+ type: 'text',
2278
+ text: text,
2279
+ });
2280
+ }
2281
+
2282
+ // Add the user message to the messages array
2283
+ payload.messages.push(userMessage);
2284
+
2285
+ return payload;
2286
+ }
2287
+
2288
+ ClaudePromptAPI(text, imgBase64 = null) {
2289
+ console.log('Claude prompt API');
2290
+ let url =
2291
+ 'https://maidr-service.azurewebsites.net/api/claude?code=I8Aa2PlPspjQ8Hks0QzGyszP8_i2-XJ3bq7Xh8-ykEe4AzFuYn_QWA%3D%3D';
2292
+
2293
+ // Create the prompt
2294
+ let prompt = constants.LLMSystemMessage;
2295
+ if (constants.LLMPreferences) {
2296
+ prompt += constants.LLMPreferences;
2297
+ }
2298
+ prompt += '\n\n' + text; // Use the text parameter as the prompt
2299
+
2300
+ if (imgBase64 == null) {
2301
+ imgBase64 = constants.LLMImage;
2302
+ } else {
2303
+ constants.LLMImage = imgBase64;
2304
+ }
2305
+ constants.LLMImage = imgBase64;
2306
+
2307
+ let requestJson = chatLLM.ClaudeJson(prompt, imgBase64);
2308
+
2309
+ fetch(url, {
2310
+ method: 'POST',
2311
+ headers: {
2312
+ 'Content-Type': 'application/json',
2313
+ Authentication: constants.emailAuthKey,
2314
+ },
2315
+ body: JSON.stringify(requestJson),
2316
+ })
2317
+ .then((response) => response.json())
2318
+ .then((data) => {
2319
+ data.text = function () {
2320
+ return data.content[0].text;
2321
+ };
2322
+ chatLLM.ProcessLLMResponse(data, 'claude');
2323
+ })
2324
+ .catch((error) => {
2325
+ chatLLM.WaitingSound(false);
2326
+ console.error('Error:', error);
2327
+ chatLLM.DisplayChatMessage('Claude', 'Error processing request.', true);
2328
+ // also todo: handle errors somehow
2329
+ });
2330
+ }
2331
+
2089
2332
  /**
2090
2333
  * Gets running prompt info, appends the latest request, and packages it into a JSON object for the LLM.
2091
2334
  * @function
@@ -2119,6 +2362,35 @@ class ChatLLM {
2119
2362
  // also todo: handle errors somehow
2120
2363
  });
2121
2364
  }
2365
+
2366
+ OpenAIPromptAPI(text, img = null) {
2367
+ // request init
2368
+ let url =
2369
+ 'https://maidr-service.azurewebsites.net/api/openai?code=I8Aa2PlPspjQ8Hks0QzGyszP8_i2-XJ3bq7Xh8-ykEe4AzFuYn_QWA%3D%3D';
2370
+ let auth = constants.openAIAuthKey;
2371
+ let requestJson = chatLLM.OpenAIJson(text, img);
2372
+ console.log('LLM request: ', requestJson);
2373
+
2374
+ fetch(url, {
2375
+ method: 'POST',
2376
+ headers: {
2377
+ 'Content-Type': 'application/json',
2378
+ Authentication: constants.emailAuthKey,
2379
+ },
2380
+ body: JSON.stringify(requestJson),
2381
+ })
2382
+ .then((response) => response.json())
2383
+ .then((data) => {
2384
+ chatLLM.ProcessLLMResponse(data, 'openai');
2385
+ })
2386
+ .catch((error) => {
2387
+ chatLLM.WaitingSound(false);
2388
+ console.error('Error:', error);
2389
+ chatLLM.DisplayChatMessage('OpenAI', 'Error processing request.', true);
2390
+ // also todo: handle errors somehow
2391
+ });
2392
+ }
2393
+
2122
2394
  OpenAIJson(text, img = null) {
2123
2395
  let sysMessage = constants.LLMSystemMessage;
2124
2396
  let backupMessage =
@@ -2167,6 +2439,110 @@ class ChatLLM {
2167
2439
  return this.requestJson;
2168
2440
  }
2169
2441
 
2442
+ GeminiJson(text, img = null) {
2443
+ let sysMessage = constants.LLMSystemMessage;
2444
+ let backupMessage =
2445
+ 'Describe ' + singleMaidr.type + ' charts to a blind person';
2446
+
2447
+ let payload = {
2448
+ generationConfig: {},
2449
+ safetySettings: [],
2450
+ contents: [],
2451
+ };
2452
+
2453
+ // System message as the initial "role" and "text" content for context
2454
+ let sysContent = {
2455
+ role: 'user',
2456
+ parts: [
2457
+ {
2458
+ text: sysMessage || backupMessage, // Fallback if sysMessage is unavailable
2459
+ },
2460
+ ],
2461
+ };
2462
+
2463
+ // Add preferences if available
2464
+ if (constants.LLMPreferences) {
2465
+ sysContent.parts.push({
2466
+ text: constants.LLMPreferences,
2467
+ });
2468
+ }
2469
+
2470
+ payload.contents.push(sysContent);
2471
+
2472
+ // Add user input content, including image if available
2473
+ let userContent = {
2474
+ role: 'user',
2475
+ parts: [],
2476
+ };
2477
+
2478
+ if (img) {
2479
+ // If there’s an image, add both text and image data
2480
+ userContent.parts.push(
2481
+ {
2482
+ text: text,
2483
+ },
2484
+ {
2485
+ inlineData: {
2486
+ data: img, // Expecting base64-encoded image data
2487
+ mimeType: 'image/png', // Adjust if different image formats are possible
2488
+ },
2489
+ }
2490
+ );
2491
+ } else {
2492
+ // If no image, only add the text
2493
+ userContent.parts.push({
2494
+ text: text,
2495
+ });
2496
+ }
2497
+
2498
+ // Add user content to the contents array
2499
+ payload.contents.push(userContent);
2500
+
2501
+ return payload;
2502
+ }
2503
+
2504
+ async GeminiPromptAPI(text, imgBase64 = null) {
2505
+ let url =
2506
+ 'https://maidr-service.azurewebsites.net/api/gemini?code=I8Aa2PlPspjQ8Hks0QzGyszP8_i2-XJ3bq7Xh8-ykEe4AzFuYn_QWA%3D%3D';
2507
+
2508
+ // Create the prompt
2509
+ let prompt = constants.LLMSystemMessage;
2510
+ if (constants.LLMPreferences) {
2511
+ prompt += constants.LLMPreferences;
2512
+ }
2513
+ prompt += '\n\n' + text; // Use the text parameter as the prompt
2514
+
2515
+ if (imgBase64 == null) {
2516
+ imgBase64 = constants.LLMImage;
2517
+ } else {
2518
+ constants.LLMImage = imgBase64;
2519
+ }
2520
+ constants.LLMImage = imgBase64;
2521
+
2522
+ let requestJson = chatLLM.GeminiJson(prompt, imgBase64);
2523
+
2524
+ const response = await fetch(url, {
2525
+ method: 'POST',
2526
+ headers: {
2527
+ 'Content-Type': 'application/json',
2528
+ Authentication: constants.emailAuthKey,
2529
+ },
2530
+ body: JSON.stringify(requestJson),
2531
+ });
2532
+ if (response.ok) {
2533
+ const responseJson = await response.json();
2534
+ responseJson.text = () => {
2535
+ return responseJson.candidates[0].content.parts[0].text;
2536
+ };
2537
+ chatLLM.ProcessLLMResponse(responseJson, 'gemini');
2538
+ } else {
2539
+ chatLLM.WaitingSound(false);
2540
+ console.error('Error:', error);
2541
+ chatLLM.DisplayChatMessage('OpenAI', 'Error processing request.', true);
2542
+ // also todo: handle errors somehow
2543
+ }
2544
+ }
2545
+
2170
2546
  async GeminiPrompt(text, imgBase64 = null) {
2171
2547
  // https://ai.google.dev/docs/gemini_api_overview#node.js
2172
2548
  try {
@@ -2366,7 +2742,7 @@ class ChatLLM {
2366
2742
  var jpegData = canvas.toDataURL('image/jpeg', 0.9); // 0.9 is the quality parameter
2367
2743
  if (model == 'openai') {
2368
2744
  resolve(jpegData);
2369
- } else if (model == 'gemini') {
2745
+ } else if (model == 'gemini' || model == 'claude') {
2370
2746
  let base64Data = jpegData.split(',')[1];
2371
2747
  resolve(base64Data);
2372
2748
  //resolve(jpegData);
@@ -2716,9 +3092,13 @@ class Helper {
2716
3092
  * @class
2717
3093
  */
2718
3094
  class Tracker {
3095
+ // URL
3096
+ logUrl =
3097
+ 'https://maidr-service.azurewebsites.net/api/log?code=I8Aa2PlPspjQ8Hks0QzGyszP8_i2-XJ3bq7Xh8-ykEe4AzFuYn_QWA%3D%3D'; // TODO Replace
3098
+ isLocal = false;
3099
+
2719
3100
  constructor() {
2720
3101
  this.DataSetup();
2721
- constants.isTracking = true;
2722
3102
  }
2723
3103
 
2724
3104
  /**
@@ -2726,14 +3106,14 @@ class Tracker {
2726
3106
  */
2727
3107
  DataSetup() {
2728
3108
  let prevData = this.GetTrackerData();
2729
- if (prevData) {
2730
- // good to go already, do nothing, but make sure we have our containers
2731
- } else {
3109
+ if (!this.isLocal || !prevData) {
2732
3110
  let data = {};
2733
3111
  data.userAgent = Object.assign(navigator.userAgent);
2734
3112
  data.vendor = Object.assign(navigator.vendor);
2735
3113
  data.language = Object.assign(navigator.language);
2736
3114
  data.platform = Object.assign(navigator.platform);
3115
+ data.geolocation = Object.assign(navigator.geolocation);
3116
+ data.log_type = 'system_data';
2737
3117
  data.events = [];
2738
3118
  data.settings = [];
2739
3119
 
@@ -2758,8 +3138,33 @@ class Tracker {
2758
3138
  * Saves the tracker data to local storage.
2759
3139
  * @param {Object} data - The data to be saved.
2760
3140
  */
2761
- SaveTrackerData(data) {
2762
- localStorage.setItem(constants.project_id, JSON.stringify(data));
3141
+ async SaveTrackerData(data) {
3142
+ console.log('about to save data', data);
3143
+ if (this.isLocal) {
3144
+ localStorage.setItem(constants.project_id, JSON.stringify(data));
3145
+ } else {
3146
+ // test this first
3147
+ try {
3148
+ const response = await fetch(this.logUrl, {
3149
+ method: 'POST',
3150
+ headers: {
3151
+ 'Content-Type': 'application/json',
3152
+ },
3153
+ body: JSON.stringify(data),
3154
+ });
3155
+
3156
+ if (!response.ok) {
3157
+ throw new Error(`HTTP error! status: ${response.status}`);
3158
+ }
3159
+
3160
+ const result = await response.json();
3161
+ console.log('Data saved successfully:', result);
3162
+ return result;
3163
+ } catch (error) {
3164
+ console.error('Error saving data:', error);
3165
+ return null;
3166
+ }
3167
+ }
2763
3168
  }
2764
3169
 
2765
3170
  /**
@@ -2792,6 +3197,10 @@ class Tracker {
2792
3197
  // don't store their auth keys
2793
3198
  settings.openAIAuthKey = 'hidden';
2794
3199
  settings.geminiAuthKey = 'hidden';
3200
+ if (constants.emailAuthKey) {
3201
+ settings.username = constants.emailAuthKey;
3202
+ }
3203
+ settings;
2795
3204
  this.SetData('settings', settings);
2796
3205
  }
2797
3206
  }
@@ -2810,6 +3219,9 @@ class Tracker {
2810
3219
  eventToLog.altKey = Object.assign(e.altKey);
2811
3220
  eventToLog.ctrlKey = Object.assign(e.ctrlKey);
2812
3221
  eventToLog.shiftKey = Object.assign(e.shiftKey);
3222
+ if (constants.emailAuthKey) {
3223
+ eventToLog.username = Object.assign(constants.emailAuthKey);
3224
+ }
2813
3225
  if (e.path) {
2814
3226
  eventToLog.focus = Object.assign(e.path[0].tagName);
2815
3227
  }
@@ -3003,18 +3415,29 @@ class Tracker {
3003
3415
  //console.log('logged an event');
3004
3416
  }
3005
3417
 
3418
+ /**
3419
+ * Saves data to the server using a POST request.
3420
+ * @param {Object} logData - The data to be saved.
3421
+ * @returns {Promise<Object>} The result of the save operation.
3422
+ */
3423
+
3006
3424
  SetData(key, value) {
3007
- let data = this.GetTrackerData();
3008
- let arrayKeys = ['events', 'ChatHistory', 'settings'];
3009
- if (!arrayKeys.includes(key)) {
3010
- data[key] = value;
3011
- } else {
3012
- if (!data[key]) {
3013
- data[key] = [];
3425
+ if (this.isLocal) {
3426
+ let data = this.GetTrackerData();
3427
+ let arrayKeys = ['events', 'ChatHistory', 'settings'];
3428
+ if (!arrayKeys.includes(key)) {
3429
+ data[key] = value;
3430
+ } else {
3431
+ if (!data[key]) {
3432
+ data[key] = [];
3433
+ }
3434
+ data[key].push(value);
3014
3435
  }
3015
- data[key].push(value);
3436
+ this.SaveTrackerData(data);
3437
+ } else {
3438
+ value['log_type'] = key;
3439
+ this.SaveTrackerData(value);
3016
3440
  }
3017
- this.SaveTrackerData(data);
3018
3441
  }
3019
3442
 
3020
3443
  /**
@@ -11121,47 +11544,6 @@ document.addEventListener('DOMContentLoaded', function (e) {
11121
11544
 
11122
11545
  // init components like alt text on just the first chart
11123
11546
  CreateChartComponents(firstMaidr, true);
11124
-
11125
- // events etc for user study page
11126
- // run tracker stuff only on user study page
11127
- if (constants.canTrack) {
11128
- window.tracker = new Tracker();
11129
- if (document.getElementById('download_data_trigger')) {
11130
- // we're on the intro page, so enable the download data button
11131
- document
11132
- .getElementById('download_data_trigger')
11133
- .addEventListener('click', function (e) {
11134
- tracker.DownloadTrackerData();
11135
- });
11136
- }
11137
-
11138
- // general events
11139
- document.addEventListener('keydown', function (e) {
11140
- // reset tracking with Ctrl + F5 / command + F5, and Ctrl + Shift + R / command + Shift + R
11141
- // future todo: this should probably be a button with a confirmation. This is dangerous
11142
- if (
11143
- (e.key == 'F5' && (constants.isMac ? e.metaKey : e.ctrlKey)) ||
11144
- (e.key == 'R' && (constants.isMac ? e.metaKey : e.ctrlKey))
11145
- ) {
11146
- e.preventDefault();
11147
- tracker.Delete();
11148
- location.reload(true);
11149
- }
11150
-
11151
- // main event tracker, built for individual charts
11152
- if (e.key == 'F10') {
11153
- tracker.DownloadTrackerData();
11154
- } else {
11155
- if (plot) {
11156
- tracker.LogEvent(e);
11157
- }
11158
- }
11159
-
11160
- // Stuff to only run if we're on a chart (so check if the info div exists?)
11161
- if (document.getElementById('info')) {
11162
- }
11163
- });
11164
- }
11165
11547
  });
11166
11548
 
11167
11549
  /**
@@ -11181,6 +11563,7 @@ function InitMaidr(thisMaidr) {
11181
11563
  }
11182
11564
  DestroyChartComponents(); // destroy so that we start fresh, in case we've created on the wrong chart
11183
11565
  CreateChartComponents(singleMaidr);
11566
+ InitTracker();
11184
11567
 
11185
11568
  window.menu = new Menu();
11186
11569
  window.chatLLM = new ChatLLM();
@@ -11239,7 +11622,7 @@ function InitMaidr(thisMaidr) {
11239
11622
  Array.isArray(singleMaidr.type) && singleMaidr.type.length > 1;
11240
11623
 
11241
11624
  // Construct the final announceText string
11242
- let announceText = `${plotTypeString} plot of ${title}: Use Arrows to navigate data points. ${
11625
+ let announceText = `${plotTypeString} plot of ${title}: Click to activate. Use Arrows to navigate data points. ${
11243
11626
  isMultiLayered ? multiLayerInstruction : ' '
11244
11627
  }Toggle B for Braille, T for Text, S for Sonification, and R for Review mode. Use H for Help.`;
11245
11628
 
@@ -11575,7 +11958,7 @@ function CreateChartComponents(thisMaidr, chartOnly = false) {
11575
11958
  Array.isArray(thisMaidr.type) && thisMaidr.type.length > 1;
11576
11959
 
11577
11960
  // Construct the final announceText string
11578
- altText = `${plotTypeString} plot of ${title}: Use Arrows to navigate data points. ${
11961
+ altText = `${plotTypeString} plot of ${title}: Click to activate. Use Arrows to navigate data points. ${
11579
11962
  isMultiLayered ? multiLayerInstruction : ' '
11580
11963
  }Toggle B for Braille, T for Text, S for Sonification, and R for Review mode. Use H for Help.`;
11581
11964
  }
@@ -11586,6 +11969,44 @@ function CreateChartComponents(thisMaidr, chartOnly = false) {
11586
11969
  }
11587
11970
  }
11588
11971
 
11972
+ function InitTracker() {
11973
+ // events etc for user study page
11974
+ // run tracker stuff only on user study page
11975
+ if (constants.canTrack) {
11976
+ window.tracker = new Tracker();
11977
+ if (document.getElementById('download_data_trigger')) {
11978
+ // we're on the intro page, so enable the download data button
11979
+ document
11980
+ .getElementById('download_data_trigger')
11981
+ .addEventListener('click', function (e) {
11982
+ tracker.DownloadTrackerData();
11983
+ });
11984
+ }
11985
+
11986
+ // general events
11987
+ document.addEventListener('keydown', function (e) {
11988
+ // reset tracking with Ctrl + F5 / command + F5, and Ctrl + Shift + R / command + Shift + R
11989
+ // future todo: this should probably be a button with a confirmation. This is dangerous
11990
+ if (
11991
+ (e.key == 'F5' && (constants.isMac ? e.metaKey : e.ctrlKey)) ||
11992
+ (e.key == 'R' && (constants.isMac ? e.metaKey : e.ctrlKey))
11993
+ ) {
11994
+ e.preventDefault();
11995
+ tracker.Delete();
11996
+ location.reload(true);
11997
+ }
11998
+
11999
+ // main event tracker, built for individual charts
12000
+ if (e.key == 'F10') {
12001
+ tracker.DownloadTrackerData();
12002
+ } else {
12003
+ if (plot) {
12004
+ tracker.LogEvent(e);
12005
+ }
12006
+ }
12007
+ });
12008
+ }
12009
+ }
11589
12010
  /**
11590
12011
  * Removes all chart components from the DOM and resets related variables to null.
11591
12012
  * @function