maidr 2.21.0 → 2.22.1

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,21 @@ 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" aria-label="Enter your email address">
1037
+ <button aria-label="Delete Email Address" title="Delete Email Address" id="delete_email_key" class="invis_button">&times;</button>
1038
+ <label for="gemini_auth_key">Email Authentication</label>
1039
+ <button type="button" id="verify">Verify</button>
1040
+ </p>
1023
1041
  <p id="openai_auth_key_container" class="multi_container hidden">
1024
1042
  <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
1043
  <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 +1045,14 @@ class Menu {
1027
1045
  <p id="gemini_auth_key_container" class="multi_container hidden">
1028
1046
  <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
1047
  <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>
1048
+ </p>
1049
+ <p id="claude_auth_key_container" class="multi_container hidden">
1050
+ <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>
1051
+ <input type="password" size="50" id="claude_auth_key"><button aria-label="Delete Claude key" title="Delete Claude key" id="delete_claude_key" class="invis_button">&times;</button><label for="claude_auth_key">Claude Authentication Key</label>
1030
1052
  </p>
1031
1053
  <p><input type="checkbox" ${
1032
1054
  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>
1055
+ } id="init_llm_on_load" name="init_llm_on_load"><label for="init_llm_on_load">Start LLM right away</label></p>
1034
1056
  <p>
1035
1057
  <select id="skill_level">
1036
1058
  <option value="basic">Basic</option>
@@ -1088,6 +1110,13 @@ class Menu {
1088
1110
  menu.Toggle(false);
1089
1111
  },
1090
1112
  ]);
1113
+ constants.events.push([
1114
+ document.getElementById('verify'),
1115
+ 'click',
1116
+ function (e) {
1117
+ menu.VerifyEmail();
1118
+ },
1119
+ ]);
1091
1120
  constants.events.push([
1092
1121
  document.getElementById('menu'),
1093
1122
  'keyup',
@@ -1170,6 +1199,54 @@ class Menu {
1170
1199
  },
1171
1200
  ]);
1172
1201
 
1202
+ constants.events.push([
1203
+ document.getElementById('LLM_model_openai'),
1204
+ 'change',
1205
+ function (e) {
1206
+ if (e.target.checked) {
1207
+ document
1208
+ .getElementById('openai_auth_key_container')
1209
+ .classList.remove('hidden');
1210
+ } else {
1211
+ document
1212
+ .getElementById('openai_auth_key_container')
1213
+ .classList.add('hidden');
1214
+ }
1215
+ },
1216
+ ]);
1217
+
1218
+ constants.events.push([
1219
+ document.getElementById('LLM_model_gemini'),
1220
+ 'change',
1221
+ function (e) {
1222
+ if (e.target.checked) {
1223
+ document
1224
+ .getElementById('gemini_auth_key_container')
1225
+ .classList.remove('hidden');
1226
+ } else {
1227
+ document
1228
+ .getElementById('gemini_auth_key_container')
1229
+ .classList.add('hidden');
1230
+ }
1231
+ },
1232
+ ]);
1233
+
1234
+ constants.events.push([
1235
+ document.getElementById('LLM_model_claude'),
1236
+ 'change',
1237
+ function (e) {
1238
+ // if (e.target.checked) {
1239
+ document
1240
+ .getElementById('claude_auth_key_container')
1241
+ .classList.add('hidden');
1242
+ // } else {
1243
+ // document
1244
+ // .getElementById('claude_auth_key_container')
1245
+ // .classList.add('hidden');
1246
+ // }
1247
+ },
1248
+ ]);
1249
+
1173
1250
  // Skill level other events
1174
1251
  constants.events.push([
1175
1252
  document.getElementById('skill_level'),
@@ -1205,6 +1282,20 @@ class Menu {
1205
1282
  },
1206
1283
  ]);
1207
1284
  }
1285
+
1286
+ // Limit selections to 2 AI models
1287
+ const llmCheckboxes = document.querySelectorAll('input[name="LLM_model"]');
1288
+ llmCheckboxes.forEach((checkbox) => {
1289
+ checkbox.addEventListener('change', () => {
1290
+ const checked = document.querySelectorAll(
1291
+ 'input[name="LLM_model"]:checked'
1292
+ );
1293
+ if (checked.length > 2) {
1294
+ checkbox.checked = false;
1295
+ alert('You can select up to 2 AI models.');
1296
+ }
1297
+ });
1298
+ });
1208
1299
  }
