@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.cjs +2 -2
- package/dist/embed.cjs.map +1 -1
- package/dist/embed.js +2128 -1696
- package/dist/embed.js.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +5 -3
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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: "
|
|
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 "
|
|
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: "
|
|
416
|
+
message: "10px"
|
|
418
417
|
};
|
|
419
|
-
const SHADOW = "0 0 15px
|
|
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="
|
|
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="
|
|
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="
|
|
427
|
-
const bulutLogoRaw = '<svg width="
|
|
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: "
|
|
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:
|
|
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
|
|
723
|
-
const
|
|
724
|
-
const
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
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
|
-
|
|
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
|
|
741
|
-
if (
|
|
742
|
-
return
|
|
822
|
+
const truncateInline = (value, maxChars) => {
|
|
823
|
+
if (value.length <= maxChars) {
|
|
824
|
+
return value;
|
|
743
825
|
}
|
|
744
|
-
return
|
|
826
|
+
return `${value.slice(0, Math.max(0, maxChars - 3))}...`;
|
|
745
827
|
};
|
|
746
|
-
const
|
|
828
|
+
const canonicalUrl = (rawUrl) => {
|
|
747
829
|
try {
|
|
748
|
-
|
|
749
|
-
return null;
|
|
750
|
-
}
|
|
751
|
-
return JSON.parse(value);
|
|
830
|
+
return new URL(rawUrl, rawUrl).href;
|
|
752
831
|
} catch {
|
|
753
|
-
return
|
|
832
|
+
return rawUrl;
|
|
754
833
|
}
|
|
755
834
|
};
|
|
756
|
-
const
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
return Boolean(error.retryable);
|
|
835
|
+
const isCacheEntry = (value) => {
|
|
836
|
+
if (typeof value !== "object" || value === null) {
|
|
837
|
+
return false;
|
|
760
838
|
}
|
|
761
|
-
|
|
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
|
|
764
|
-
|
|
765
|
-
|
|
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
|
|
775
|
-
|
|
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
|
|
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
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
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
|
|
806
|
-
|
|
807
|
-
|
|
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
|
-
|
|
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
|
|
833
|
-
|
|
834
|
-
|
|
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
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
}
|
|
856
|
-
|
|
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
|
-
|
|
860
|
-
|
|
861
|
-
if (
|
|
862
|
-
|
|
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
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
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
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
return null;
|
|
928
|
+
const pruneOldestCacheEntries = () => {
|
|
929
|
+
if (pageContextCache.size <= MAX_CACHED_PAGES) {
|
|
930
|
+
return;
|
|
896
931
|
}
|
|
897
|
-
const
|
|
898
|
-
|
|
899
|
-
|
|
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
|
|
903
|
-
} catch
|
|
904
|
-
|
|
905
|
-
return null;
|
|
975
|
+
return new URL(href, window.location.href).href;
|
|
976
|
+
} catch {
|
|
977
|
+
return href;
|
|
906
978
|
}
|
|
907
979
|
};
|
|
908
|
-
const
|
|
909
|
-
|
|
910
|
-
|
|
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
|
|
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
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
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
|
|
941
|
-
if (
|
|
942
|
-
|
|
991
|
+
const name = element.getAttribute("name");
|
|
992
|
+
if (name) {
|
|
993
|
+
return `${tag}[name="${escapeCssValue(name)}"]`;
|
|
943
994
|
}
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
const
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
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
|
-
|
|
955
|
-
if (
|
|
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
|
-
|
|
977
|
-
setReader(void 0);
|
|
978
|
-
return { chunks, mimeType, sampleRate };
|
|
1048
|
+
return eventHints;
|
|
979
1049
|
};
|
|
980
|
-
const
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
const
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
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
|
-
|
|
993
|
-
|
|
994
|
-
|
|
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
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
}
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
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
|
-
|
|
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
|
|
2201
|
-
if (
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2259
|
-
|
|
1771
|
+
x: call2.x,
|
|
1772
|
+
y: call2.y
|
|
2260
1773
|
};
|
|
2261
1774
|
}
|
|
2262
|
-
|
|
2263
|
-
|
|
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
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
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
|
|
2277
|
-
|
|
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
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
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
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
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
|
|
2303
|
-
|
|
2304
|
-
|
|
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
|
|
2308
|
-
|
|
2309
|
-
|
|
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
|
|
1896
|
+
return false;
|
|
2323
1897
|
}
|
|
2324
1898
|
};
|
|
2325
|
-
const
|
|
2326
|
-
|
|
2327
|
-
return;
|
|
2328
|
-
}
|
|
1899
|
+
const findMatchingLinkForTarget = (targetUrl) => {
|
|
1900
|
+
let parsedTarget = null;
|
|
2329
1901
|
try {
|
|
2330
|
-
|
|
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
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
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
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
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
|
-
|
|
2363
|
-
|
|
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
|
|
2366
|
-
const
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
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
|
-
|
|
2381
|
-
|
|
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
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
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
|
|
2416
|
-
const
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
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
|
|
2423
|
-
const
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
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
|
|
2430
|
-
const
|
|
2431
|
-
var _a, _b, _c;
|
|
2051
|
+
const executeSingleToolCall = async (call2) => {
|
|
2052
|
+
const callId = call2.call_id;
|
|
2432
2053
|
try {
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
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
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
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
|
-
|
|
2447
|
-
const
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
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
|
-
|
|
2461
|
-
|
|
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
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
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
|
-
|
|
2472
|
-
}
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
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
|
|
2494
|
-
|
|
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
|
-
|
|
2503
|
-
|
|
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
|
-
|
|
2509
|
-
"AuticBot interact: type action requires input, textarea, or contenteditable target."
|
|
2510
|
-
);
|
|
2100
|
+
ensureCursor();
|
|
2511
2101
|
};
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
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
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
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
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
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
|
-
|
|
2133
|
+
return `tts-${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
2528
2134
|
};
|
|
2529
|
-
const
|
|
2530
|
-
|
|
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
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
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
|
-
|
|
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
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
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
|
|
2558
|
-
|
|
2559
|
-
|
|
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
|
|
2562
|
-
|
|
2563
|
-
|
|
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
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
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
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
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
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
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
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
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
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
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
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
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
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
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
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
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
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
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
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
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
|
-
|
|
2626
|
-
|
|
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
|
-
|
|
2630
|
-
|
|
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
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
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
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
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
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
if (
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
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
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
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
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
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
|
-
|
|
2709
|
-
}
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
if (
|
|
2713
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
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(
|
|
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: "
|
|
3975
|
+
gap: "16px"
|
|
3587
3976
|
};
|
|
3588
3977
|
const messageStyle = (isUser) => ({
|
|
3589
3978
|
maxWidth: "84%",
|
|
3590
|
-
padding: isUser ? "9px 14px" : "
|
|
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(
|
|
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
|
|
3645
|
-
transition: `
|
|
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: "
|
|
3731
|
-
|
|
3732
|
-
fontSize: "12px",
|
|
4119
|
+
padding: "9px 14px",
|
|
4120
|
+
fontSize: "14px",
|
|
3733
4121
|
lineHeight: "1.4",
|
|
3734
|
-
color: "
|
|
3735
|
-
|
|
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: "
|
|
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: "
|
|
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"
|
|
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 (!
|
|
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
|
-
|
|
7984
|
+
liveConfig.backendBaseUrl,
|
|
7546
7985
|
file,
|
|
7547
|
-
|
|
7986
|
+
liveConfig.projectId,
|
|
7548
7987
|
sessionId,
|
|
7549
7988
|
{
|
|
7550
|
-
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
|
-
|
|
7836
|
-
|
|
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:
|
|
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:
|
|
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) {
|