@auticlabs/bulut 1.0.1 → 1.0.3

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/embed.js CHANGED
@@ -385,15 +385,14 @@ const POSITION_RIGHT = 20;
385
385
  const COLORS = {
386
386
  primary: "#006BF8",
387
387
  primaryHover: "#0056C7",
388
- text: "#1F1F1F",
389
- border: "#e5e7eb",
388
+ text: "hsla(215, 100%, 5%, 1)",
390
389
  messageUser: "#6C03C1",
391
390
  messageUserText: "#ffffff"
392
391
  };
393
392
  const normalizeHexColor$1 = (hex) => {
394
393
  const trimmed = hex.trim();
395
394
  if (!/^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(trimmed)) {
396
- return "#000000";
395
+ return "hsla(215, 100%, 5%, 1)";
397
396
  }
398
397
  if (trimmed.length === 4) {
399
398
  const r2 = trimmed[1];
@@ -414,17 +413,17 @@ const getContrastIconFilter = (backgroundHex) => {
414
413
  const BORDER_RADIUS = {
415
414
  button: "50%",
416
415
  window: "17px",
417
- message: "14px"
416
+ message: "10px"
418
417
  };
419
- const SHADOW = "0 0 15px rgba(0, 0, 0, 0.15)";
418
+ const SHADOW = "0 0 15px hsla(215, 100%, 5%, 0.15)";
420
419
  const TRANSITIONS = {
421
420
  fast: "150ms ease-in-out",
422
421
  medium: "250ms ease-in-out"
423
422
  };
424
- const microphoneIconRaw = '<svg width="50" height="50" viewBox="0 0 50 50" xmlns="http://www.w3.org/2000/svg">\r\n<path d="M17.6667 47H32.3333M39.6666 19.5001V23.1667C39.6666 31.2334 33.0666 37.8334 25 37.8334M25 37.8334C16.9333 37.8334 10.3334 31.2334 10.3334 23.1667V19.5001M25 37.8334V47M25 3.00009C24.0356 2.99523 23.0798 3.1816 22.1879 3.54841C21.296 3.91523 20.4857 4.45521 19.8037 5.13714C19.1218 5.81907 18.5818 6.62942 18.215 7.52133C17.8482 8.41325 17.6618 9.36903 17.6667 10.3334V23.0521C17.6667 27.0855 20.9896 30.5 25 30.5C29.0104 30.5 32.3333 27.1771 32.3333 23.0521V10.3334C32.3333 6.20842 29.125 3.00009 25 3.00009Z" stroke="black" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>\r\n</svg>\r\n';
425
- const restartIconRaw = '<svg width="50" height="50" viewBox="0 0 50 50" fill="none" xmlns="http://www.w3.org/2000/svg">\r\n<path d="M31.4571 10.4169C30.714 10.783 30.4083 11.6822 30.7744 12.4254C31.1405 13.1685 32.0397 13.4742 32.7829 13.1081L32.12 11.7625L31.4571 10.4169ZM44.3 28.2275C44.3 27.3991 43.6284 26.7275 42.8 26.7275C41.9716 26.7275 41.3 27.3991 41.3 28.2275H42.8H44.3ZM26.0607 0.911874C25.4749 0.326088 24.5251 0.326088 23.9393 0.911874C23.3536 1.49766 23.3536 2.44741 23.9393 3.03319L25 1.97253L26.0607 0.911874ZM33.9 10.8725L34.9607 11.9332C35.5464 11.3474 35.5464 10.3977 34.9607 9.81187L33.9 10.8725ZM23.9393 18.7119C23.3536 19.2977 23.3536 20.2474 23.9393 20.8332C24.5251 21.419 25.4749 21.419 26.0607 20.8332L25 19.7725L23.9393 18.7119ZM32.12 11.7625C32.7829 13.1081 32.7838 13.1077 32.7848 13.1072C32.7851 13.107 32.7861 13.1065 32.7868 13.1062C32.7882 13.1055 32.7896 13.1048 32.791 13.1041C32.794 13.1026 32.7971 13.101 32.8005 13.0993C32.8071 13.0959 32.8146 13.0921 32.8229 13.0878C32.8393 13.0792 32.859 13.0686 32.8813 13.0561C32.9252 13.0315 32.9828 12.9974 33.0478 12.9543C33.1597 12.8798 33.3709 12.728 33.5596 12.493C33.7406 12.2674 34.0582 11.7606 33.9356 11.0758C33.8134 10.3931 33.3372 10.002 33.0232 9.81189C32.4374 9.45724 31.5535 9.27046 30.4249 9.14527C29.2187 9.01147 27.4833 8.92753 25 8.92753V10.4275V11.9275C27.4317 11.9275 29.0439 12.0105 30.0942 12.127C31.2222 12.2521 31.5039 12.3991 31.4694 12.3782C31.4309 12.3549 31.0768 12.1307 30.9826 11.6044C30.888 11.0761 31.1396 10.7153 31.2201 10.6149C31.3083 10.5051 31.3846 10.4575 31.3868 10.4561C31.3968 10.4494 31.4058 10.444 31.4148 10.439C31.4196 10.4363 31.4253 10.4332 31.4321 10.4296C31.4355 10.4278 31.4393 10.4259 31.4434 10.4238C31.4455 10.4228 31.4477 10.4217 31.4499 10.4205C31.4511 10.4199 31.4523 10.4194 31.4535 10.4188C31.4541 10.4185 31.455 10.418 31.4553 10.4179C31.4562 10.4174 31.4571 10.4169 32.12 11.7625ZM25 10.4275V8.92753C21.1828 8.92753 17.4514 10.0595 14.2775 12.1802L15.1109 13.4274L15.9442 14.6746C18.6247 12.8835 21.7762 11.9275 25 11.9275V10.4275ZM15.1109 13.4274L14.2775 12.1802C11.1036 14.3009 8.6299 17.3151 7.16913 20.8417L8.55495 21.4158L9.94077 21.9898C11.1745 19.0114 13.2637 16.4656 15.9442 14.6746L15.1109 13.4274ZM8.55495 21.4158L7.16913 20.8417C5.70836 24.3684 5.32616 28.2489 6.07085 31.9928L7.54203 31.7001L9.01321 31.4075C8.38427 28.2456 8.70706 24.9682 9.94077 21.9898L8.55495 21.4158ZM7.54203 31.7001L6.07085 31.9928C6.81555 35.7366 8.65369 39.1755 11.3528 41.8747L12.4135 40.814L13.4742 39.7534C11.1946 37.4738 9.64215 34.5694 9.01321 31.4075L7.54203 31.7001ZM12.4135 40.814L11.3528 41.8747C14.052 44.5738 17.4909 46.412 21.2348 47.1567L21.5274 45.6855L21.82 44.2143C18.6581 43.5854 15.7538 42.033 13.4742 39.7534L12.4135 40.814ZM21.5274 45.6855L21.2348 47.1567C24.9786 47.9014 28.8592 47.5192 32.3858 46.0584L31.8118 44.6726L31.2377 43.2868C28.2593 44.5205 24.9819 44.8433 21.82 44.2143L21.5274 45.6855ZM31.8118 44.6726L32.3858 46.0584C35.9124 44.5976 38.9267 42.1239 41.0474 38.95L39.8002 38.1167L38.553 37.2833C36.7619 39.9638 34.2162 42.0531 31.2377 43.2868L31.8118 44.6726ZM39.8002 38.1167L41.0474 38.95C43.1681 35.7762 44.3 32.0447 44.3 28.2275H42.8H41.3C41.3 31.4514 40.344 34.6028 38.553 37.2833L39.8002 38.1167ZM25 1.97253L23.9393 3.03319L32.8393 11.9332L33.9 10.8725L34.9607 9.81187L26.0607 0.911874L25 1.97253ZM33.9 10.8725L32.8393 9.81187L23.9393 18.7119L25 19.7725L26.0607 20.8332L34.9607 11.9332L33.9 10.8725Z" fill="black"/>\r\n</svg>\r\n';
426
- const closeIconRaw = '<svg width="50" height="50" viewBox="0 0 50 50" fill="none" xmlns="http://www.w3.org/2000/svg">\r\n<path d="M42.5 42.5L7.5 7.5M42.5 7.5L7.5 42.5" stroke="black" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>\r\n</svg>\r\n';
427
- const bulutLogoRaw = '<svg width="2430" height="438" viewBox="0 0 2430 438" fill="none" xmlns="http://www.w3.org/2000/svg">\r\n<mask id="path-2-inside-1_13_38" fill="white">\r\n<path d="M251.823 0C313.458 0 366.34 37.767 388.932 91.5918C409.253 75.8883 434.678 66.5567 462.264 66.5566C528.885 66.5567 582.893 120.989 582.893 188.134C582.893 188.786 582.885 189.436 582.875 190.086C648.144 193.552 700 247.572 700 313.704C700 382.074 644.575 437.5 576.204 437.5H123.796C55.4255 437.5 7.42386e-05 382.074 0 313.704C0 250.475 47.4025 198.32 108.608 190.833C104.967 177.844 103.019 164.138 103.019 149.975C103.019 67.1461 169.641 0.000357483 251.823 0Z"/>\r\n</mask>\r\n<path d="M251.823 0V-175H251.822L251.823 0ZM388.932 91.5918L227.569 159.319L315.714 369.33L495.936 230.066L388.932 91.5918ZM462.264 66.5566L462.264 -108.443H462.263L462.264 66.5566ZM582.893 188.134L757.893 188.134V188.134H582.893ZM582.875 190.086L407.896 187.352L405.263 355.902L573.596 364.84L582.875 190.086ZM700 313.704L875 313.704V313.704H700ZM0 313.704H-175V313.704L0 313.704ZM108.608 190.833L129.858 364.538L332.108 339.797L277.114 143.6L108.608 190.833ZM103.019 149.975L-71.9814 149.975V149.975H103.019ZM251.823 0V175C239.901 175 231.045 167.601 227.569 159.319L388.932 91.5918L550.295 23.8646C501.636 -92.0671 387.015 -175 251.823 -175V0ZM388.932 91.5918L495.936 230.066C486.783 237.139 474.857 241.557 462.264 241.557L462.264 66.5566L462.263 -108.443C394.499 -108.443 331.724 -85.3623 281.927 -46.8823L388.932 91.5918ZM462.264 66.5566L462.264 241.557C430.946 241.557 407.893 216.344 407.893 188.134H582.893H757.893C757.893 25.6332 626.824 -108.443 462.264 -108.443L462.264 66.5566ZM582.893 188.134L407.893 188.133C407.893 187.799 407.894 187.555 407.896 187.429C407.897 187.307 407.898 187.265 407.896 187.352L582.875 190.086L757.854 192.819C757.871 191.726 757.893 190.068 757.893 188.134L582.893 188.134ZM582.875 190.086L573.596 364.84C546.451 363.398 525 341.115 525 313.704H700H875C875 154.028 749.837 23.7047 592.154 15.3321L582.875 190.086ZM700 313.704L525 313.704C525 285.425 547.924 262.5 576.204 262.5V437.5V612.5C741.225 612.5 875 478.724 875 313.704L700 313.704ZM576.204 437.5V262.5H123.796V437.5V612.5H576.204V437.5ZM123.796 437.5V262.5C152.076 262.5 175 285.425 175 313.704L0 313.704L-175 313.704C-175 478.724 -41.2245 612.5 123.796 612.5V437.5ZM0 313.704H175C175 339.991 155.35 361.42 129.858 364.538L108.608 190.833L87.3591 17.1279C-60.5447 35.2209 -175 160.96 -175 313.704H0ZM108.608 190.833L277.114 143.6C277.741 145.838 278.019 148.019 278.019 149.975H103.019H-71.9814C-71.9814 180.258 -67.806 209.85 -59.897 238.066L108.608 190.833ZM103.019 149.975L278.019 149.975C278.019 162.502 267.58 175 251.824 175L251.823 0L251.822 -175C71.7018 -174.999 -71.9814 -28.2097 -71.9814 149.975L103.019 149.975Z" fill="black" mask="url(#path-2-inside-1_13_38)"/>\r\n<path d="M2430 424.954H2353.35C2317.08 424.954 2288.64 416.094 2268.04 398.373C2247.43 380.652 2237.13 352.216 2237.13 313.065V55.2871H2329.86V302.556C2329.86 318.217 2333.15 328.726 2339.75 334.083C2346.75 339.028 2358.29 341.501 2374.36 341.501H2430V424.954ZM2430 191.903H2188.29V115.25H2430V191.903Z" fill="black"/>\r\n<path d="M1933.18 431.136C1904.33 431.136 1880.22 425.779 1860.85 415.064C1841.89 403.937 1827.68 389.101 1818.2 370.555C1809.13 352.01 1804.6 331.198 1804.6 308.12V115.25H1897.32V277.211C1897.32 301.938 1903.3 320.071 1915.25 331.61C1927.2 342.738 1947.81 348.301 1977.07 348.301C2007.56 348.301 2028.79 342.325 2040.74 330.374C2053.1 318.011 2059.28 298.641 2059.28 272.266L2071.03 271.648L2077.83 330.374H2060.52C2058.05 347.683 2051.87 363.962 2041.98 379.21C2032.5 394.458 2018.69 407.028 2000.56 416.918C1982.84 426.397 1960.38 431.136 1933.18 431.136ZM2152.01 424.955H2065.47V329.138L2059.28 326.047V115.25H2152.01V424.955Z" fill="black"/>\r\n<path d="M1744.32 424.954H1651.59V10.7783H1744.32V424.954Z" fill="black"/>\r\n<path d="M1372.98 431.136C1344.13 431.136 1320.03 425.779 1300.66 415.064C1281.7 403.937 1267.48 389.101 1258 370.555C1248.94 352.01 1244.4 331.198 1244.4 308.12V115.25H1337.13V277.211C1337.13 301.938 1343.1 320.071 1355.06 331.61C1367.01 342.738 1387.61 348.301 1416.87 348.301C1447.37 348.301 1468.59 342.325 1480.54 330.374C1492.91 318.011 1499.09 298.641 1499.09 272.266L1510.84 271.648L1517.64 330.374H1500.33C1497.85 347.683 1491.67 363.962 1481.78 379.21C1472.3 394.458 1458.5 407.028 1440.36 416.918C1422.64 426.397 1400.18 431.136 1372.98 431.136ZM1591.82 424.955H1505.27V329.138L1499.09 326.047V115.25H1591.82V424.955Z" fill="black"/>\r\n<path d="M1045.12 431.136C1009.27 431.136 981.04 423.512 960.434 408.263C940.241 392.603 927.259 369.525 921.49 339.028H904.181L910.981 272.265H922.726C922.726 289.986 926.023 304.41 932.617 315.537C939.623 326.252 949.719 334.083 962.907 339.028C976.507 343.973 993.198 346.446 1012.98 346.446C1033.17 346.446 1049.66 343.973 1062.43 339.028C1075.21 334.083 1084.69 326.046 1090.87 314.919C1097.05 303.792 1100.14 288.956 1100.14 270.411C1100.14 251.041 1097.05 235.999 1090.87 225.284C1084.69 214.157 1075.21 206.121 1062.43 201.176C1049.66 196.23 1033.38 193.757 1013.6 193.757C983.513 193.757 960.847 199.321 945.598 210.448C930.35 221.575 922.726 240.533 922.726 267.32H910.981V197.466H927.671C933.029 171.503 945.392 150.279 964.762 133.795C984.543 117.31 1012.77 109.068 1049.45 109.068C1080.36 109.068 1106.53 115.662 1127.96 128.849C1149.39 142.037 1165.67 160.788 1176.79 185.103C1188.33 209.006 1194.1 237.442 1194.1 270.411C1194.1 302.968 1188.33 331.404 1176.79 355.719C1165.67 379.621 1148.98 398.167 1126.72 411.354C1104.47 424.542 1077.27 431.136 1045.12 431.136ZM915.926 424.954H830V10.7783H922.726V325.428L915.926 333.464V424.954Z" fill="black"/>\r\n</svg>\r\n';
423
+ const microphoneIconRaw = '<svg width="50" height="50" viewBox="0 0 50 50" xmlns="http://www.w3.org/2000/svg">\r\n<path d="M17.6667 47H32.3333M39.6666 19.5001V23.1667C39.6666 31.2334 33.0666 37.8334 25 37.8334M25 37.8334C16.9333 37.8334 10.3334 31.2334 10.3334 23.1667V19.5001M25 37.8334V47M25 3.00009C24.0356 2.99523 23.0798 3.1816 22.1879 3.54841C21.296 3.91523 20.4857 4.45521 19.8037 5.13714C19.1218 5.81907 18.5818 6.62942 18.215 7.52133C17.8482 8.41325 17.6618 9.36903 17.6667 10.3334V23.0521C17.6667 27.0855 20.9896 30.5 25 30.5C29.0104 30.5 32.3333 27.1771 32.3333 23.0521V10.3334C32.3333 6.20842 29.125 3.00009 25 3.00009Z" stroke="hsla(215, 100%, 5%, 1)" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>\r\n</svg>\r\n';
424
+ const restartIconRaw = '<svg width="50" height="50" viewBox="0 0 50 50" fill="none" xmlns="http://www.w3.org/2000/svg">\r\n<path d="M31.4571 10.4169C30.714 10.783 30.4083 11.6822 30.7744 12.4254C31.1405 13.1685 32.0397 13.4742 32.7829 13.1081L32.12 11.7625L31.4571 10.4169ZM44.3 28.2275C44.3 27.3991 43.6284 26.7275 42.8 26.7275C41.9716 26.7275 41.3 27.3991 41.3 28.2275H42.8H44.3ZM26.0607 0.911874C25.4749 0.326088 24.5251 0.326088 23.9393 0.911874C23.3536 1.49766 23.3536 2.44741 23.9393 3.03319L25 1.97253L26.0607 0.911874ZM33.9 10.8725L34.9607 11.9332C35.5464 11.3474 35.5464 10.3977 34.9607 9.81187L33.9 10.8725ZM23.9393 18.7119C23.3536 19.2977 23.3536 20.2474 23.9393 20.8332C24.5251 21.419 25.4749 21.419 26.0607 20.8332L25 19.7725L23.9393 18.7119ZM32.12 11.7625C32.7829 13.1081 32.7838 13.1077 32.7848 13.1072C32.7851 13.107 32.7861 13.1065 32.7868 13.1062C32.7882 13.1055 32.7896 13.1048 32.791 13.1041C32.794 13.1026 32.7971 13.101 32.8005 13.0993C32.8071 13.0959 32.8146 13.0921 32.8229 13.0878C32.8393 13.0792 32.859 13.0686 32.8813 13.0561C32.9252 13.0315 32.9828 12.9974 33.0478 12.9543C33.1597 12.8798 33.3709 12.728 33.5596 12.493C33.7406 12.2674 34.0582 11.7606 33.9356 11.0758C33.8134 10.3931 33.3372 10.002 33.0232 9.81189C32.4374 9.45724 31.5535 9.27046 30.4249 9.14527C29.2187 9.01147 27.4833 8.92753 25 8.92753V10.4275V11.9275C27.4317 11.9275 29.0439 12.0105 30.0942 12.127C31.2222 12.2521 31.5039 12.3991 31.4694 12.3782C31.4309 12.3549 31.0768 12.1307 30.9826 11.6044C30.888 11.0761 31.1396 10.7153 31.2201 10.6149C31.3083 10.5051 31.3846 10.4575 31.3868 10.4561C31.3968 10.4494 31.4058 10.444 31.4148 10.439C31.4196 10.4363 31.4253 10.4332 31.4321 10.4296C31.4355 10.4278 31.4393 10.4259 31.4434 10.4238C31.4455 10.4228 31.4477 10.4217 31.4499 10.4205C31.4511 10.4199 31.4523 10.4194 31.4535 10.4188C31.4541 10.4185 31.455 10.418 31.4553 10.4179C31.4562 10.4174 31.4571 10.4169 32.12 11.7625ZM25 10.4275V8.92753C21.1828 8.92753 17.4514 10.0595 14.2775 12.1802L15.1109 13.4274L15.9442 14.6746C18.6247 12.8835 21.7762 11.9275 25 11.9275V10.4275ZM15.1109 13.4274L14.2775 12.1802C11.1036 14.3009 8.6299 17.3151 7.16913 20.8417L8.55495 21.4158L9.94077 21.9898C11.1745 19.0114 13.2637 16.4656 15.9442 14.6746L15.1109 13.4274ZM8.55495 21.4158L7.16913 20.8417C5.70836 24.3684 5.32616 28.2489 6.07085 31.9928L7.54203 31.7001L9.01321 31.4075C8.38427 28.2456 8.70706 24.9682 9.94077 21.9898L8.55495 21.4158ZM7.54203 31.7001L6.07085 31.9928C6.81555 35.7366 8.65369 39.1755 11.3528 41.8747L12.4135 40.814L13.4742 39.7534C11.1946 37.4738 9.64215 34.5694 9.01321 31.4075L7.54203 31.7001ZM12.4135 40.814L11.3528 41.8747C14.052 44.5738 17.4909 46.412 21.2348 47.1567L21.5274 45.6855L21.82 44.2143C18.6581 43.5854 15.7538 42.033 13.4742 39.7534L12.4135 40.814ZM21.5274 45.6855L21.2348 47.1567C24.9786 47.9014 28.8592 47.5192 32.3858 46.0584L31.8118 44.6726L31.2377 43.2868C28.2593 44.5205 24.9819 44.8433 21.82 44.2143L21.5274 45.6855ZM31.8118 44.6726L32.3858 46.0584C35.9124 44.5976 38.9267 42.1239 41.0474 38.95L39.8002 38.1167L38.553 37.2833C36.7619 39.9638 34.2162 42.0531 31.2377 43.2868L31.8118 44.6726ZM39.8002 38.1167L41.0474 38.95C43.1681 35.7762 44.3 32.0447 44.3 28.2275H42.8H41.3C41.3 31.4514 40.344 34.6028 38.553 37.2833L39.8002 38.1167ZM25 1.97253L23.9393 3.03319L32.8393 11.9332L33.9 10.8725L34.9607 9.81187L26.0607 0.911874L25 1.97253ZM33.9 10.8725L32.8393 9.81187L23.9393 18.7119L25 19.7725L26.0607 20.8332L34.9607 11.9332L33.9 10.8725Z" fill="hsla(215, 100%, 5%, 1)"/>\r\n</svg>\r\n';
425
+ const closeIconRaw = '<svg width="50" height="50" viewBox="0 0 50 50" fill="none" xmlns="http://www.w3.org/2000/svg">\r\n<path d="M42.5 42.5L7.5 7.5M42.5 7.5L7.5 42.5" stroke="hsla(215, 100%, 5%, 1)" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>\r\n</svg>\r\n';
426
+ const bulutLogoRaw = '<svg width="2420" height="438" viewBox="0 0 2420 438" fill="none" xmlns="http://www.w3.org/2000/svg">\r\n<path d="M251.823 0C313.458 0 366.34 37.767 388.932 91.5918C409.253 75.8883 434.678 66.5567 462.264 66.5566C528.885 66.5567 582.893 120.989 582.893 188.134C582.893 188.786 582.885 189.436 582.875 190.086C648.144 193.552 700 247.572 700 313.704C700 382.074 644.575 437.5 576.204 437.5H123.796C55.4255 437.5 7.42386e-05 382.074 0 313.704C0 250.475 47.4025 198.32 108.608 190.833C104.967 177.844 103.019 164.138 103.019 149.975C103.019 67.1461 169.641 0.000357483 251.823 0ZM234.696 141.93C218.117 134.066 199 146.158 199 164.507C199 174.32 204.744 183.225 213.684 187.271L336.761 242.985C337.971 243.533 338.748 244.738 338.748 246.066C338.748 247.394 337.971 248.6 336.761 249.147L213.684 304.861C204.744 308.908 199 317.812 199 327.625C199 345.974 218.117 358.066 234.696 350.202L364.672 288.554C375.142 283.588 381.815 273.036 381.815 261.448V230.684C381.815 219.096 375.142 208.544 364.672 203.578L234.696 141.93ZM413.877 296.146C402.078 296.146 392.513 305.711 392.513 317.511C392.513 329.31 402.078 338.875 413.877 338.875H526.636C538.435 338.875 548 329.31 548 317.511C548 305.711 538.435 296.146 526.636 296.146H413.877Z" fill="#000B1A"/>\r\n<path d="M2420 424.954H2343.35C2307.08 424.954 2278.64 416.094 2258.04 398.373C2237.43 380.652 2227.13 352.216 2227.13 313.065V55.2871H2319.86V302.556C2319.86 318.217 2323.15 328.726 2329.75 334.083C2336.75 339.028 2348.29 341.501 2364.36 341.501H2420V424.954ZM2420 191.903H2178.29V115.25H2420V191.903Z" fill="#000B1A"/>\r\n<path d="M1923.18 431.136C1894.33 431.136 1870.22 425.779 1850.85 415.064C1831.89 403.937 1817.68 389.101 1808.2 370.555C1799.13 352.01 1794.6 331.198 1794.6 308.12V115.25H1887.32V277.211C1887.32 301.938 1893.3 320.071 1905.25 331.61C1917.2 342.738 1937.81 348.301 1967.07 348.301C1997.56 348.301 2018.79 342.325 2030.74 330.374C2043.1 318.011 2049.28 298.641 2049.28 272.266L2061.03 271.648L2067.83 330.374H2050.52C2048.05 347.683 2041.87 363.962 2031.98 379.21C2022.5 394.458 2008.69 407.028 1990.56 416.918C1972.84 426.397 1950.38 431.136 1923.18 431.136ZM2142.01 424.955H2055.47V329.138L2049.28 326.047V115.25H2142.01V424.955Z" fill="#000B1A"/>\r\n<path d="M1734.32 424.955H1641.59V10.7793H1734.32V424.955Z" fill="#000B1A"/>\r\n<path d="M1362.98 431.136C1334.13 431.136 1310.03 425.779 1290.66 415.064C1271.7 403.937 1257.48 389.101 1248 370.555C1238.94 352.01 1234.4 331.198 1234.4 308.12V115.25H1327.13V277.211C1327.13 301.938 1333.1 320.071 1345.06 331.61C1357.01 342.738 1377.61 348.301 1406.87 348.301C1437.37 348.301 1458.59 342.325 1470.54 330.374C1482.91 318.011 1489.09 298.641 1489.09 272.266L1500.83 271.648L1507.63 330.374H1490.33C1487.85 347.683 1481.67 363.962 1471.78 379.21C1462.3 394.458 1448.5 407.028 1430.36 416.918C1412.64 426.397 1390.18 431.136 1362.98 431.136ZM1581.82 424.955H1495.27V329.138L1489.09 326.047V115.25H1581.82V424.955Z" fill="#000B1A"/>\r\n<path d="M1035.12 431.137C999.27 431.137 971.04 423.513 950.434 408.264C930.241 392.604 917.259 369.526 911.49 339.029H894.181L900.981 272.266H912.726C912.726 289.987 916.023 304.411 922.617 315.538C929.623 326.253 939.719 334.084 952.907 339.029C966.507 343.974 983.198 346.447 1002.98 346.447C1023.17 346.447 1039.66 343.974 1052.43 339.029C1065.21 334.084 1074.69 326.047 1080.87 314.92C1087.05 303.793 1090.14 288.957 1090.14 270.412C1090.14 251.042 1087.05 236 1080.87 225.285C1074.69 214.158 1065.21 206.122 1052.43 201.177C1039.66 196.231 1023.38 193.758 1003.6 193.758C973.513 193.758 950.847 199.322 935.598 210.449C920.35 221.576 912.726 240.533 912.726 267.321H900.981V197.467H917.671C923.029 171.504 935.392 150.28 954.762 133.796C974.543 117.311 1002.77 109.069 1039.45 109.069C1070.36 109.069 1096.53 115.663 1117.96 128.85C1139.39 142.038 1155.67 160.789 1166.79 185.104C1178.33 209.007 1184.1 237.443 1184.1 270.412C1184.1 302.969 1178.33 331.405 1166.79 355.72C1155.67 379.622 1138.98 398.168 1116.72 411.355C1094.47 424.543 1067.27 431.137 1035.12 431.137ZM905.926 424.955H820V10.7793H912.726V325.429L905.926 333.465V424.955Z" fill="#000B1A"/>\r\n</svg>\r\n';
428
427
  const microphoneIconContent = microphoneIconRaw;
429
428
  const restartIconContent = restartIconRaw;
430
429
  const closeIconContent = closeIconRaw;
@@ -506,7 +505,8 @@ const ChatButton = ({
506
505
  flexDirection: "column",
507
506
  alignItems: "flex-end",
508
507
  gap: "8px",
509
- zIndex: "9999"
508
+ zIndex: "9999",
509
+ fontFamily: '"Geist", sans-serif'
510
510
  };
511
511
  const controlsRowStyle = {
512
512
  display: "flex",
@@ -548,7 +548,7 @@ const ChatButton = ({
548
548
  height: "20px",
549
549
  borderRadius: "50%",
550
550
  border: "none",
551
- background: "rgba(0,0,0,0.08)",
551
+ background: "transparent",
552
552
  cursor: "pointer",
553
553
  display: "flex",
554
554
  alignItems: "center",
@@ -602,17 +602,18 @@ const ChatButton = ({
602
602
  );
603
603
  return /* @__PURE__ */ u$1(k$1, { children: [
604
604
  /* @__PURE__ */ u$1("style", { children: `
605
+ @import url('https://fonts.googleapis.com/css2?family=Geist:wght@100..900&display=swap');
606
+
605
607
  .bulut-popup {
606
608
  background: #ffffff;
607
609
  color: ${COLORS.text};
608
610
  padding: 10px 14px;
609
611
  border-radius: 12px;
610
- font-size: 13px;
612
+ font-size: 14px;
611
613
  line-height: 1.4;
612
614
  position: relative;
613
615
  overflow: visible;
614
616
  box-shadow: ${SHADOW};
615
- border: 1px solid ${COLORS.border};
616
617
  }
617
618
  .bulut-popup-bubble {
618
619
  animation: bulut-bubbleIn 400ms ease-out;
@@ -719,1154 +720,417 @@ const ChatButton = ({
719
720
  ] }) })
720
721
  ] });
721
722
  };
722
- const TTS_WS_RETRY_DELAYS_MS = [250, 750, 1500];
723
- const FORCED_TTS_VOICE = "zeynep";
724
- const normalizeBaseUrl = (baseUrl) => {
725
- const trimmed = baseUrl.trim().replace(/\/+$/, "");
726
- if (/^https?:\/\//i.test(trimmed)) {
727
- return trimmed;
723
+ const MAX_LINKS = 20;
724
+ const MAX_INTERACTABLES = 24;
725
+ const MAX_HEADINGS = 10;
726
+ const MAX_TEXT_SNIPPETS = 4;
727
+ const MAX_OUTER_HTML_DIGEST = 760;
728
+ const MAX_CACHED_PAGES = 20;
729
+ const MAX_PAGE_SCAN_ELEMENTS = 2e3;
730
+ const MAX_EVENT_HINTS_PER_ELEMENT = 4;
731
+ const MAX_BRANCH_SAMPLES = 4;
732
+ const MAX_BRANCH_DEPTH = 2;
733
+ const MAX_CONTEXT_SUMMARY_CHARS = 3400;
734
+ const MAX_CONTEXT_WITH_HISTORY_CHARS = 4200;
735
+ const PAGE_CONTEXT_CACHE_VERSION = 2;
736
+ const PAGE_CONTEXT_CACHE_KEY = "auticbot_page_context_cache_v2";
737
+ const NON_CONTENT_TAGS = /* @__PURE__ */ new Set([
738
+ "script",
739
+ "style",
740
+ "noscript",
741
+ "template",
742
+ "link",
743
+ "meta"
744
+ ]);
745
+ const NATIVE_INTERACTIVE_TAGS = /* @__PURE__ */ new Set([
746
+ "a",
747
+ "button",
748
+ "input",
749
+ "textarea",
750
+ "select",
751
+ "summary",
752
+ "details",
753
+ "option"
754
+ ]);
755
+ const INTERACTIVE_ROLES = /* @__PURE__ */ new Set([
756
+ "button",
757
+ "link",
758
+ "tab",
759
+ "menuitem",
760
+ "option",
761
+ "checkbox",
762
+ "radio",
763
+ "switch",
764
+ "combobox",
765
+ "textbox",
766
+ "searchbox",
767
+ "slider",
768
+ "spinbutton",
769
+ "treeitem"
770
+ ]);
771
+ const TRACKED_DISPLAY_VALUES = /* @__PURE__ */ new Set([
772
+ "block",
773
+ "inline",
774
+ "inline-block",
775
+ "flex",
776
+ "inline-flex",
777
+ "grid",
778
+ "inline-grid"
779
+ ]);
780
+ const TRACKED_POSITION_VALUES = /* @__PURE__ */ new Set([
781
+ "relative",
782
+ "absolute",
783
+ "fixed",
784
+ "sticky"
785
+ ]);
786
+ const EVENT_HINT_NAMES = [
787
+ "click",
788
+ "dblclick",
789
+ "mousedown",
790
+ "mouseup",
791
+ "pointerdown",
792
+ "pointerup",
793
+ "touchstart",
794
+ "touchend",
795
+ "keydown",
796
+ "keyup",
797
+ "keypress",
798
+ "input",
799
+ "change",
800
+ "submit",
801
+ "focus",
802
+ "blur"
803
+ ];
804
+ const ARIA_INTERACTION_ATTRS = [
805
+ "aria-controls",
806
+ "aria-expanded",
807
+ "aria-haspopup",
808
+ "aria-pressed",
809
+ "aria-selected"
810
+ ];
811
+ const DATA_INTERACTION_PATTERN = /(action|click|press|toggle|target|trigger|nav|open|close|menu|modal|command|submit)/i;
812
+ const pageContextCache = /* @__PURE__ */ new Map();
813
+ let cacheHydrated = false;
814
+ const normalizeWhitespace = (value) => value.replace(/\s+/g, " ").trim();
815
+ const truncate = (value, maxChars) => {
816
+ if (value.length <= maxChars) {
817
+ return value;
728
818
  }
729
- return `https://${trimmed}`;
730
- };
731
- const toWebSocketUrl = (baseUrl, path2) => {
732
- const normalized = normalizeBaseUrl(baseUrl);
733
- const url = new URL(normalized);
734
- url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
735
- url.pathname = `${url.pathname.replace(/\/$/, "")}${path2}`;
736
- url.search = "";
737
- url.hash = "";
738
- return url.toString();
819
+ const suffix = "\n...[truncated]";
820
+ return `${value.slice(0, Math.max(0, maxChars - suffix.length))}${suffix}`;
739
821
  };
740
- const createRequestId = () => {
741
- if (typeof crypto !== "undefined" && "randomUUID" in crypto) {
742
- return crypto.randomUUID();
822
+ const truncateInline = (value, maxChars) => {
823
+ if (value.length <= maxChars) {
824
+ return value;
743
825
  }
744
- return `tts-${Date.now()}-${Math.random().toString(16).slice(2)}`;
826
+ return `${value.slice(0, Math.max(0, maxChars - 3))}...`;
745
827
  };
746
- const parseTtsWsEventPayload = (value) => {
828
+ const canonicalUrl = (rawUrl) => {
747
829
  try {
748
- if (typeof value !== "string") {
749
- return null;
750
- }
751
- return JSON.parse(value);
830
+ return new URL(rawUrl, rawUrl).href;
752
831
  } catch {
753
- return null;
832
+ return rawUrl;
754
833
  }
755
834
  };
756
- const shouldAcceptAudioSeq = (incomingSeq, highestSeqSeen) => incomingSeq > highestSeqSeen;
757
- const shouldFallbackToSse = (error) => {
758
- if (typeof error === "object" && error !== null && "retryable" in error) {
759
- return Boolean(error.retryable);
835
+ const isCacheEntry = (value) => {
836
+ if (typeof value !== "object" || value === null) {
837
+ return false;
760
838
  }
761
- return true;
839
+ const obj = value;
840
+ return typeof obj.url === "string" && typeof obj.summary === "string" && Array.isArray(obj.links) && Array.isArray(obj.interactables) && typeof obj.capturedAt === "number" && typeof obj.version === "number";
762
841
  };
763
- const parseErrorBody = async (response) => {
764
- try {
765
- const data2 = await response.json();
766
- const detail = data2.detail;
767
- if (typeof detail === "string") return detail;
768
- if (detail && typeof detail === "object") return JSON.stringify(detail);
769
- return data2.error || data2.message || response.statusText;
770
- } catch {
771
- return response.statusText;
842
+ const bumpCount = (map, key) => {
843
+ if (!key) {
844
+ return;
772
845
  }
846
+ map.set(key, (map.get(key) ?? 0) + 1);
773
847
  };
774
- const sleep = (ms) => new Promise((resolve) => {
775
- setTimeout(resolve, ms);
776
- });
777
- const base64ToUint8Array = (base64) => {
778
- const cleanBase64 = base64.replace(/^data:audio\/\w+;base64,/, "");
779
- const binaryString = atob(cleanBase64);
780
- const bytes = new Uint8Array(binaryString.length);
781
- for (let i = 0; i < binaryString.length; i += 1) {
782
- bytes[i] = binaryString.charCodeAt(i);
848
+ const formatTopCounts = (map, maxItems) => {
849
+ if (map.size === 0) {
850
+ return "none";
783
851
  }
784
- return bytes;
852
+ return Array.from(map.entries()).sort((a2, b) => b[1] - a2[1] || a2[0].localeCompare(b[0])).slice(0, maxItems).map(([name, count]) => `${name}*${count}`).join(", ");
785
853
  };
786
- const createWavHeader = (length, sampleRate = 16e3) => {
787
- const buffer = new ArrayBuffer(44);
788
- const view = new DataView(buffer);
789
- const channels = 1;
790
- view.setUint32(0, 1380533830, false);
791
- view.setUint32(4, 36 + length, true);
792
- view.setUint32(8, 1463899717, false);
793
- view.setUint32(12, 1718449184, false);
794
- view.setUint32(16, 16, true);
795
- view.setUint16(20, 1, true);
796
- view.setUint16(22, channels, true);
797
- view.setUint32(24, sampleRate, true);
798
- view.setUint32(28, sampleRate * channels * 2, true);
799
- view.setUint16(32, channels * 2, true);
800
- view.setUint16(34, 16, true);
801
- view.setUint32(36, 1684108385, false);
802
- view.setUint32(40, length, true);
803
- return new Uint8Array(buffer);
854
+ const parseTabIndex = (value) => {
855
+ if (value === null) {
856
+ return null;
857
+ }
858
+ const parsed = Number.parseInt(value, 10);
859
+ return Number.isNaN(parsed) ? null : parsed;
804
860
  };
805
- const waitForPlaybackEnd = async (audioElement) => {
806
- if (audioElement.ended) {
807
- return;
861
+ const compactToken = (value, maxChars = 18) => {
862
+ const compact = value.replace(/\s+/g, "-").replace(/[^a-zA-Z0-9_-]/g, "");
863
+ return compact ? truncateInline(compact, maxChars) : "";
864
+ };
865
+ const getElementDepth = (element) => {
866
+ let depth = 0;
867
+ let cursor = element;
868
+ while (cursor == null ? void 0 : cursor.parentElement) {
869
+ depth += 1;
870
+ cursor = cursor.parentElement;
871
+ if (cursor === document.body) {
872
+ break;
873
+ }
808
874
  }
809
- await new Promise((resolve, reject) => {
810
- const watchdog = window.setInterval(() => {
811
- if (!audioElement.ended) {
812
- console.info("[Bulut] playback watchdog: still playing...");
813
- }
814
- }, 3e4);
815
- const onEnded = () => {
816
- cleanup();
817
- resolve();
818
- };
819
- const onError = () => {
820
- cleanup();
821
- reject(new Error("Ses oynatma hatası oluştu."));
822
- };
823
- const cleanup = () => {
824
- window.clearInterval(watchdog);
825
- audioElement.removeEventListener("ended", onEnded);
826
- audioElement.removeEventListener("error", onError);
827
- };
828
- audioElement.addEventListener("ended", onEnded);
829
- audioElement.addEventListener("error", onError);
830
- });
875
+ return depth;
831
876
  };
832
- const playBufferedAudio = async (chunks, mimeType, sampleRate = 16e3, onAudioStateChange) => {
833
- if (chunks.length === 0) {
834
- onAudioStateChange == null ? void 0 : onAudioStateChange("done");
877
+ const getPrimaryRole = (element) => {
878
+ const rawRole = normalizeWhitespace(element.getAttribute("role") || "").toLowerCase().split(" ")[0];
879
+ return rawRole || "";
880
+ };
881
+ const hydrateCacheFromStorage = () => {
882
+ if (cacheHydrated || typeof sessionStorage === "undefined") {
835
883
  return;
836
884
  }
837
- const totalBytes = chunks.reduce((acc, c2) => acc + c2.byteLength, 0);
838
- console.log(`[Bulut] Playing buffered audio: ${chunks.length} chunks, ${totalBytes} bytes, type=${mimeType}`);
839
- onAudioStateChange == null ? void 0 : onAudioStateChange("fallback");
840
- const blobParts = chunks.map((chunk) => {
841
- const copied = new Uint8Array(chunk.byteLength);
842
- copied.set(chunk);
843
- return copied.buffer;
844
- });
845
- let detectedMime = mimeType;
846
- if (chunks.length > 0 && chunks[0].length >= 4) {
847
- const header = Array.from(chunks[0].slice(0, 4)).map((b) => b.toString(16).padStart(2, "0").toUpperCase()).join(" ");
848
- console.log(`[Bulut] Audio header (hex): ${header}`);
849
- if (header.startsWith("49 44 33")) {
850
- detectedMime = "audio/mpeg";
851
- } else if (header.startsWith("FF F3") || header.startsWith("FF F2")) {
852
- detectedMime = "audio/mpeg";
853
- } else if (header.startsWith("52 49 46 46")) {
854
- detectedMime = "audio/wav";
855
- } else if (header.startsWith("1A 45 DF A3")) {
856
- detectedMime = "audio/webm";
885
+ cacheHydrated = true;
886
+ try {
887
+ const raw = sessionStorage.getItem(PAGE_CONTEXT_CACHE_KEY);
888
+ if (!raw) {
889
+ return;
890
+ }
891
+ const parsed = JSON.parse(raw);
892
+ if (!Array.isArray(parsed)) {
893
+ return;
894
+ }
895
+ for (const value of parsed) {
896
+ if (!isCacheEntry(value)) {
897
+ continue;
898
+ }
899
+ if (value.version !== PAGE_CONTEXT_CACHE_VERSION) {
900
+ continue;
901
+ }
902
+ pageContextCache.set(value.url, value);
903
+ }
904
+ if (pageContextCache.size > 0) {
905
+ console.info(
906
+ `[Autic] context cache restored entries=${pageContextCache.size}`
907
+ );
857
908
  }
909
+ } catch (error) {
910
+ console.warn("[Autic] context cache restore failed", error);
858
911
  }
859
- let safeMimeType = detectedMime && detectedMime.includes("/") ? detectedMime : "audio/mpeg";
860
- let finalBlobParts = blobParts;
861
- if (mimeType === "audio/pcm") {
862
- const totalLength = chunks.reduce((acc, c2) => acc + c2.byteLength, 0);
863
- const header = createWavHeader(totalLength, sampleRate);
864
- finalBlobParts = [header.buffer, ...blobParts];
865
- safeMimeType = "audio/wav";
866
- console.log(`[Bulut] Wrapped raw PCM in WAV (rate=${sampleRate})`);
912
+ };
913
+ const persistCacheToStorage = () => {
914
+ if (typeof sessionStorage === "undefined") {
915
+ return;
867
916
  }
868
- console.log(`[Bulut] Creating blob with type: ${safeMimeType} (original: ${mimeType})`);
869
- const blob = new Blob(finalBlobParts, { type: safeMimeType });
870
- const audioElement = new Audio();
871
- const objectUrl = URL.createObjectURL(blob);
872
917
  try {
873
- audioElement.preload = "auto";
874
- audioElement.autoplay = true;
875
- audioElement.setAttribute("playsinline", "true");
876
- audioElement.src = objectUrl;
877
- await audioElement.play();
878
- onAudioStateChange == null ? void 0 : onAudioStateChange("playing");
879
- await waitForPlaybackEnd(audioElement);
880
- onAudioStateChange == null ? void 0 : onAudioStateChange("done");
881
- } catch (err) {
882
- console.error(`[Bulut] Playback failed: ${err}`, { mimeType: safeMimeType, size: blob.size });
883
- onAudioStateChange == null ? void 0 : onAudioStateChange("done");
884
- throw err;
885
- } finally {
886
- audioElement.pause();
887
- audioElement.removeAttribute("src");
888
- audioElement.load();
889
- URL.revokeObjectURL(objectUrl);
918
+ const serialized = JSON.stringify(
919
+ Array.from(pageContextCache.values()).sort(
920
+ (a2, b) => a2.capturedAt - b.capturedAt
921
+ )
922
+ );
923
+ sessionStorage.setItem(PAGE_CONTEXT_CACHE_KEY, serialized);
924
+ } catch (error) {
925
+ console.warn("[Autic] context cache persist failed", error);
890
926
  }
891
927
  };
892
- const parseSseEventPayload = (eventBlock) => {
893
- const dataLines = eventBlock.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trimStart());
894
- if (dataLines.length === 0) {
895
- return null;
928
+ const pruneOldestCacheEntries = () => {
929
+ if (pageContextCache.size <= MAX_CACHED_PAGES) {
930
+ return;
896
931
  }
897
- const dataStr = dataLines.join("\n");
898
- if (dataStr === "[DONE]") {
899
- return { type: "done" };
932
+ const sorted = Array.from(pageContextCache.values()).sort(
933
+ (a2, b) => a2.capturedAt - b.capturedAt
934
+ );
935
+ const overflow = sorted.length - MAX_CACHED_PAGES;
936
+ for (let i = 0; i < overflow; i += 1) {
937
+ pageContextCache.delete(sorted[i].url);
938
+ }
939
+ };
940
+ const buildSummaryWithHistory = (current) => {
941
+ const recentPages = Array.from(pageContextCache.values()).filter((entry) => entry.url !== current.url).sort((a2, b) => b.capturedAt - a2.capturedAt).slice(0, 3);
942
+ if (recentPages.length === 0) {
943
+ return current.summary;
944
+ }
945
+ const historySection = [
946
+ "Recent Page Memory:",
947
+ ...recentPages.map((entry) => {
948
+ const compactSummary = normalizeWhitespace(entry.summary).slice(0, 180);
949
+ return `- ${entry.url} :: ${compactSummary}`;
950
+ })
951
+ ].join("\n");
952
+ return truncate(
953
+ `${current.summary}
954
+
955
+ ${historySection}`,
956
+ MAX_CONTEXT_WITH_HISTORY_CHARS
957
+ );
958
+ };
959
+ const isVisible = (element) => {
960
+ if (element.getAttribute("aria-hidden") === "true") {
961
+ return false;
962
+ }
963
+ if (element instanceof HTMLElement && element.hidden) {
964
+ return false;
965
+ }
966
+ const style = window.getComputedStyle(element);
967
+ if (style.display === "none" || style.visibility === "hidden") {
968
+ return false;
900
969
  }
970
+ const rect = element.getBoundingClientRect();
971
+ return rect.width > 0 && rect.height > 0;
972
+ };
973
+ const toAbsoluteUrl = (href) => {
901
974
  try {
902
- return JSON.parse(dataStr);
903
- } catch (error) {
904
- console.warn("Error parsing SSE chunk:", error);
905
- return null;
975
+ return new URL(href, window.location.href).href;
976
+ } catch {
977
+ return href;
906
978
  }
907
979
  };
908
- const isAudioSsePayload = (payload) => typeof payload.audio === "string" && (payload.type === void 0 || payload.type === "audio");
909
- async function transcribeAudio(baseUrl, file, projectId, sessionId, language) {
910
- const url = `${normalizeBaseUrl(baseUrl)}/chat/stt`;
911
- const formData = new FormData();
912
- formData.append("file", file);
913
- formData.append("project_id", projectId);
914
- if (sessionId) formData.append("session_id", sessionId);
915
- formData.append("language", language);
916
- const response = await fetch(url, { method: "POST", body: formData });
917
- if (!response.ok) {
918
- throw new Error(await parseErrorBody(response));
980
+ const escapeCssValue = (value) => {
981
+ if (typeof CSS !== "undefined" && typeof CSS.escape === "function") {
982
+ return CSS.escape(value);
919
983
  }
920
- return response.json();
921
- }
922
- const buildError = (message, retryable = true) => {
923
- const error = new Error(message);
924
- error.retryable = retryable;
925
- return error;
984
+ return value.replace(/([ #;&,.+*~':"!^$\[\]()=>|\/@])/g, "\\$1");
926
985
  };
927
- const collectTtsViaSse = async (baseUrl, assistantText, accessibilityMode, isStopped, setReader) => {
928
- var _a;
929
- const ttsFormData = new FormData();
930
- ttsFormData.append("text", assistantText);
931
- ttsFormData.append("voice", FORCED_TTS_VOICE);
932
- ttsFormData.append("accessibility_mode", String(accessibilityMode));
933
- const ttsResponse = await fetch(`${normalizeBaseUrl(baseUrl)}/chat/tts`, {
934
- method: "POST",
935
- body: ttsFormData
936
- });
937
- if (!ttsResponse.ok) {
938
- throw buildError(await parseErrorBody(ttsResponse), false);
986
+ const buildSelector = (element) => {
987
+ const tag = element.tagName.toLowerCase();
988
+ if (element.id) {
989
+ return `#${escapeCssValue(element.id)}`;
939
990
  }
940
- const reader = (_a = ttsResponse.body) == null ? void 0 : _a.getReader();
941
- if (!reader) {
942
- throw buildError("TTS response body is not readable", false);
991
+ const name = element.getAttribute("name");
992
+ if (name) {
993
+ return `${tag}[name="${escapeCssValue(name)}"]`;
943
994
  }
944
- setReader(reader);
945
- const chunks = [];
946
- let mimeType = "audio/mpeg";
947
- let sampleRate = 16e3;
948
- const decoder = new TextDecoder();
949
- let buffer = "";
950
- while (true) {
951
- if (isStopped()) {
952
- break;
995
+ const ariaLabel = element.getAttribute("aria-label");
996
+ if (ariaLabel) {
997
+ return `${tag}[aria-label="${escapeCssValue(ariaLabel)}"]`;
998
+ }
999
+ const classes = Array.from(element.classList).filter(Boolean).slice(0, 2).map((className) => `.${escapeCssValue(className)}`).join("");
1000
+ if (classes) {
1001
+ return `${tag}${classes}`;
1002
+ }
1003
+ const parent2 = element.parentElement;
1004
+ if (!parent2) {
1005
+ return tag;
1006
+ }
1007
+ const siblingsOfTag = Array.from(parent2.children).filter(
1008
+ (sibling) => sibling.tagName === element.tagName
1009
+ );
1010
+ const index = siblingsOfTag.indexOf(element) + 1;
1011
+ return `${tag}:nth-of-type(${index})`;
1012
+ };
1013
+ const getElementLabel = (element) => {
1014
+ const text = normalizeWhitespace(
1015
+ (element instanceof HTMLElement ? element.innerText : element.textContent) || ""
1016
+ );
1017
+ const ariaLabel = normalizeWhitespace(element.getAttribute("aria-label") || "");
1018
+ const title = normalizeWhitespace(element.getAttribute("title") || "");
1019
+ const placeholder = normalizeWhitespace(
1020
+ element.getAttribute("placeholder") || ""
1021
+ );
1022
+ const name = normalizeWhitespace(element.getAttribute("name") || "");
1023
+ const value = element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement || element instanceof HTMLButtonElement ? normalizeWhitespace(element.value || "") : "";
1024
+ const classHint = Array.from(element.classList).map((item) => compactToken(item, 16)).find(Boolean);
1025
+ const fallback = element.id && `#${element.id}` || classHint && `.${classHint}` || buildSelector(element);
1026
+ const label = text || ariaLabel || title || placeholder || value || name || fallback;
1027
+ if (element.tagName.toLowerCase() === "input") {
1028
+ const inputType = element.getAttribute("type") || "text";
1029
+ return `${inputType} ${label || "input"}`;
1030
+ }
1031
+ return label || "untitled";
1032
+ };
1033
+ const getEventHints = (element) => {
1034
+ const record = element;
1035
+ const eventHints = [];
1036
+ for (const eventName of EVENT_HINT_NAMES) {
1037
+ const handlerKey = `on${eventName}`;
1038
+ const hasInlineHandler = Boolean(element.getAttribute(handlerKey));
1039
+ const hasPropertyHandler = typeof record[handlerKey] === "function";
1040
+ if (!hasInlineHandler && !hasPropertyHandler) {
1041
+ continue;
953
1042
  }
954
- const { done, value } = await reader.read();
955
- if (done) {
1043
+ eventHints.push(eventName);
1044
+ if (eventHints.length >= MAX_EVENT_HINTS_PER_ELEMENT) {
956
1045
  break;
957
1046
  }
958
- buffer += decoder.decode(value, { stream: true });
959
- const blocks = buffer.split(/\r?\n\r?\n/);
960
- buffer = blocks.pop() || "";
961
- for (const block of blocks) {
962
- const payload = parseSseEventPayload(block);
963
- if (!payload) {
964
- continue;
965
- }
966
- if (isAudioSsePayload(payload)) {
967
- const format = payload.format || "mp3";
968
- mimeType = payload.mime_type || (format === "webm" ? "audio/webm" : "audio/mpeg");
969
- chunks.push(base64ToUint8Array(payload.audio));
970
- if (payload.sample_rate) {
971
- sampleRate = payload.sample_rate;
972
- }
973
- }
974
- }
975
1047
  }
976
- reader.releaseLock();
977
- setReader(void 0);
978
- return { chunks, mimeType, sampleRate };
1048
+ return eventHints;
979
1049
  };
980
- const collectTtsViaWebSocket = async (baseUrl, assistantText, accessibilityMode, isStopped, setSocket) => {
981
- const wsUrl = toWebSocketUrl(baseUrl, "/chat/tts/ws");
982
- const requestId = createRequestId();
983
- const chunks = [];
984
- let mimeType = "audio/mpeg";
985
- let sampleRate = 16e3;
986
- let highestSeqSeen = 0;
987
- const connectOnce = () => new Promise((resolve, reject) => {
988
- if (isStopped()) {
989
- reject(buildError("stream_stopped", false));
990
- return;
1050
+ const getAriaInteractionHints = (element) => ARIA_INTERACTION_ATTRS.filter((attrName) => element.hasAttribute(attrName)).map(
1051
+ (attrName) => attrName.replace("aria-", "")
1052
+ );
1053
+ const getDataInteractionHints = (element) => element.getAttributeNames().filter(
1054
+ (attrName) => attrName.startsWith("data-") && DATA_INTERACTION_PATTERN.test(attrName)
1055
+ ).slice(0, 2).map((attrName) => attrName.replace("data-", ""));
1056
+ const getStyleHints = (style) => {
1057
+ const styleHints = [];
1058
+ if (style.cursor === "pointer") {
1059
+ styleHints.push("cursor:pointer");
1060
+ }
1061
+ if (style.display === "flex" || style.display === "grid" || style.display === "inline-flex" || style.display === "inline-grid") {
1062
+ styleHints.push(`display:${style.display}`);
1063
+ }
1064
+ if (style.position === "fixed" || style.position === "sticky") {
1065
+ styleHints.push(`position:${style.position}`);
1066
+ }
1067
+ return styleHints.slice(0, 2);
1068
+ };
1069
+ const buildBlueprintToken = (element) => {
1070
+ const tag = element.tagName.toLowerCase();
1071
+ const idToken = element.id ? `#${compactToken(element.id)}` : "";
1072
+ const classToken = Array.from(element.classList).map((item) => compactToken(item, 16)).find(Boolean);
1073
+ return `${tag}${idToken}${classToken ? `.${classToken}` : ""}`;
1074
+ };
1075
+ const buildBranchDigest = (element, depth) => {
1076
+ const token = buildBlueprintToken(element);
1077
+ if (depth <= 0) {
1078
+ return token;
1079
+ }
1080
+ const children = Array.from(element.children).filter((child) => !NON_CONTENT_TAGS.has(child.tagName.toLowerCase())).filter((child) => isVisible(child));
1081
+ if (children.length === 0) {
1082
+ return token;
1083
+ }
1084
+ const sampled = children.slice(0, 3).map((child) => buildBranchDigest(child, depth - 1));
1085
+ const overflow = children.length > sampled.length ? `+${children.length - sampled.length}` : "";
1086
+ return `${token}>${sampled.join("+")}${overflow}`;
1087
+ };
1088
+ const collectDomBranchDigest = () => {
1089
+ const root = document.body ?? document.documentElement;
1090
+ const topLevelNodes = Array.from(root.children).filter((child) => !NON_CONTENT_TAGS.has(child.tagName.toLowerCase())).filter((child) => isVisible(child)).slice(0, MAX_BRANCH_SAMPLES);
1091
+ return topLevelNodes.map(
1092
+ (child) => truncateInline(buildBranchDigest(child, MAX_BRANCH_DEPTH), 140)
1093
+ );
1094
+ };
1095
+ const formatSection = (title, lines) => {
1096
+ if (lines.length === 0) {
1097
+ return `${title}:
1098
+ - none`;
1099
+ }
1100
+ return `${title}:
1101
+ ${lines.join("\n")}`;
1102
+ };
1103
+ const buildOuterHtmlDigest = () => {
1104
+ var _a;
1105
+ const raw = ((_a = document.body) == null ? void 0 : _a.outerHTML) || document.documentElement.outerHTML;
1106
+ const withoutScripts = raw.replace(/<script[\s\S]*?<\/script>/gi, "").replace(/<style[\s\S]*?<\/style>/gi, "").replace(/<noscript[\s\S]*?<\/noscript>/gi, "").replace(/<!--[\s\S]*?-->/g, "").replace(/\s+/g, " ").trim();
1107
+ const structural = withoutScripts.replace(/>[^<]*</g, "><").replace(/\s+/g, " ").trim();
1108
+ return truncate(structural, MAX_OUTER_HTML_DIGEST);
1109
+ };
1110
+ const collectTextSnippets = () => {
1111
+ const root = document.querySelector("main, article, [role='main']") ?? document.body;
1112
+ const snippets = [];
1113
+ const seen = /* @__PURE__ */ new Set();
1114
+ const candidates = Array.from(root.querySelectorAll("p, li, h1, h2, h3"));
1115
+ for (const node of candidates) {
1116
+ if (!isVisible(node)) {
1117
+ continue;
991
1118
  }
992
- let done = false;
993
- let finalError = null;
994
- const socket = new WebSocket(wsUrl);
995
- setSocket(socket);
996
- const finalize = (mode, error) => {
997
- socket.onopen = null;
998
- socket.onmessage = null;
999
- socket.onerror = null;
1000
- socket.onclose = null;
1001
- setSocket(null);
1002
- if (mode === "resolve") {
1003
- resolve();
1004
- return;
1005
- }
1006
- reject(error || buildError("tts_ws_closed", true));
1007
- };
1008
- socket.onopen = () => {
1009
- console.info(
1010
- `[Bulut] TTS WS connected request_id=${requestId} resume_seq=${highestSeqSeen}`
1011
- );
1012
- socket.send(
1013
- JSON.stringify({
1014
- type: "start",
1015
- request_id: requestId,
1016
- text: assistantText,
1017
- voice: FORCED_TTS_VOICE,
1018
- accessibility_mode: accessibilityMode,
1019
- last_seq: highestSeqSeen
1020
- })
1021
- );
1022
- };
1023
- socket.onmessage = (event) => {
1024
- const payload = parseTtsWsEventPayload(String(event.data));
1025
- if (!payload) {
1026
- console.warn("[Bulut] TTS WS invalid JSON payload");
1027
- return;
1028
- }
1029
- if (payload.type === "audio" && typeof payload.audio === "string") {
1030
- const seq = typeof payload.seq === "number" ? payload.seq : 0;
1031
- if (shouldAcceptAudioSeq(seq, highestSeqSeen)) {
1032
- chunks.push(base64ToUint8Array(payload.audio));
1033
- highestSeqSeen = seq;
1034
- if (payload.mime_type) {
1035
- mimeType = payload.mime_type;
1036
- }
1037
- if (typeof payload.sample_rate === "number") {
1038
- sampleRate = payload.sample_rate;
1039
- }
1040
- } else {
1041
- console.info(
1042
- `[Bulut] TTS WS duplicate chunk ignored request_id=${requestId} seq=${seq} seen=${highestSeqSeen}`
1043
- );
1044
- }
1045
- if (socket.readyState === WebSocket.OPEN) {
1046
- socket.send(
1047
- JSON.stringify({
1048
- type: "ack",
1049
- request_id: requestId,
1050
- last_seq: highestSeqSeen
1051
- })
1052
- );
1053
- }
1054
- return;
1055
- }
1056
- if (payload.type === "done") {
1057
- const streamLastSeq = typeof payload.last_seq === "number" ? payload.last_seq : highestSeqSeen;
1058
- if (streamLastSeq > highestSeqSeen) {
1059
- finalError = buildError("tts_ws_sequence_gap", true);
1060
- done = false;
1061
- socket.close();
1062
- return;
1063
- }
1064
- done = true;
1065
- socket.close();
1066
- return;
1067
- }
1068
- if (payload.type === "error") {
1069
- finalError = buildError(payload.error || "tts_ws_error", payload.retryable !== false);
1070
- done = false;
1071
- socket.close();
1072
- }
1073
- };
1074
- socket.onerror = () => {
1075
- if (!finalError) {
1076
- finalError = buildError("tts_ws_transport_error", true);
1077
- }
1078
- };
1079
- socket.onclose = () => {
1080
- if (isStopped()) {
1081
- finalize("reject", buildError("stream_stopped", false));
1082
- return;
1083
- }
1084
- if (done) {
1085
- finalize("resolve");
1086
- return;
1087
- }
1088
- finalize("reject", finalError || buildError("tts_ws_closed_before_done", true));
1089
- };
1090
- });
1091
- for (let attempt = 0; attempt <= TTS_WS_RETRY_DELAYS_MS.length; attempt += 1) {
1092
- if (attempt > 0) {
1093
- const delay = TTS_WS_RETRY_DELAYS_MS[attempt - 1];
1094
- console.warn(
1095
- `[Bulut] TTS WS retry attempt=${attempt} delay_ms=${delay} last_seq=${highestSeqSeen}`
1096
- );
1097
- await sleep(delay);
1119
+ const text = normalizeWhitespace(node.textContent || "");
1120
+ if (!text || text.length < 20) {
1121
+ continue;
1098
1122
  }
1099
- try {
1100
- await connectOnce();
1101
- return { chunks, mimeType, sampleRate };
1102
- } catch (error) {
1103
- const retryable = shouldFallbackToSse(error);
1104
- const message = error instanceof Error ? error.message : String(error);
1105
- console.warn(
1106
- `[Bulut] TTS WS attempt failed attempt=${attempt} retryable=${retryable} error=${message}`
1107
- );
1108
- if (!retryable || attempt === TTS_WS_RETRY_DELAYS_MS.length) {
1109
- throw error;
1110
- }
1123
+ const compact = truncateInline(text, 180);
1124
+ if (seen.has(compact)) {
1125
+ continue;
1126
+ }
1127
+ seen.add(compact);
1128
+ snippets.push(`- ${compact}`);
1129
+ if (snippets.length >= MAX_TEXT_SNIPPETS) {
1130
+ break;
1111
1131
  }
1112
1132
  }
1113
- throw buildError("tts_ws_exhausted", true);
1114
- };
1115
- const voiceChatStream = (baseUrl, audioFile, projectId, sessionId, config, events) => {
1116
- let isStopped = false;
1117
- let activeReader;
1118
- let activeSocket = null;
1119
- const donePromise = new Promise(async (resolve, reject) => {
1120
- var _a, _b, _c, _d, _e, _f, _g, _h;
1121
- try {
1122
- if (isStopped) return resolve();
1123
- const sttResult = await transcribeAudio(baseUrl, audioFile, projectId, sessionId, "tr");
1124
- const currentSessionId = sttResult.session_id;
1125
- const userText = sttResult.text;
1126
- (_a = events.onTranscription) == null ? void 0 : _a.call(events, {
1127
- session_id: currentSessionId,
1128
- user_text: userText
1129
- });
1130
- if (isStopped) return resolve();
1131
- const llmFormData = new FormData();
1132
- llmFormData.append("project_id", projectId);
1133
- llmFormData.append("session_id", currentSessionId);
1134
- llmFormData.append("user_text", userText);
1135
- llmFormData.append("model", config.model);
1136
- if (config.pageContext) llmFormData.append("page_context", config.pageContext);
1137
- llmFormData.append("accessibility_mode", String(Boolean(config.accessibilityMode)));
1138
- const llmResponse = await fetch(`${normalizeBaseUrl(baseUrl)}/chat/llm`, {
1139
- method: "POST",
1140
- body: llmFormData
1141
- });
1142
- if (!llmResponse.ok) {
1143
- throw new Error(await parseErrorBody(llmResponse));
1144
- }
1145
- activeReader = (_b = llmResponse.body) == null ? void 0 : _b.getReader();
1146
- if (!activeReader) throw new Error("LLM response body is not readable");
1147
- const decoder = new TextDecoder();
1148
- let buffer = "";
1149
- let assistantText = "";
1150
- while (true) {
1151
- if (isStopped) break;
1152
- const { done, value } = await activeReader.read();
1153
- if (done) break;
1154
- buffer += decoder.decode(value, { stream: true });
1155
- const chunks = buffer.split(/\r?\n\r?\n/);
1156
- buffer = chunks.pop() || "";
1157
- for (const chunk of chunks) {
1158
- const data2 = parseSseEventPayload(chunk);
1159
- if (!data2) continue;
1160
- if (data2.type === "session" && data2.session_id) {
1161
- (_c = events.onTranscription) == null ? void 0 : _c.call(events, {
1162
- session_id: data2.session_id,
1163
- user_text: sttResult.text
1164
- });
1165
- continue;
1166
- }
1167
- if (data2.type === "llm_delta" && typeof data2.delta === "string") {
1168
- (_d = events.onAssistantDelta) == null ? void 0 : _d.call(events, data2.delta);
1169
- continue;
1170
- }
1171
- if (data2.type === "llm_done") {
1172
- assistantText = data2.assistant_text || "";
1173
- (_e = events.onAssistantDone) == null ? void 0 : _e.call(events, assistantText);
1174
- continue;
1175
- }
1176
- if (data2.type === "error") {
1177
- throw new Error(data2.error || "LLM Error");
1178
- }
1179
- }
1180
- }
1181
- if (activeReader) {
1182
- activeReader.releaseLock();
1183
- activeReader = void 0;
1184
- }
1185
- if (isStopped || !assistantText) {
1186
- return resolve();
1187
- }
1188
- console.info(
1189
- `[Bulut] TTS start mode=voice requested_voice=${config.voice} forced_voice=${FORCED_TTS_VOICE} accessibility_mode=${Boolean(config.accessibilityMode)}`
1190
- );
1191
- (_f = events.onAudioStateChange) == null ? void 0 : _f.call(events, "rendering");
1192
- let ttsResult;
1193
- try {
1194
- ttsResult = await collectTtsViaWebSocket(
1195
- baseUrl,
1196
- assistantText,
1197
- Boolean(config.accessibilityMode),
1198
- () => isStopped,
1199
- (socket) => {
1200
- activeSocket = socket;
1201
- }
1202
- );
1203
- } catch (wsError) {
1204
- if (isStopped) {
1205
- return resolve();
1206
- }
1207
- console.warn(
1208
- `[Bulut] TTS WS failed, falling back to SSE: ${wsError instanceof Error ? wsError.message : String(wsError)}`
1209
- );
1210
- ttsResult = await collectTtsViaSse(
1211
- baseUrl,
1212
- assistantText,
1213
- Boolean(config.accessibilityMode),
1214
- () => isStopped,
1215
- (reader) => {
1216
- activeReader = reader;
1217
- }
1218
- );
1219
- }
1220
- if (!isStopped && ttsResult.chunks.length > 0) {
1221
- await playBufferedAudio(
1222
- ttsResult.chunks,
1223
- ttsResult.mimeType,
1224
- ttsResult.sampleRate,
1225
- events.onAudioStateChange
1226
- );
1227
- } else {
1228
- (_g = events.onAudioStateChange) == null ? void 0 : _g.call(events, "done");
1229
- }
1230
- resolve();
1231
- } catch (err) {
1232
- const msg = err instanceof Error ? err.message : String(err);
1233
- (_h = events.onError) == null ? void 0 : _h.call(events, msg);
1234
- reject(err);
1235
- } finally {
1236
- activeReader == null ? void 0 : activeReader.cancel().catch(() => {
1237
- });
1238
- if (activeSocket && activeSocket.readyState <= WebSocket.OPEN) {
1239
- activeSocket.close();
1240
- }
1241
- activeSocket = null;
1242
- }
1243
- });
1244
- return {
1245
- stop: () => {
1246
- isStopped = true;
1247
- if (activeReader) {
1248
- activeReader.cancel().catch(() => {
1249
- });
1250
- }
1251
- if (activeSocket && activeSocket.readyState <= WebSocket.OPEN) {
1252
- activeSocket.close();
1253
- }
1254
- },
1255
- done: donePromise
1256
- };
1257
- };
1258
- const agentVoiceChatStream = (baseUrl, audioFile, projectId, sessionId, config, events, executeTool) => {
1259
- let isStopped = false;
1260
- let activeSocket = null;
1261
- let activeReader;
1262
- let errorEmitted = false;
1263
- const donePromise = new Promise(async (resolve, reject) => {
1264
- var _a, _b, _c, _d;
1265
- try {
1266
- if (isStopped) return resolve();
1267
- const sttResult = await transcribeAudio(
1268
- baseUrl,
1269
- audioFile,
1270
- projectId,
1271
- sessionId,
1272
- "tr"
1273
- );
1274
- const currentSessionId = sttResult.session_id;
1275
- const userText = sttResult.text;
1276
- (_a = events.onTranscription) == null ? void 0 : _a.call(events, {
1277
- session_id: currentSessionId,
1278
- user_text: userText
1279
- });
1280
- if (isStopped) return resolve();
1281
- const assistantText = await new Promise((agentResolve, agentReject) => {
1282
- if (isStopped) {
1283
- agentResolve("");
1284
- return;
1285
- }
1286
- const wsUrl = toWebSocketUrl(baseUrl, "/chat/agent/ws");
1287
- const socket = new WebSocket(wsUrl);
1288
- activeSocket = socket;
1289
- let finalReply = "";
1290
- let resolved = false;
1291
- const finish = (reply) => {
1292
- if (resolved) return;
1293
- resolved = true;
1294
- agentResolve(reply);
1295
- };
1296
- const fail = (error) => {
1297
- if (resolved) return;
1298
- resolved = true;
1299
- agentReject(error);
1300
- };
1301
- socket.onopen = () => {
1302
- console.info("[Bulut] Agent WS connected");
1303
- socket.send(JSON.stringify({
1304
- type: "start",
1305
- project_id: projectId,
1306
- session_id: currentSessionId,
1307
- user_text: userText,
1308
- model: config.model,
1309
- page_context: config.pageContext,
1310
- accessibility_mode: config.accessibilityMode
1311
- }));
1312
- };
1313
- socket.onmessage = async (event) => {
1314
- var _a2, _b2, _c2, _d2, _e, _f, _g, _h;
1315
- let data2;
1316
- try {
1317
- data2 = JSON.parse(String(event.data));
1318
- } catch {
1319
- console.warn("[Bulut] Agent WS invalid JSON");
1320
- return;
1321
- }
1322
- const msgType = data2.type;
1323
- if (msgType === "session" && typeof data2.session_id === "string") {
1324
- (_a2 = events.onSessionId) == null ? void 0 : _a2.call(events, data2.session_id);
1325
- return;
1326
- }
1327
- if (msgType === "iteration") {
1328
- (_b2 = events.onIteration) == null ? void 0 : _b2.call(
1329
- events,
1330
- data2.iteration,
1331
- data2.max_iterations
1332
- );
1333
- return;
1334
- }
1335
- if (msgType === "reply_delta" && typeof data2.delta === "string") {
1336
- (_c2 = events.onAssistantDelta) == null ? void 0 : _c2.call(events, data2.delta);
1337
- return;
1338
- }
1339
- if (msgType === "tool_calls" && Array.isArray(data2.calls)) {
1340
- const calls = data2.calls;
1341
- (_d2 = events.onToolCalls) == null ? void 0 : _d2.call(events, calls);
1342
- const results = [];
1343
- for (const call2 of calls) {
1344
- const result = await executeTool(call2);
1345
- (_e = events.onToolResult) == null ? void 0 : _e.call(events, call2.call_id, call2.tool, result.result);
1346
- results.push(result);
1347
- }
1348
- if (socket.readyState === WebSocket.OPEN) {
1349
- socket.send(JSON.stringify({
1350
- type: "tool_results",
1351
- results
1352
- }));
1353
- }
1354
- return;
1355
- }
1356
- if (msgType === "agent_done") {
1357
- finalReply = data2.final_reply || "";
1358
- (_f = events.onAssistantDone) == null ? void 0 : _f.call(events, finalReply);
1359
- if (typeof data2.session_id === "string") {
1360
- (_g = events.onSessionId) == null ? void 0 : _g.call(events, data2.session_id);
1361
- }
1362
- finish(finalReply);
1363
- return;
1364
- }
1365
- if (msgType === "error") {
1366
- const errMsg = data2.error || "Agent error";
1367
- errorEmitted = true;
1368
- (_h = events.onError) == null ? void 0 : _h.call(events, errMsg);
1369
- fail(new Error(errMsg));
1370
- return;
1371
- }
1372
- };
1373
- socket.onerror = () => {
1374
- var _a2;
1375
- console.error("[Bulut] Agent WS error");
1376
- errorEmitted = true;
1377
- (_a2 = events.onError) == null ? void 0 : _a2.call(events, "Agent WebSocket connection error");
1378
- fail(new Error("Agent WebSocket connection error"));
1379
- };
1380
- socket.onclose = () => {
1381
- console.info("[Bulut] Agent WS closed");
1382
- finish(finalReply);
1383
- };
1384
- });
1385
- activeSocket = null;
1386
- if (isStopped || !assistantText) {
1387
- return resolve();
1388
- }
1389
- console.info(
1390
- `[Bulut] TTS start mode=agent forced_voice=${FORCED_TTS_VOICE}`
1391
- );
1392
- (_b = events.onAudioStateChange) == null ? void 0 : _b.call(events, "rendering");
1393
- let ttsResult;
1394
- try {
1395
- ttsResult = await collectTtsViaWebSocket(
1396
- baseUrl,
1397
- assistantText,
1398
- Boolean(config.accessibilityMode),
1399
- () => isStopped,
1400
- (socket) => {
1401
- activeSocket = socket;
1402
- }
1403
- );
1404
- } catch (wsError) {
1405
- if (isStopped) return resolve();
1406
- console.warn(
1407
- `[Bulut] TTS WS failed, falling back to SSE: ${wsError instanceof Error ? wsError.message : String(wsError)}`
1408
- );
1409
- ttsResult = await collectTtsViaSse(
1410
- baseUrl,
1411
- assistantText,
1412
- Boolean(config.accessibilityMode),
1413
- () => isStopped,
1414
- (reader) => {
1415
- activeReader = reader;
1416
- }
1417
- );
1418
- }
1419
- if (!isStopped && ttsResult.chunks.length > 0) {
1420
- await playBufferedAudio(
1421
- ttsResult.chunks,
1422
- ttsResult.mimeType,
1423
- ttsResult.sampleRate,
1424
- events.onAudioStateChange
1425
- );
1426
- } else {
1427
- (_c = events.onAudioStateChange) == null ? void 0 : _c.call(events, "done");
1428
- }
1429
- resolve();
1430
- } catch (err) {
1431
- if (!errorEmitted) {
1432
- const msg = err instanceof Error ? err.message : String(err);
1433
- (_d = events.onError) == null ? void 0 : _d.call(events, msg);
1434
- }
1435
- reject(err);
1436
- } finally {
1437
- activeReader == null ? void 0 : activeReader.cancel().catch(() => {
1438
- });
1439
- if (activeSocket && activeSocket.readyState <= WebSocket.OPEN) {
1440
- activeSocket.close();
1441
- }
1442
- activeSocket = null;
1443
- }
1444
- });
1445
- return {
1446
- stop: () => {
1447
- isStopped = true;
1448
- if (activeReader) {
1449
- activeReader.cancel().catch(() => {
1450
- });
1451
- }
1452
- if (activeSocket && activeSocket.readyState <= WebSocket.OPEN) {
1453
- activeSocket.close();
1454
- }
1455
- },
1456
- done: donePromise
1457
- };
1458
- };
1459
- const MAX_LINKS = 20;
1460
- const MAX_INTERACTABLES = 24;
1461
- const MAX_HEADINGS = 10;
1462
- const MAX_TEXT_SNIPPETS = 4;
1463
- const MAX_OUTER_HTML_DIGEST = 760;
1464
- const MAX_CACHED_PAGES = 20;
1465
- const MAX_PAGE_SCAN_ELEMENTS = 2e3;
1466
- const MAX_EVENT_HINTS_PER_ELEMENT = 4;
1467
- const MAX_BRANCH_SAMPLES = 4;
1468
- const MAX_BRANCH_DEPTH = 2;
1469
- const MAX_CONTEXT_SUMMARY_CHARS = 3400;
1470
- const MAX_CONTEXT_WITH_HISTORY_CHARS = 4200;
1471
- const PAGE_CONTEXT_CACHE_VERSION = 2;
1472
- const PAGE_CONTEXT_CACHE_KEY = "auticbot_page_context_cache_v2";
1473
- const NON_CONTENT_TAGS = /* @__PURE__ */ new Set([
1474
- "script",
1475
- "style",
1476
- "noscript",
1477
- "template",
1478
- "link",
1479
- "meta"
1480
- ]);
1481
- const NATIVE_INTERACTIVE_TAGS = /* @__PURE__ */ new Set([
1482
- "a",
1483
- "button",
1484
- "input",
1485
- "textarea",
1486
- "select",
1487
- "summary",
1488
- "details",
1489
- "option"
1490
- ]);
1491
- const INTERACTIVE_ROLES = /* @__PURE__ */ new Set([
1492
- "button",
1493
- "link",
1494
- "tab",
1495
- "menuitem",
1496
- "option",
1497
- "checkbox",
1498
- "radio",
1499
- "switch",
1500
- "combobox",
1501
- "textbox",
1502
- "searchbox",
1503
- "slider",
1504
- "spinbutton",
1505
- "treeitem"
1506
- ]);
1507
- const TRACKED_DISPLAY_VALUES = /* @__PURE__ */ new Set([
1508
- "block",
1509
- "inline",
1510
- "inline-block",
1511
- "flex",
1512
- "inline-flex",
1513
- "grid",
1514
- "inline-grid"
1515
- ]);
1516
- const TRACKED_POSITION_VALUES = /* @__PURE__ */ new Set([
1517
- "relative",
1518
- "absolute",
1519
- "fixed",
1520
- "sticky"
1521
- ]);
1522
- const EVENT_HINT_NAMES = [
1523
- "click",
1524
- "dblclick",
1525
- "mousedown",
1526
- "mouseup",
1527
- "pointerdown",
1528
- "pointerup",
1529
- "touchstart",
1530
- "touchend",
1531
- "keydown",
1532
- "keyup",
1533
- "keypress",
1534
- "input",
1535
- "change",
1536
- "submit",
1537
- "focus",
1538
- "blur"
1539
- ];
1540
- const ARIA_INTERACTION_ATTRS = [
1541
- "aria-controls",
1542
- "aria-expanded",
1543
- "aria-haspopup",
1544
- "aria-pressed",
1545
- "aria-selected"
1546
- ];
1547
- const DATA_INTERACTION_PATTERN = /(action|click|press|toggle|target|trigger|nav|open|close|menu|modal|command|submit)/i;
1548
- const pageContextCache = /* @__PURE__ */ new Map();
1549
- let cacheHydrated = false;
1550
- const normalizeWhitespace = (value) => value.replace(/\s+/g, " ").trim();
1551
- const truncate = (value, maxChars) => {
1552
- if (value.length <= maxChars) {
1553
- return value;
1554
- }
1555
- const suffix = "\n...[truncated]";
1556
- return `${value.slice(0, Math.max(0, maxChars - suffix.length))}${suffix}`;
1557
- };
1558
- const truncateInline = (value, maxChars) => {
1559
- if (value.length <= maxChars) {
1560
- return value;
1561
- }
1562
- return `${value.slice(0, Math.max(0, maxChars - 3))}...`;
1563
- };
1564
- const canonicalUrl = (rawUrl) => {
1565
- try {
1566
- return new URL(rawUrl, rawUrl).href;
1567
- } catch {
1568
- return rawUrl;
1569
- }
1570
- };
1571
- const isCacheEntry = (value) => {
1572
- if (typeof value !== "object" || value === null) {
1573
- return false;
1574
- }
1575
- const obj = value;
1576
- return typeof obj.url === "string" && typeof obj.summary === "string" && Array.isArray(obj.links) && Array.isArray(obj.interactables) && typeof obj.capturedAt === "number" && typeof obj.version === "number";
1577
- };
1578
- const bumpCount = (map, key) => {
1579
- if (!key) {
1580
- return;
1581
- }
1582
- map.set(key, (map.get(key) ?? 0) + 1);
1583
- };
1584
- const formatTopCounts = (map, maxItems) => {
1585
- if (map.size === 0) {
1586
- return "none";
1587
- }
1588
- return Array.from(map.entries()).sort((a2, b) => b[1] - a2[1] || a2[0].localeCompare(b[0])).slice(0, maxItems).map(([name, count]) => `${name}*${count}`).join(", ");
1589
- };
1590
- const parseTabIndex = (value) => {
1591
- if (value === null) {
1592
- return null;
1593
- }
1594
- const parsed = Number.parseInt(value, 10);
1595
- return Number.isNaN(parsed) ? null : parsed;
1596
- };
1597
- const compactToken = (value, maxChars = 18) => {
1598
- const compact = value.replace(/\s+/g, "-").replace(/[^a-zA-Z0-9_-]/g, "");
1599
- return compact ? truncateInline(compact, maxChars) : "";
1600
- };
1601
- const getElementDepth = (element) => {
1602
- let depth = 0;
1603
- let cursor = element;
1604
- while (cursor == null ? void 0 : cursor.parentElement) {
1605
- depth += 1;
1606
- cursor = cursor.parentElement;
1607
- if (cursor === document.body) {
1608
- break;
1609
- }
1610
- }
1611
- return depth;
1612
- };
1613
- const getPrimaryRole = (element) => {
1614
- const rawRole = normalizeWhitespace(element.getAttribute("role") || "").toLowerCase().split(" ")[0];
1615
- return rawRole || "";
1616
- };
1617
- const hydrateCacheFromStorage = () => {
1618
- if (cacheHydrated || typeof sessionStorage === "undefined") {
1619
- return;
1620
- }
1621
- cacheHydrated = true;
1622
- try {
1623
- const raw = sessionStorage.getItem(PAGE_CONTEXT_CACHE_KEY);
1624
- if (!raw) {
1625
- return;
1626
- }
1627
- const parsed = JSON.parse(raw);
1628
- if (!Array.isArray(parsed)) {
1629
- return;
1630
- }
1631
- for (const value of parsed) {
1632
- if (!isCacheEntry(value)) {
1633
- continue;
1634
- }
1635
- if (value.version !== PAGE_CONTEXT_CACHE_VERSION) {
1636
- continue;
1637
- }
1638
- pageContextCache.set(value.url, value);
1639
- }
1640
- if (pageContextCache.size > 0) {
1641
- console.info(
1642
- `[Autic] context cache restored entries=${pageContextCache.size}`
1643
- );
1644
- }
1645
- } catch (error) {
1646
- console.warn("[Autic] context cache restore failed", error);
1647
- }
1648
- };
1649
- const persistCacheToStorage = () => {
1650
- if (typeof sessionStorage === "undefined") {
1651
- return;
1652
- }
1653
- try {
1654
- const serialized = JSON.stringify(
1655
- Array.from(pageContextCache.values()).sort(
1656
- (a2, b) => a2.capturedAt - b.capturedAt
1657
- )
1658
- );
1659
- sessionStorage.setItem(PAGE_CONTEXT_CACHE_KEY, serialized);
1660
- } catch (error) {
1661
- console.warn("[Autic] context cache persist failed", error);
1662
- }
1663
- };
1664
- const pruneOldestCacheEntries = () => {
1665
- if (pageContextCache.size <= MAX_CACHED_PAGES) {
1666
- return;
1667
- }
1668
- const sorted = Array.from(pageContextCache.values()).sort(
1669
- (a2, b) => a2.capturedAt - b.capturedAt
1670
- );
1671
- const overflow = sorted.length - MAX_CACHED_PAGES;
1672
- for (let i = 0; i < overflow; i += 1) {
1673
- pageContextCache.delete(sorted[i].url);
1674
- }
1675
- };
1676
- const buildSummaryWithHistory = (current) => {
1677
- const recentPages = Array.from(pageContextCache.values()).filter((entry) => entry.url !== current.url).sort((a2, b) => b.capturedAt - a2.capturedAt).slice(0, 3);
1678
- if (recentPages.length === 0) {
1679
- return current.summary;
1680
- }
1681
- const historySection = [
1682
- "Recent Page Memory:",
1683
- ...recentPages.map((entry) => {
1684
- const compactSummary = normalizeWhitespace(entry.summary).slice(0, 180);
1685
- return `- ${entry.url} :: ${compactSummary}`;
1686
- })
1687
- ].join("\n");
1688
- return truncate(
1689
- `${current.summary}
1690
-
1691
- ${historySection}`,
1692
- MAX_CONTEXT_WITH_HISTORY_CHARS
1693
- );
1694
- };
1695
- const isVisible = (element) => {
1696
- if (element.getAttribute("aria-hidden") === "true") {
1697
- return false;
1698
- }
1699
- if (element instanceof HTMLElement && element.hidden) {
1700
- return false;
1701
- }
1702
- const style = window.getComputedStyle(element);
1703
- if (style.display === "none" || style.visibility === "hidden") {
1704
- return false;
1705
- }
1706
- const rect = element.getBoundingClientRect();
1707
- return rect.width > 0 && rect.height > 0;
1708
- };
1709
- const toAbsoluteUrl = (href) => {
1710
- try {
1711
- return new URL(href, window.location.href).href;
1712
- } catch {
1713
- return href;
1714
- }
1715
- };
1716
- const escapeCssValue = (value) => {
1717
- if (typeof CSS !== "undefined" && typeof CSS.escape === "function") {
1718
- return CSS.escape(value);
1719
- }
1720
- return value.replace(/([ #;&,.+*~':"!^$\[\]()=>|\/@])/g, "\\$1");
1721
- };
1722
- const buildSelector = (element) => {
1723
- const tag = element.tagName.toLowerCase();
1724
- if (element.id) {
1725
- return `#${escapeCssValue(element.id)}`;
1726
- }
1727
- const name = element.getAttribute("name");
1728
- if (name) {
1729
- return `${tag}[name="${escapeCssValue(name)}"]`;
1730
- }
1731
- const ariaLabel = element.getAttribute("aria-label");
1732
- if (ariaLabel) {
1733
- return `${tag}[aria-label="${escapeCssValue(ariaLabel)}"]`;
1734
- }
1735
- const classes = Array.from(element.classList).filter(Boolean).slice(0, 2).map((className) => `.${escapeCssValue(className)}`).join("");
1736
- if (classes) {
1737
- return `${tag}${classes}`;
1738
- }
1739
- const parent2 = element.parentElement;
1740
- if (!parent2) {
1741
- return tag;
1742
- }
1743
- const siblingsOfTag = Array.from(parent2.children).filter(
1744
- (sibling) => sibling.tagName === element.tagName
1745
- );
1746
- const index = siblingsOfTag.indexOf(element) + 1;
1747
- return `${tag}:nth-of-type(${index})`;
1748
- };
1749
- const getElementLabel = (element) => {
1750
- const text = normalizeWhitespace(
1751
- (element instanceof HTMLElement ? element.innerText : element.textContent) || ""
1752
- );
1753
- const ariaLabel = normalizeWhitespace(element.getAttribute("aria-label") || "");
1754
- const title = normalizeWhitespace(element.getAttribute("title") || "");
1755
- const placeholder = normalizeWhitespace(
1756
- element.getAttribute("placeholder") || ""
1757
- );
1758
- const name = normalizeWhitespace(element.getAttribute("name") || "");
1759
- const value = element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement || element instanceof HTMLButtonElement ? normalizeWhitespace(element.value || "") : "";
1760
- const classHint = Array.from(element.classList).map((item) => compactToken(item, 16)).find(Boolean);
1761
- const fallback = element.id && `#${element.id}` || classHint && `.${classHint}` || buildSelector(element);
1762
- const label = text || ariaLabel || title || placeholder || value || name || fallback;
1763
- if (element.tagName.toLowerCase() === "input") {
1764
- const inputType = element.getAttribute("type") || "text";
1765
- return `${inputType} ${label || "input"}`;
1766
- }
1767
- return label || "untitled";
1768
- };
1769
- const getEventHints = (element) => {
1770
- const record = element;
1771
- const eventHints = [];
1772
- for (const eventName of EVENT_HINT_NAMES) {
1773
- const handlerKey = `on${eventName}`;
1774
- const hasInlineHandler = Boolean(element.getAttribute(handlerKey));
1775
- const hasPropertyHandler = typeof record[handlerKey] === "function";
1776
- if (!hasInlineHandler && !hasPropertyHandler) {
1777
- continue;
1778
- }
1779
- eventHints.push(eventName);
1780
- if (eventHints.length >= MAX_EVENT_HINTS_PER_ELEMENT) {
1781
- break;
1782
- }
1783
- }
1784
- return eventHints;
1785
- };
1786
- const getAriaInteractionHints = (element) => ARIA_INTERACTION_ATTRS.filter((attrName) => element.hasAttribute(attrName)).map(
1787
- (attrName) => attrName.replace("aria-", "")
1788
- );
1789
- const getDataInteractionHints = (element) => element.getAttributeNames().filter(
1790
- (attrName) => attrName.startsWith("data-") && DATA_INTERACTION_PATTERN.test(attrName)
1791
- ).slice(0, 2).map((attrName) => attrName.replace("data-", ""));
1792
- const getStyleHints = (style) => {
1793
- const styleHints = [];
1794
- if (style.cursor === "pointer") {
1795
- styleHints.push("cursor:pointer");
1796
- }
1797
- if (style.display === "flex" || style.display === "grid" || style.display === "inline-flex" || style.display === "inline-grid") {
1798
- styleHints.push(`display:${style.display}`);
1799
- }
1800
- if (style.position === "fixed" || style.position === "sticky") {
1801
- styleHints.push(`position:${style.position}`);
1802
- }
1803
- return styleHints.slice(0, 2);
1804
- };
1805
- const buildBlueprintToken = (element) => {
1806
- const tag = element.tagName.toLowerCase();
1807
- const idToken = element.id ? `#${compactToken(element.id)}` : "";
1808
- const classToken = Array.from(element.classList).map((item) => compactToken(item, 16)).find(Boolean);
1809
- return `${tag}${idToken}${classToken ? `.${classToken}` : ""}`;
1810
- };
1811
- const buildBranchDigest = (element, depth) => {
1812
- const token = buildBlueprintToken(element);
1813
- if (depth <= 0) {
1814
- return token;
1815
- }
1816
- const children = Array.from(element.children).filter((child) => !NON_CONTENT_TAGS.has(child.tagName.toLowerCase())).filter((child) => isVisible(child));
1817
- if (children.length === 0) {
1818
- return token;
1819
- }
1820
- const sampled = children.slice(0, 3).map((child) => buildBranchDigest(child, depth - 1));
1821
- const overflow = children.length > sampled.length ? `+${children.length - sampled.length}` : "";
1822
- return `${token}>${sampled.join("+")}${overflow}`;
1823
- };
1824
- const collectDomBranchDigest = () => {
1825
- const root = document.body ?? document.documentElement;
1826
- const topLevelNodes = Array.from(root.children).filter((child) => !NON_CONTENT_TAGS.has(child.tagName.toLowerCase())).filter((child) => isVisible(child)).slice(0, MAX_BRANCH_SAMPLES);
1827
- return topLevelNodes.map(
1828
- (child) => truncateInline(buildBranchDigest(child, MAX_BRANCH_DEPTH), 140)
1829
- );
1830
- };
1831
- const formatSection = (title, lines) => {
1832
- if (lines.length === 0) {
1833
- return `${title}:
1834
- - none`;
1835
- }
1836
- return `${title}:
1837
- ${lines.join("\n")}`;
1838
- };
1839
- const buildOuterHtmlDigest = () => {
1840
- var _a;
1841
- const raw = ((_a = document.body) == null ? void 0 : _a.outerHTML) || document.documentElement.outerHTML;
1842
- const withoutScripts = raw.replace(/<script[\s\S]*?<\/script>/gi, "").replace(/<style[\s\S]*?<\/style>/gi, "").replace(/<noscript[\s\S]*?<\/noscript>/gi, "").replace(/<!--[\s\S]*?-->/g, "").replace(/\s+/g, " ").trim();
1843
- const structural = withoutScripts.replace(/>[^<]*</g, "><").replace(/\s+/g, " ").trim();
1844
- return truncate(structural, MAX_OUTER_HTML_DIGEST);
1845
- };
1846
- const collectTextSnippets = () => {
1847
- const root = document.querySelector("main, article, [role='main']") ?? document.body;
1848
- const snippets = [];
1849
- const seen = /* @__PURE__ */ new Set();
1850
- const candidates = Array.from(root.querySelectorAll("p, li, h1, h2, h3"));
1851
- for (const node of candidates) {
1852
- if (!isVisible(node)) {
1853
- continue;
1854
- }
1855
- const text = normalizeWhitespace(node.textContent || "");
1856
- if (!text || text.length < 20) {
1857
- continue;
1858
- }
1859
- const compact = truncateInline(text, 180);
1860
- if (seen.has(compact)) {
1861
- continue;
1862
- }
1863
- seen.add(compact);
1864
- snippets.push(`- ${compact}`);
1865
- if (snippets.length >= MAX_TEXT_SNIPPETS) {
1866
- break;
1867
- }
1868
- }
1869
- return snippets;
1133
+ return snippets;
1870
1134
  };
1871
1135
  const collectLandmarkSnapshot = () => {
1872
1136
  const probes = [
@@ -2127,12 +1391,44 @@ const getPageContext = () => {
2127
1391
  summary: buildSummaryWithHistory(entry)
2128
1392
  };
2129
1393
  };
2130
- const AGENT_CURSOR_ID = "auticbot-agent-cursor";
2131
- const CURSOR_STORAGE_KEY = "auticbot_agent_cursor_state";
2132
- const CURSOR_MOVE_DURATION_MS = 900;
2133
- const SCROLL_DURATION_MS = 900;
2134
- const CURSOR_EASING = "cubic-bezier(0.4, 0, 0.2, 1)";
2135
- const CURSOR_HOVER_RADIUS_PX = 14;
1394
+ const AGENT_CURSOR_ID = "auticbot-agent-cursor";
1395
+ const CURSOR_STORAGE_KEY = "auticbot_agent_cursor_state";
1396
+ const CURSOR_MOVE_DURATION_MS = 900;
1397
+ const SCROLL_DURATION_MS = 900;
1398
+ const CURSOR_EASING = "cubic-bezier(0.4, 0, 0.2, 1)";
1399
+ const CURSOR_HOVER_RADIUS_PX = 14;
1400
+ const RESUME_STORAGE_KEY = "bulut_agent_resume";
1401
+ const RESUME_TTL_MS = 6e4;
1402
+ const savePendingAgentResume = (state) => {
1403
+ if (typeof localStorage === "undefined") return;
1404
+ try {
1405
+ localStorage.setItem(
1406
+ RESUME_STORAGE_KEY,
1407
+ JSON.stringify({ ...state, savedAt: Date.now() })
1408
+ );
1409
+ } catch {
1410
+ }
1411
+ };
1412
+ const getPendingAgentResume = () => {
1413
+ if (typeof localStorage === "undefined") return null;
1414
+ const raw = localStorage.getItem(RESUME_STORAGE_KEY);
1415
+ if (!raw) return null;
1416
+ try {
1417
+ const parsed = JSON.parse(raw);
1418
+ if (Date.now() - parsed.savedAt > RESUME_TTL_MS) {
1419
+ clearPendingAgentResume();
1420
+ return null;
1421
+ }
1422
+ return parsed;
1423
+ } catch {
1424
+ clearPendingAgentResume();
1425
+ return null;
1426
+ }
1427
+ };
1428
+ const clearPendingAgentResume = () => {
1429
+ if (typeof localStorage === "undefined") return;
1430
+ localStorage.removeItem(RESUME_STORAGE_KEY);
1431
+ };
2136
1432
  const isObject$b = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2137
1433
  const asString = (value) => typeof value === "string" && value.trim() ? value.trim() : void 0;
2138
1434
  const asNumber = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
@@ -2191,627 +1487,1594 @@ const parseJsonFromRaw = (raw) => {
2191
1487
  return null;
2192
1488
  }
2193
1489
  try {
2194
- return JSON.parse(objectCandidate);
2195
- } catch {
1490
+ return JSON.parse(objectCandidate);
1491
+ } catch {
1492
+ return null;
1493
+ }
1494
+ }
1495
+ };
1496
+ const sanitizeToolCalls = (value) => {
1497
+ if (!Array.isArray(value)) {
1498
+ return [];
1499
+ }
1500
+ const toolCalls = [];
1501
+ for (const item of value) {
1502
+ if (!isObject$b(item)) {
1503
+ continue;
1504
+ }
1505
+ if (item.tool === "interact") {
1506
+ const action = asString(item.action);
1507
+ if (!action || !["move", "click", "type", "submit"].includes(action)) {
1508
+ continue;
1509
+ }
1510
+ toolCalls.push({
1511
+ tool: "interact",
1512
+ action,
1513
+ selector: asString(item.selector),
1514
+ text: typeof item.text === "string" ? item.text : void 0,
1515
+ x: asNumber(item.x),
1516
+ y: asNumber(item.y)
1517
+ });
1518
+ continue;
1519
+ }
1520
+ if (item.tool === "navigate") {
1521
+ const url = asString(item.url);
1522
+ if (!url) {
1523
+ continue;
1524
+ }
1525
+ toolCalls.push({
1526
+ tool: "navigate",
1527
+ url
1528
+ });
1529
+ continue;
1530
+ }
1531
+ if (item.tool === "getPageContext") {
1532
+ toolCalls.push({
1533
+ tool: "getPageContext"
1534
+ });
1535
+ continue;
1536
+ }
1537
+ if (item.tool === "scroll") {
1538
+ const selector = asString(item.selector);
1539
+ if (!selector) {
1540
+ continue;
1541
+ }
1542
+ toolCalls.push({
1543
+ tool: "scroll",
1544
+ selector
1545
+ });
1546
+ }
1547
+ }
1548
+ return toolCalls;
1549
+ };
1550
+ const parseAgentResponse = (raw) => {
1551
+ const parsed = parseJsonFromRaw(raw);
1552
+ if (!isObject$b(parsed)) {
1553
+ return {
1554
+ reply: raw.trim(),
1555
+ toolCalls: []
1556
+ };
1557
+ }
1558
+ const reply = asString(parsed.reply) || "";
1559
+ const toolCalls = sanitizeToolCalls(parsed.tool_calls ?? parsed.toolCalls);
1560
+ return {
1561
+ reply,
1562
+ toolCalls
1563
+ };
1564
+ };
1565
+ const clamp = (value, min2, max2) => Math.min(max2, Math.max(min2, value));
1566
+ const easeInOutSine = (progress) => -(Math.cos(Math.PI * progress) - 1) / 2;
1567
+ const isRectOutsideViewport = (rect, viewportHeight) => rect.top < 0 || rect.bottom > viewportHeight;
1568
+ const computeCenteredScrollTop = (currentScrollY, rectTop, rectHeight, viewportHeight, maxScrollTop) => {
1569
+ const desired = currentScrollY + rectTop - (viewportHeight / 2 - rectHeight / 2);
1570
+ return clamp(desired, 0, Math.max(0, maxScrollTop));
1571
+ };
1572
+ const animateWindowScrollTo = async (targetY, durationMs = SCROLL_DURATION_MS) => {
1573
+ if (typeof window === "undefined") {
1574
+ return;
1575
+ }
1576
+ const startY = window.scrollY;
1577
+ const delta = targetY - startY;
1578
+ if (Math.abs(delta) < 1) {
1579
+ return;
1580
+ }
1581
+ await new Promise((resolve) => {
1582
+ const raf = window.requestAnimationFrame || ((callback) => window.setTimeout(() => callback(performance.now()), 16));
1583
+ const startTime = performance.now();
1584
+ const step = (now) => {
1585
+ const elapsed = now - startTime;
1586
+ const progress = clamp(elapsed / durationMs, 0, 1);
1587
+ const eased = easeInOutSine(progress);
1588
+ window.scrollTo(0, startY + delta * eased);
1589
+ if (progress < 1) {
1590
+ raf(step);
1591
+ } else {
1592
+ resolve();
1593
+ }
1594
+ };
1595
+ raf(step);
1596
+ });
1597
+ };
1598
+ const getPersistedCursorState = () => {
1599
+ if (typeof localStorage === "undefined") {
1600
+ return null;
1601
+ }
1602
+ try {
1603
+ const raw = localStorage.getItem(CURSOR_STORAGE_KEY);
1604
+ if (!raw) {
1605
+ return null;
1606
+ }
1607
+ const parsed = JSON.parse(raw);
1608
+ if (typeof parsed.url !== "string" || typeof parsed.x !== "number" || !Number.isFinite(parsed.x) || typeof parsed.y !== "number" || !Number.isFinite(parsed.y)) {
1609
+ return null;
1610
+ }
1611
+ return {
1612
+ url: parsed.url,
1613
+ x: parsed.x,
1614
+ y: parsed.y,
1615
+ visible: parsed.visible !== false
1616
+ };
1617
+ } catch {
1618
+ return null;
1619
+ }
1620
+ };
1621
+ const persistCursorState = (x2, y2, visible) => {
1622
+ if (typeof localStorage === "undefined") {
1623
+ return;
1624
+ }
1625
+ try {
1626
+ const payload = {
1627
+ url: window.location.href,
1628
+ x: x2,
1629
+ y: y2,
1630
+ visible
1631
+ };
1632
+ localStorage.setItem(CURSOR_STORAGE_KEY, JSON.stringify(payload));
1633
+ } catch {
1634
+ }
1635
+ };
1636
+ const setCursorPosition = (cursor, x2, y2) => {
1637
+ cursor.style.left = `${x2}px`;
1638
+ cursor.style.top = `${y2}px`;
1639
+ };
1640
+ const getCursorPosition = (cursor) => ({
1641
+ x: Number.parseFloat(cursor.style.left) || 0,
1642
+ y: Number.parseFloat(cursor.style.top) || 0
1643
+ });
1644
+ const setCursorVisibility = (cursor, visible) => {
1645
+ cursor.style.opacity = visible ? "1" : "0";
1646
+ };
1647
+ let cursorHoverTrackingInitialized = false;
1648
+ const initializeCursorHoverTracking = () => {
1649
+ if (cursorHoverTrackingInitialized) {
1650
+ return;
1651
+ }
1652
+ cursorHoverTrackingInitialized = true;
1653
+ document.addEventListener("mousemove", (event) => {
1654
+ const cursor = document.getElementById(AGENT_CURSOR_ID);
1655
+ if (!(cursor instanceof HTMLElement)) {
1656
+ return;
1657
+ }
1658
+ if (cursor.style.opacity !== "1") {
1659
+ return;
1660
+ }
1661
+ const { x: x2, y: y2 } = getCursorPosition(cursor);
1662
+ const pointerX = event.pageX;
1663
+ const pointerY = event.pageY;
1664
+ const distance = Math.hypot(pointerX - x2, pointerY - y2);
1665
+ if (distance <= CURSOR_HOVER_RADIUS_PX) {
1666
+ setCursorVisibility(cursor, false);
1667
+ persistCursorState(x2, y2, false);
1668
+ }
1669
+ });
1670
+ };
1671
+ const applyStoredCursorStateForCurrentUrl = (cursor) => {
1672
+ const stored = getPersistedCursorState();
1673
+ if (!stored || stored.url !== window.location.href) {
1674
+ return;
1675
+ }
1676
+ setCursorPosition(cursor, stored.x, stored.y);
1677
+ setCursorVisibility(cursor, stored.visible);
1678
+ };
1679
+ const ensureCursor = () => {
1680
+ const existing = document.getElementById(AGENT_CURSOR_ID);
1681
+ if (existing) {
1682
+ initializeCursorHoverTracking();
1683
+ return existing;
1684
+ }
1685
+ const cursor = document.createElement("div");
1686
+ cursor.id = AGENT_CURSOR_ID;
1687
+ cursor.style.position = "absolute";
1688
+ cursor.style.left = "0px";
1689
+ cursor.style.top = "0px";
1690
+ cursor.style.opacity = "0";
1691
+ const width = 25;
1692
+ cursor.style.width = `${width}px`;
1693
+ cursor.style.height = `${width}px`;
1694
+ cursor.style.borderRadius = "50%";
1695
+ const baseColor = COLORS.primary;
1696
+ cursor.style.background = baseColor;
1697
+ const border = 25 * 16 / 100;
1698
+ cursor.style.border = `${border}px solid #ffffff`;
1699
+ cursor.style.boxShadow = "0px 0px 10px rgba(0, 11, 26, 0.5)";
1700
+ cursor.style.boxSizing = "border-box";
1701
+ cursor.style.zIndex = "2147483647";
1702
+ cursor.style.pointerEvents = "none";
1703
+ cursor.style.transform = "translate(-50%, -50%)";
1704
+ cursor.style.transition = `left ${CURSOR_MOVE_DURATION_MS}ms ${CURSOR_EASING}, top ${CURSOR_MOVE_DURATION_MS}ms ${CURSOR_EASING}, opacity 150ms ease-out`;
1705
+ document.body.appendChild(cursor);
1706
+ initializeCursorHoverTracking();
1707
+ applyStoredCursorStateForCurrentUrl(cursor);
1708
+ console.info(`[Autic] cursor created color=${baseColor} duration=${CURSOR_MOVE_DURATION_MS}ms`);
1709
+ return cursor;
1710
+ };
1711
+ const moveCursor = async (x2, y2) => {
1712
+ const cursor = ensureCursor();
1713
+ setCursorPosition(cursor, x2, y2);
1714
+ setCursorVisibility(cursor, true);
1715
+ persistCursorState(x2, y2, true);
1716
+ await new Promise((resolve) => setTimeout(resolve, CURSOR_MOVE_DURATION_MS));
1717
+ };
1718
+ const getElementCenter = (element) => {
1719
+ const rect = element.getBoundingClientRect();
1720
+ return {
1721
+ x: rect.left + window.scrollX + rect.width / 2,
1722
+ y: rect.top + window.scrollY + rect.height / 2
1723
+ };
1724
+ };
1725
+ const CONTAINS_SELECTOR_PATTERN = /^(.*?):contains\((['"])(.*?)\2\)\s*$/;
1726
+ const findElementBySelector = (selector) => {
1727
+ var _a, _b, _c;
1728
+ try {
1729
+ return document.querySelector(selector);
1730
+ } catch (error) {
1731
+ const containsMatch = selector.match(CONTAINS_SELECTOR_PATTERN);
1732
+ if (!containsMatch) {
1733
+ console.warn(`AuticBot selector invalid: ${selector}`, error);
1734
+ return null;
1735
+ }
1736
+ const baseSelector = ((_a = containsMatch[1]) == null ? void 0 : _a.trim()) || "*";
1737
+ const expectedText = ((_b = containsMatch[3]) == null ? void 0 : _b.trim()) || "";
1738
+ if (!expectedText) {
1739
+ console.warn(`AuticBot selector contains empty text: ${selector}`);
1740
+ return null;
1741
+ }
1742
+ try {
1743
+ const candidates = document.querySelectorAll(baseSelector);
1744
+ for (const candidate of candidates) {
1745
+ if ((_c = candidate.textContent) == null ? void 0 : _c.includes(expectedText)) {
1746
+ return candidate;
1747
+ }
1748
+ }
1749
+ return null;
1750
+ } catch (fallbackError) {
1751
+ console.warn(`AuticBot selector fallback invalid: ${selector}`, fallbackError);
2196
1752
  return null;
2197
1753
  }
2198
1754
  }
2199
1755
  };
2200
- const sanitizeToolCalls = (value) => {
2201
- if (!Array.isArray(value)) {
2202
- return [];
2203
- }
2204
- const toolCalls = [];
2205
- for (const item of value) {
2206
- if (!isObject$b(item)) {
2207
- continue;
2208
- }
2209
- if (item.tool === "interact") {
2210
- const action = asString(item.action);
2211
- if (!action || !["move", "click", "type", "submit"].includes(action)) {
2212
- continue;
2213
- }
2214
- toolCalls.push({
2215
- tool: "interact",
2216
- action,
2217
- selector: asString(item.selector),
2218
- text: typeof item.text === "string" ? item.text : void 0,
2219
- x: asNumber(item.x),
2220
- y: asNumber(item.y)
2221
- });
2222
- continue;
2223
- }
2224
- if (item.tool === "navigate") {
2225
- const url = asString(item.url);
2226
- if (!url) {
2227
- continue;
2228
- }
2229
- toolCalls.push({
2230
- tool: "navigate",
2231
- url
2232
- });
2233
- continue;
2234
- }
2235
- if (item.tool === "getPageContext") {
2236
- toolCalls.push({
2237
- tool: "getPageContext"
2238
- });
2239
- continue;
2240
- }
2241
- if (item.tool === "scroll") {
2242
- const selector = asString(item.selector);
2243
- if (!selector) {
2244
- continue;
2245
- }
2246
- toolCalls.push({
2247
- tool: "scroll",
2248
- selector
2249
- });
1756
+ const resolveTarget = (call2) => {
1757
+ if (call2.selector) {
1758
+ const selected = findElementBySelector(call2.selector);
1759
+ if (selected instanceof HTMLElement) {
1760
+ const center = getElementCenter(selected);
1761
+ return {
1762
+ element: selected,
1763
+ x: center.x,
1764
+ y: center.y
1765
+ };
2250
1766
  }
1767
+ console.warn(`AuticBot interact: selector not found: ${call2.selector}`);
2251
1768
  }
2252
- return toolCalls;
2253
- };
2254
- const parseAgentResponse = (raw) => {
2255
- const parsed = parseJsonFromRaw(raw);
2256
- if (!isObject$b(parsed)) {
1769
+ if (typeof call2.x === "number" && typeof call2.y === "number") {
2257
1770
  return {
2258
- reply: raw.trim(),
2259
- toolCalls: []
1771
+ x: call2.x,
1772
+ y: call2.y
2260
1773
  };
2261
1774
  }
2262
- const reply = asString(parsed.reply) || "";
2263
- const toolCalls = sanitizeToolCalls(parsed.tool_calls ?? parsed.toolCalls);
2264
- return {
2265
- reply,
2266
- toolCalls
2267
- };
1775
+ console.warn("AuticBot interact: missing target selector or coordinates.", call2);
1776
+ return null;
2268
1777
  };
2269
- const clamp = (value, min2, max2) => Math.min(max2, Math.max(min2, value));
2270
- const easeInOutSine = (progress) => -(Math.cos(Math.PI * progress) - 1) / 2;
2271
- const isRectOutsideViewport = (rect, viewportHeight) => rect.top < 0 || rect.bottom > viewportHeight;
2272
- const computeCenteredScrollTop = (currentScrollY, rectTop, rectHeight, viewportHeight, maxScrollTop) => {
2273
- const desired = currentScrollY + rectTop - (viewportHeight / 2 - rectHeight / 2);
2274
- return clamp(desired, 0, Math.max(0, maxScrollTop));
1778
+ const dispatchMouseEvent = (element, type, x2, y2) => {
1779
+ element.dispatchEvent(
1780
+ new MouseEvent(type, {
1781
+ bubbles: true,
1782
+ cancelable: true,
1783
+ view: window,
1784
+ clientX: x2 - window.scrollX,
1785
+ clientY: y2 - window.scrollY
1786
+ })
1787
+ );
2275
1788
  };
2276
- const animateWindowScrollTo = async (targetY, durationMs = SCROLL_DURATION_MS) => {
2277
- if (typeof window === "undefined") {
1789
+ const typeIntoElement = (element, text) => {
1790
+ const tagName = element.tagName.toUpperCase();
1791
+ if (tagName === "INPUT" || tagName === "TEXTAREA") {
1792
+ element.focus();
1793
+ element.value = text;
1794
+ element.dispatchEvent(new Event("input", { bubbles: true }));
1795
+ element.dispatchEvent(new Event("change", { bubbles: true }));
2278
1796
  return;
2279
1797
  }
2280
- const startY = window.scrollY;
2281
- const delta = targetY - startY;
2282
- if (Math.abs(delta) < 1) {
1798
+ if (element.isContentEditable) {
1799
+ element.focus();
1800
+ element.textContent = text;
1801
+ element.dispatchEvent(new Event("input", { bubbles: true }));
2283
1802
  return;
2284
1803
  }
2285
- await new Promise((resolve) => {
2286
- const raf = window.requestAnimationFrame || ((callback) => window.setTimeout(() => callback(performance.now()), 16));
2287
- const startTime = performance.now();
2288
- const step = (now) => {
2289
- const elapsed = now - startTime;
2290
- const progress = clamp(elapsed / durationMs, 0, 1);
2291
- const eased = easeInOutSine(progress);
2292
- window.scrollTo(0, startY + delta * eased);
2293
- if (progress < 1) {
2294
- raf(step);
2295
- } else {
2296
- resolve();
2297
- }
2298
- };
2299
- raf(step);
2300
- });
1804
+ console.warn(
1805
+ "AuticBot interact: type action requires input, textarea, or contenteditable target."
1806
+ );
2301
1807
  };
2302
- const getPersistedCursorState = () => {
2303
- if (typeof localStorage === "undefined") {
2304
- return null;
1808
+ const submitElement = (element) => {
1809
+ var _a;
1810
+ if (element.tagName === "FORM") {
1811
+ element.requestSubmit();
1812
+ return;
1813
+ }
1814
+ if (element.tagName === "BUTTON" && element.form) {
1815
+ (_a = element.form) == null ? void 0 : _a.requestSubmit();
1816
+ return;
1817
+ }
1818
+ const parentForm = element.closest("form");
1819
+ if (parentForm) {
1820
+ parentForm.requestSubmit();
1821
+ return;
1822
+ }
1823
+ console.warn("AuticBot interact: submit action requires a form target.");
1824
+ };
1825
+ const slowScrollElementIntoView = async (element) => {
1826
+ await slowScrollElementIntoViewWithMode(element, false);
1827
+ };
1828
+ const slowScrollElementIntoViewWithMode = async (element, forceCenter) => {
1829
+ const rect = element.getBoundingClientRect();
1830
+ const viewportHeight = window.innerHeight;
1831
+ if (!forceCenter && !isRectOutsideViewport(rect, viewportHeight)) {
1832
+ return;
1833
+ }
1834
+ const maxScrollTop = Math.max(
1835
+ 0,
1836
+ Math.max(document.body.scrollHeight, document.documentElement.scrollHeight) - viewportHeight
1837
+ );
1838
+ const targetY = computeCenteredScrollTop(
1839
+ window.scrollY,
1840
+ rect.top,
1841
+ rect.height,
1842
+ viewportHeight,
1843
+ maxScrollTop
1844
+ );
1845
+ await animateWindowScrollTo(targetY, SCROLL_DURATION_MS);
1846
+ };
1847
+ const executeScroll = async (call2) => {
1848
+ const selected = findElementBySelector(call2.selector);
1849
+ if (!(selected instanceof HTMLElement)) {
1850
+ console.warn(`AuticBot scroll: selector not found: ${call2.selector}`);
1851
+ return;
1852
+ }
1853
+ await slowScrollElementIntoViewWithMode(selected, true);
1854
+ const center = getElementCenter(selected);
1855
+ await moveCursor(center.x, center.y);
1856
+ };
1857
+ const executeInteract = async (call2) => {
1858
+ const target = resolveTarget(call2);
1859
+ if (!target) {
1860
+ return;
1861
+ }
1862
+ if (call2.action === "click" && target.element) {
1863
+ await slowScrollElementIntoView(target.element);
1864
+ const center = getElementCenter(target.element);
1865
+ target.x = center.x;
1866
+ target.y = center.y;
1867
+ }
1868
+ await moveCursor(target.x, target.y);
1869
+ if (call2.action === "move") {
1870
+ return;
1871
+ }
1872
+ if (!target.element) {
1873
+ console.warn("AuticBot interact: target element not available for action.", call2.action);
1874
+ return;
1875
+ }
1876
+ if (call2.action === "click") {
1877
+ dispatchMouseEvent(target.element, "pointerdown", target.x, target.y);
1878
+ dispatchMouseEvent(target.element, "mousedown", target.x, target.y);
1879
+ dispatchMouseEvent(target.element, "pointerup", target.x, target.y);
1880
+ dispatchMouseEvent(target.element, "mouseup", target.x, target.y);
1881
+ target.element.click();
1882
+ return;
1883
+ }
1884
+ if (call2.action === "type") {
1885
+ typeIntoElement(target.element, call2.text ?? "");
1886
+ return;
2305
1887
  }
1888
+ submitElement(target.element);
1889
+ };
1890
+ const isSamePageNavigation = (targetUrl) => {
2306
1891
  try {
2307
- const raw = localStorage.getItem(CURSOR_STORAGE_KEY);
2308
- if (!raw) {
2309
- return null;
2310
- }
2311
- const parsed = JSON.parse(raw);
2312
- if (typeof parsed.url !== "string" || typeof parsed.x !== "number" || !Number.isFinite(parsed.x) || typeof parsed.y !== "number" || !Number.isFinite(parsed.y)) {
2313
- return null;
2314
- }
2315
- return {
2316
- url: parsed.url,
2317
- x: parsed.x,
2318
- y: parsed.y,
2319
- visible: parsed.visible !== false
2320
- };
1892
+ const current = new URL(window.location.href);
1893
+ const target = new URL(targetUrl);
1894
+ return current.origin === target.origin && current.pathname === target.pathname;
2321
1895
  } catch {
2322
- return null;
1896
+ return false;
2323
1897
  }
2324
1898
  };
2325
- const persistCursorState = (x2, y2, visible) => {
2326
- if (typeof localStorage === "undefined") {
2327
- return;
2328
- }
1899
+ const findMatchingLinkForTarget = (targetUrl) => {
1900
+ let parsedTarget = null;
2329
1901
  try {
2330
- const payload = {
2331
- url: window.location.href,
2332
- x: x2,
2333
- y: y2,
2334
- visible
2335
- };
2336
- localStorage.setItem(CURSOR_STORAGE_KEY, JSON.stringify(payload));
1902
+ parsedTarget = new URL(targetUrl, window.location.href);
2337
1903
  } catch {
2338
1904
  }
2339
- };
2340
- const setCursorPosition = (cursor, x2, y2) => {
2341
- cursor.style.left = `${x2}px`;
2342
- cursor.style.top = `${y2}px`;
2343
- };
2344
- const getCursorPosition = (cursor) => ({
2345
- x: Number.parseFloat(cursor.style.left) || 0,
2346
- y: Number.parseFloat(cursor.style.top) || 0
2347
- });
2348
- const setCursorVisibility = (cursor, visible) => {
2349
- cursor.style.opacity = visible ? "1" : "0";
2350
- };
2351
- let cursorHoverTrackingInitialized = false;
2352
- const initializeCursorHoverTracking = () => {
2353
- if (cursorHoverTrackingInitialized) {
2354
- return;
1905
+ const allLinks = Array.from(
1906
+ document.querySelectorAll('a[href], [role="link"][href], [data-href]')
1907
+ );
1908
+ for (const el of allLinks) {
1909
+ if (el instanceof HTMLAnchorElement && el.href === (parsedTarget == null ? void 0 : parsedTarget.href)) {
1910
+ return el;
1911
+ }
2355
1912
  }
2356
- cursorHoverTrackingInitialized = true;
2357
- document.addEventListener("mousemove", (event) => {
2358
- const cursor = document.getElementById(AGENT_CURSOR_ID);
2359
- if (!(cursor instanceof HTMLElement)) {
2360
- return;
1913
+ if (parsedTarget) {
1914
+ for (const el of allLinks) {
1915
+ if (!(el instanceof HTMLAnchorElement)) continue;
1916
+ try {
1917
+ const elUrl = new URL(el.href, window.location.href);
1918
+ if (elUrl.pathname === parsedTarget.pathname && elUrl.search === parsedTarget.search && elUrl.hash === parsedTarget.hash) {
1919
+ return el;
1920
+ }
1921
+ } catch {
1922
+ continue;
1923
+ }
2361
1924
  }
2362
- if (cursor.style.opacity !== "1") {
2363
- return;
1925
+ for (const el of allLinks) {
1926
+ if (!(el instanceof HTMLAnchorElement)) continue;
1927
+ try {
1928
+ const elUrl = new URL(el.href, window.location.href);
1929
+ if (elUrl.pathname === parsedTarget.pathname) {
1930
+ return el;
1931
+ }
1932
+ } catch {
1933
+ continue;
1934
+ }
2364
1935
  }
2365
- const { x: x2, y: y2 } = getCursorPosition(cursor);
2366
- const pointerX = event.pageX;
2367
- const pointerY = event.pageY;
2368
- const distance = Math.hypot(pointerX - x2, pointerY - y2);
2369
- if (distance <= CURSOR_HOVER_RADIUS_PX) {
2370
- setCursorVisibility(cursor, false);
2371
- persistCursorState(x2, y2, false);
1936
+ const rawUrl = targetUrl.replace(/^\//, "");
1937
+ for (const el of allLinks) {
1938
+ const href = el.getAttribute("href") || el.getAttribute("data-href") || "";
1939
+ if (href && (href === targetUrl || href === rawUrl || href === `/${rawUrl}`)) {
1940
+ return el;
1941
+ }
2372
1942
  }
2373
- });
2374
- };
2375
- const applyStoredCursorStateForCurrentUrl = (cursor) => {
2376
- const stored = getPersistedCursorState();
2377
- if (!stored || stored.url !== window.location.href) {
2378
- return;
2379
1943
  }
2380
- setCursorPosition(cursor, stored.x, stored.y);
2381
- setCursorVisibility(cursor, stored.visible);
1944
+ const urlSegments = targetUrl.replace(/^https?:\/\/[^/]+/, "").replace(/[?#].*$/, "").split("/").filter(Boolean);
1945
+ const lastSegment = urlSegments[urlSegments.length - 1] || "";
1946
+ if (lastSegment) {
1947
+ let searchTerms = [lastSegment];
1948
+ if (parsedTarget) {
1949
+ for (const [, value] of parsedTarget.searchParams.entries()) {
1950
+ if (value) searchTerms.push(value);
1951
+ }
1952
+ if (parsedTarget.hash) {
1953
+ searchTerms.push(parsedTarget.hash.replace(/^#/, ""));
1954
+ }
1955
+ }
1956
+ searchTerms = searchTerms.map((t2) => t2.toLowerCase());
1957
+ const clickables = Array.from(
1958
+ document.querySelectorAll(
1959
+ 'a, button, [role="link"], [role="tab"], [role="button"], [data-tab], [onclick]'
1960
+ )
1961
+ );
1962
+ for (const el of clickables) {
1963
+ const text = (el.textContent || "").trim().toLowerCase();
1964
+ const ariaLabel = (el.getAttribute("aria-label") || "").toLowerCase();
1965
+ const dataTab = (el.getAttribute("data-tab") || "").toLowerCase();
1966
+ for (const term of searchTerms) {
1967
+ if (text === term || ariaLabel === term || dataTab === term || text.includes(term)) {
1968
+ return el;
1969
+ }
1970
+ }
1971
+ }
1972
+ }
1973
+ return null;
2382
1974
  };
2383
- const ensureCursor = () => {
2384
- const existing = document.getElementById(AGENT_CURSOR_ID);
2385
- if (existing) {
2386
- initializeCursorHoverTracking();
2387
- return existing;
1975
+ const executeNavigate = async (call2) => {
1976
+ try {
1977
+ const targetUrl = call2.url;
1978
+ let resolvedUrl;
1979
+ try {
1980
+ resolvedUrl = new URL(targetUrl, window.location.href).href;
1981
+ } catch {
1982
+ resolvedUrl = targetUrl;
1983
+ }
1984
+ const matchingElement = findMatchingLinkForTarget(targetUrl);
1985
+ if (matchingElement) {
1986
+ console.log("AuticBot navigate: clicking element", resolvedUrl, matchingElement.tagName);
1987
+ await slowScrollElementIntoView(matchingElement);
1988
+ const center = getElementCenter(matchingElement);
1989
+ await moveCursor(center.x, center.y);
1990
+ matchingElement.dispatchEvent(new MouseEvent("pointerdown", { bubbles: true, view: window }));
1991
+ matchingElement.dispatchEvent(new MouseEvent("mousedown", { bubbles: true, view: window }));
1992
+ matchingElement.dispatchEvent(new MouseEvent("pointerup", { bubbles: true, view: window }));
1993
+ matchingElement.dispatchEvent(new MouseEvent("mouseup", { bubbles: true, view: window }));
1994
+ matchingElement.click();
1995
+ return !isSamePageNavigation(resolvedUrl);
1996
+ }
1997
+ console.log("AuticBot navigate: no matching element found, using direct navigation", resolvedUrl);
1998
+ try {
1999
+ const parsed = new URL(resolvedUrl);
2000
+ if (parsed.origin === window.location.origin && parsed.pathname === window.location.pathname && parsed.hash) {
2001
+ window.location.hash = parsed.hash;
2002
+ return false;
2003
+ }
2004
+ } catch {
2005
+ }
2006
+ try {
2007
+ const parsed = new URL(resolvedUrl);
2008
+ if (parsed.origin === window.location.origin) {
2009
+ const newPath = parsed.pathname + parsed.search + parsed.hash;
2010
+ window.history.pushState({}, "", newPath);
2011
+ window.dispatchEvent(new PopStateEvent("popstate", { state: {} }));
2012
+ return false;
2013
+ }
2014
+ } catch {
2015
+ }
2016
+ window.location.href = resolvedUrl;
2017
+ return true;
2018
+ } catch (error) {
2019
+ console.warn("AuticBot navigate: error", call2.url, error);
2020
+ return false;
2388
2021
  }
2389
- const cursor = document.createElement("div");
2390
- cursor.id = AGENT_CURSOR_ID;
2391
- cursor.style.position = "absolute";
2392
- cursor.style.left = "0px";
2393
- cursor.style.top = "0px";
2394
- cursor.style.opacity = "0";
2395
- const width = 25;
2396
- cursor.style.width = `${width}px`;
2397
- cursor.style.height = `${width}px`;
2398
- cursor.style.borderRadius = "50%";
2399
- const baseColor = COLORS.primary;
2400
- cursor.style.background = baseColor;
2401
- const border = 25 * 16 / 100;
2402
- cursor.style.border = `${border}px solid #ffffff`;
2403
- cursor.style.boxShadow = "0px 0px 10px rgba(0, 11, 26, 0.5)";
2404
- cursor.style.boxSizing = "border-box";
2405
- cursor.style.zIndex = "2147483647";
2406
- cursor.style.pointerEvents = "none";
2407
- cursor.style.transform = "translate(-50%, -50%)";
2408
- cursor.style.transition = `left ${CURSOR_MOVE_DURATION_MS}ms ${CURSOR_EASING}, top ${CURSOR_MOVE_DURATION_MS}ms ${CURSOR_EASING}, opacity 150ms ease-out`;
2409
- document.body.appendChild(cursor);
2410
- initializeCursorHoverTracking();
2411
- applyStoredCursorStateForCurrentUrl(cursor);
2412
- console.info(`[Autic] cursor created color=${baseColor} duration=${CURSOR_MOVE_DURATION_MS}ms`);
2413
- return cursor;
2414
2022
  };
2415
- const moveCursor = async (x2, y2) => {
2416
- const cursor = ensureCursor();
2417
- setCursorPosition(cursor, x2, y2);
2418
- setCursorVisibility(cursor, true);
2419
- persistCursorState(x2, y2, true);
2420
- await new Promise((resolve) => setTimeout(resolve, CURSOR_MOVE_DURATION_MS));
2023
+ const executeGetPageContext = async () => {
2024
+ const context = getPageContext();
2025
+ console.info(
2026
+ `[Autic] getPageContext tool executed links=${context.links.length} interactables=${context.interactables.length} summary_len=${context.summary.length}`
2027
+ );
2421
2028
  };
2422
- const getElementCenter = (element) => {
2423
- const rect = element.getBoundingClientRect();
2424
- return {
2425
- x: rect.left + window.scrollX + rect.width / 2,
2426
- y: rect.top + window.scrollY + rect.height / 2
2427
- };
2029
+ const executeToolCalls = async (toolCalls) => {
2030
+ for (const toolCall of toolCalls) {
2031
+ if (toolCall.tool === "interact") {
2032
+ await executeInteract(toolCall);
2033
+ continue;
2034
+ }
2035
+ if (toolCall.tool === "scroll") {
2036
+ await executeScroll(toolCall);
2037
+ continue;
2038
+ }
2039
+ if (toolCall.tool === "getPageContext") {
2040
+ await executeGetPageContext();
2041
+ continue;
2042
+ }
2043
+ if (toolCall.tool === "navigate") {
2044
+ const terminalNavigation = await executeNavigate(toolCall);
2045
+ if (terminalNavigation) {
2046
+ break;
2047
+ }
2048
+ }
2049
+ }
2428
2050
  };
2429
- const CONTAINS_SELECTOR_PATTERN = /^(.*?):contains\((['"])(.*?)\2\)\s*$/;
2430
- const findElementBySelector = (selector) => {
2431
- var _a, _b, _c;
2051
+ const executeSingleToolCall = async (call2) => {
2052
+ const callId = call2.call_id;
2432
2053
  try {
2433
- return document.querySelector(selector);
2434
- } catch (error) {
2435
- const containsMatch = selector.match(CONTAINS_SELECTOR_PATTERN);
2436
- if (!containsMatch) {
2437
- console.warn(`AuticBot selector invalid: ${selector}`, error);
2438
- return null;
2054
+ if (call2.tool === "interact") {
2055
+ await executeInteract(call2);
2056
+ return {
2057
+ call_id: callId,
2058
+ result: `Etkileşim başarılı: ${call2.action}`
2059
+ };
2439
2060
  }
2440
- const baseSelector = ((_a = containsMatch[1]) == null ? void 0 : _a.trim()) || "*";
2441
- const expectedText = ((_b = containsMatch[3]) == null ? void 0 : _b.trim()) || "";
2442
- if (!expectedText) {
2443
- console.warn(`AuticBot selector contains empty text: ${selector}`);
2444
- return null;
2061
+ if (call2.tool === "scroll") {
2062
+ await executeScroll(call2);
2063
+ return {
2064
+ call_id: callId,
2065
+ result: "Öğeye kaydırma başarılı."
2066
+ };
2445
2067
  }
2446
- try {
2447
- const candidates = document.querySelectorAll(baseSelector);
2448
- for (const candidate of candidates) {
2449
- if ((_c = candidate.textContent) == null ? void 0 : _c.includes(expectedText)) {
2450
- return candidate;
2451
- }
2452
- }
2453
- return null;
2454
- } catch (fallbackError) {
2455
- console.warn(`AuticBot selector fallback invalid: ${selector}`, fallbackError);
2456
- return null;
2068
+ if (call2.tool === "getPageContext") {
2069
+ const context = getPageContext();
2070
+ return {
2071
+ call_id: callId,
2072
+ result: context.summary
2073
+ };
2457
2074
  }
2458
- }
2459
- };
2460
- const resolveTarget = (call2) => {
2461
- if (call2.selector) {
2462
- const selected = findElementBySelector(call2.selector);
2463
- if (selected instanceof HTMLElement) {
2464
- const center = getElementCenter(selected);
2075
+ if (call2.tool === "navigate") {
2076
+ await executeNavigate(call2);
2077
+ await new Promise((resolve) => setTimeout(resolve, 1500));
2078
+ const context = getPageContext();
2465
2079
  return {
2466
- element: selected,
2467
- x: center.x,
2468
- y: center.y
2080
+ call_id: callId,
2081
+ result: `Navigasyon tamamlandı. Şu anki sayfa: ${window.location.href}
2082
+ Sayfa bağlamı: ${context.summary}`
2469
2083
  };
2470
2084
  }
2471
- console.warn(`AuticBot interact: selector not found: ${call2.selector}`);
2472
- }
2473
- if (typeof call2.x === "number" && typeof call2.y === "number") {
2474
- return {
2475
- x: call2.x,
2476
- y: call2.y
2477
- };
2085
+ return { call_id: callId, result: "Bilinmeyen araç." };
2086
+ } catch (error) {
2087
+ const msg = error instanceof Error ? error.message : String(error);
2088
+ console.warn(`[Autic] Tool execution error: ${call2.tool}`, error);
2089
+ return { call_id: callId, result: `Hata: ${msg}` };
2478
2090
  }
2479
- console.warn("AuticBot interact: missing target selector or coordinates.", call2);
2480
- return null;
2481
- };
2482
- const dispatchMouseEvent = (element, type, x2, y2) => {
2483
- element.dispatchEvent(
2484
- new MouseEvent(type, {
2485
- bubbles: true,
2486
- cancelable: true,
2487
- view: window,
2488
- clientX: x2 - window.scrollX,
2489
- clientY: y2 - window.scrollY
2490
- })
2491
- );
2492
2091
  };
2493
- const typeIntoElement = (element, text) => {
2494
- const tagName = element.tagName.toUpperCase();
2495
- if (tagName === "INPUT" || tagName === "TEXTAREA") {
2496
- element.focus();
2497
- element.value = text;
2498
- element.dispatchEvent(new Event("input", { bubbles: true }));
2499
- element.dispatchEvent(new Event("change", { bubbles: true }));
2092
+ const restoreCursorFromStorageForCurrentUrl = () => {
2093
+ if (typeof document === "undefined" || typeof window === "undefined") {
2500
2094
  return;
2501
2095
  }
2502
- if (element.isContentEditable) {
2503
- element.focus();
2504
- element.textContent = text;
2505
- element.dispatchEvent(new Event("input", { bubbles: true }));
2096
+ const stored = getPersistedCursorState();
2097
+ if (!stored || stored.url !== window.location.href) {
2506
2098
  return;
2507
2099
  }
2508
- console.warn(
2509
- "AuticBot interact: type action requires input, textarea, or contenteditable target."
2510
- );
2100
+ ensureCursor();
2511
2101
  };
2512
- const submitElement = (element) => {
2513
- var _a;
2514
- if (element.tagName === "FORM") {
2515
- element.requestSubmit();
2516
- return;
2102
+ if (typeof document !== "undefined") {
2103
+ if (document.readyState === "loading") {
2104
+ document.addEventListener("DOMContentLoaded", restoreCursorFromStorageForCurrentUrl, {
2105
+ once: true
2106
+ });
2107
+ } else {
2108
+ restoreCursorFromStorageForCurrentUrl();
2517
2109
  }
2518
- if (element.tagName === "BUTTON" && element.form) {
2519
- (_a = element.form) == null ? void 0 : _a.requestSubmit();
2520
- return;
2110
+ }
2111
+ const TTS_WS_RETRY_DELAYS_MS = [250, 750, 1500];
2112
+ const FORCED_TTS_VOICE = "zeynep";
2113
+ const normalizeBaseUrl = (baseUrl) => {
2114
+ const trimmed = baseUrl.trim().replace(/\/+$/, "");
2115
+ if (/^https?:\/\//i.test(trimmed)) {
2116
+ return trimmed;
2521
2117
  }
2522
- const parentForm = element.closest("form");
2523
- if (parentForm) {
2524
- parentForm.requestSubmit();
2525
- return;
2118
+ return `https://${trimmed}`;
2119
+ };
2120
+ const toWebSocketUrl = (baseUrl, path2) => {
2121
+ const normalized = normalizeBaseUrl(baseUrl);
2122
+ const url = new URL(normalized);
2123
+ url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
2124
+ url.pathname = `${url.pathname.replace(/\/$/, "")}${path2}`;
2125
+ url.search = "";
2126
+ url.hash = "";
2127
+ return url.toString();
2128
+ };
2129
+ const createRequestId = () => {
2130
+ if (typeof crypto !== "undefined" && "randomUUID" in crypto) {
2131
+ return crypto.randomUUID();
2526
2132
  }
2527
- console.warn("AuticBot interact: submit action requires a form target.");
2133
+ return `tts-${Date.now()}-${Math.random().toString(16).slice(2)}`;
2528
2134
  };
2529
- const slowScrollElementIntoView = async (element) => {
2530
- await slowScrollElementIntoViewWithMode(element, false);
2135
+ const parseTtsWsEventPayload = (value) => {
2136
+ try {
2137
+ if (typeof value !== "string") {
2138
+ return null;
2139
+ }
2140
+ return JSON.parse(value);
2141
+ } catch {
2142
+ return null;
2143
+ }
2531
2144
  };
2532
- const slowScrollElementIntoViewWithMode = async (element, forceCenter) => {
2533
- const rect = element.getBoundingClientRect();
2534
- const viewportHeight = window.innerHeight;
2535
- if (!forceCenter && !isRectOutsideViewport(rect, viewportHeight)) {
2536
- return;
2145
+ const shouldAcceptAudioSeq = (incomingSeq, highestSeqSeen) => incomingSeq > highestSeqSeen;
2146
+ const shouldFallbackToSse = (error) => {
2147
+ if (typeof error === "object" && error !== null && "retryable" in error) {
2148
+ return Boolean(error.retryable);
2537
2149
  }
2538
- const maxScrollTop = Math.max(
2539
- 0,
2540
- Math.max(document.body.scrollHeight, document.documentElement.scrollHeight) - viewportHeight
2541
- );
2542
- const targetY = computeCenteredScrollTop(
2543
- window.scrollY,
2544
- rect.top,
2545
- rect.height,
2546
- viewportHeight,
2547
- maxScrollTop
2548
- );
2549
- await animateWindowScrollTo(targetY, SCROLL_DURATION_MS);
2150
+ return true;
2550
2151
  };
2551
- const executeScroll = async (call2) => {
2552
- const selected = findElementBySelector(call2.selector);
2553
- if (!(selected instanceof HTMLElement)) {
2554
- console.warn(`AuticBot scroll: selector not found: ${call2.selector}`);
2152
+ const parseErrorBody = async (response) => {
2153
+ try {
2154
+ const data2 = await response.json();
2155
+ const detail = data2.detail;
2156
+ if (typeof detail === "string") return detail;
2157
+ if (detail && typeof detail === "object") return JSON.stringify(detail);
2158
+ return data2.error || data2.message || response.statusText;
2159
+ } catch {
2160
+ return response.statusText;
2161
+ }
2162
+ };
2163
+ const sleep = (ms) => new Promise((resolve) => {
2164
+ setTimeout(resolve, ms);
2165
+ });
2166
+ const base64ToUint8Array = (base64) => {
2167
+ const cleanBase64 = base64.replace(/^data:audio\/\w+;base64,/, "");
2168
+ const binaryString = atob(cleanBase64);
2169
+ const bytes = new Uint8Array(binaryString.length);
2170
+ for (let i = 0; i < binaryString.length; i += 1) {
2171
+ bytes[i] = binaryString.charCodeAt(i);
2172
+ }
2173
+ return bytes;
2174
+ };
2175
+ const createWavHeader = (length, sampleRate = 16e3) => {
2176
+ const buffer = new ArrayBuffer(44);
2177
+ const view = new DataView(buffer);
2178
+ const channels = 1;
2179
+ view.setUint32(0, 1380533830, false);
2180
+ view.setUint32(4, 36 + length, true);
2181
+ view.setUint32(8, 1463899717, false);
2182
+ view.setUint32(12, 1718449184, false);
2183
+ view.setUint32(16, 16, true);
2184
+ view.setUint16(20, 1, true);
2185
+ view.setUint16(22, channels, true);
2186
+ view.setUint32(24, sampleRate, true);
2187
+ view.setUint32(28, sampleRate * channels * 2, true);
2188
+ view.setUint16(32, channels * 2, true);
2189
+ view.setUint16(34, 16, true);
2190
+ view.setUint32(36, 1684108385, false);
2191
+ view.setUint32(40, length, true);
2192
+ return new Uint8Array(buffer);
2193
+ };
2194
+ const waitForPlaybackEnd = async (audioElement) => {
2195
+ if (audioElement.ended) {
2555
2196
  return;
2556
2197
  }
2557
- await slowScrollElementIntoViewWithMode(selected, true);
2558
- const center = getElementCenter(selected);
2559
- await moveCursor(center.x, center.y);
2198
+ await new Promise((resolve, reject) => {
2199
+ const watchdog = window.setInterval(() => {
2200
+ if (!audioElement.ended) {
2201
+ console.info("[Bulut] playback watchdog: still playing...");
2202
+ }
2203
+ }, 3e4);
2204
+ const onEnded = () => {
2205
+ cleanup();
2206
+ resolve();
2207
+ };
2208
+ const onError = () => {
2209
+ cleanup();
2210
+ reject(new Error("Ses oynatma hatası oluştu."));
2211
+ };
2212
+ const cleanup = () => {
2213
+ window.clearInterval(watchdog);
2214
+ audioElement.removeEventListener("ended", onEnded);
2215
+ audioElement.removeEventListener("error", onError);
2216
+ };
2217
+ audioElement.addEventListener("ended", onEnded);
2218
+ audioElement.addEventListener("error", onError);
2219
+ });
2560
2220
  };
2561
- const executeInteract = async (call2) => {
2562
- const target = resolveTarget(call2);
2563
- if (!target) {
2221
+ const playBufferedAudio = async (chunks, mimeType, sampleRate = 16e3, onAudioStateChange) => {
2222
+ if (chunks.length === 0) {
2223
+ onAudioStateChange == null ? void 0 : onAudioStateChange("done");
2564
2224
  return;
2565
2225
  }
2566
- if (call2.action === "click" && target.element) {
2567
- await slowScrollElementIntoView(target.element);
2568
- const center = getElementCenter(target.element);
2569
- target.x = center.x;
2570
- target.y = center.y;
2226
+ const totalBytes = chunks.reduce((acc, c2) => acc + c2.byteLength, 0);
2227
+ console.log(`[Bulut] Playing buffered audio: ${chunks.length} chunks, ${totalBytes} bytes, type=${mimeType}`);
2228
+ onAudioStateChange == null ? void 0 : onAudioStateChange("fallback");
2229
+ const blobParts = chunks.map((chunk) => {
2230
+ const copied = new Uint8Array(chunk.byteLength);
2231
+ copied.set(chunk);
2232
+ return copied.buffer;
2233
+ });
2234
+ let detectedMime = mimeType;
2235
+ if (chunks.length > 0 && chunks[0].length >= 4) {
2236
+ const header = Array.from(chunks[0].slice(0, 4)).map((b) => b.toString(16).padStart(2, "0").toUpperCase()).join(" ");
2237
+ console.log(`[Bulut] Audio header (hex): ${header}`);
2238
+ if (header.startsWith("49 44 33")) {
2239
+ detectedMime = "audio/mpeg";
2240
+ } else if (header.startsWith("FF F3") || header.startsWith("FF F2")) {
2241
+ detectedMime = "audio/mpeg";
2242
+ } else if (header.startsWith("52 49 46 46")) {
2243
+ detectedMime = "audio/wav";
2244
+ } else if (header.startsWith("1A 45 DF A3")) {
2245
+ detectedMime = "audio/webm";
2246
+ }
2247
+ }
2248
+ let safeMimeType = detectedMime && detectedMime.includes("/") ? detectedMime : "audio/mpeg";
2249
+ let finalBlobParts = blobParts;
2250
+ if (mimeType === "audio/pcm") {
2251
+ const totalLength = chunks.reduce((acc, c2) => acc + c2.byteLength, 0);
2252
+ const header = createWavHeader(totalLength, sampleRate);
2253
+ finalBlobParts = [header.buffer, ...blobParts];
2254
+ safeMimeType = "audio/wav";
2255
+ console.log(`[Bulut] Wrapped raw PCM in WAV (rate=${sampleRate})`);
2571
2256
  }
2572
- await moveCursor(target.x, target.y);
2573
- if (call2.action === "move") {
2574
- return;
2257
+ console.log(`[Bulut] Creating blob with type: ${safeMimeType} (original: ${mimeType})`);
2258
+ const blob = new Blob(finalBlobParts, { type: safeMimeType });
2259
+ const audioElement = new Audio();
2260
+ const objectUrl = URL.createObjectURL(blob);
2261
+ try {
2262
+ audioElement.preload = "auto";
2263
+ audioElement.autoplay = true;
2264
+ audioElement.setAttribute("playsinline", "true");
2265
+ audioElement.src = objectUrl;
2266
+ await audioElement.play();
2267
+ onAudioStateChange == null ? void 0 : onAudioStateChange("playing");
2268
+ await waitForPlaybackEnd(audioElement);
2269
+ onAudioStateChange == null ? void 0 : onAudioStateChange("done");
2270
+ } catch (err) {
2271
+ console.error(`[Bulut] Playback failed: ${err}`, { mimeType: safeMimeType, size: blob.size });
2272
+ onAudioStateChange == null ? void 0 : onAudioStateChange("done");
2273
+ throw err;
2274
+ } finally {
2275
+ audioElement.pause();
2276
+ audioElement.removeAttribute("src");
2277
+ audioElement.load();
2278
+ URL.revokeObjectURL(objectUrl);
2575
2279
  }
2576
- if (!target.element) {
2577
- console.warn("AuticBot interact: target element not available for action.", call2.action);
2578
- return;
2280
+ };
2281
+ const parseSseEventPayload = (eventBlock) => {
2282
+ const dataLines = eventBlock.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trimStart());
2283
+ if (dataLines.length === 0) {
2284
+ return null;
2579
2285
  }
2580
- if (call2.action === "click") {
2581
- dispatchMouseEvent(target.element, "pointerdown", target.x, target.y);
2582
- dispatchMouseEvent(target.element, "mousedown", target.x, target.y);
2583
- dispatchMouseEvent(target.element, "pointerup", target.x, target.y);
2584
- dispatchMouseEvent(target.element, "mouseup", target.x, target.y);
2585
- target.element.click();
2586
- return;
2286
+ const dataStr = dataLines.join("\n");
2287
+ if (dataStr === "[DONE]") {
2288
+ return { type: "done" };
2587
2289
  }
2588
- if (call2.action === "type") {
2589
- typeIntoElement(target.element, call2.text ?? "");
2590
- return;
2290
+ try {
2291
+ return JSON.parse(dataStr);
2292
+ } catch (error) {
2293
+ console.warn("Error parsing SSE chunk:", error);
2294
+ return null;
2591
2295
  }
2592
- submitElement(target.element);
2593
2296
  };
2594
- const isSamePageNavigation = (targetUrl) => {
2595
- try {
2596
- const current = new URL(window.location.href);
2597
- const target = new URL(targetUrl);
2598
- return current.origin === target.origin && current.pathname === target.pathname;
2599
- } catch {
2600
- return false;
2297
+ const isAudioSsePayload = (payload) => typeof payload.audio === "string" && (payload.type === void 0 || payload.type === "audio");
2298
+ async function transcribeAudio(baseUrl, file, projectId, sessionId, language) {
2299
+ const url = `${normalizeBaseUrl(baseUrl)}/chat/stt`;
2300
+ const formData = new FormData();
2301
+ formData.append("file", file);
2302
+ formData.append("project_id", projectId);
2303
+ if (sessionId) formData.append("session_id", sessionId);
2304
+ formData.append("language", language);
2305
+ const response = await fetch(url, { method: "POST", body: formData });
2306
+ if (!response.ok) {
2307
+ throw new Error(await parseErrorBody(response));
2601
2308
  }
2309
+ return response.json();
2310
+ }
2311
+ const buildError = (message, retryable = true) => {
2312
+ const error = new Error(message);
2313
+ error.retryable = retryable;
2314
+ return error;
2602
2315
  };
2603
- const findMatchingLinkForTarget = (targetUrl) => {
2604
- let parsedTarget = null;
2605
- try {
2606
- parsedTarget = new URL(targetUrl, window.location.href);
2607
- } catch {
2316
+ const collectTtsViaSse = async (baseUrl, assistantText, accessibilityMode, isStopped, setReader) => {
2317
+ var _a;
2318
+ const ttsFormData = new FormData();
2319
+ ttsFormData.append("text", assistantText);
2320
+ ttsFormData.append("voice", FORCED_TTS_VOICE);
2321
+ ttsFormData.append("accessibility_mode", String(accessibilityMode));
2322
+ const ttsResponse = await fetch(`${normalizeBaseUrl(baseUrl)}/chat/tts`, {
2323
+ method: "POST",
2324
+ body: ttsFormData
2325
+ });
2326
+ if (!ttsResponse.ok) {
2327
+ throw buildError(await parseErrorBody(ttsResponse), false);
2608
2328
  }
2609
- const allLinks = Array.from(
2610
- document.querySelectorAll('a[href], [role="link"][href], [data-href]')
2611
- );
2612
- for (const el of allLinks) {
2613
- if (el instanceof HTMLAnchorElement && el.href === (parsedTarget == null ? void 0 : parsedTarget.href)) {
2614
- return el;
2329
+ const reader = (_a = ttsResponse.body) == null ? void 0 : _a.getReader();
2330
+ if (!reader) {
2331
+ throw buildError("TTS response body is not readable", false);
2332
+ }
2333
+ setReader(reader);
2334
+ const chunks = [];
2335
+ let mimeType = "audio/mpeg";
2336
+ let sampleRate = 16e3;
2337
+ const decoder = new TextDecoder();
2338
+ let buffer = "";
2339
+ while (true) {
2340
+ if (isStopped()) {
2341
+ break;
2342
+ }
2343
+ const { done, value } = await reader.read();
2344
+ if (done) {
2345
+ break;
2346
+ }
2347
+ buffer += decoder.decode(value, { stream: true });
2348
+ const blocks = buffer.split(/\r?\n\r?\n/);
2349
+ buffer = blocks.pop() || "";
2350
+ for (const block of blocks) {
2351
+ const payload = parseSseEventPayload(block);
2352
+ if (!payload) {
2353
+ continue;
2354
+ }
2355
+ if (isAudioSsePayload(payload)) {
2356
+ const format = payload.format || "mp3";
2357
+ mimeType = payload.mime_type || (format === "webm" ? "audio/webm" : "audio/mpeg");
2358
+ chunks.push(base64ToUint8Array(payload.audio));
2359
+ if (payload.sample_rate) {
2360
+ sampleRate = payload.sample_rate;
2361
+ }
2362
+ }
2363
+ }
2364
+ }
2365
+ reader.releaseLock();
2366
+ setReader(void 0);
2367
+ return { chunks, mimeType, sampleRate };
2368
+ };
2369
+ const collectTtsViaWebSocket = async (baseUrl, assistantText, accessibilityMode, isStopped, setSocket) => {
2370
+ const wsUrl = toWebSocketUrl(baseUrl, "/chat/tts/ws");
2371
+ const requestId = createRequestId();
2372
+ const chunks = [];
2373
+ let mimeType = "audio/mpeg";
2374
+ let sampleRate = 16e3;
2375
+ let highestSeqSeen = 0;
2376
+ const connectOnce = () => new Promise((resolve, reject) => {
2377
+ if (isStopped()) {
2378
+ reject(buildError("stream_stopped", false));
2379
+ return;
2380
+ }
2381
+ let done = false;
2382
+ let finalError = null;
2383
+ const socket = new WebSocket(wsUrl);
2384
+ setSocket(socket);
2385
+ const finalize = (mode, error) => {
2386
+ socket.onopen = null;
2387
+ socket.onmessage = null;
2388
+ socket.onerror = null;
2389
+ socket.onclose = null;
2390
+ setSocket(null);
2391
+ if (mode === "resolve") {
2392
+ resolve();
2393
+ return;
2394
+ }
2395
+ reject(error || buildError("tts_ws_closed", true));
2396
+ };
2397
+ socket.onopen = () => {
2398
+ console.info(
2399
+ `[Bulut] TTS WS connected request_id=${requestId} resume_seq=${highestSeqSeen}`
2400
+ );
2401
+ socket.send(
2402
+ JSON.stringify({
2403
+ type: "start",
2404
+ request_id: requestId,
2405
+ text: assistantText,
2406
+ voice: FORCED_TTS_VOICE,
2407
+ accessibility_mode: accessibilityMode,
2408
+ last_seq: highestSeqSeen
2409
+ })
2410
+ );
2411
+ };
2412
+ socket.onmessage = (event) => {
2413
+ const payload = parseTtsWsEventPayload(String(event.data));
2414
+ if (!payload) {
2415
+ console.warn("[Bulut] TTS WS invalid JSON payload");
2416
+ return;
2417
+ }
2418
+ if (payload.type === "audio" && typeof payload.audio === "string") {
2419
+ const seq = typeof payload.seq === "number" ? payload.seq : 0;
2420
+ if (shouldAcceptAudioSeq(seq, highestSeqSeen)) {
2421
+ chunks.push(base64ToUint8Array(payload.audio));
2422
+ highestSeqSeen = seq;
2423
+ if (payload.mime_type) {
2424
+ mimeType = payload.mime_type;
2425
+ }
2426
+ if (typeof payload.sample_rate === "number") {
2427
+ sampleRate = payload.sample_rate;
2428
+ }
2429
+ } else {
2430
+ console.info(
2431
+ `[Bulut] TTS WS duplicate chunk ignored request_id=${requestId} seq=${seq} seen=${highestSeqSeen}`
2432
+ );
2433
+ }
2434
+ if (socket.readyState === WebSocket.OPEN) {
2435
+ socket.send(
2436
+ JSON.stringify({
2437
+ type: "ack",
2438
+ request_id: requestId,
2439
+ last_seq: highestSeqSeen
2440
+ })
2441
+ );
2442
+ }
2443
+ return;
2444
+ }
2445
+ if (payload.type === "done") {
2446
+ const streamLastSeq = typeof payload.last_seq === "number" ? payload.last_seq : highestSeqSeen;
2447
+ if (streamLastSeq > highestSeqSeen) {
2448
+ finalError = buildError("tts_ws_sequence_gap", true);
2449
+ done = false;
2450
+ socket.close();
2451
+ return;
2452
+ }
2453
+ done = true;
2454
+ socket.close();
2455
+ return;
2456
+ }
2457
+ if (payload.type === "error") {
2458
+ finalError = buildError(payload.error || "tts_ws_error", payload.retryable !== false);
2459
+ done = false;
2460
+ socket.close();
2461
+ }
2462
+ };
2463
+ socket.onerror = () => {
2464
+ if (!finalError) {
2465
+ finalError = buildError("tts_ws_transport_error", true);
2466
+ }
2467
+ };
2468
+ socket.onclose = () => {
2469
+ if (isStopped()) {
2470
+ finalize("reject", buildError("stream_stopped", false));
2471
+ return;
2472
+ }
2473
+ if (done) {
2474
+ finalize("resolve");
2475
+ return;
2476
+ }
2477
+ finalize("reject", finalError || buildError("tts_ws_closed_before_done", true));
2478
+ };
2479
+ });
2480
+ for (let attempt = 0; attempt <= TTS_WS_RETRY_DELAYS_MS.length; attempt += 1) {
2481
+ if (attempt > 0) {
2482
+ const delay = TTS_WS_RETRY_DELAYS_MS[attempt - 1];
2483
+ console.warn(
2484
+ `[Bulut] TTS WS retry attempt=${attempt} delay_ms=${delay} last_seq=${highestSeqSeen}`
2485
+ );
2486
+ await sleep(delay);
2487
+ }
2488
+ try {
2489
+ await connectOnce();
2490
+ return { chunks, mimeType, sampleRate };
2491
+ } catch (error) {
2492
+ const retryable = shouldFallbackToSse(error);
2493
+ const message = error instanceof Error ? error.message : String(error);
2494
+ console.warn(
2495
+ `[Bulut] TTS WS attempt failed attempt=${attempt} retryable=${retryable} error=${message}`
2496
+ );
2497
+ if (!retryable || attempt === TTS_WS_RETRY_DELAYS_MS.length) {
2498
+ throw error;
2499
+ }
2615
2500
  }
2616
2501
  }
2617
- if (parsedTarget) {
2618
- for (const el of allLinks) {
2619
- if (!(el instanceof HTMLAnchorElement)) continue;
2502
+ throw buildError("tts_ws_exhausted", true);
2503
+ };
2504
+ const voiceChatStream = (baseUrl, audioFile, projectId, sessionId, config, events) => {
2505
+ let isStopped = false;
2506
+ let activeReader;
2507
+ let activeSocket = null;
2508
+ const donePromise = new Promise(async (resolve, reject) => {
2509
+ var _a, _b, _c, _d, _e, _f, _g, _h;
2510
+ try {
2511
+ if (isStopped) return resolve();
2512
+ const sttResult = await transcribeAudio(baseUrl, audioFile, projectId, sessionId, "tr");
2513
+ const currentSessionId = sttResult.session_id;
2514
+ const userText = sttResult.text;
2515
+ (_a = events.onTranscription) == null ? void 0 : _a.call(events, {
2516
+ session_id: currentSessionId,
2517
+ user_text: userText
2518
+ });
2519
+ if (isStopped) return resolve();
2520
+ const llmFormData = new FormData();
2521
+ llmFormData.append("project_id", projectId);
2522
+ llmFormData.append("session_id", currentSessionId);
2523
+ llmFormData.append("user_text", userText);
2524
+ llmFormData.append("model", config.model);
2525
+ if (config.pageContext) llmFormData.append("page_context", config.pageContext);
2526
+ llmFormData.append("accessibility_mode", String(Boolean(config.accessibilityMode)));
2527
+ const llmResponse = await fetch(`${normalizeBaseUrl(baseUrl)}/chat/llm`, {
2528
+ method: "POST",
2529
+ body: llmFormData
2530
+ });
2531
+ if (!llmResponse.ok) {
2532
+ throw new Error(await parseErrorBody(llmResponse));
2533
+ }
2534
+ activeReader = (_b = llmResponse.body) == null ? void 0 : _b.getReader();
2535
+ if (!activeReader) throw new Error("LLM response body is not readable");
2536
+ const decoder = new TextDecoder();
2537
+ let buffer = "";
2538
+ let assistantText = "";
2539
+ while (true) {
2540
+ if (isStopped) break;
2541
+ const { done, value } = await activeReader.read();
2542
+ if (done) break;
2543
+ buffer += decoder.decode(value, { stream: true });
2544
+ const chunks = buffer.split(/\r?\n\r?\n/);
2545
+ buffer = chunks.pop() || "";
2546
+ for (const chunk of chunks) {
2547
+ const data2 = parseSseEventPayload(chunk);
2548
+ if (!data2) continue;
2549
+ if (data2.type === "session" && data2.session_id) {
2550
+ (_c = events.onTranscription) == null ? void 0 : _c.call(events, {
2551
+ session_id: data2.session_id,
2552
+ user_text: sttResult.text
2553
+ });
2554
+ continue;
2555
+ }
2556
+ if (data2.type === "llm_delta" && typeof data2.delta === "string") {
2557
+ (_d = events.onAssistantDelta) == null ? void 0 : _d.call(events, data2.delta);
2558
+ continue;
2559
+ }
2560
+ if (data2.type === "llm_done") {
2561
+ assistantText = data2.assistant_text || "";
2562
+ (_e = events.onAssistantDone) == null ? void 0 : _e.call(events, assistantText);
2563
+ continue;
2564
+ }
2565
+ if (data2.type === "error") {
2566
+ throw new Error(data2.error || "LLM Error");
2567
+ }
2568
+ }
2569
+ }
2570
+ if (activeReader) {
2571
+ activeReader.releaseLock();
2572
+ activeReader = void 0;
2573
+ }
2574
+ if (isStopped || !assistantText) {
2575
+ return resolve();
2576
+ }
2577
+ console.info(
2578
+ `[Bulut] TTS start mode=voice requested_voice=${config.voice} forced_voice=${FORCED_TTS_VOICE} accessibility_mode=${Boolean(config.accessibilityMode)}`
2579
+ );
2580
+ (_f = events.onAudioStateChange) == null ? void 0 : _f.call(events, "rendering");
2581
+ let ttsResult;
2620
2582
  try {
2621
- const elUrl = new URL(el.href, window.location.href);
2622
- if (elUrl.pathname === parsedTarget.pathname && elUrl.search === parsedTarget.search && elUrl.hash === parsedTarget.hash) {
2623
- return el;
2583
+ ttsResult = await collectTtsViaWebSocket(
2584
+ baseUrl,
2585
+ assistantText,
2586
+ Boolean(config.accessibilityMode),
2587
+ () => isStopped,
2588
+ (socket) => {
2589
+ activeSocket = socket;
2590
+ }
2591
+ );
2592
+ } catch (wsError) {
2593
+ if (isStopped) {
2594
+ return resolve();
2624
2595
  }
2625
- } catch {
2626
- continue;
2596
+ console.warn(
2597
+ `[Bulut] TTS WS failed, falling back to SSE: ${wsError instanceof Error ? wsError.message : String(wsError)}`
2598
+ );
2599
+ ttsResult = await collectTtsViaSse(
2600
+ baseUrl,
2601
+ assistantText,
2602
+ Boolean(config.accessibilityMode),
2603
+ () => isStopped,
2604
+ (reader) => {
2605
+ activeReader = reader;
2606
+ }
2607
+ );
2608
+ }
2609
+ if (!isStopped && ttsResult.chunks.length > 0) {
2610
+ await playBufferedAudio(
2611
+ ttsResult.chunks,
2612
+ ttsResult.mimeType,
2613
+ ttsResult.sampleRate,
2614
+ events.onAudioStateChange
2615
+ );
2616
+ } else {
2617
+ (_g = events.onAudioStateChange) == null ? void 0 : _g.call(events, "done");
2618
+ }
2619
+ resolve();
2620
+ } catch (err) {
2621
+ const msg = err instanceof Error ? err.message : String(err);
2622
+ (_h = events.onError) == null ? void 0 : _h.call(events, msg);
2623
+ reject(err);
2624
+ } finally {
2625
+ activeReader == null ? void 0 : activeReader.cancel().catch(() => {
2626
+ });
2627
+ if (activeSocket && activeSocket.readyState <= WebSocket.OPEN) {
2628
+ activeSocket.close();
2629
+ }
2630
+ activeSocket = null;
2631
+ }
2632
+ });
2633
+ return {
2634
+ stop: () => {
2635
+ isStopped = true;
2636
+ if (activeReader) {
2637
+ activeReader.cancel().catch(() => {
2638
+ });
2639
+ }
2640
+ if (activeSocket && activeSocket.readyState <= WebSocket.OPEN) {
2641
+ activeSocket.close();
2642
+ }
2643
+ },
2644
+ done: donePromise
2645
+ };
2646
+ };
2647
+ const agentVoiceChatStream = (baseUrl, audioFile, projectId, sessionId, config, events, executeTool) => {
2648
+ let isStopped = false;
2649
+ let activeSocket = null;
2650
+ let activeReader;
2651
+ let errorEmitted = false;
2652
+ const donePromise = new Promise(async (resolve, reject) => {
2653
+ var _a, _b, _c, _d;
2654
+ try {
2655
+ if (isStopped) return resolve();
2656
+ const sttResult = await transcribeAudio(
2657
+ baseUrl,
2658
+ audioFile,
2659
+ projectId,
2660
+ sessionId,
2661
+ "tr"
2662
+ );
2663
+ const currentSessionId = sttResult.session_id;
2664
+ let effectiveSessionId = currentSessionId;
2665
+ const userText = sttResult.text;
2666
+ (_a = events.onTranscription) == null ? void 0 : _a.call(events, {
2667
+ session_id: currentSessionId,
2668
+ user_text: userText
2669
+ });
2670
+ if (isStopped) return resolve();
2671
+ const assistantText = await new Promise((agentResolve, agentReject) => {
2672
+ if (isStopped) {
2673
+ agentResolve("");
2674
+ return;
2675
+ }
2676
+ const wsUrl = toWebSocketUrl(baseUrl, "/chat/agent/ws");
2677
+ const socket = new WebSocket(wsUrl);
2678
+ activeSocket = socket;
2679
+ let finalReply = "";
2680
+ let resolved = false;
2681
+ const finish = (reply) => {
2682
+ if (resolved) return;
2683
+ resolved = true;
2684
+ agentResolve(reply);
2685
+ };
2686
+ const fail = (error) => {
2687
+ if (resolved) return;
2688
+ resolved = true;
2689
+ agentReject(error);
2690
+ };
2691
+ socket.onopen = () => {
2692
+ console.info("[Bulut] Agent WS connected");
2693
+ socket.send(JSON.stringify({
2694
+ type: "start",
2695
+ project_id: projectId,
2696
+ session_id: currentSessionId,
2697
+ user_text: userText,
2698
+ model: config.model,
2699
+ page_context: config.pageContext,
2700
+ accessibility_mode: config.accessibilityMode
2701
+ }));
2702
+ };
2703
+ socket.onmessage = async (event) => {
2704
+ var _a2, _b2, _c2, _d2, _e, _f, _g, _h;
2705
+ let data2;
2706
+ try {
2707
+ data2 = JSON.parse(String(event.data));
2708
+ } catch {
2709
+ console.warn("[Bulut] Agent WS invalid JSON");
2710
+ return;
2711
+ }
2712
+ const msgType = data2.type;
2713
+ if (msgType === "session" && typeof data2.session_id === "string") {
2714
+ effectiveSessionId = data2.session_id;
2715
+ (_a2 = events.onSessionId) == null ? void 0 : _a2.call(events, effectiveSessionId);
2716
+ return;
2717
+ }
2718
+ if (msgType === "iteration") {
2719
+ (_b2 = events.onIteration) == null ? void 0 : _b2.call(
2720
+ events,
2721
+ data2.iteration,
2722
+ data2.max_iterations
2723
+ );
2724
+ return;
2725
+ }
2726
+ if (msgType === "reply_delta" && typeof data2.delta === "string") {
2727
+ (_c2 = events.onAssistantDelta) == null ? void 0 : _c2.call(events, data2.delta);
2728
+ return;
2729
+ }
2730
+ if (msgType === "tool_calls" && Array.isArray(data2.calls)) {
2731
+ const calls = data2.calls;
2732
+ (_d2 = events.onToolCalls) == null ? void 0 : _d2.call(events, calls);
2733
+ const results = [];
2734
+ for (const call2 of calls) {
2735
+ const isNavigate = call2.tool === "navigate";
2736
+ if (isNavigate) {
2737
+ savePendingAgentResume({
2738
+ sessionId: effectiveSessionId,
2739
+ projectId,
2740
+ model: config.model,
2741
+ accessibilityMode: Boolean(config.accessibilityMode),
2742
+ pendingToolCalls: calls.map((c2) => ({
2743
+ call_id: c2.call_id,
2744
+ tool: c2.tool,
2745
+ args: c2.args
2746
+ })),
2747
+ completedResults: [...results]
2748
+ });
2749
+ }
2750
+ const result = await executeTool(call2);
2751
+ if (isNavigate) {
2752
+ clearPendingAgentResume();
2753
+ }
2754
+ (_e = events.onToolResult) == null ? void 0 : _e.call(events, call2.call_id, call2.tool, result.result);
2755
+ results.push(result);
2756
+ }
2757
+ if (socket.readyState === WebSocket.OPEN) {
2758
+ socket.send(JSON.stringify({
2759
+ type: "tool_results",
2760
+ results
2761
+ }));
2762
+ }
2763
+ return;
2764
+ }
2765
+ if (msgType === "agent_done") {
2766
+ finalReply = data2.final_reply || "";
2767
+ (_f = events.onAssistantDone) == null ? void 0 : _f.call(events, finalReply);
2768
+ if (typeof data2.session_id === "string") {
2769
+ (_g = events.onSessionId) == null ? void 0 : _g.call(events, data2.session_id);
2770
+ }
2771
+ finish(finalReply);
2772
+ return;
2773
+ }
2774
+ if (msgType === "error") {
2775
+ const errMsg = data2.error || "Agent error";
2776
+ errorEmitted = true;
2777
+ (_h = events.onError) == null ? void 0 : _h.call(events, errMsg);
2778
+ fail(new Error(errMsg));
2779
+ return;
2780
+ }
2781
+ };
2782
+ socket.onerror = () => {
2783
+ var _a2;
2784
+ console.error("[Bulut] Agent WS error");
2785
+ errorEmitted = true;
2786
+ (_a2 = events.onError) == null ? void 0 : _a2.call(events, "Agent WebSocket connection error");
2787
+ fail(new Error("Agent WebSocket connection error"));
2788
+ };
2789
+ socket.onclose = () => {
2790
+ console.info("[Bulut] Agent WS closed");
2791
+ finish(finalReply);
2792
+ };
2793
+ });
2794
+ activeSocket = null;
2795
+ if (isStopped || !assistantText) {
2796
+ return resolve();
2627
2797
  }
2628
- }
2629
- for (const el of allLinks) {
2630
- if (!(el instanceof HTMLAnchorElement)) continue;
2798
+ console.info(
2799
+ `[Bulut] TTS start mode=agent forced_voice=${FORCED_TTS_VOICE}`
2800
+ );
2801
+ (_b = events.onAudioStateChange) == null ? void 0 : _b.call(events, "rendering");
2802
+ let ttsResult;
2631
2803
  try {
2632
- const elUrl = new URL(el.href, window.location.href);
2633
- if (elUrl.pathname === parsedTarget.pathname) {
2634
- return el;
2635
- }
2636
- } catch {
2637
- continue;
2804
+ ttsResult = await collectTtsViaWebSocket(
2805
+ baseUrl,
2806
+ assistantText,
2807
+ Boolean(config.accessibilityMode),
2808
+ () => isStopped,
2809
+ (socket) => {
2810
+ activeSocket = socket;
2811
+ }
2812
+ );
2813
+ } catch (wsError) {
2814
+ if (isStopped) return resolve();
2815
+ console.warn(
2816
+ `[Bulut] TTS WS failed, falling back to SSE: ${wsError instanceof Error ? wsError.message : String(wsError)}`
2817
+ );
2818
+ ttsResult = await collectTtsViaSse(
2819
+ baseUrl,
2820
+ assistantText,
2821
+ Boolean(config.accessibilityMode),
2822
+ () => isStopped,
2823
+ (reader) => {
2824
+ activeReader = reader;
2825
+ }
2826
+ );
2827
+ }
2828
+ if (!isStopped && ttsResult.chunks.length > 0) {
2829
+ await playBufferedAudio(
2830
+ ttsResult.chunks,
2831
+ ttsResult.mimeType,
2832
+ ttsResult.sampleRate,
2833
+ events.onAudioStateChange
2834
+ );
2835
+ } else {
2836
+ (_c = events.onAudioStateChange) == null ? void 0 : _c.call(events, "done");
2837
+ }
2838
+ resolve();
2839
+ } catch (err) {
2840
+ if (!errorEmitted) {
2841
+ const msg = err instanceof Error ? err.message : String(err);
2842
+ (_d = events.onError) == null ? void 0 : _d.call(events, msg);
2843
+ }
2844
+ reject(err);
2845
+ } finally {
2846
+ activeReader == null ? void 0 : activeReader.cancel().catch(() => {
2847
+ });
2848
+ if (activeSocket && activeSocket.readyState <= WebSocket.OPEN) {
2849
+ activeSocket.close();
2638
2850
  }
2851
+ activeSocket = null;
2639
2852
  }
2640
- const rawUrl = targetUrl.replace(/^\//, "");
2641
- for (const el of allLinks) {
2642
- const href = el.getAttribute("href") || el.getAttribute("data-href") || "";
2643
- if (href && (href === targetUrl || href === rawUrl || href === `/${rawUrl}`)) {
2644
- return el;
2853
+ });
2854
+ return {
2855
+ stop: () => {
2856
+ isStopped = true;
2857
+ if (activeReader) {
2858
+ activeReader.cancel().catch(() => {
2859
+ });
2860
+ }
2861
+ if (activeSocket && activeSocket.readyState <= WebSocket.OPEN) {
2862
+ activeSocket.close();
2645
2863
  }
2864
+ },
2865
+ done: donePromise
2866
+ };
2867
+ };
2868
+ const agentResumeStream = (baseUrl, resumeState, pageContext, events, executeTool) => {
2869
+ let isStopped = false;
2870
+ let activeSocket = null;
2871
+ let activeReader;
2872
+ let errorEmitted = false;
2873
+ const allResults = [...resumeState.completedResults];
2874
+ for (const tc of resumeState.pendingToolCalls) {
2875
+ if (allResults.some((r2) => r2.call_id === tc.call_id)) continue;
2876
+ if (tc.tool === "navigate") {
2877
+ allResults.push({
2878
+ call_id: tc.call_id,
2879
+ result: `Navigasyon tamamlandı. Şu anki sayfa: ${typeof window !== "undefined" ? window.location.href : ""}
2880
+ Sayfa bağlamı: ${pageContext}`
2881
+ });
2882
+ } else {
2883
+ allResults.push({
2884
+ call_id: tc.call_id,
2885
+ result: "Sayfa yeniden yüklendi, bu araç çalıştırılamadı."
2886
+ });
2646
2887
  }
2647
2888
  }
2648
- const urlSegments = targetUrl.replace(/^https?:\/\/[^/]+/, "").replace(/[?#].*$/, "").split("/").filter(Boolean);
2649
- const lastSegment = urlSegments[urlSegments.length - 1] || "";
2650
- if (lastSegment) {
2651
- let searchTerms = [lastSegment];
2652
- if (parsedTarget) {
2653
- for (const [, value] of parsedTarget.searchParams.entries()) {
2654
- if (value) searchTerms.push(value);
2655
- }
2656
- if (parsedTarget.hash) {
2657
- searchTerms.push(parsedTarget.hash.replace(/^#/, ""));
2889
+ const donePromise = new Promise(async (resolve, reject) => {
2890
+ var _a, _b, _c;
2891
+ try {
2892
+ if (isStopped) return resolve();
2893
+ let effectiveSessionId = resumeState.sessionId;
2894
+ const assistantText = await new Promise((agentResolve, agentReject) => {
2895
+ if (isStopped) {
2896
+ agentResolve("");
2897
+ return;
2898
+ }
2899
+ const wsUrl = toWebSocketUrl(baseUrl, "/chat/agent/ws");
2900
+ const socket = new WebSocket(wsUrl);
2901
+ activeSocket = socket;
2902
+ let finalReply = "";
2903
+ let resolved = false;
2904
+ const finish = (reply) => {
2905
+ if (resolved) return;
2906
+ resolved = true;
2907
+ agentResolve(reply);
2908
+ };
2909
+ const fail = (error) => {
2910
+ if (resolved) return;
2911
+ resolved = true;
2912
+ agentReject(error);
2913
+ };
2914
+ socket.onopen = () => {
2915
+ console.info("[Bulut] Agent WS resume connected");
2916
+ socket.send(JSON.stringify({
2917
+ type: "resume",
2918
+ project_id: resumeState.projectId,
2919
+ session_id: resumeState.sessionId,
2920
+ model: resumeState.model,
2921
+ page_context: pageContext,
2922
+ accessibility_mode: resumeState.accessibilityMode,
2923
+ pending_tool_calls: resumeState.pendingToolCalls,
2924
+ tool_results: allResults
2925
+ }));
2926
+ };
2927
+ socket.onmessage = async (event) => {
2928
+ var _a2, _b2, _c2, _d, _e, _f, _g, _h;
2929
+ let data2;
2930
+ try {
2931
+ data2 = JSON.parse(String(event.data));
2932
+ } catch {
2933
+ return;
2934
+ }
2935
+ const msgType = data2.type;
2936
+ if (msgType === "session" && typeof data2.session_id === "string") {
2937
+ effectiveSessionId = data2.session_id;
2938
+ (_a2 = events.onSessionId) == null ? void 0 : _a2.call(events, effectiveSessionId);
2939
+ return;
2940
+ }
2941
+ if (msgType === "iteration") {
2942
+ (_b2 = events.onIteration) == null ? void 0 : _b2.call(
2943
+ events,
2944
+ data2.iteration,
2945
+ data2.max_iterations
2946
+ );
2947
+ return;
2948
+ }
2949
+ if (msgType === "reply_delta" && typeof data2.delta === "string") {
2950
+ (_c2 = events.onAssistantDelta) == null ? void 0 : _c2.call(events, data2.delta);
2951
+ return;
2952
+ }
2953
+ if (msgType === "tool_calls" && Array.isArray(data2.calls)) {
2954
+ const calls = data2.calls;
2955
+ (_d = events.onToolCalls) == null ? void 0 : _d.call(events, calls);
2956
+ const results = [];
2957
+ for (const call2 of calls) {
2958
+ const isNavigate = call2.tool === "navigate";
2959
+ if (isNavigate) {
2960
+ savePendingAgentResume({
2961
+ sessionId: effectiveSessionId,
2962
+ projectId: resumeState.projectId,
2963
+ model: resumeState.model,
2964
+ accessibilityMode: resumeState.accessibilityMode,
2965
+ pendingToolCalls: calls.map((c2) => ({
2966
+ call_id: c2.call_id,
2967
+ tool: c2.tool,
2968
+ args: c2.args
2969
+ })),
2970
+ completedResults: [...results]
2971
+ });
2972
+ }
2973
+ const result = await executeTool(call2);
2974
+ if (isNavigate) {
2975
+ clearPendingAgentResume();
2976
+ }
2977
+ (_e = events.onToolResult) == null ? void 0 : _e.call(events, call2.call_id, call2.tool, result.result);
2978
+ results.push(result);
2979
+ }
2980
+ if (socket.readyState === WebSocket.OPEN) {
2981
+ socket.send(JSON.stringify({ type: "tool_results", results }));
2982
+ }
2983
+ return;
2984
+ }
2985
+ if (msgType === "agent_done") {
2986
+ finalReply = data2.final_reply || "";
2987
+ (_f = events.onAssistantDone) == null ? void 0 : _f.call(events, finalReply);
2988
+ if (typeof data2.session_id === "string") {
2989
+ (_g = events.onSessionId) == null ? void 0 : _g.call(events, data2.session_id);
2990
+ }
2991
+ finish(finalReply);
2992
+ return;
2993
+ }
2994
+ if (msgType === "error") {
2995
+ const errMsg = data2.error || "Agent error";
2996
+ errorEmitted = true;
2997
+ (_h = events.onError) == null ? void 0 : _h.call(events, errMsg);
2998
+ fail(new Error(errMsg));
2999
+ return;
3000
+ }
3001
+ };
3002
+ socket.onerror = () => {
3003
+ var _a2;
3004
+ errorEmitted = true;
3005
+ (_a2 = events.onError) == null ? void 0 : _a2.call(events, "Agent WebSocket error");
3006
+ fail(new Error("Agent WebSocket error"));
3007
+ };
3008
+ socket.onclose = () => finish(finalReply);
3009
+ });
3010
+ activeSocket = null;
3011
+ if (isStopped || !assistantText) return resolve();
3012
+ console.info(`[Bulut] TTS start mode=resume forced_voice=${FORCED_TTS_VOICE}`);
3013
+ (_a = events.onAudioStateChange) == null ? void 0 : _a.call(events, "rendering");
3014
+ let ttsResult;
3015
+ try {
3016
+ ttsResult = await collectTtsViaWebSocket(
3017
+ baseUrl,
3018
+ assistantText,
3019
+ Boolean(resumeState.accessibilityMode),
3020
+ () => isStopped,
3021
+ (socket) => {
3022
+ activeSocket = socket;
3023
+ }
3024
+ );
3025
+ } catch (wsError) {
3026
+ if (isStopped) return resolve();
3027
+ console.warn(
3028
+ `[Bulut] TTS WS failed, falling back to SSE: ${wsError instanceof Error ? wsError.message : String(wsError)}`
3029
+ );
3030
+ ttsResult = await collectTtsViaSse(
3031
+ baseUrl,
3032
+ assistantText,
3033
+ Boolean(resumeState.accessibilityMode),
3034
+ () => isStopped,
3035
+ (reader) => {
3036
+ activeReader = reader;
3037
+ }
3038
+ );
2658
3039
  }
2659
- }
2660
- searchTerms = searchTerms.map((t2) => t2.toLowerCase());
2661
- const clickables = Array.from(
2662
- document.querySelectorAll(
2663
- 'a, button, [role="link"], [role="tab"], [role="button"], [data-tab], [onclick]'
2664
- )
2665
- );
2666
- for (const el of clickables) {
2667
- const text = (el.textContent || "").trim().toLowerCase();
2668
- const ariaLabel = (el.getAttribute("aria-label") || "").toLowerCase();
2669
- const dataTab = (el.getAttribute("data-tab") || "").toLowerCase();
2670
- for (const term of searchTerms) {
2671
- if (text === term || ariaLabel === term || dataTab === term || text.includes(term)) {
2672
- return el;
2673
- }
3040
+ if (!isStopped && ttsResult.chunks.length > 0) {
3041
+ await playBufferedAudio(
3042
+ ttsResult.chunks,
3043
+ ttsResult.mimeType,
3044
+ ttsResult.sampleRate,
3045
+ events.onAudioStateChange
3046
+ );
3047
+ } else {
3048
+ (_b = events.onAudioStateChange) == null ? void 0 : _b.call(events, "done");
2674
3049
  }
2675
- }
2676
- }
2677
- return null;
2678
- };
2679
- const executeNavigate = async (call2) => {
2680
- try {
2681
- const targetUrl = call2.url;
2682
- let resolvedUrl;
2683
- try {
2684
- resolvedUrl = new URL(targetUrl, window.location.href).href;
2685
- } catch {
2686
- resolvedUrl = targetUrl;
2687
- }
2688
- const matchingElement = findMatchingLinkForTarget(targetUrl);
2689
- if (matchingElement) {
2690
- console.log("AuticBot navigate: clicking element", resolvedUrl, matchingElement.tagName);
2691
- await slowScrollElementIntoView(matchingElement);
2692
- const center = getElementCenter(matchingElement);
2693
- await moveCursor(center.x, center.y);
2694
- matchingElement.dispatchEvent(new MouseEvent("pointerdown", { bubbles: true, view: window }));
2695
- matchingElement.dispatchEvent(new MouseEvent("mousedown", { bubbles: true, view: window }));
2696
- matchingElement.dispatchEvent(new MouseEvent("pointerup", { bubbles: true, view: window }));
2697
- matchingElement.dispatchEvent(new MouseEvent("mouseup", { bubbles: true, view: window }));
2698
- matchingElement.click();
2699
- return !isSamePageNavigation(resolvedUrl);
2700
- }
2701
- console.log("AuticBot navigate: no matching element found, using direct navigation", resolvedUrl);
2702
- try {
2703
- const parsed = new URL(resolvedUrl);
2704
- if (parsed.origin === window.location.origin && parsed.pathname === window.location.pathname && parsed.hash) {
2705
- window.location.hash = parsed.hash;
2706
- return false;
3050
+ resolve();
3051
+ } catch (err) {
3052
+ if (!errorEmitted) {
3053
+ const msg = err instanceof Error ? err.message : String(err);
3054
+ (_c = events.onError) == null ? void 0 : _c.call(events, msg);
2707
3055
  }
2708
- } catch {
2709
- }
2710
- try {
2711
- const parsed = new URL(resolvedUrl);
2712
- if (parsed.origin === window.location.origin) {
2713
- const newPath = parsed.pathname + parsed.search + parsed.hash;
2714
- window.history.pushState({}, "", newPath);
2715
- window.dispatchEvent(new PopStateEvent("popstate", { state: {} }));
2716
- return false;
3056
+ reject(err);
3057
+ } finally {
3058
+ activeReader == null ? void 0 : activeReader.cancel().catch(() => {
3059
+ });
3060
+ if (activeSocket && activeSocket.readyState <= WebSocket.OPEN) {
3061
+ activeSocket.close();
2717
3062
  }
2718
- } catch {
2719
- }
2720
- window.location.href = resolvedUrl;
2721
- return true;
2722
- } catch (error) {
2723
- console.warn("AuticBot navigate: error", call2.url, error);
2724
- return false;
2725
- }
2726
- };
2727
- const executeGetPageContext = async () => {
2728
- const context = getPageContext();
2729
- console.info(
2730
- `[Autic] getPageContext tool executed links=${context.links.length} interactables=${context.interactables.length} summary_len=${context.summary.length}`
2731
- );
2732
- };
2733
- const executeToolCalls = async (toolCalls) => {
2734
- for (const toolCall of toolCalls) {
2735
- if (toolCall.tool === "interact") {
2736
- await executeInteract(toolCall);
2737
- continue;
2738
- }
2739
- if (toolCall.tool === "scroll") {
2740
- await executeScroll(toolCall);
2741
- continue;
2742
- }
2743
- if (toolCall.tool === "getPageContext") {
2744
- await executeGetPageContext();
2745
- continue;
3063
+ activeSocket = null;
2746
3064
  }
2747
- if (toolCall.tool === "navigate") {
2748
- const terminalNavigation = await executeNavigate(toolCall);
2749
- if (terminalNavigation) {
2750
- break;
3065
+ });
3066
+ return {
3067
+ stop: () => {
3068
+ isStopped = true;
3069
+ if (activeReader) activeReader.cancel().catch(() => {
3070
+ });
3071
+ if (activeSocket && activeSocket.readyState <= WebSocket.OPEN) {
3072
+ activeSocket.close();
2751
3073
  }
2752
- }
2753
- }
2754
- };
2755
- const executeSingleToolCall = async (call2) => {
2756
- const callId = call2.call_id;
2757
- try {
2758
- if (call2.tool === "interact") {
2759
- await executeInteract(call2);
2760
- return {
2761
- call_id: callId,
2762
- result: `Etkileşim başarılı: ${call2.action}`
2763
- };
2764
- }
2765
- if (call2.tool === "scroll") {
2766
- await executeScroll(call2);
2767
- return {
2768
- call_id: callId,
2769
- result: "Öğeye kaydırma başarılı."
2770
- };
2771
- }
2772
- if (call2.tool === "getPageContext") {
2773
- const context = getPageContext();
2774
- return {
2775
- call_id: callId,
2776
- result: context.summary
2777
- };
2778
- }
2779
- if (call2.tool === "navigate") {
2780
- await executeNavigate(call2);
2781
- await new Promise((resolve) => setTimeout(resolve, 1500));
2782
- const context = getPageContext();
2783
- return {
2784
- call_id: callId,
2785
- result: `Navigasyon tamamlandı. Şu anki sayfa: ${window.location.href}
2786
- Sayfa bağlamı: ${context.summary}`
2787
- };
2788
- }
2789
- return { call_id: callId, result: "Bilinmeyen araç." };
2790
- } catch (error) {
2791
- const msg = error instanceof Error ? error.message : String(error);
2792
- console.warn(`[Autic] Tool execution error: ${call2.tool}`, error);
2793
- return { call_id: callId, result: `Hata: ${msg}` };
2794
- }
2795
- };
2796
- const restoreCursorFromStorageForCurrentUrl = () => {
2797
- if (typeof document === "undefined" || typeof window === "undefined") {
2798
- return;
2799
- }
2800
- const stored = getPersistedCursorState();
2801
- if (!stored || stored.url !== window.location.href) {
2802
- return;
2803
- }
2804
- ensureCursor();
3074
+ },
3075
+ done: donePromise
3076
+ };
2805
3077
  };
2806
- if (typeof document !== "undefined") {
2807
- if (document.readyState === "loading") {
2808
- document.addEventListener("DOMContentLoaded", restoreCursorFromStorageForCurrentUrl, {
2809
- once: true
2810
- });
2811
- } else {
2812
- restoreCursorFromStorageForCurrentUrl();
2813
- }
2814
- }
2815
3078
  const STORAGE_KEY = "bulut_chat_history";
2816
3079
  const TIMESTAMP_KEY = "bulut_chat_timestamp";
2817
3080
  const SESSION_ID_KEY$1 = "bulut_session_id";
@@ -3087,6 +3350,132 @@ const ChatWindow = ({
3087
3350
  },
3088
3351
  []
3089
3352
  );
3353
+ y(() => {
3354
+ const resumeState = getPendingAgentResume();
3355
+ if (!resumeState) return;
3356
+ clearPendingAgentResume();
3357
+ console.info("[Bulut] Resuming agent after navigation");
3358
+ if (resumeState.sessionId) {
3359
+ sessionIdRef.current = resumeState.sessionId;
3360
+ if (typeof localStorage !== "undefined") {
3361
+ localStorage.setItem(SESSION_ID_KEY$1, resumeState.sessionId);
3362
+ }
3363
+ }
3364
+ setIsBusy(true);
3365
+ setIsRunningTools(true);
3366
+ setStatusOverride(STATUS_LABELS.thinking);
3367
+ const freshPageContext = getPageContext().summary;
3368
+ const resumeToolExec = async (call2) => {
3369
+ const toolCall = {
3370
+ tool: call2.tool,
3371
+ call_id: call2.call_id,
3372
+ ...call2.args
3373
+ };
3374
+ return executeSingleToolCall(toolCall);
3375
+ };
3376
+ const controller = agentResumeStream(
3377
+ config.backendBaseUrl,
3378
+ resumeState,
3379
+ freshPageContext,
3380
+ {
3381
+ onSessionId: (sid) => {
3382
+ if (sid && sid !== sessionIdRef.current) {
3383
+ sessionIdRef.current = sid;
3384
+ if (typeof localStorage !== "undefined") {
3385
+ localStorage.setItem(SESSION_ID_KEY$1, sid);
3386
+ }
3387
+ }
3388
+ },
3389
+ onAssistantDelta: (delta) => {
3390
+ setIsRunningTools(false);
3391
+ setIsThinking(true);
3392
+ setStatusOverride(null);
3393
+ pendingAssistantTextRef.current += delta;
3394
+ if (assistantMessageIdRef.current === null) {
3395
+ assistantMessageIdRef.current = appendMessage(
3396
+ pendingAssistantTextRef.current,
3397
+ false
3398
+ );
3399
+ } else {
3400
+ updateMessageText(
3401
+ assistantMessageIdRef.current,
3402
+ pendingAssistantTextRef.current
3403
+ );
3404
+ }
3405
+ },
3406
+ onAssistantDone: (assistantText) => {
3407
+ setStatusOverride(null);
3408
+ setIsThinking(false);
3409
+ setIsRenderingAudio(true);
3410
+ const finalDisplayText = assistantText || pendingAssistantTextRef.current;
3411
+ pendingAssistantTextRef.current = finalDisplayText;
3412
+ if (assistantMessageIdRef.current !== null) {
3413
+ updateMessageText(
3414
+ assistantMessageIdRef.current,
3415
+ finalDisplayText
3416
+ );
3417
+ } else {
3418
+ assistantMessageIdRef.current = appendMessage(
3419
+ finalDisplayText,
3420
+ false
3421
+ );
3422
+ }
3423
+ },
3424
+ onToolCalls: (calls) => {
3425
+ setIsRunningTools(true);
3426
+ setStatusOverride(STATUS_LABELS.runningTools);
3427
+ for (const call2 of calls) {
3428
+ const toolLabel = call2.tool === "navigate" ? `Sayfaya gidiliyor: ${call2.args.url ?? ""}` : call2.tool === "getPageContext" ? "Sayfa bağlamı alınıyor…" : call2.tool === "interact" ? `Etkileşim: ${call2.args.action ?? ""}` : call2.tool === "scroll" ? "Kaydırılıyor…" : call2.tool;
3429
+ appendMessage(`${toolLabel}`, false);
3430
+ setMessages((prev) => {
3431
+ const last = prev[prev.length - 1];
3432
+ if (last && !last.isUser) {
3433
+ return [
3434
+ ...prev.slice(0, -1),
3435
+ { ...last, type: "tool" }
3436
+ ];
3437
+ }
3438
+ return prev;
3439
+ });
3440
+ }
3441
+ assistantMessageIdRef.current = null;
3442
+ pendingAssistantTextRef.current = "";
3443
+ },
3444
+ onToolResult: () => {
3445
+ },
3446
+ onIteration: () => {
3447
+ setIsThinking(true);
3448
+ setStatusOverride(STATUS_LABELS.thinking);
3449
+ },
3450
+ onAudioStateChange: handleAudioStateChange,
3451
+ onError: (err) => {
3452
+ setStatusOverride(null);
3453
+ appendMessage(`Hata: ${err}`, false);
3454
+ }
3455
+ },
3456
+ resumeToolExec
3457
+ );
3458
+ activeStreamControllerRef.current = controller;
3459
+ controller.done.catch(() => {
3460
+ }).finally(() => {
3461
+ setIsBusy(false);
3462
+ setIsRunningTools(false);
3463
+ setIsThinking(false);
3464
+ setIsRenderingAudio(false);
3465
+ setIsPlayingAudio(false);
3466
+ setStatusOverride(null);
3467
+ pendingAssistantTextRef.current = "";
3468
+ assistantMessageIdRef.current = null;
3469
+ activeStreamControllerRef.current = null;
3470
+ if (shouldAutoListenAfterAudio(
3471
+ accessibilityMode,
3472
+ isRecordingRef.current,
3473
+ isBusyRef.current
3474
+ )) {
3475
+ void startRecording("vad");
3476
+ }
3477
+ });
3478
+ }, []);
3090
3479
  const appendMessage = (text, isUser) => {
3091
3480
  const id2 = nextMessageIdRef.current++;
3092
3481
  setMessages((previous) => [
@@ -3240,7 +3629,7 @@ const ChatWindow = ({
3240
3629
  setStatusOverride(STATUS_LABELS.runningTools);
3241
3630
  for (const call2 of calls) {
3242
3631
  const toolLabel = call2.tool === "navigate" ? `Sayfaya gidiliyor: ${call2.args.url ?? ""}` : call2.tool === "getPageContext" ? "Sayfa bağlamı alınıyor…" : call2.tool === "interact" ? `Etkileşim: ${call2.args.action ?? ""}` : call2.tool === "scroll" ? "Kaydırılıyor…" : call2.tool;
3243
- appendMessage(`🔧 ${toolLabel}`, false);
3632
+ appendMessage(`${toolLabel}`, false);
3244
3633
  setMessages((prev) => {
3245
3634
  const last = prev[prev.length - 1];
3246
3635
  if (last && !last.isUser) {
@@ -3544,14 +3933,14 @@ const ChatWindow = ({
3544
3933
  width: `${WINDOW_WIDTH}px`,
3545
3934
  maxHeight: `${WINDOW_HEIGHT}px`,
3546
3935
  backgroundColor: "hsla(0, 0%, 100%, 1)",
3547
- border: "1px solid rgba(0, 0, 0, 0.2)",
3548
3936
  borderRadius: BORDER_RADIUS.window,
3549
3937
  display: "flex",
3550
3938
  flexDirection: "column",
3551
3939
  overflow: "hidden",
3552
3940
  zIndex: "10000",
3553
3941
  animation: `slideIn ${TRANSITIONS.medium}`,
3554
- boxShadow: SHADOW
3942
+ boxShadow: SHADOW,
3943
+ fontFamily: '"Geist", sans-serif'
3555
3944
  };
3556
3945
  const headerStyle = {
3557
3946
  padding: "14px 16px",
@@ -3583,11 +3972,11 @@ const ChatWindow = ({
3583
3972
  const messagesListStyle = {
3584
3973
  display: "flex",
3585
3974
  flexDirection: "column",
3586
- gap: "8px"
3975
+ gap: "16px"
3587
3976
  };
3588
3977
  const messageStyle = (isUser) => ({
3589
3978
  maxWidth: "84%",
3590
- padding: isUser ? "9px 14px" : "9px 0px",
3979
+ padding: isUser ? "9px 14px" : "0px 0px",
3591
3980
  borderRadius: BORDER_RADIUS.message,
3592
3981
  fontSize: "14px",
3593
3982
  lineHeight: "140%",
@@ -3595,7 +3984,7 @@ const ChatWindow = ({
3595
3984
  whiteSpace: "pre-wrap",
3596
3985
  alignSelf: isUser ? "flex-end" : "flex-start",
3597
3986
  backgroundColor: isUser ? COLORS.messageUser : "",
3598
- color: isUser ? COLORS.messageUserText : "hsla(0, 0%, 10%, 0.8)"
3987
+ color: isUser ? COLORS.messageUserText : "hsla(215, 100%, 5%, 1)"
3599
3988
  });
3600
3989
  const footerStyle = {
3601
3990
  padding: "10px 12px",
@@ -3631,18 +4020,18 @@ const ChatWindow = ({
3631
4020
  color: COLORS.text,
3632
4021
  textAlign: "right"
3633
4022
  };
3634
- const micBackgroundColor = COLORS.primary;
3635
4023
  const micFooterButtonStyle = {
3636
4024
  width: "37px",
3637
4025
  height: "37px",
3638
4026
  borderRadius: "999px",
4027
+ background: "transparent",
3639
4028
  display: "flex",
3640
4029
  alignItems: "center",
3641
4030
  justifyContent: "center",
3642
4031
  cursor: "pointer",
3643
4032
  color: "#ffffff",
3644
- border: "1px solid" + micBackgroundColor,
3645
- transition: `background-color ${TRANSITIONS.fast}, transform ${TRANSITIONS.fast}`
4033
+ border: "1px solid hsla(215, 100%, 5%, 0.5)",
4034
+ transition: `transform ${TRANSITIONS.fast}`
3646
4035
  };
3647
4036
  const disableMicControl = isBusy;
3648
4037
  return /* @__PURE__ */ u$1("div", { style: windowStyle, children: [
@@ -3727,15 +4116,15 @@ const ChatWindow = ({
3727
4116
  "div",
3728
4117
  {
3729
4118
  style: message.type === "tool" ? {
3730
- padding: "5px 12px",
3731
- borderRadius: "8px",
3732
- fontSize: "12px",
4119
+ padding: "9px 14px",
4120
+ fontSize: "14px",
3733
4121
  lineHeight: "1.4",
3734
- color: "#888",
3735
- backgroundColor: "rgba(0, 0, 0, 0.03)",
4122
+ color: "hsla(215, 100%, 5%, 1)",
4123
+ fontWeight: 600,
4124
+ backgroundColor: "hsla(215, 100%, 5%, 0.05)",
4125
+ borderRadius: "10px",
3736
4126
  alignSelf: "flex-start",
3737
- maxWidth: "90%",
3738
- fontStyle: "italic"
4127
+ maxWidth: "84%"
3739
4128
  } : messageStyle(message.isUser),
3740
4129
  children: message.text
3741
4130
  },
@@ -3743,7 +4132,7 @@ const ChatWindow = ({
3743
4132
  )) }) }),
3744
4133
  /* @__PURE__ */ u$1("div", { style: footerStyle, children: [
3745
4134
  /* @__PURE__ */ u$1("div", { style: { ...statusPanelStyle, opacity: statusText === "Hazır" ? 0 : 1, transition: "opacity 0.2s ease-out" }, title: statusText, children: statusText }),
3746
- /* @__PURE__ */ u$1("div", { style: footerActionsStyle, children: [
4135
+ /* @__PURE__ */ u$1("div", { className: "bebek", style: footerActionsStyle, children: [
3747
4136
  isRecording ? /* @__PURE__ */ u$1("span", { style: recordingTimerStyle, children: formatDurationMs(recordingDurationMs) }) : null,
3748
4137
  /* @__PURE__ */ u$1(
3749
4138
  "button",
@@ -3761,7 +4150,7 @@ const ChatWindow = ({
3761
4150
  SvgIcon,
3762
4151
  {
3763
4152
  "fill-opacity": "0",
3764
- stroke: "black",
4153
+ stroke: "hsla(215, 100%, 5%, 1)",
3765
4154
  src: microphoneIconContent,
3766
4155
  width: 22
3767
4156
  }
@@ -7368,12 +7757,14 @@ const extractReplyText = (parser) => {
7368
7757
  return parser.extractField("reply");
7369
7758
  };
7370
7759
  const DEFAULT_LLM_MODEL = "google/gemini-3-flash-preview:nitro";
7760
+ const DEFAULT_AGENT_NAME = "Bulut";
7371
7761
  const DEFAULT_CONFIG = {
7372
7762
  backendBaseUrl: "https://api.bulut.lu",
7373
7763
  projectId: "",
7374
7764
  // Must be provided
7375
7765
  model: DEFAULT_LLM_MODEL,
7376
- baseColor: COLORS.primary
7766
+ baseColor: COLORS.primary,
7767
+ agentName: DEFAULT_AGENT_NAME
7377
7768
  };
7378
7769
  const isValidHexColor = (value) => /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(value);
7379
7770
  const normalizeHexColor = (value) => {
@@ -7407,6 +7798,16 @@ const applyTheme = (baseColor) => {
7407
7798
  COLORS.primaryHover = shadeHexColor(normalized, -0.15);
7408
7799
  COLORS.messageUser = normalized;
7409
7800
  };
7801
+ const fetchRemoteConfig = async (baseUrl, projectId) => {
7802
+ try {
7803
+ const url = baseUrl.replace(/\/+$/, "");
7804
+ const res = await fetch(`${url}/projects/${projectId}/config`);
7805
+ if (!res.ok) return null;
7806
+ return await res.json();
7807
+ } catch {
7808
+ return null;
7809
+ }
7810
+ };
7410
7811
  const resolveRuntimeConfig = (options) => {
7411
7812
  const voice = options.voice === "zeynep" ? "zeynep" : "ali";
7412
7813
  return {
@@ -7414,7 +7815,8 @@ const resolveRuntimeConfig = (options) => {
7414
7815
  projectId: options.projectId || DEFAULT_CONFIG.projectId,
7415
7816
  model: options.model || DEFAULT_CONFIG.model,
7416
7817
  voice,
7417
- baseColor: normalizeHexColor(options.baseColor || DEFAULT_CONFIG.baseColor)
7818
+ baseColor: normalizeHexColor(options.baseColor || DEFAULT_CONFIG.baseColor),
7819
+ agentName: options.agentName || DEFAULT_CONFIG.agentName
7418
7820
  };
7419
7821
  };
7420
7822
  const CHAT_STORAGE_KEY = "bulut_chat_history";
@@ -7423,6 +7825,22 @@ const SESSION_ID_KEY = "bulut_session_id";
7423
7825
  const ACCESSIBILITY_MODE_KEY = "bulut_accessibility_mode_enabled";
7424
7826
  const VAD_THRESHOLD = 0.06;
7425
7827
  const SILENCE_DURATION_MS = 1e3;
7828
+ const GEIST_FONT_FAMILY = "Geist";
7829
+ const GEIST_STYLESHEET_ID = "bulut-geist-font-stylesheet";
7830
+ const GEIST_STYLESHEET_URL = "https://fonts.googleapis.com/css2?family=Geist:wght@100..900&display=swap";
7831
+ const ensureGeistStylesheet = () => {
7832
+ if (typeof document === "undefined") {
7833
+ return;
7834
+ }
7835
+ if (document.getElementById(GEIST_STYLESHEET_ID)) {
7836
+ return;
7837
+ }
7838
+ const link = document.createElement("link");
7839
+ link.id = GEIST_STYLESHEET_ID;
7840
+ link.rel = "stylesheet";
7841
+ link.href = GEIST_STYLESHEET_URL;
7842
+ document.head.appendChild(link);
7843
+ };
7426
7844
  const appendToStoredMessages = (text, isUser) => {
7427
7845
  let messages = [];
7428
7846
  if (typeof localStorage !== "undefined") {
@@ -7458,6 +7876,25 @@ const updateStoredMessage = (id2, text) => {
7458
7876
  }
7459
7877
  };
7460
7878
  const BulutWidget = ({ config }) => {
7879
+ const [liveConfig, setLiveConfig] = d(config);
7880
+ y(() => {
7881
+ if (!config.projectId) return;
7882
+ let cancelled = false;
7883
+ fetchRemoteConfig(config.backendBaseUrl, config.projectId).then((remote) => {
7884
+ if (cancelled || !remote) return;
7885
+ const merged = {
7886
+ ...config,
7887
+ baseColor: normalizeHexColor(remote.base_color || config.baseColor),
7888
+ model: remote.model || config.model,
7889
+ agentName: remote.agent_name || config.agentName
7890
+ };
7891
+ applyTheme(merged.baseColor);
7892
+ setLiveConfig(merged);
7893
+ });
7894
+ return () => {
7895
+ cancelled = true;
7896
+ };
7897
+ }, [config]);
7461
7898
  const [isOpen, setIsOpen] = d(() => {
7462
7899
  if (typeof localStorage !== "undefined") {
7463
7900
  return localStorage.getItem("bulut_panel_open") === "true";
@@ -7495,7 +7932,9 @@ const BulutWidget = ({ config }) => {
7495
7932
  return;
7496
7933
  }
7497
7934
  if (isOpen) return;
7498
- if (typeof localStorage !== "undefined" && localStorage.getItem("bulut_bubble_shown") === "true") return;
7935
+ if (typeof localStorage !== "undefined") {
7936
+ if (localStorage.getItem("bulut_bubble_shown") === "true") return;
7937
+ }
7499
7938
  setShowBubble(true);
7500
7939
  const timer = setTimeout(() => {
7501
7940
  setShowBubble(false);
@@ -7528,7 +7967,7 @@ const BulutWidget = ({ config }) => {
7528
7967
  silenceStartRef.current = null;
7529
7968
  };
7530
7969
  const sendAudioAndPreview = async (blob, mode) => {
7531
- if (!config.projectId) return;
7970
+ if (!liveConfig.projectId) return;
7532
7971
  const fileType = blob.type || "audio/webm";
7533
7972
  const ext = fileType.includes("ogg") ? "ogg" : fileType.includes("wav") ? "wav" : "webm";
7534
7973
  const file = new File([blob], `voice.${ext}`, { type: fileType });
@@ -7542,12 +7981,12 @@ const BulutWidget = ({ config }) => {
7542
7981
  console.info(`[Bulut] voice request started accessibility_mode=${mode}`);
7543
7982
  try {
7544
7983
  const controller = voiceChatStream(
7545
- config.backendBaseUrl,
7984
+ liveConfig.backendBaseUrl,
7546
7985
  file,
7547
- config.projectId,
7986
+ liveConfig.projectId,
7548
7987
  sessionId,
7549
7988
  {
7550
- model: config.model,
7989
+ model: liveConfig.model,
7551
7990
  voice: "zeynep",
7552
7991
  pageContext,
7553
7992
  accessibilityMode: mode
@@ -7814,26 +8253,18 @@ const BulutWidget = ({ config }) => {
7814
8253
  ChatWindow,
7815
8254
  {
7816
8255
  onClose: handleClose,
7817
- config,
8256
+ config: liveConfig,
7818
8257
  accessibilityMode
7819
8258
  }
7820
8259
  )
7821
8260
  ] });
7822
8261
  };
7823
8262
  const SHADOW_STYLE = `
7824
- @font-face {
7825
- font-family: "Clash Display";
7826
- src: url("https://bulut.lu/clash-display.woff2") format("woff2");
7827
- font-style: normal;
7828
- font-weight: 400;
7829
- font-display: swap;
7830
- }
7831
-
7832
8263
  :host {
7833
8264
  all: initial;
7834
8265
  contain: layout style paint;
7835
- color: #111111;
7836
- font-family: "Clash Display", sans-serif;
8266
+ font-family: "${GEIST_FONT_FAMILY}", sans-serif;
8267
+ color: hsla(215, 100%, 5%, 1);
7837
8268
  font-size: 16px;
7838
8269
  line-height: 1.4;
7839
8270
  -webkit-font-smoothing: antialiased;
@@ -7843,13 +8274,13 @@ const SHADOW_STYLE = `
7843
8274
  #bulut-shadow-mount {
7844
8275
  all: initial;
7845
8276
  color: inherit;
7846
- font-family: inherit;
8277
+ font-family: "${GEIST_FONT_FAMILY}", sans-serif;
7847
8278
  font-size: inherit;
7848
8279
  line-height: inherit;
7849
8280
  }
7850
8281
 
7851
8282
  #bulut-shadow-mount * {
7852
- font-family: inherit;
8283
+ font-family: "${GEIST_FONT_FAMILY}", sans-serif !important;
7853
8284
  color: inherit;
7854
8285
  }
7855
8286
 
@@ -7866,6 +8297,7 @@ const init = (options = {}) => {
7866
8297
  console.warn("Bulut is already initialized");
7867
8298
  return;
7868
8299
  }
8300
+ ensureGeistStylesheet();
7869
8301
  const runtimeConfig = resolveRuntimeConfig(options);
7870
8302
  applyTheme(runtimeConfig.baseColor);
7871
8303
  if (options.containerId) {