1209
1300
 
1210
1301
  /**
@@ -1276,16 +1367,22 @@ class Menu {
1276
1367
  document.getElementById('openai_auth_key').value =
1277
1368
  constants.openAIAuthKey;
1278
1369
  }
1370
+ if (typeof constants.emailAuthKey == 'string') {
1371
+ document.getElementById('email_auth_key').value = constants.emailAuthKey;
1372
+ }
1279
1373
  if (typeof constants.geminiAuthKey == 'string') {
1280
1374
  document.getElementById('gemini_auth_key').value =
1281
1375
  constants.geminiAuthKey;
1282
1376
  }
1377
+ if (typeof constants.claudeAuthKey == 'string') {
1378
+ document.getElementById('claude_auth_key').value =
1379
+ constants.claudeAuthKey;
1380
+ }
1283
1381
  document.getElementById('skill_level').value = constants.skillLevel;
1284
1382
  if (constants.skillLevelOther) {
1285
1383
  document.getElementById('skill_level_other').value =
1286
1384
  constants.skillLevelOther;
1287
1385
  }
1288
- document.getElementById('LLM_model').value = constants.LLMModel;
1289
1386
 
1290
1387
  // aria mode
1291
1388
  if (constants.ariaMode == 'assertive') {
@@ -1295,44 +1392,18 @@ class Menu {
1295
1392
  document.getElementById('aria_mode_polite').checked = true;
1296
1393
  document.getElementById('aria_mode_assertive').checked = false;
1297
1394
  }
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');
1395
+
1396
+ for (let model in constants.LLMModels) {
1397
+ document.getElementById(`LLM_model_${model}`).checked = true;
1398
+
1324
1399
  document
1325
- .getElementById('gemini_multi_container')
1400
+ .getElementById(`${model}_auth_key_container`)
1326
1401
  .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
1402
  }
1403
+ document
1404
+ .getElementById(`claude_auth_key_container`)
1405
+ .classList.add('hidden');
1406
+
1336
1407
  // skill level other
1337
1408
  if (constants.skillLevel == 'other') {
1338
1409
  document
@@ -1368,10 +1439,22 @@ class Menu {
1368
1439
 
1369
1440
  constants.openAIAuthKey = document.getElementById('openai_auth_key').value;
1370
1441
  constants.geminiAuthKey = document.getElementById('gemini_auth_key').value;
1442
+ constants.claudeAuthKey = document.getElementById('claude_auth_key').value;
1443
+ constants.emailAuthKey = document.getElementById('email_auth_key').value;
1371
1444
  constants.skillLevel = document.getElementById('skill_level').value;
1372
1445
  constants.skillLevelOther =
1373
1446
  document.getElementById('skill_level_other').value;
1374
- constants.LLMModel = document.getElementById('LLM_model').value;
1447
+ // constants.LLMModel = document.getElementById('LLM_model').value;
1448
+
1449
+ const llmCheckboxes = document.querySelectorAll('input[name="LLM_model"]');
1450
+ llmCheckboxes.forEach((checkbox) => {
1451
+ if (checkbox.checked) {
1452
+ constants.LLMModels[checkbox.value] = true;
1453
+ } else {
1454
+ delete constants.LLMModels[checkbox.value];
1455
+ }
1456
+ });
1457
+
1375
1458
  constants.LLMPreferences = document.getElementById('LLM_preferences').value;
1376
1459
  constants.LLMOpenAiMulti = document.getElementById('openai_multi').checked;
1377
1460
  constants.LLMGeminiMulti = document.getElementById('gemini_multi').checked;
@@ -1394,6 +1477,41 @@ class Menu {
1394
1477
  }
1395
1478
  }
1396
1479
 
1480
+ VerifyEmail() {
1481
+ let email = document.getElementById('email_auth_key').value;
1482
+ if (email && email.indexOf('@') !== -1) {
1483
+ let url = `https://maidr-service.azurewebsites.net/api/send_email?code=I8Aa2PlPspjQ8Hks0QzGyszP8_i2-XJ3bq7Xh8-ykEe4AzFuYn_QWA%3D%3D`;
1484
+
1485
+ let requestJson = {
1486
+ email: email,
1487
+ };
1488
+
1489
+ fetch(url, {
1490
+ method: 'POST',
1491
+ headers: {
1492
+ 'Content-Type': 'application/json',
1493
+ Authentication: constants.emailAuthKey,
1494
+ },
1495
+ body: JSON.stringify(requestJson),
1496
+ })
1497
+ .then((response) => response.json())
1498
+ .then((data) => {
1499
+ if (data && data.success) {
1500
+ alert('Link sent to email address: ' + email);
1501
+ } else {
1502
+ console.log(data);
1503
+ alert(data.data);
1504
+ }
1505
+ })
1506
+ .catch((error) => {
1507
+ console.log(error);
1508
+ alert(error.data);
1509
+ });
1510
+ } else {
1511
+ alert('Please enter a valid email address.');
1512
+ }
1513
+ }
1514
+
1397
1515
  /**
1398
1516
  * Sets the aria attributes on the HTML elements in the menu
1399
1517
  * @returns {void}
@@ -1475,14 +1593,16 @@ class Menu {
1475
1593
  ) {
1476
1594
  shouldReset = true;
1477
1595
  }
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;
1596
+
1597
+ // check if LLMModels have changed
1598
+ let llmCheckboxes = document.querySelectorAll('input[name="LLM_model"]');
1599
+ for (let i = 0; i < llmCheckboxes.length; i++) {
1600
+ if (
1601
+ !shouldReset &&
1602
+ constants.LLMModels[llmCheckboxes[i].value] != llmCheckboxes[i].checked
1603
+ ) {
1604
+ shouldReset = true;
1605
+ }
1486
1606
  }
1487
1607
 
1488
1608
  return shouldReset;
@@ -1501,10 +1621,11 @@ class Menu {
1501
1621
  localStorage.setItem('settings_data', JSON.stringify(data));
1502
1622
 
1503
1623
  // also save to tracking if we're doing that
1504
- if (constants.isTracking) {
1624
+ if (constants.canTrack) {
1505
1625
  // but not auth keys
1506
1626
  data.openAIAuthKey = 'hidden';
1507
1627
  data.geminiAuthKey = 'hidden';
1628
+ data.claudeAuthKey = 'hidden';
1508
1629
  // and need a timestamp
1509
1630
  data.timestamp = new Date().toISOString();
1510
1631
  tracker.SetData('settings', data);
@@ -1517,9 +1638,10 @@ class Menu {
1517
1638
  let data = JSON.parse(localStorage.getItem('settings_data'));
1518
1639
  if (data) {
1519
1640
  for (let i = 0; i < constants.userSettingsKeys.length; i++) {
1520
- constants[constants.userSettingsKeys[i]] =
1521
- data[constants.userSettingsKeys[i]] ||
1522
- constants[constants.userSettingsKeys[i]];
1641
+ const key = constants.userSettingsKeys[i];
1642
+ if (key in data) {
1643
+ constants[key] = data[key];
1644
+ }
1523
1645
  }
1524
1646
  }
1525
1647
  this.PopulateData();
@@ -1541,11 +1663,9 @@ class ChatLLM {
1541
1663
  if (constants.autoInitLLM) {
1542
1664
  // only run if we have API keys set
1543
1665
  if (
1544
- (constants.LLMModel == 'openai' && constants.openAIAuthKey) ||
1545
- (constants.LLMModel == 'gemini' && constants.geminiAuthKey) ||
1546
- (constants.LLMModel == 'multi' &&
1547
- constants.openAIAuthKey &&
1548
- constants.geminiAuthKey)
1666
+ ('gemini' in constants.LLMModels && constants.geminiAuthKey) ||
1667
+ ('openai' in constants.LLMModels && constants.openAIAuthKey) ||
1668
+ ('claude' in constants.LLMModels && constants.claudeAuthKey)
1549
1669
  ) {
1550
1670
  this.InitChatMessage();
1551
1671
  }
@@ -1686,6 +1806,13 @@ class ChatLLM {
1686
1806
  document.getElementById('openai_auth_key').value = '';
1687
1807
  },
1688
1808
  ]);
1809
+ constants.events.push([
1810
+ document.getElementById('delete_email_key'),
1811
+ 'click',
1812
+ function (e) {
1813
+ document.getElementById('email_auth_key').value = '';
1814
+ },
1815
+ ]);
1689
1816
  constants.events.push([
1690
1817
  document.getElementById('delete_gemini_key'),
1691
1818
  'click',
@@ -1853,7 +1980,7 @@ class ChatLLM {
1853
1980
 
1854
1981
  // if this is the user's first message (or we're gemini, in which case we need to send every time), prepend prompt with user position
1855
1982
  if (
1856
- (this.firstOpen || constants.LLMModel == 'gemini') &&
1983
+ (this.firstOpen || 'gemini' in constants.LLMModels) &&
1857
1984
  !firsttime &&
1858
1985
  constants.verboseText.length > 0
1859
1986
  ) {
@@ -1871,17 +1998,36 @@ class ChatLLM {
1871
1998
  this.WaitingSound(true);
1872
1999
  }
1873
2000
 
1874
- if (constants.LLMOpenAiMulti || constants.LLMModel == 'openai') {
2001
+ if ('openai' in constants.LLMModels) {
1875
2002
  if (firsttime) {
1876
2003
  img = await this.ConvertSVGtoJPG(singleMaidr.id, 'openai');
1877
2004
  }
1878
- chatLLM.OpenAIPrompt(text, img);
2005
+ if (constants.openAIAuthKey) {
2006
+ chatLLM.OpenAIPrompt(text, img);
2007
+ } else {
2008
+ chatLLM.OpenAIPromptAPI(text, img);
2009
+ }
1879
2010
  }
1880
- if (constants.LLMGeminiMulti || constants.LLMModel == 'gemini') {
2011
+ if ('gemini' in constants.LLMModels) {
1881
2012
  if (firsttime) {
1882
2013
  img = await this.ConvertSVGtoJPG(singleMaidr.id, 'gemini');
1883
2014
  }
1884
- chatLLM.GeminiPrompt(text, img);
2015
+ if (constants.geminiAuthKey) {
2016
+ chatLLM.GeminiPrompt(text, img);
2017
+ } else {
2018
+ chatLLM.GeminiPromptAPI(text, img);
2019
+ }
2020
+ }
2021
+
2022
+ if ('claude' in constants.LLMModels) {
2023
+ if (firsttime) {
2024
+ img = await this.ConvertSVGtoJPG(singleMaidr.id, 'claude');
2025
+ }
2026
+ if (constants.claudeAuthKey) {
2027
+ chatLLM.ClaudePrompt(text, img);
2028
+ } else {
2029
+ chatLLM.ClaudePromptAPI(text, img);
2030
+ }
1885
2031
  }
1886
2032
  }
1887
2033
 
@@ -1969,7 +2115,7 @@ class ChatLLM {
1969
2115
  }
1970
2116
 
1971
2117
  InitChatMessage() {
1972
- // get name from resource
2118
+ // get name from resource]
1973
2119
  let LLMName = resources.GetString(constants.LLMModel);
1974
2120
  this.firstTime = false;
1975
2121
  this.DisplayChatMessage(LLMName, resources.GetString('processing'), true);
@@ -1984,7 +2130,6 @@ class ChatLLM {
1984
2130
  */
1985
2131
  ProcessLLMResponse(data, model) {
1986
2132
  chatLLM.WaitingSound(false);
1987
- //console.log('LLM response: ', data);
1988
2133
  let text = '';
1989
2134
  let LLMName = resources.GetString(model);
1990
2135
 
@@ -2018,9 +2163,20 @@ class ChatLLM {
2018
2163
  // todo: display actual response
2019
2164
  }
2020
2165
  }
2166
+ if (model == 'claude') {
2167
+ console.log('Claude response: ', data);
2168
+ if (data.text()) {
2169
+ text = data.text();
2170
+ chatLLM.DisplayChatMessage(LLMName, text);
2171
+ }
2172
+ if (data.error) {
2173
+ chatLLM.DisplayChatMessage(LLMName, 'Error processing request.', true);
2174
+ chatLLM.WaitingSound(false);
2175
+ }
2176
+ }
2021
2177
 
2022
2178
  // if we're tracking, log the data
2023
- if (constants.isTracking) {
2179
+ if (constants.canTrack) {
2024
2180
  let chatHist = chatLLM.CopyChatHistory();
2025
2181
  tracker.SetData('ChatHistory', chatHist);
2026
2182
  }
@@ -2086,6 +2242,96 @@ class ChatLLM {
2086
2242
  return responseText;
2087
2243
  }
2088
2244
 
2245
+ ClaudeJson(text, img = null) {
2246
+ const anthropicVersion = 'vertex-2023-10-16';
2247
+ const maxTokens = 256;
2248
+
2249
+ const payload = {
2250
+ anthropic_version: anthropicVersion,
2251
+ max_tokens: maxTokens,
2252
+ messages: [],
2253
+ };
2254
+
2255
+ // Construct the user message object
2256
+ const userMessage = {
2257
+ role: 'user',
2258
+ content: [],
2259
+ };
2260
+
2261
+ // Add the image content if provided
2262
+ if (img) {
2263
+ userMessage.content.push(
2264
+ {
2265
+ type: 'image',
2266
+ source: {
2267
+ type: 'base64',
2268
+ media_type: 'image/jpeg', // Update if other formats are supported
2269
+ data: img,
2270
+ },
2271
+ },
2272
+ {
2273
+ type: 'text',
2274
+ text: text,
2275
+ }
2276
+ );
2277
+ } else {
2278
+ // Add only the text content if no image is provided
2279
+ userMessage.content.push({
2280
+ type: 'text',
2281
+ text: text,
2282
+ });
2283
+ }
2284
+
2285
+ // Add the user message to the messages array
2286
+ payload.messages.push(userMessage);
2287
+
2288
+ return payload;
2289
+ }
2290
+
2291
+ ClaudePromptAPI(text, imgBase64 = null) {
2292
+ console.log('Claude prompt API');
2293
+ let url =
2294
+ 'https://maidr-service.azurewebsites.net/api/claude?code=I8Aa2PlPspjQ8Hks0QzGyszP8_i2-XJ3bq7Xh8-ykEe4AzFuYn_QWA%3D%3D';
2295
+
2296
+ // Create the prompt
2297
+ let prompt = constants.LLMSystemMessage;
2298
+ if (constants.LLMPreferences) {
2299
+ prompt += constants.LLMPreferences;
2300
+ }
2301
+ prompt += '\n\n' + text; // Use the text parameter as the prompt
2302
+
2303
+ if (imgBase64 == null) {
2304
+ imgBase64 = constants.LLMImage;
2305
+ } else {
2306
+ constants.LLMImage = imgBase64;
2307
+ }
2308
+ constants.LLMImage = imgBase64;
2309
+
2310
+ let requestJson = chatLLM.ClaudeJson(prompt, imgBase64);
2311
+
2312
+ fetch(url, {
2313
+ method: 'POST',
2314
+ headers: {
2315
+ 'Content-Type': 'application/json',
2316
+ Authentication: constants.emailAuthKey,
2317
+ },
2318
+ body: JSON.stringify(requestJson),
2319
+ })
2320
+ .then((response) => response.json())
2321
+ .then((data) => {
2322
+ data.text = function () {
2323
+ return data.content[0].text;
2324
+ };
2325
+ chatLLM.ProcessLLMResponse(data, 'claude');
2326
+ })
2327
+ .catch((error) => {
2328
+ chatLLM.WaitingSound(false);
2329
+ console.error('Error:', error);
2330
+ chatLLM.DisplayChatMessage('Claude', 'Error processing request.', true);
2331
+ // also todo: handle errors somehow
2332
+ });
2333
+ }
2334
+
2089
2335
  /**
2090
2336
  * Gets running prompt info, appends the latest request, and packages it into a JSON object for the LLM.
2091
2337
  * @function
@@ -2119,6 +2365,35 @@ class ChatLLM {
2119
2365
  // also todo: handle errors somehow
2120
2366
  });
2121
2367
  }
2368
+
2369
+ OpenAIPromptAPI(text, img = null) {
2370
+ // request init
2371
+ let url =
2372
+ 'https://maidr-service.azurewebsites.net/api/openai?code=I8Aa2PlPspjQ8Hks0QzGyszP8_i2-XJ3bq7Xh8-ykEe4AzFuYn_QWA%3D%3D';
2373
+ let auth = constants.openAIAuthKey;
2374
+ let requestJson = chatLLM.OpenAIJson(text, img);
2375
+ console.log('LLM request: ', requestJson);
2376
+
2377
+ fetch(url, {
2378
+ method: 'POST',
2379
+ headers: {
2380
+ 'Content-Type': 'application/json',
2381
+ Authentication: constants.emailAuthKey,
2382
+ },
2383
+ body: JSON.stringify(requestJson),
2384
+ })
2385
+ .then((response) => response.json())
2386
+ .then((data) => {
2387
+ chatLLM.ProcessLLMResponse(data, 'openai');
2388
+ })
2389
+ .catch((error) => {
2390
+ chatLLM.WaitingSound(false);
2391
+ console.error('Error:', error);
2392
+ chatLLM.DisplayChatMessage('OpenAI', 'Error processing request.', true);
2393
+ // also todo: handle errors somehow
2394
+ });
2395
+ }
2396
+
2122
2397
  OpenAIJson(text, img = null) {
2123
2398
  let sysMessage = constants.LLMSystemMessage;
2124
2399
  let backupMessage =
@@ -2167,6 +2442,110 @@ class ChatLLM {
2167
2442
  return this.requestJson;
2168
2443
  }
2169
2444
 
2445
+ GeminiJson(text, img = null) {
2446
+ let sysMessage = constants.LLMSystemMessage;
2447
+ let backupMessage =
2448
+ 'Describe ' + singleMaidr.type + ' charts to a blind person';
2449
+
2450
+ let payload = {
2451
+ generationConfig: {},
2452
+ safetySettings: [],
2453
+ contents: [],
2454
+ };
2455
+
2456
+ // System message as the initial "role" and "text" content for context
2457
+ let sysContent = {
2458
+ role: 'user',
2459
+ parts: [
2460
+ {
2461
+ text: sysMessage || backupMessage, // Fallback if sysMessage is unavailable
2462
+ },
2463
+ ],
2464
+ };
2465
+
2466
+ // Add preferences if available
2467
+ if (constants.LLMPreferences) {
2468
+ sysContent.parts.push({
2469
+ text: constants.LLMPreferences,
2470
+ });
2471
+ }
2472
+
2473
+ payload.contents.push(sysContent);
2474
+
2475
+ // Add user input content, including image if available
2476
+ let userContent = {
2477
+ role: 'user',
2478
+ parts: [],
2479
+ };
2480
+
2481
+ if (img) {
2482
+ // If there’s an image, add both text and image data
2483
+ userContent.parts.push(
2484
+ {
2485
+ text: text,
2486
+ },
2487
+ {
2488
+ inlineData: {
2489
+ data: img, // Expecting base64-encoded image data
2490
+ mimeType: 'image/png', // Adjust if different image formats are possible
2491
+ },
2492
+ }
2493
+ );
2494
+ } else {
2495
+ // If no image, only add the text
2496
+ userContent.parts.push({
2497
+ text: text,
2498
+ });
2499
+ }
2500
+
2501
+ // Add user content to the contents array
2502
+ payload.contents.push(userContent);
2503
+
2504
+ return payload;
2505
+ }
2506
+
2507
+ async GeminiPromptAPI(text, imgBase64 = null) {
2508
+ let url =
2509
+ 'https://maidr-service.azurewebsites.net/api/gemini?code=I8Aa2PlPspjQ8Hks0QzGyszP8_i2-XJ3bq7Xh8-ykEe4AzFuYn_QWA%3D%3D';
2510
+
2511
+ // Create the prompt
2512
+ let prompt = constants.LLMSystemMessage;
2513
+ if (constants.LLMPreferences) {
2514
+ prompt += constants.LLMPreferences;
2515
+ }
2516
+ prompt += '\n\n' + text; // Use the text parameter as the prompt
2517
+
2518
+ if (imgBase64 == null) {
2519
+ imgBase64 = constants.LLMImage;
2520
+ } else {
2521
+ constants.LLMImage = imgBase64;
2522
+ }
2523
+ constants.LLMImage = imgBase64;
2524
+
2525
+ let requestJson = chatLLM.GeminiJson(prompt, imgBase64);
2526
+
2527
+ const response = await fetch(url, {
2528
+ method: 'POST',
2529
+ headers: {
2530
+ 'Content-Type': 'application/json',
2531
+ Authentication: constants.emailAuthKey,
2532
+ },
2533
+ body: JSON.stringify(requestJson),
2534
+ });
2535
+ if (response.ok) {
2536
+ const responseJson = await response.json();
2537
+ responseJson.text = () => {
2538
+ return responseJson.candidates[0].content.parts[0].text;
2539
+ };
2540
+ chatLLM.ProcessLLMResponse(responseJson, 'gemini');
2541
+ } else {
2542
+ chatLLM.WaitingSound(false);
2543
+ console.error('Error:', error);
2544
+ chatLLM.DisplayChatMessage('OpenAI', 'Error processing request.', true);
2545
+ // also todo: handle errors somehow
2546
+ }
2547
+ }
2548
+
2170
2549
  async GeminiPrompt(text, imgBase64 = null) {
2171
2550
  // https://ai.google.dev/docs/gemini_api_overview#node.js
2172
2551
  try {
@@ -2366,7 +2745,7 @@ class ChatLLM {
2366
2745
  var jpegData = canvas.toDataURL('image/jpeg', 0.9); // 0.9 is the quality parameter
2367
2746
  if (model == 'openai') {
2368
2747
  resolve(jpegData);
2369
- } else if (model == 'gemini') {
2748
+ } else if (model == 'gemini' || model == 'claude') {
2370
2749
  let base64Data = jpegData.split(',')[1];
2371
2750
  resolve(base64Data);
2372
2751
  //resolve(jpegData);
@@ -2716,9 +3095,13 @@ class Helper {
2716
3095
  * @class
2717
3096
  */
2718
3097
  class Tracker {
3098
+ // URL
3099
+ logUrl =
3100
+ 'https://maidr-service.azurewebsites.net/api/log?code=I8Aa2PlPspjQ8Hks0QzGyszP8_i2-XJ3bq7Xh8-ykEe4AzFuYn_QWA%3D%3D'; // TODO Replace
3101
+ isLocal = false;
3102
+
2719
3103
  constructor() {
2720
3104
  this.DataSetup();
2721
- constants.isTracking = true;
2722
3105
  }
2723
3106
 
2724
3107
  /**
@@ -2726,14 +3109,14 @@ class Tracker {
2726
3109
  */
2727
3110
  DataSetup() {
2728
3111
  let prevData = this.GetTrackerData();
2729
- if (prevData) {
2730
- // good to go already, do nothing, but make sure we have our containers
2731
- } else {
3112
+ if (!this.isLocal || !prevData) {
2732
3113
  let data = {};
2733
3114
  data.userAgent = Object.assign(navigator.userAgent);
2734
3115
  data.vendor = Object.assign(navigator.vendor);
2735
3116
  data.language = Object.assign(navigator.language);
2736
3117
  data.platform = Object.assign(navigator.platform);
3118
+ data.geolocation = Object.assign(navigator.geolocation);
3119
+ data.log_type = 'system_data';
2737
3120
  data.events = [];
2738
3121
  data.settings = [];
2739
3122
 
@@ -2758,8 +3141,33 @@ class Tracker {
2758
3141
  * Saves the tracker data to local storage.
2759
3142
  * @param {Object} data - The data to be saved.
2760
3143
  */
2761
- SaveTrackerData(data) {
2762
- localStorage.setItem(constants.project_id, JSON.stringify(data));
3144
+ async SaveTrackerData(data) {
3145
+ console.log('about to save data', data);
3146
+ if (this.isLocal) {
3147
+ localStorage.setItem(constants.project_id, JSON.stringify(data));
3148
+ } else {
3149
+ // test this first
3150
+ try {
3151
+ const response = await fetch(this.logUrl, {
3152
+ method: 'POST',
3153
+ headers: {
3154
+ 'Content-Type': 'application/json',
3155
+ },
3156
+ body: JSON.stringify(data),
3157
+ });
3158
+
3159
+ if (!response.ok) {
3160
+ throw new Error(`HTTP error! status: ${response.status}`);
3161
+ }
3162
+
3163
+ const result = await response.json();
3164
+ console.log('Data saved successfully:', result);
3165
+ return result;
3166
+ } catch (error) {
3167
+ console.error('Error saving data:', error);
3168
+ return null;
3169
+ }
3170
+ }
2763
3171
  }
2764
3172
 
2765
3173
  /**
@@ -2792,6 +3200,10 @@ class Tracker {
2792
3200
  // don't store their auth keys
2793
3201
  settings.openAIAuthKey = 'hidden';
2794
3202
  settings.geminiAuthKey = 'hidden';
3203
+ if (constants.emailAuthKey) {
3204
+ settings.username = constants.emailAuthKey;
3205
+ }
3206
+ settings;
2795
3207
  this.SetData('settings', settings);
2796
3208
  }
2797
3209
  }
@@ -2810,6 +3222,9 @@ class Tracker {
2810
3222
  eventToLog.altKey = Object.assign(e.altKey);
2811
3223
  eventToLog.ctrlKey = Object.assign(e.ctrlKey);
2812
3224
  eventToLog.shiftKey = Object.assign(e.shiftKey);
3225
+ if (constants.emailAuthKey) {
3226
+ eventToLog.username = Object.assign(constants.emailAuthKey);
3227
+ }
2813
3228
  if (e.path) {
2814
3229
  eventToLog.focus = Object.assign(e.path[0].tagName);
2815
3230
  }
@@ -3003,18 +3418,29 @@ class Tracker {
3003
3418
  //console.log('logged an event');
3004
3419
  }
3005
3420
 
3421
+ /**
3422
+ * Saves data to the server using a POST request.
3423
+ * @param {Object} logData - The data to be saved.
3424
+ * @returns {Promise<Object>} The result of the save operation.
3425
+ */
3426
+
3006
3427
  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] = [];
3428
+ if (this.isLocal) {
3429
+ let data = this.GetTrackerData();
3430
+ let arrayKeys = ['events', 'ChatHistory', 'settings'];
3431
+ if (!arrayKeys.includes(key)) {
3432
+ data[key] = value;
3433
+ } else {
3434
+ if (!data[key]) {
3435
+ data[key] = [];
3436
+ }
3437
+ data[key].push(value);
3014
3438
  }
3015
- data[key].push(value);
3439
+ this.SaveTrackerData(data);
3440
+ } else {
3441
+ value['log_type'] = key;
3442
+ this.SaveTrackerData(value);
3016
3443
  }
3017
- this.SaveTrackerData(data);
3018
3444
  }
3019
3445
 
3020
3446
  /**
@@ -11121,47 +11547,6 @@ document.addEventListener('DOMContentLoaded', function (e) {
11121
11547
 
11122
11548
  // init components like alt text on just the first chart
11123
11549
  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
11550
  });
11166
11551
 
11167
11552
  /**
@@ -11181,6 +11566,7 @@ function InitMaidr(thisMaidr) {
11181
11566
  }
11182
11567
  DestroyChartComponents(); // destroy so that we start fresh, in case we've created on the wrong chart
11183
11568
  CreateChartComponents(singleMaidr);
11569
+ InitTracker();
11184
11570
 
11185
11571
  window.menu = new Menu();
11186
11572
  window.chatLLM = new ChatLLM();
@@ -11586,6 +11972,44 @@ function CreateChartComponents(thisMaidr, chartOnly = false) {
11586
11972
  }
11587
11973
  }
11588
11974
 
11975
+ function InitTracker() {
11976
+ // events etc for user study page
11977
+ // run tracker stuff only on user study page
11978
+ if (constants.canTrack) {
11979
+ window.tracker = new Tracker();
11980
+ if (document.getElementById('download_data_trigger')) {
11981
+ // we're on the intro page, so enable the download data button
11982
+ document
11983
+ .getElementById('download_data_trigger')
11984
+ .addEventListener('click', function (e) {
11985
+ tracker.DownloadTrackerData();
11986
+ });
11987
+ }
11988
+
11989
+ // general events
11990
+ document.addEventListener('keydown', function (e) {
11991
+ // reset tracking with Ctrl + F5 / command + F5, and Ctrl + Shift + R / command + Shift + R
11992
+ // future todo: this should probably be a button with a confirmation. This is dangerous
11993
+ if (
11994
+ (e.key == 'F5' && (constants.isMac ? e.metaKey : e.ctrlKey)) ||
11995
+ (e.key == 'R' && (constants.isMac ? e.metaKey : e.ctrlKey))
11996
+ ) {
11997
+ e.preventDefault();
11998
+ tracker.Delete();
11999
+ location.reload(true);
12000
+ }
12001
+
12002
+ // main event tracker, built for individual charts
12003
+ if (e.key == 'F10') {
12004
+ tracker.DownloadTrackerData();
12005
+ } else {
12006
+ if (plot) {
12007
+ tracker.LogEvent(e);
12008
+ }
12009
+ }
12010
+ });
12011
+ }
12012
+ }
11589
12013
  /**
11590
12014
  * Removes all chart components from the DOM and resets related variables to null.
11591
12015
  * @function