1 2 3 4 5 6 7 8 9 10 11 12 13 14<!DOCTYPE html> 15<html lang="zh-CN"> 16<head> 17 <meta charset="UTF-8"> 18 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 19 <title>Proteintech AI</title> 20 <!-- å¯è½åå¨å¤±æä¿çä¸ä¸ª --> 21 <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"> 22 <!-- <link href="/css/font-awesome.min.css" rel="stylesheet"> --> 23 <link rel="icon" sizes="16x16" href="/favicon.ico"> 24 <link href="/css/tailwind.min.css" rel="stylesheet"> 25
25<script src="/scripts/markdown-it.js"></script>
25 26
26<script src="/scripts/jquery-2.2.4.min-jzy.js"></script>
26 27 28 <!-- Google Tag Manager --> 29
29<script>
vendor: 495 bytes, lines 29-37
29 30 (function (w, d, s, l, i) { 31 w[l] = w[l] || []; w[l].push({ 32 'gtm.start': 33 new Date().getTime(), event: 'gtm.js' 34 }); var f = d.getElementsByTagName(s)[0], 35 j = d.createElement(s), dl = l != 'dataLayer' ? '&l=' + l : ''; j.async = true; j.src = 36 'https://www.googletagmanager.com/gtm.js?id=' + i + dl; f.parentNode.insertBefore(j, f); 37 })(window, document, 'script', 'dataLayer', '
37GTM-P6XSTD9D
vendor: 3 bytes, line 37
37');
37</script>
37 38 <!-- End Google Tag Manager --> 39 40 41 <style type="text/tailwindcss"> 42 @layer utilities { 43 .content-auto { 44 content-visibility: auto; 45 } 46 47 .scrollbar-hide::-webkit-scrollbar { 48 display: none; 49 } 50 51 .scrollbar-hide { 52 -ms-overflow-style: none; 53 scrollbar-width: none; 54 } 55 56 .message-appear { 57 animation: messageAppear 0.3s ease-out forwards; 58 } 59 60 .to-secondary { 61 --tw-gradient-to: #818CF8 var(--tw-gradient-to-position); 62 } 63 /* ç´æ¥å®ä¹æ¸åèæ¯ç±» */ 64 .bg-primary-to-secondary { 65 background: linear-gradient(to right, #4F46E5, #818CF8); 66 } 67 68 @keyframes messageAppear { 69 from { 70 opacity: 0; 71 transform: translateY(10px); 72 } 73 74 to { 75 opacity: 1; 76 transform: translateY(0); 77 } 78 } 79 80 .typing-animation span { 81 animation: typing 1.4s infinite both; 82 } 83 84 .typing-animation span:nth-child(2) { 85 animation-delay: 0.2s; 86 } 87 88 .typing-animation span:nth-child(3) { 89 animation-delay: 0.4s; 90 } 91 92 @keyframes typing { 93 0% { 94 opacity: 0.2; 95 } 96 97 20% { 98 opacity: 1; 99 } 100 101 100% { 102 opacity: 0.2; 103 } 104 } 105 106 .markdown-preview { 107 padding-bottom: 5px; 108 } 109 110 .markdown-preview h1, .markdown-preview h2, .markdown-preview h3 { 111 margin-top: 1rem; 112 margin-bottom: 0.5rem; 113 } 114 115 .markdown-preview ul, .markdown-preview ol { 116 margin-top: 0.5rem; 117 margin-bottom: 0.75rem; 118 padding-left: 1.5rem; 119 } 120 121 .markdown-preview li { 122 margin-bottom: 0.25rem; 123 } 124 125 .incomplete-placeholder::after { 126 content: ''; 127 display: inline-block; 128 width: 0.5em; 129 height: 1em; 130 vertical-align: text-bottom; 131 animation: blink 1s infinite; 132 } 133 134 /* å®¡æ ¸å è½½æ ·å¼ */ 135 .audit-loading { 136 display: flex; 137 align-items: center; 138 justify-content: center; 139 padding: 20px; 140 background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%); 141 border: 2px solid #e2e8f0; 142 border-radius: 12px; 143 margin: 10px 0; 144 box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05); 145 animation: audit-glow 2s ease-in-out infinite !important; 146 } 147 148 .audit-loading-content { 149 display: flex; 150 flex-direction: column; 151 align-items: center; 152 gap: 12px; 153 } 154 155 .audit-spinner { 156 width: 32px; 157 height: 32px; 158 border: 3px solid #e2e8f0; 159 border-top: 3px solid #00ae9e; 160 border-radius: 50%; 161 animation: audit-spin 1s linear infinite; 162 } 163 164 .audit-text { 165 color: #4a5568; 166 font-size: 14px; 167 font-weight: 500; 168 text-align: center; 169 } 170 171 .audit-subtitle { 172 color: #718096; 173 font-size: 12px; 174 text-align: center; 175 } 176 177 @keyframes audit-spin { 178 0% { 179 transform: rotate(0deg); 180 } 181 182 100% { 183 transform: rotate(360deg); 184 } 185 } 186 187 .audit-loading-fade-out { 188 animation: audit-fade-out 0.5s ease-out forwards; 189 } 190 191 @keyframes audit-fade-out { 192 0% { 193 opacity: 1;
194 transform: translateY(0); 195 } 196 197 100% { 198 opacity: 0; 199 transform: translateY(-10px); 200 } 201 } 202 203 /* å®¡æ ¸å è½½å¨ç»ææ - ç®åçæ¬ */ 204 .audit-loading .audit-dots { 205 display: inline-block !important; 206 animation: audit-dots-bounce 1s infinite !important; 207 color: #ff0000 !important; 208 font-weight: bold !important; 209 font-size: 20px !important; 210 } 211 212 .audit-loading .audit-pulse { 213 animation: audit-pulse 1s ease-in-out infinite !important; 214 color: #ff0000 !important; 215 font-size: 16px !important; 216 } 217 218 @keyframes audit-dots-bounce { 219 0%, 33% { 220 opacity: 0.3; 221 transform: scale(0.8); 222 } 223 224 50% { 225 opacity: 1; 226 transform: scale(1.2); 227 } 228 229 66%, 100% { 230 opacity: 0.3; 231 transform: scale(0.8); 232 } 233 } 234 235 @keyframes audit-pulse { 236 0%, 100% { 237 opacity: 0.7; 238 transform: scale(1); 239 } 240 241 50% { 242 opacity: 1; 243 transform: scale(1.08); 244 } 245 } 246 247 /* å¢å¼ºå®¡æ ¸å è½½æ´ä½ææ - å·²åå¹¶å°ä¸é¢ç .audit-loading ä¸ */ 248 249 @keyframes audit-glow { 250 0%, 100% { 251 box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05); 252 border-color: #e2e8f0; 253 background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%); 254 } 255 256 50% { 257 box-shadow: 0 8px 20px rgba(255, 0, 0, 0.5); 258 border-color: #ff0000; 259 background: linear-gradient(135deg, #ffe6e6 0%, #ffcccc 100%); 260 } 261 } 262 263 @keyframes blink { 264 0%, 100% { 265 opacity: 1; 266 } 267 268 50% { 269 opacity: 0; 270 } 271 } 272 273 a { 274 color: red; 275 } 276 } 277 </style> 278 279 <style> 280 .fa { 281 font-size: inherit !important; /* ç»§æ¿ç¶å ç´ åä½å¤§å°ï¼èéé»è®¤ç 14px */ 282 line-height: inherit !important; /* ç»§æ¿ç¶å ç´ è¡é« */ 283 font-weight: normal !important; /* å¼ºå¶æ®éåé */ 284 font-style: normal !important; /* å¼ºå¶æ£å¸¸æ ·å¼ï¼éæä½ï¼ */ 285 } 286 /* å¯éï¼æ¸ é¤å¾æ 大å°ç¸å ³çè¾ å©ç±»ï¼å¦ fa-lgãfa-2x çï¼ */ 287 .fa-lg, .fa-2x, .fa-3x, .fa-4x, .fa-5x { 288 font-size: inherit !important; 289 } 290 291 :root { 292 --tw-color-primary: #4F46E5; 293 --tw-color-secondary: #818CF8; 294 --tw-color-neutral: #1F2937; 295 --tw-color-neutral-light: #F3F4F6; 296 --tw-gradient-to-secondary: #818CF8; 297 } 298 299 .to-secondary { 300 --tw-gradient-to: var(--tw-gradient-to-secondary) var(--tw-gradient-to-position); 301 } 302 303 .from-primary { 304 background: linear-gradient(to right, var(--tw-gradient-from, #4F46E5), var(--tw-gradient-to-secondary)); 305 --tw-gradient-from: #4F46E5 var(--tw-gradient-from-position); 306 } 307 308 .to-secondary { 309 --tw-gradient-to: #818CF8 var(--tw-gradient-to-position); 310 } 311 312 .border-primary { 313 border-color: rgb(79,70,229) !important; 314 } 315 316 .bg-primary\/10 { 317 background-color: rgb(79 70 229 / 0.1); 318 } 319 320 .bg-primary { 321 background-color: #00ae9e !important; 322 /* background-color: var(--tw-color-primary); */ 323 } 324 325 .text-secondary { 326 color: var(--tw-color-secondary); 327 } 328 329 @font-face { 330 font-family: "Aller"; 331 font-style: normal; 332 font-weight: normal; 333 src: local("Aller"), url("/css/webfont/Aller_Rg.woff") format("woff"); 334 } 335 336 @font-face { 337 font-family: "Aller Bold"; 338 font-style: normal; 339 font-weight: normal; 340 src: local("Aller Bold"), url("/css/webfont/Aller_Bd.woff") format("woff"); 341 } 342 343 .markdown-body { 344 font-family: "Aller" !important; 345 } 346 347 #app { 348 padding-bottom: 10px; 349 /* background:linear-gradient(to bottom, #ffffff, #e0f3ff); */ 350 } 351 352 .aifontSize { 353 font-size: 15px; 354 } 355 356 .avatar-container { 357 margin-right: 12px !important; 358 } 359 360 @media (max-width: 991.98px) { 361 #messagesContainer { 362 max-height: calc(100vh - 155px); 363 } 364 } 365 366 #messagesContainer {
367 max-height: calc(100vh - 220px); 368 overflow-y: auto; 369 scrollbar-width: thin; 370 scrollbar-color: rgba(156, 163, 175, 0.5) transparent; 371 /* padding-top: 0px; */ 372 } 373 374 #messagesContainer::-webkit-scrollbar { 375 width: 6px; 376 } 377 378 #messagesContainer::-webkit-scrollbar-track { 379 background: transparent; 380 } 381 382 #messagesContainer::-webkit-scrollbar-thumb { 383 background-color: rgba(156, 163, 175, 0.5); 384 border-radius: 3px; 385 } 386 387 .think-content, .markdown-content, .prose { 388 word-break: break-word; 389 } 390 391 .thinkHtml a { 392 color: #00ae9e !important; 393 } 394 395 pre { 396 white-space: pre-wrap; 397 } 398 399 .incomplete-placeholder { 400 display: none; 401 width: 10px; 402 height: 10px; 403 background-color: #f3f4f6; 404 border-radius: 50%; 405 margin-left: 2px; 406 animation: dot-pulse 1.5s infinite; 407 } 408 409 .loading-icon { 410 position: absolute; 411 } 412 413 @keyframes dot-pulse { 414 0%, 100% { 415 opacity: 1; 416 } 417 418 50% { 419 opacity: 0.3; 420 } 421 } 422 423 .send-icon, .cancel-icon { 424 transition: opacity 0.3s ease; /* æ·»å è¿æ¸¡å¨ç» */ 425 } 426 427 .cancel-icon { 428 display: none; /* é»è®¤éè忶徿 */ 429 } 430 431 .hideThinking { 432 display: none; 433 } 434 435 .showThinkinglogo { 436 white-space: nowrap; 437 display: flex; 438 align-items: center; 439 gap: 6px; 440 font-size: 14px; 441 padding: 8px 0; 442 } 443 444 /* æåï¼æ·±ä¸»é¢ç»¿ï¼#008a7dï¼ï¼ååç¹äº®ç»¿å¼åº */ 445 .thinking-text { 446 display: flex; 447 gap: 1px; 448 } 449 450 .thinking-text span { 451 font-weight: 700; 452 color: #1f2328; /* 深主é¢ç»¿ï¼æ¯åç¹æä¸ç¹ï¼æå±æ¬¡ */ 453 text-shadow: 0 0 1px #1f2328; /* åç¹åè²æè¾¹ï¼å¼ºåç»ä¸æ */ 454 animation: charJump 1.2s infinite ease-in-out; 455 } 456 457 .thinking-text span:nth-child(1) { 458 animation-delay: 0s; 459 } 460 461 .thinking-text span:nth-child(2) { 462 animation-delay: 0.1s; 463 } 464 465 .thinking-text span:nth-child(3) { 466 animation-delay: 0.2s; 467 } 468 469 .thinking-text span:nth-child(4) { 470 animation-delay: 0.3s; 471 } 472 473 .thinking-text span:nth-child(5) { 474 animation-delay: 0.4s; 475 } 476 477 /* åç¹ï¼ä¿æäº®ä¸»é¢ç»¿ï¼#00ae9eï¼ */ 478 .dot { 479 width: 6px; 480 height: 6px; 481 border-radius: 50%; 482 background-color: #1f2328; /* åæååè²ç³»ï¼æåº¦æ´é« */ 483 animation: dotJump 1.2s infinite ease-in-out; 484 } 485 486 .dot:nth-child(2) { 487 animation-delay: 0.2s; 488 } 489 490 .dot:nth-child(3) { 491 animation-delay: 0.4s; 492 } 493 494 .dot:nth-child(4) { 495 animation-delay: 0.6s; 496 } 497 498 /* å¨ç»ä¸åï¼ä» æ¹é è²ï¼å¨æä¿çï¼ */ 499 @keyframes charJump { 500 0%, 100% { 501 opacity: 0.8; 502 transform: translateY(0); 503 } 504 505 50% { 506 opacity: 1; 507 transform: translateY(-2px); 508 } 509 } 510 511 @keyframes dotJump { 512 0%, 100% { 513 transform: translateY(0); 514 opacity: 0.7; 515 } 516 517 50% { 518 transform: translateY(-3px); 519 opacity: 1;
520 } 521 } 522 /* å¨ç»å ³é®å¸§ï¼ç¹ç缩æ¾+éæåº¦åå */ 523 @keyframes loadingDot { 524 0%, 100% { 525 transform: scale(1); 526 opacity: 0.6; 527 } 528 529 50% { 530 transform: scale(1.4); 531 opacity: 1; 532 } 533 } 534 535 #sendBtn.loading .send-icon { 536 display: none; 537 } 538 539 #sendBtn.loading .cancel-icon { 540 display: block; 541 } 542 543 #sendBtn.loading { 544 background-color: #ef4444 !important; /* å è½½æ¶æé®åçº¢ï¼æç¤ºå¯åæ¶ */ 545 } 546 547 .send-tooltip, .cancel-tooltip { 548 white-space: nowrap; 549 transform-origin: bottom center; 550 } 551 552 #sendBtn.loading .send-tooltip { 553 display: none; 554 } 555 556 #sendBtn.loading .cancel-tooltip { 557 display: block; 558 } 559 560 /* åéæé®ç¦ç¨ç¶ææ ·å¼ */ 561 #sendBtn:disabled { 562 background-color: #9ca3af !important; /* ç°è²èæ¯ */ 563 color: #6b7280 !important; /* ç°è²æå */ 564 cursor: not-allowed !important; /* ç¦ç¨å æ */ 565 opacity: 0.6 !important; /* éä½éæåº¦ */ 566 transform: none !important; /* ç¦ç¨ç¼©æ¾ææ */ 567 box-shadow: none !important; /* ç§»é¤é´å½± */ 568 } 569 570 #sendBtn:disabled:hover { 571 background-color: #9ca3af !important; /* æ¬åæ¶ä¿æç°è² */ 572 transform: none !important; /* ç¦ç¨æ¬åç¼©æ¾ */ 573 } 574 575 #sendBtn:disabled .send-icon { 576 color: #6b7280 !important; /* 徿 ä¹åç°è² */ 577 } 578 579 .slide-in { 580 transform: translateX(0) !important; 581 opacity: 1; 582 transition: transform 0.3s ease, opacity 0.3s ease; 583 } 584 585 .slide-out { 586 transform: translateX(100%) !important; 587 opacity: 0; 588 transition: transform 0.3s ease, opacity 0.3s ease; 589 } 590 591 .right-panel { 592 transform: translateX(100%); 593 opacity: 0; 594 } 595 596 /* 左侧å å®¹è¿æ¸¡ææ */ 597 .left-content { 598 transition: width 0.3s ease; 599 } 600 601 .sticky { 602 position: sticky; /* ç²æ§å®ä½ï¼ç¸å¯¹äºç¶å ç´ åºå® */ 603 top: 0; /* è·ç¦»é¡¶é¨0åç´ */ 604 z-index: 10; /* ç¡®ä¿å¨å 容䏿¹æ¾ç¤º */ 605 } 606 /* æ¶æ¯é¡¹åºç¡æ ·å¼ */ 607 .message-item { 608 transition: all 0.2s ease; /* å¹³æ»è¿æ¸¡ææ */ 609 } 610 611 /* 触æ¸/é¼ æ æ¬åç¶æ */ 612 .message-item:hover, 613 .message-item:focus-within { 614 cursor: pointer; 615 border-color: rgb(79,70,229) !important; /* 亮èè²è¾¹æ¡ */ 616 box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.1); /* 轻微åå ææ */ 617 } 618 619 .group-header h3 { 620 font-size: 15px; 621 white-space: nowrap; /* å¼ºå¶ææ¬ä¸æ¢è¡ */ 622 overflow: hidden; /* éèè¶ åºå®¹å¨çå 容 */ 623 text-overflow: ellipsis; /* è¶ åºé¨åæ¾ç¤ºçç¥å· */ 624 max-width: calc(100% - 40px); /* é¢ç徿 ç空é´ï¼æ ¹æ®å®é è°æ´ï¼ */ 625 } 626 627 @media (max-width: 750px) { 628 #rightPanel { 629 width: 100% !important; /* !importantç¡®ä¿è¦çå ¶ä»æ ·å¼ï¼å¿ è¦æ¶ä½¿ç¨ï¼ */ 630 } 631 } 632 633 .think-section { 634 margin: 10px 0px; 635 } 636 637 .markdown-content table { 638 width: fit-content !important; 639 } 640 641 ul { 642 list-style: disc; /* disc æ¯ ul çé»è®¤åç¹æ ·å¼ */ 643 } 644 645 /* ç¡®ä¿ olï¼æåºå表ï¼ä¿çæ°å */ 646 ol { 647 list-style: decimal; /* decimal æ¯ ol çé»è®¤æ°åæ ·å¼ */ 648 } 649 650 /* é对åµå¥å表ï¼å¦ ul éç ulï¼ï¼å¯åç¬è®¾ç½® */ 651 ul ul { 652 list-style: circle; /* åµå¥æ åºå表é»è®¤æ¯ç©ºå¿åï¼å¯é */ 653 } 654 655 ol ol {
656 list-style: lower-alpha; /* åµå¥æåºå表é»è®¤æ¯å°å忝ï¼å¯é */ 657 } 658 659 .able-intro { 660 max-width: 1000px; 661 margin: 0px auto; 662 margin-bottom: 20px; 663 /* padding: 0 20px; */ 664 text-align: center; 665 margin-top: 8px; 666 } 667 /* æ 颿 ·å¼ï¼çªåºæ¾ç¤º */ 668 .able-intro h1 { 669 font-size: 28px; 670 font-weight: 600; 671 margin-bottom: 12px; 672 display: flex; 673 align-items: center; 674 justify-content: center; 675 } 676 /* èè²æ ç¾æ ·å¼ï¼æ¨¡æ âAble betaâ ææ */ 677 .able-badge { 678 /* background: linear-gradient(to right, #60a5fa, #3b82f6); */ 679 /* color: #fff; */ 680 /* padding: 6px 12px; */ 681 /* border-radius: 6px; */ 682 /* font-size: 16px; */ 683 /* font-weight: 500; */ 684 margin: 0 8px; 685 /* box-shadow: 0 2px 4px rgba(0,0,0,0.1); */ 686 position: relative; 687 } 688 /* beta æ è®°æ ·å¼ï¼å°å䏿 */ 689 .able-badge .beta { 690 font-size: 13px; 691 padding: 2px 4px; 692 } 693 /* 坿 颿 ·å¼ï¼å¼å¯¼æ§æå */ 694 .able-intro .subtitle { 695 font-size: 16px; 696 color: #6b7280; /* æµ ç°è²æåï¼å¼±å次è¦ä¿¡æ¯ */ 697 /* margin-bottom: 30px; */ 698 } 699 /* 使ç¨è¯´ææ é¢ï¼çªåºå±çº§ */ 700 .able-intro h2 { 701 font-size: 20px; 702 font-weight: 600; 703 margin-bottom: 20px; 704 } 705 706 .able-intro h3 { 707 font-weight: 600; 708 margin-bottom: 20px; 709 text-align: left; /* 左对é½ï¼å示ä¾ä¸è´ */ 710 } 711 /* å å®¹åæ å®¹å¨ï¼ç¨ Flex å®ç°å·¦å³å¸å± */ 712 .intro-content { 713 display: flex; 714 flex-wrap: wrap; /* å°å±å¹èªå¨æ¢è¡ */ 715 gap: 40px; 716 justify-content: center; /* è®©åæ å¨å°å±å¹å± ä¸ */ 717 } 718 /* å·¦å³åæ éç¨æ ·å¼ */ 719 .intro-column { 720 flex: 1; 721 min-width: 250px; /* å°å±å¹ä¸ä¿è¯å®½åº¦ */ 722 text-align: left; 723 } 724 725 .intro-column h3 { 726 font-weight: 600; 727 margin-bottom: 10px; 728 } 729 /* åè¡¨é¡¹æ ·å¼ï¼å¸¦å¾æ */ 730 .intro-column ul { 731 list-style: none; 732 padding-left: 0; 733 } 734 735 .intro-column li { 736 margin-bottom: 8px; 737 display: flex; 738 align-items: center; 739 } 740 /* 对å¾ååå·å¾æ ï¼ç¨ä¼ªå ç´ å®ç°ï¼ä¹å¯ä»¥æ¢æåä½å¾æ */ 741 .intro-column li::before { 742 content: "â"; 743 color: #22c55e; /* 绿è²å¯¹å¾ */ 744 margin-right: 8px; 745 font-weight: bold; 746 } 747 748 .intro-column.cant-help li::before { 749 content: "â"; /* åå·ç¬¦å·ï¼æ¯"Ã"æ´ç²å®ï¼è§è§æ´æ¸ æ°ï¼ */ 750 color: #ef4444; /* 红è²åå·ï¼å对å¾é¢è²åºåææ¾ */ 751 font-size: 12px; 752 /* æ ééå¤å margin/weightï¼ä¼ç»§æ¿ä¸é¢çæ ·å¼ï¼åªéæ¹ content å color */ 753 } 754 755 .intro-column .cant-help li::before { 756 content: "â"; 757 color: #ef4444; /* 红è²åå· */ 758 } 759 760 .font-inter { 761 background: linear-gradient(to bottom, #ffffff, #e0f3ff) 762 } 763 764 #userInput { 765 box-sizing: border-box; 766 font-size: 15px; 767 line-height: 24px; 768 min-height: 48px; /* ä¸ sendBtn h-12 å¯¹é½ */ 769 max-height: calc(24px * 5 + 24px); /* æå¤ 5 è¡ + ä¸ä¸ padding */ 770 padding: 12px; 771 overflow-y: auto; 772 vertical-align: middle; 773 } 774 775 #attachFileBtn { 776 color: #6b7280; 777 width: 40px; 778 height: 40px; 779 border-radius: 9999px; 780 display: flex; 781 align-items: center; 782 justify-content: center; 783 transition: all 0.2s ease; 784 flex-shrink: 0; 785 } 786
787 #attachFileBtn:hover { 788 color: #00ae9e; 789 background-color: #f0fdfa; 790 } 791 792 .file-preview-bar { 793 margin-bottom: 8px; 794 padding: 0 4px; 795 } 796 797 .file-preview-chip { 798 display: inline-flex; 799 align-items: center; 800 gap: 8px; 801 max-width: 100%; 802 padding: 8px 12px; 803 background: #f3f4f6; 804 border: 1px solid #e5e7eb; 805 border-radius: 10px; 806 font-size: 13px; 807 color: #374151; 808 } 809 810 .file-preview-chip img { 811 width: 36px; 812 height: 36px; 813 object-fit: cover; 814 border-radius: 6px; 815 flex-shrink: 0; 816 } 817 818 .file-preview-name { 819 overflow: hidden; 820 text-overflow: ellipsis; 821 white-space: nowrap; 822 max-width: 220px; 823 } 824 825 .file-preview-size { 826 color: #9ca3af; 827 flex-shrink: 0; 828 } 829 830 .file-preview-remove { 831 color: #9ca3af; 832 width: 24px; 833 height: 24px; 834 border-radius: 9999px; 835 display: flex; 836 align-items: center; 837 justify-content: center; 838 flex-shrink: 0; 839 transition: all 0.2s ease; 840 } 841 842 .file-preview-remove:hover { 843 color: #ef4444; 844 background: #fee2e2; 845 } 846 847 #chatInputDropzone.drag-over { 848 border-color: #00ae9e !important; 849 background-color: #f0fdfa; 850 box-shadow: 0 0 0 2px rgba(0, 174, 158, 0.25); 851 } 852 853 .user-attachment { 854 margin-bottom: 8px; 855 padding: 8px 10px; 856 background: rgba(255, 255, 255, 0.7); 857 border-radius: 8px; 858 font-size: 13px; 859 color: #374151; 860 display: inline-flex; 861 align-items: center; 862 gap: 8px; 863 max-width: 100%; 864 } 865 866 .user-attachment img { 867 width: 48px; 868 height: 48px; 869 object-fit: cover; 870 border-radius: 6px; 871 } 872 873 .open-live-chat { 874 cursor: pointer; 875 color: #00ae9e; 876 } 877 /* 3D容卿 ·å¼ */ 878 .pdb-3d-container { 879 background: #f8f9fa; 880 border: 1px solid #e5e7eb; 881 border-radius: 8px; 882 padding: 15px; 883 margin: 10px 0; 884 box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); 885 } 886 887 .pdb-3d-container .mol-container { 888 border-radius: 6px; 889 overflow: hidden; 890 position: relative; 891 } 892 893 .pdb-3d-container .btn { 894 font-size: 12px; 895 padding: 4px 8px; 896 } 897 898 .pdb-3d-container h6 { 899 color: #374151; 900 font-weight: 600; 901 } 902 903 /* å¤å¶å¾æ æ ·å¼ */ 904 .copy-icon-container { 905 display: flex; 906 justify-content: flex-end; 907 margin-top: 8px; 908 padding-top: 8px; 909 border-top: 1px solid #e5e7eb; 910 } 911 912 .copy-icon { 913 display: inline-flex; 914 align-items: center; 915 justify-content: center; 916 width: 28px; 917 height: 28px; 918 background-color: #f3f4f6; 919 border: 1px solid #d1d5db; 920 border-radius: 6px; 921 cursor: pointer; 922 transition: all 0.2s ease; 923 color: #6b7280; 924 font-size: 12px; 925 } 926 927 .copy-icon:hover { 928 background-color: #e5e7eb; 929 border-color: #9ca3af; 930 color: #374151; 931 transform: translateY(-1px); 932 } 933 934 .copy-icon:active { 935 transform: translateY(0); 936 background-color: #d1d5db; 937 } 938 939 .copy-icon.copied { 940 background-color: #10b981; 941 border-color: #059669; 942 color: white; 943 } 944 945 .copy-icon.copied::after { 946 content: "â"; 947 font-weight: bold; 948 } 949 950 /* 导åºPDFæé®æ ·å¼ */ 951 .export-pdf-icon { 952 display: inline-flex; 953 align-items: center; 954 justify-content: center; 955 width: 28px; 956 height: 28px; 957 background-color: #f3f4f6; 958 border: 1px solid #d1d5db; 959 border-radius: 6px; 960 cursor: pointer; 961 transition: all 0.2s ease; 962 color: #6b7280; 963 font-size: 12px; 964 margin-left: 8px; 965 } 966 967 .export-pdf-icon:hover { 968 background-color: #e5e7eb; 969 border-color: #9ca3af; 970 color: #374151; 971 transform: translateY(-1px); 972 } 973 974 .export-pdf-icon:active { 975 transform: translateY(0); 976 background-color: #d1d5db; 977 } 978 979 .export-pdf-icon.exporting { 980 background-color: #3b82f6; 981 border-color: #2563eb; 982 color: white; 983 } 984 985 .export-pdf-icon.exporting::after { 986 content: "â³"; 987 font-weight: bold; 988 } 989 990 .export-pdf-icon.success { 991 background-color: #10b981; 992 border-color: #059669; 993 color: white; 994 } 995 996 .export-pdf-icon.success::after { 997 content: "â"; 998 font-weight: bold; 999 } 1000 1001 /* éç½®æé®æ ·å¼ */ 1002 .reset-icon { 1003 display: inline-flex; 1004 align-items: center; 1005 justify-content: center; 1006 width: 28px; 1007 height: 28px; 1008 background-color: #f3f4f6; 1009 border: 1px solid #d1d5db; 1010 border-radius: 6px; 1011 cursor: pointer; 1012 transition: all 0.2s ease; 1013 color: #6b7280; 1014 font-size: 12px; 1015 margin-right: 8px; 1016 } 1017 1018 .reset-icon:hover { 1019 background-color: #e5e7eb; 1020 border-color: #9ca3af; 1021 color: #374151; 1022 transform: translateY(-1px); 1023 } 1024 1025 .reset-icon:active { 1026 transform: translateY(0); 1027 background-color: #d1d5db; 1028 } 1029 1030 .reset-icon.resetting { 1031 background-color: #f59e0b; 1032 border-color: #d97706; 1033 color: white; 1034 } 1035 1036 .reset-icon.resetting::after { 1037 content: "â³"; 1038 font-weight: bold; 1039 } 1040 1041 .reset-icon.success { 1042 background-color: #10b981; 1043 border-color: #059669; 1044 color: white; 1045 } 1046 1047 .reset-icon.success::after { 1048 content: "â"; 1049 font-weight: bold; 1050 } 1051 1052 a:hover { 1053 cursor: pointer; 1054 } 1055 1056 /* ãæ°å¢ãend-message-btn æé®æ ·å¼ */ 1057 .end-message-btn { 1058 display: none; 1059 /* display: inline-block; */ 1060 margin: 4px 8px 4px 0; 1061 padding: 8px 16px; 1062 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); 1063 color: white; 1064 border: none; 1065 border-radius: 20px; 1066 cursor: pointer; 1067 font-size: 14px; 1068 transition: all 0.3s ease; 1069 box-shadow: 0 2px 8px rgba(102, 126, 234, 0.3); 1070 } 1071 1072 .end-message-btn:hover { 1073 transform: translateY(-2px); 1074 box-shadow: 0 4px 12px rgba(102, 126, 234, 0.5); 1075 } 1076 1077 .end-message-btn:active { 1078 transform: translateY(0); 1079 box-shadow: 0 2px 8px rgba(102, 126, 234, 0.3); 1080 } 1081 1082 .end-message-btn.visible { 1083 display: inline-block; 1084 } 1085 1086 .task-content-body p { 1087 margin: 0px; 1088 } 1089 /* ç»ä¸task容卿 ·å¼ */ 1090 .unified-task-group { 1091 border: 1px solid #dee2e6; 1092 border-radius: 10px; 1093 padding: 5px; 1094 margin: 10px 0; 1095 box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); 1096 } 1097 1098 .unified-task-group .think-header { 1099 padding: 8px 12px; 1100 background: white; 1101 border-radius: 6px; 1102 transition: all 0.2s ease; 1103 } 1104 1105 .unified-task-group .think-header:hover { 1106 background: #f1f3f5; 1107 box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1); 1108 } 1109 1110 .unified-task-group .think-content-body { 1111 padding: 8px; 1112 padding-top: 0px; 1113 } 1114 /* éèæ¥éç Mermaid å®¹å¨ */ 1115 body > div[id^="dmermaid-graph-"] svg[aria-roledescription="error"] { 1116 display: none !important; 1117 } 1118 /* .content-body { 1119 margin-top: 10px; 1120 } 1121 1122 .avatar-container { 1123 margin-right: 12px !important; 1124 } */ 1125 1126 1127 1128 </style> 1129 1130 1131</head> 1132<body class="font-inter bg-gradient-to-br min-h-screen flex flex-col"> 1133 <div id="app" class="flex-1 flex flex-col max-w-7xl w-full mx-auto
1133sm:p-3 lg:p-3" style="padding-top: 0px;"> 1134 <!-- å¤´é¨ --> 1135 <!-- <header class="mb-6 flex items-center justify-between border-b border-gray-200 pb-4"> 1136 <div class="flex items-center space-x-3"> 1137 <div class="w-10 h-10 rounded-lg bg-gradient-to-br from-primary to-secondary flex items-center justify-center text-white text-xl font-bold shadow-lg" id="openRight">AI</div> 1138 <h1 class="text-[clamp(1.5rem,3vw,2rem)] font-bold text-neutral titleLanguage">æºè½å¯¹è¯å©æ</h1> 1139 </div> 1140 <button id="clearBtn" class="text-gray-500 hover:text-primary transition-colors duration-300 flex items-center space-x-1.5"> 1141 <i class="fa fa-refresh"></i> 1142 <span class="hidden sm:inline clearLanguage">æ¸ ç©ºå¯¹è¯</span> 1143 </button> 1144 </header> --> 1145 <!-- Able ä»ç»æ¨¡åå®¹å¨ --> 1146 <!-- æ¶æ¯å®¹å¨ --> 1147 <main id="messagesContainer" class="flex-1 overflow-y-auto space-y-6 p-2 max-h-[calc(100vh-80px)] min-[992px]:max-h-[calc(100vh-150px)]"> 1148 <section class="able-intro"> 1149 <!-- æ é¢è¡ï¼Hello, Iâm Able beta --> 1150 <h2 style="margin-bottom: 0px;"> 1151 Hello, Iâm 1152 <span class=""> 1153 Able 1154 </span> 1155 </h2> 1156 <!-- 坿 é¢ï¼è¾ å©è¯´æ --> 1157 <p class="subtitle"> 1158 Helping you find the right product in multiple languages. 1159 </p> 1160 <div style="background-color: white;border-radius: 15px;padding:20px"> 1161 <!-- 使ç¨è¯´ææ é¢ --> 1162 <h2>How to use this search</h2> 1163 <h3>Able is trained to provide you tailored product recommendations</h3> 1164 1165 <!-- å å®¹åæ ï¼è½åç / ä¸è½åç --> 1166 <div class="intro-content"> 1167 <div class="intro-column"> 1168 <h3>Able can help you with:</h3> 1169 <ul> 1170 <li>Find the right product for your experiment or project.</li> 1171 <li>Recommend products for research areas and applications. e.g âAble, find markers for subcellular organelles.</li> 1172 <li>Gain deeper insights into our products through validation data, scientific knowledge, and citations.</li> 1173 <!-- å¯ç»§ç»æ·»å è½åçå 容 --> 1174 </ul> 1175 </div> 1176 <div class="intro-column cant-help"> 1177 <h3>Able canât help you with:</h3> 1178 <ul> 1179 <li><span> General troubleshooting (use <span class="open-live-chat">Live Chat</span> instead) </span></li> 1180 <li><span>Customer service and shipping queries (use <span class="open-live-chat">Live Chat</span> instead)</span></li> 1181 <!-- å¯ç»§ç»æ·»å ä¸è½åçå 容 --> 1182 </ul> 1183 </div> 1184 </div> 1185 1186 <div style="text-align: left;word-break: break-all;"> 1187 <!-- <div style="font-size: 15px;font-weight: bold;">Disclaimer:</div> --> 1188 <div class="text-xs text-gray-500 mt-1.5 pl-1 flex items-center"> 1189 <!-- <i class="fa fa-info-circle mr-1"></i> --> 1190 <span class="inputClearLanguage"><span style="font-weight: bold;">Disclaimer</span> Able AI is experimental and still learning how to provide the best assistance. It may occasionally generate incorrect or incomplete responses. Please do not rely solely on its recommendations when making purchasing decisions or designing experiments.</span> 1191 </div> 1192 </div> 1193 1194 <!-- <div class="intro-contet"> 1195 <h3 style="margin-bottom: 5px;">Disclaimer: </h3> 1196 <div class="subtitle" style="text-align: left;font-size:14px;">Able AI is experimental and still learning how to provide the best assistance. It may occasionally generate incorrect or incomplete responses. Please do not rely solely on its recommendations when making purchasing decisions or designing experiments.</div> 1197 </div> --> 1198 </div> 1199 1200 </section> 1201 <!-- åå§æ¬¢è¿æ¶æ¯ --> 1202 <!-- <div class="message bot-message message-appear"> 1203 <div class="flex items-start space-x-3"> 1204 <div class="w-8 h-8 rounded-full overflow-hidden shadow-md flex-shrink-0"> 1205 <img src="/img/ptg.jpg" alt="AI婿头å" class="w-full h-full object-cover"> 1206 </div> 1207 <div class="bg-white rounded-lg shadow-md p-4 max-w-[90%] sm:max-w-[80%] md:max-w-[75%] border-l-4 border-primary"> 1208 <div class="prose max-w-none markdown-preview welcomeLanguage"> 1209 <p>
1209ä½ å¥½ï¼ææ¯ä½ çAIå©æï¼æä»ä¹å¯ä»¥å¸®å©ä½ çåï¼</p> 1210 </div> 1211 </div> 1212 </div> 1213 </div> --> 1214 </main> 1215 1216 <!-- è¾å ¥åºå --> 1217 1218 <div class="relative" style="position: sticky;bottom:0px" id="chatInputWrapper"> 1219 <div id="filePreviewBar" class="file-preview-bar" style="display:none;"> 1220 <div class="file-preview-chip"> 1221 <i class="fa fa-file-o file-preview-icon"></i> 1222 <img class="file-preview-thumb hidden" alt="preview"> 1223 <span class="file-preview-name"></span> 1224 <span class="file-preview-size"></span> 1225 <span class="file-preview-status" style="color:#00ae9e;font-size:12px;"></span> 1226 <button type="button" id="removeFileBtn" class="file-preview-remove" title="Remove file"> 1227 <i class="fa fa-times"></i> 1228 </button> 1229 </div> 1230 </div> 1231 <div id="chatInputDropzone" class="bg-white rounded-xl shadow-lg p-2 flex items-center space-x-2 border border-gray-200 focus-within:border-primary/50 focus-within:ring-2 focus-within:ring-primary/20 transition-all duration-300"> 1232 <input type="file" id="fileInput" class="hidden" accept="image/*,.pdf,.doc,.docx,.txt,.csv,.xls,.xlsx,.ppt,.pptx"> 1233 <button type="button" id="attachFileBtn" title="Attach file"> 1234 <i class="fa fa-paperclip text-lg"></i> 1235 </button> 1236 <textarea id="userInput" rows="1" autocomplete="off" placeholder="Ask Able to find you the perfect product" class="flex-1 bg-transparent outline-none rounded-lg text-neutral resize-none focus:ring-0"></textarea> 1237 <button id="sendBtn" class="bg-primary hover:bg-primary/90 text-white w-12 h-12 rounded-full flex items-center justify-center transition-all duration-300 transform hover:scale-105 active:scale-95 shadow-md"> 1238 <!-- ä½¿ç¨æ´ç»é¿çç®å¤´å¾æ --> 1239 <i class="fa fa-long-arrow-up text-lg send-icon" style="font-size: 20px;"></i> 1240 <i class="fa fa-ban text-xl cancel-icon hidden"></i> 1241 <!-- åéç¶ææç¤º --> 1242 </button> 1243 </div> 1244 1245 <div class="text-xs text-gray-500 mt-1.5 pl-1 flex items-center"> 1246 <i class="fa fa-info-circle mr-1"></i> 1247 <span class="inputClearLanguage">By submitting a query and entering into conversation with the Able AI Tool, you are agreeing to the terms of the Proteintech <a href="https://www.ptglab.com/news/blog/privacy-policy/" style="color:#00ae9e;" target="_blank">privacy policy</a>.</span> 1248 </div> 1249 </div> 1250 1251 1252 </div> 1253 1254 <!-- å³ä¾§å¼¹åºå±ï¼å ³éæé®åºå®å¨é¡¶é¨ï¼ --> 1255 <div id="rightPanel" class="fixed top-0 right-0 w-1/4 h-full bg-white shadow-lg z-10 overflow-hidden right-panel"> 1256 <!-- åºå®å¨é¡¶é¨ç头é¨ï¼å å«å ³éæé®ï¼ --> 1257 <div class="sticky top-0 z-10 bg-white border-b border-gray-200"> 1258 <div class="p-4 flex justify-between items-center"> 1259 <h2 class="text-xl font-bold canText">åèå 容</h2> 1260 <button id="closeBtn" class="text-gray-500 hover:text-gray-700 transition-colors"> 1261 <i class="fa fa-times text-xl"></i> 1262 </button> 1263 </div> 1264 </div> 1265 1266 <!-- 坿»å¨çå 容åºå --> 1267 <div class="p-4 overflow-y-auto h-[calc(100%-64px)]" id="scrollableContent" style="height: 95%;"> 1268 <!-- åå»å¤´é¨é«åº¦ --> 1269 <!-- å³ä¾§å å®¹ç¤ºä¾ --> 1270 <div class="mb-4"> 1271 <h3 class="font-bold"></h3> 1272 <ul class="mt-2 space-y-2"> 1273 </ul> 1274 </div> 1275 </div> 1276 </div> 1277 1278
1278<script type="text/javascript" src="https://ptglab.my.site.com/ESWLiveChatEU1694620743265/assets/js/bootstrap.min.js"></script>
1278 1279 1280 <!-- html2canvasåºç¨äºå¾ç导åºåè½ --> 1281
1281<script src="/scripts/html2canvas.min.js"></script>
1281 1282 <!-- jsPDFåºç¨äºPDF导åºåè½ --> 1283
1283<script src="/scripts/jspdf.umd.min.js"></script>
1283 1284 <!-- html2pdfåºç¨äºHTMLå°PDFè½¬æ¢ --> 1285
1285<script src="/scripts/html2pdf.bundle.min.js"></script>
1285 1286
1286<script src="/scripts/3d.js"></script>
1286 1287 1288
1288<script> 1289 const mdParser = window.markdownit({ 1290 html: true, 1291 linkify: true, 1292 typographer: false, 1293 breaks: true 1294 }); 1295 // å ¨å±åé 1296 let isLoading = false; 1297 let abortController = null; 1298 const enableLogging = true; 1299 // ãå ³é®ä¿®æ¹1ãç»ä¸ç¼å²åºç»æï¼å卿¯ä¸ªæ¶æ¯çthink/content/isLoadingç¶æï¼é¿å å ¨å±åéå²çªï¼ 1300 const responseBuffers = {}; // æ ¼å¼: { messageId: { think: '', content: '', isLoading: true } } 1301 const responseCompleted = {}; 1302 const auditLoadingStates = {}; // å卿¯ä¸ªæ¶æ¯çå®¡æ ¸å è½½ç¶æ 1303 let language = "en-US"; 1304 const messageGroups = new Map(); 1305 const collapseStates = new Map(); 1306 const processedPdbLinks = new Map(); 1307 1308 let ReviewID = ""; 1309 const userLanguage = "" 1310 1311 let isShowReview = "" 1312 // ãæ°å¢ãç§»å¨ç«¯æ£æµå½æ° 1313 function isMobileDevice() { 1314 // ç²¾åå¹é ææºè®¾å¤çUAæ£åï¼æé¤å¹³æ¿/PCçï¼ 1315 // æ©å±UAæ£åï¼æ°å¢å¾®ä¿¡/QQæµè§å¨ãå®å/è¹æç§»å¨ç«¯æ è¯ãå½äº§æµè§å¨å ³é®è¯ 1316 const mobileUA = /Android|iPhone|iPod|BlackBerry|IEMobile|Opera Mini|Windows Phone|Mobile|MicroMessenger|QQBrowser|MiuiBrowser|UCWEB/i.test(navigator.userAgent); 1317 // æ£æµiPad/å¹³æ¿ï¼å¯éï¼å¦éå å«å¹³æ¿ï¼å é¤è¿è¡å¤æï¼ 1318 const isTablet = /iPad|Macintosh/.test(navigator.userAgent) && 'ontouchend' in document; 1319 const result = mobileUA && !isTablet; 1320 return result; 1321 } 1322 // ãæ°å¢ã页é¢å¯è§æ§ç¶æåè¿æ¥æ¢å¤ 1323 let isPageVisible = true; 1324 // let connectionLostWhileHidden = false; // æ è®°æ¯å¦å¨éèæ¶è¿æ¥æå¼ 1325 const isMobile = isMobileDevice(); // æ£æµæ¯å¦æ¯ç§»å¨ç«¯ 1326 1327 // ãæ°å¢ãçå¬é¡µé¢å¯è§æ§åå 1328 document.addEventListener('visibilitychange', () => { 1329 isPageVisible = !document.hidden; 1330 // å½é¡µé¢æ¢å¤å¯è§æ¶ï¼æ£æ¥æ¯å¦ææ£å¨è¿è¡çè¯·æ± 1331 if (isPageVisible && isMobile && uuid) { 1332 // æ¾ç¤ºæ¢å¤å è½½å¨ç» 1333 showConversationRestoreLoading(); 1334 1335 // å»¶è¿ä¸ä¸ï¼ç¡®ä¿é¡µé¢å®å ¨æ¢å¤ 1336 setTimeout(() => { 1337 if (uuid) { 1338 loadConversationHistory(uuid); 1339 } 1340 }, 500); 1341 } 1342 }); 1343 // åå¨rebuild-chatæ¥å£è¿åçround_idéå 1344 const rebuildChatRoundIds = new Set(); 1345 let resultList = []; 1346 let userChat = []; 1347 let pendingFileAttachment = null; 1348 let pendingFilePreviewUrl = null; 1349 let pendingUploadedFileInfo = null; 1350 let pendingFileUploading = false; 1351 let pendingFileUploadError = null; 1352 const UPLOAD_API_BASE = 'https://aipublic.ptglab.com'; 1353 const IMAGE_EXTENSIONS = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp', '.svg']; 1354 const ACCEPTED_FILE_EXTENSIONS = ['.pdf', '.doc', '.docx', '.txt', '.csv', '.xls', '.xlsx', '.ppt', '.pptx']; 1355 1356 function isImageFile(file) { 1357 if (!file) return false; 1358 if (file.type && file.type.startsWith('image/')) return true; 1359 const lowerName = file.name.toLowerCase(); 1360 return IMAGE_EXTENSIONS.some(ext => lowerName.endsWith(ext)); 1361 } 1362 1363 function isAcceptedAttachmentFile(file) { 1364 if (!file) return false; 1365 if (isImageFile(file)) return true; 1366 const lowerName = (file.name || '').toLowerCase(); 1367 return ACCEPTED_FILE_EXTENSIONS.some(ext => lowerName.endsWith(ext)); 1368 } 1369 1370 function normalizePastedFile(file) { 1371 if (!file) return file; 1372 if (file.name) return file; 1373 if (file.type && file.type.startsWith('image/')) { 1374 const ext = (file.type.split('/')[1] || 'png').replace('jpeg', 'jpg'); 1375 return new File([file], `pasted-image.${ext}`, { type: file.type }); 1376 } 1377 return new File([file], 'pasted-file', { type: file.type || 'application/octet-stream' }); 1378 } 1379 1380 function getFileFromDataTransfer(dataTransfer) { 1381 if (!dataTransfer || !dataTransfer.files || !dataTransfer.files.length) return null; 1382 return dataTransfer.files[0]; 1383 } 1384 1385 function getFileFromClipboardEvent(e) { 1386 const clipboardData = e.originalEvent ? e.originalEvent.clipboardData : e.clipboardData; 1387 if (!clipboardData) return null; 1388 const items = clipboardData.items; 1389 if (items) { 1390 for (let i = 0; i < items.length; i++) { 1391 const item = items[i]; 1392 if (item.kind === 'file') { 1393 const file = item.getAsFile(); 1394 if (file) return file; 1395 } 1396 } 1397 } 1398 if (clipboardData.files && clipboardData.files.length > 0) { 1399 return clipboardData.files[0]; 1400 } 1401 return null; 1402 } 1403 1404 function attemptAttachFile(file, isPaste) { 1405 if (isLoading) return; 1406 if (!file) return; 1407 const normalizedFile = isPaste ? normalizePastedFile(file) : file; 1408 if (!isAcceptedAttachmentFile(normalizedFile)) { 1409 showError(language === 'Chinese' 1410 ? '䏿¯æçæä»¶ç±»åï¼è¯·ä¸ä¼ å¾çæ PDF/Office ææ¡£ã' 1411 : 'Unsupported file type. Please upload an image or PDF/Office document.'); 1412 return; 1413 } 1414 handleFileSelect(normalizedFile); 1415 } 1416 1417 1418 function formatFileSize(bytes) { 1419 if (bytes < 1024) return `${bytes} B`; 1420 if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`; 1421 return `${(bytes / (1024 * 1024)).toFixed(1)} MB`; 1422 } 1423
1424 function clearPendingFile(revokePreview = true) { 1425 if (revokePreview && pendingFilePreviewUrl) { 1426 URL.revokeObjectURL(pendingFilePreviewUrl); 1427 pendingFilePreviewUrl = null; 1428 } 1429 pendingFileAttachment = null; 1430 pendingUploadedFileInfo = null; 1431 pendingFileUploading = false; 1432 pendingFileUploadError = null; 1433 $('#fileInput').val(''); 1434 $('#filePreviewBar').hide(); 1435 $('#filePreviewBar .file-preview-status').text(''); 1436 } 1437 1438 async function ensureDialogId() { 1439 if (!localStorage.getItem('chat_uuid') && !uuid) { 1440 const id = await generateSafeNumericId(); 1441 uuid = id; 1442 localStorage.setItem('chat_uuid', id); 1443 } 1444 return localStorage.getItem('chat_uuid') || uuid; 1445 } 1446 1447 function updateFilePreview() { 1448 if (!pendingFileAttachment) { 1449 $('#filePreviewBar').hide(); 1450 return; 1451 } 1452 1453 const $previewBar = $('#filePreviewBar'); 1454 const isImage = isImageFile(pendingFileAttachment); 1455 $previewBar.find('.file-preview-name').text(pendingFileAttachment.name); 1456 $previewBar.find('.file-preview-size').text(formatFileSize(pendingFileAttachment.size)); 1457 1458 if (pendingFileUploading) { 1459 $previewBar.find('.file-preview-status').text(language === 'Chinese' ? 'ä¸ä¼ ä¸...' : 'Uploading...'); 1460 } else if (pendingFileUploadError) { 1461 $previewBar.find('.file-preview-status').text(pendingFileUploadError); 1462 } else if (pendingUploadedFileInfo) { 1463 $previewBar.find('.file-preview-status').text(language === 'Chinese' ? 'å·²ä¸ä¼ ' : 'Uploaded'); 1464 } else { 1465 $previewBar.find('.file-preview-status').text(''); 1466 } 1467 1468 if (isImage && pendingFilePreviewUrl) { 1469 $previewBar.find('.file-preview-icon').hide(); 1470 $previewBar.find('.file-preview-thumb').removeClass('hidden').attr('src', pendingFilePreviewUrl).show(); 1471 } else { 1472 $previewBar.find('.file-preview-thumb').addClass('hidden').hide(); 1473 $previewBar.find('.file-preview-icon') 1474 .show() 1475 .removeClass('fa-file-image-o fa-file-pdf-o fa-file-o') 1476 .addClass(isImage ? 'fa-file-image-o' : (pendingFileAttachment.name.toLowerCase().endsWith('.pdf') ? 'fa-file-pdf-o' : 'fa-file-o')); 1477 } 1478 1479 $previewBar.show(); 1480 } 1481 1482 async function uploadAttachmentFile(file, dialogId) { 1483 const formData = new FormData(); 1484 formData.append('file', file); 1485 const response = await fetch(`${UPLOAD_API_BASE}/llm/v2/upload-file/${dialogId}`, { 1486 method: 'POST', 1487 body: formData 1488 }); 1489 const result = await response.json(); 1490 if (!response.ok || !result.ok || !result.data?.url) { 1491 console.error('upload-file response:', result); 1492 throw new Error(result.msg || `Upload failed: ${response.status}`); 1493 } 1494 const { url, filename, mime_type } = result.data; 1495 const isImage = isImageFile(file); 1496 return { 1497 url, 1498 name: filename || file.name, 1499 mimeType: mime_type || file.type || 'application/octet-stream', 1500 isImage 1501 }; 1502 } 1503 1504 async function handleFileSelect(file) { 1505 clearPendingFile(); 1506 pendingFileAttachment = file; 1507 if (isImageFile(file)) { 1508 pendingFilePreviewUrl = URL.createObjectURL(file); 1509 } 1510 updateFilePreview(); 1511 1512 pendingFileUploading = true; 1513 pendingFileUploadError = null; 1514 updateFilePreview(); 1515 1516 try { 1517 const dialogId = await ensureDialogId(); 1518 pendingUploadedFileInfo = await uploadAttachmentFile(file, dialogId); 1519 } catch (err) { 1520 console.error('éæ©æä»¶åä¸ä¼ 失败:', err); 1521 pendingFileUploadError = language === 'Chinese' ? 'ä¸ä¼ 失败' : 'Upload failed'; 1522 pendingUploadedFileInfo = null; 1523 } finally { 1524 pendingFileUploading = false; 1525 updateFilePreview(); 1526 } 1527 userInput.focus(); 1528 } 1529 1530 function buildUserInputMultiContent(userText, uploadedFileInfo) { 1531 const multiContent = []; 1532 if (uploadedFileInfo) { 1533 multiContent.push({ 1534 type: 'file_url', 1535 file: { 1536 url: uploadedFileInfo.url, 1537 name: uploadedFileInfo.name, 1538 mime_type: uploadedFileInfo.mimeType 1539 } 1540 }); 1541 } 1542 if (userText) { 1543 multiContent.push({ 1544 type: 'text', 1545 text: userText 1546 }); 1547 } 1548 return multiContent; 1549 } 1550 1551 function buildFileChatRequestBody(dialogId, userText, uploadedFileInfo, topHref, topTitle,messageId) { 1552 return { 1553 dialog_id: dialogId, 1554 messages: [{ 1555 role: 'user', 1556 user_input_multi_content: buildUserInputMultiContent(userText, uploadedFileInfo) 1557 }], 1558 round_id:messageId, 1559 agent_name: 'Able', 1560 country: language === 'Chinese' ? 'China' : (isMobile ? 'Mobile United States' : 'United States'), 1561 current_title: topTitle || '', 1562 current_url: topHref || '' 1563 }; 1564 } 1565 1566 function buildDownloadFileUrl(dialogId, fileUrl) { 1567 return `${UPLOAD_API_BASE}/llm/v2/download-file/${dialogId}/${fileUrl}`; 1568 } 1569 1570 function isImageByMimeOrName(mimeType, name) { 1571 if (mimeType && mimeType.startsWith('image/')) return true; 1572 if (name) { 1573 const lowerName = name.toLowerCase(); 1574 return IMAGE_EXTENSIONS.some(ext =>
1574 lowerName.endsWith(ext)); 1575 } 1576 return false; 1577 } 1578 1579 function buildUserHistoryMessageContent(message) { 1580 const dialogId = message.dialog_id || localStorage.getItem('chat_uuid') || uuid || ''; 1581 let attachmentHtml = ''; 1582 let textContent = message.content || ''; 1583 1584 if (message.user_input_multi_content && Array.isArray(message.user_input_multi_content)) { 1585 const textParts = []; 1586 message.user_input_multi_content.forEach(item => { 1587 if (item.type === 'file_url' && item.file && item.file.url) { 1588 const fileUrl = item.file.url; 1589 const fileName = item.file.name || fileUrl; 1590 const mimeType = item.file.mime_type || ''; 1591 const downloadUrl = buildDownloadFileUrl(dialogId, fileUrl); 1592 if (isImageByMimeOrName(mimeType, fileName)) { 1593 attachmentHtml += '<div class="user-attachment"><img src="' + downloadUrl + '" alt="' + fileName + '"><span>' + fileName + '</span></div>'; 1594 } else { 1595 const iconClass = fileName.toLowerCase().endsWith('.pdf') ? 'fa-file-pdf-o' : 'fa-file-o'; 1596 attachmentHtml += '<div class="user-attachment"><i class="fa ' + iconClass + '"></i><span>' + fileName + '</span></div>'; 1597 } 1598 } else if (item.type === 'text' && item.text) { 1599 textParts.push(item.text); 1600 } 1601 }); 1602 if (!textContent.trim() && textParts.length > 0) { 1603 textContent = textParts.join('\n'); 1604 } 1605 } 1606 1607 const textHtml = textContent ? mdParser.render(textContent) : ''; 1608 return attachmentHtml + (textHtml ? '<div>' + textHtml + '</div>' : ''); 1609 } 1610 // ãå ³é®ä¿®æ¹2ãåºå¼å ¨å±loadMoreï¼æ¹ç¨ç¼å²åºisLoadingæ§å¶ï¼é¿å 夿¶æ¯ç¶ææ··ä¹±ï¼ 1611 // let loadMore = false; 1612 let record_MCP = ""; 1613 1614 // DOMå ç´ 1615 const messagesContainer = $('#messagesContainer'); 1616 const userInput = $('#userInput'); 1617 const sendBtn = $('#sendBtn'); 1618 const clearBtn = $('#clearBtn'); 1619 1620 // 设置æåææåæ° 1621 const baseSpeed = 1; 1622 const speedMultiplier = 6; 1623 const typingSpeed = Math.max(1, Math.floor(baseSpeed / speedMultiplier)); 1624 const sessionId = Math.floor(Math.random() * 1000); 1625 1626 if ($('#global-tooltip').length === 0) { 1627 $('body').append(` 1628 <div id="global-tooltip" class="fixed z-50 pointer-events-none"> 1629 <div style="position:absolute;width:max-content;" class="bg-gray-800 text-white text-xs py-1 px-2 rounded shadow-lg opacity-0 transition-opacity duration-200 max-w-xs whitespace-normal"></div> 1630 </div> 1631 `); 1632 } 1633 1634 const $tooltip = $('#global-tooltip > div'); 1635 function showTooltip(element) { 1636 const $element = $(element); 1637 const content = $element.data('full-content'); 1638 if (!content) return; 1639 const offset = $element.offset(); 1640 const height = $element.outerHeight(); 1641 $tooltip.text(content) 1642 .css({ 1643 top: `${offset.top + height + 5}px`, 1644 left: `${offset.left}px` 1645 }) 1646 .addClass('opacity-100'); 1647 } 1648 function hideTooltip() { 1649 $tooltip.removeClass('opacity-100'); 1650 } 1651 1652 const getUserLanguage = () => { 1653 const primaryLanguage = navigator.language || navigator.userLanguage; 1654 const allLanguages = navigator.languages || [primaryLanguage]; 1655 return { primary: "en-US", all: allLanguages }; 1656 }; 1657 const languageInfo = getUserLanguage(); 1658 // æ¿å°å¯ä¸IDï¼ä¿æéå¢çï¼ 1659 async function generateSafeNumericId() { 1660 try { 1661 // æ ¸å¿ä¿®æ¹ï¼fetch æ·»å POST 请æ±é ç½® 1662 const timestampdA = Date.now().toString(); 1663 const res = await fetch(`https://aipublic.ptglab.com/llm/v2/snowflake-id/${timestampdA}`, { 1664 method: 'POST', // æ¹ä¸º POST è¯·æ± 1665 headers: { 1666 // å¸¸è§ JSON æ¥å£ç请æ±å¤´ï¼æ ¹æ®å®é æ¥å£è¦æ±è°æ´ï¼ 1667 'Content-Type': 'application/json', 1668 // 妿æ¥å£éè¦å ¶ä»è¯·æ±å¤´ï¼å¦ tokenãappId çï¼ï¼å¯å¨æ¤æ·»å 1669 // 'Authorization': 'Bearer xxx', 1670 // 'App-Id': 'xxx' 1671 }, 1672 // è¥æ¥å£è¦æ±ä¼ 请æ±ä½ï¼å¯å¨è¿éè¡¥å ï¼æ åæ°åä¼ ç©ºå¯¹è±¡ï¼ 1673 body: JSON.stringify({}) 1674 }); 1675 1676 if (!res.ok) { 1677 throw new Error(`æ¥å£æ¥éï¼${res.status} ${res.statusText}`); 1678 } 1679 const data = await res.json(); 1680 const snowflakeId = data.snowflakeId || data; 1681 const finalId = snowflakeId.data;
1681 // å设æ¥å£è¿åç snowflakeId æ¬èº«æ¯éå¢çï¼snowflake ç®æ³ç¹æ§ï¼ 1682 console.log('éè¿æ¥å£è·åéå¢ snowflake-idï¼', finalId); 1683 return finalId; 1684 1685 } catch (error) { 1686 console.error('çæå¯ä¸ID失败ï¼ä½¿ç¨ä¿åºé墿¹æ¡:', error); 1687 1688 const timestamp = Date.now().toString(); 1689 // æ ¸å¿ï¼ç¨å½æ°å±æ§æä¹ å计æ°å¨ï¼å½æ°ä¹æ¯å¯¹è±¡ï¼å¯åå¨å±æ§ï¼å¤æ¬¡è°ç¨ä¸éç½®ï¼ 1690 if (!generateSafeNumericId.counter) { 1691 // åå§åï¼éæº 0~9999 ä¹é´çæ°ï¼é¿å æ¯æ¬¡å¯å¨é½ä» 0 å¼å§ï¼åå°éå¤é£é©ï¼ 1692 generateSafeNumericId.counter = Math.floor(Math.random() * 10000); 1693 } 1694 // 计æ°å¨èªå¢ + 忍¡ï¼ç¡®ä¿å§ç»æ¯ 5 ä½ï¼é¿å 溢åºï¼ 1695 generateSafeNumericId.counter = (generateSafeNumericId.counter + 1) % 100000; // 0~99999 1696 // è¡¥å ¨ 5 ä½ï¼ä¸è¶³åé¢è¡¥ 0ï¼ 1697 const counter = generateSafeNumericId.counter.toString().padStart(5, '0'); 1698 1699 const fallbackId = timestamp + counter; 1700 reportErrorToApi( 1701 fallbackId, 1702 `0`, 1703 `${error},msg:snowflake-id error`, 1704 ); 1705 console.log('ä¿åºé墿¹æ¡çæIDï¼', fallbackId); 1706 return fallbackId; 1707 } 1708 } 1709 1710 1711 //夿æ¯å¦åµå¥é¡µé¢ 1712 function isInIframe() { 1713 try { 1714 return window.self !== window.top; 1715 } catch (e) { 1716 return true; 1717 } 1718 } 1719 //夿æ¯å¦æ¯è¿ä¸ªç½å 1720 function isCurrentPageTargetUrlFlexible() { 1721 try { 1722 const targetBaseUrls = [ 1723 'http://www.ptglab.com/Able', 1724 'http://www.ptglab.com/Able/', 1725 'https://www.ptglab.com/Able', 1726 'https://www.ptglab.com/Able/', 1727 'http://www.ptglab.com/able', 1728 'http://www.ptglab.com/able/', 1729 'https://www.ptglab.com/able', 1730 'https://www.ptglab.com/able/' 1731 ]; 1732 1733 // å ³é®ä¿®æ¹ï¼è·åé¡¶å±é¡µé¢ï¼æµè§å¨å°åæ ï¼çURLï¼èéiframeçURL 1734 // window.top æåæµè§å¨çé¡¶å±window对象ï¼å ¶locationæ¯å°åæ çURL 1735 const currentUrl = window.top.location.href; 1736 //console.log('é¡¶å±é¡µé¢URL:', currentUrl); 1737 1738 // ä¿çåæçå¹é é»è¾ 1739 const isMatch = targetBaseUrls.includes(currentUrl); 1740 return isMatch; 1741 } catch (error) { 1742 // è¡¥å è·¨åå¼å¸¸è¯´æï¼iframeè·¨å访é®top.locationä¼è§¦åå®å ¨é误 1743 console.warn('å¤æç®æ 页颿¶åçå¼å¸¸ï¼å¤§æ¦çæ¯è·¨å/ç¹æ®ç¯å¢ï¼:', error); 1744 return false; // å¼å¸¸æ¶è¿å falseï¼ä¸å½±ååç»ä»£ç æ§è¡ 1745 } 1746 } 1747 1748 let uuid; // ä½¿ç¨ let è䏿¯ constï¼å ä¸ºæ¸ ç©ºå¯¹è¯æ¶éè¦éæ°çæ 1749 const storedUuid = localStorage.getItem('chat_uuid'); 1750 if (storedUuid) { 1751 uuid = storedUuid; 1752 } else { 1753 //generateSafeNumericId() 1754 // .then(id => { 1755 // uuid = id 1756 // localStorage.setItem('chat_uuid', uuid); 1757 // // åç»é»è¾ 1758 // }) 1759 } 1760 1761 // 妿ä¸å¨å½åçªå£å°± 1762 if (!isInIframe() || isCurrentPageTargetUrlFlexible()) { 1763 generateSafeNumericId() 1764 .then(id => { 1765 uuid = id 1766 localStorage.setItem('chat_uuid', uuid); 1767 // åç»é»è¾ 1768 }) 1769 localStorage.setItem('chat_uuid', uuid); 1770 } 1771 1772 // loadConversationHistory(uuid) 1773 function handleLinkClick(event) { 1774 // è¿éå¯ä»¥ä¿çä½ åæçé»è¾ 1775 const originalUrl = $(this).attr('data-original-href'); 1776 try { 1777 const urlObj = new URL(originalUrl); 1778 const pdbName = urlObj.pathname.split('/af-pdbs/')[1] || ''; 1779 const epitopeStart = urlObj.searchParams.get('epitopeStart') || ''; 1780 const epitopeEnd = urlObj.searchParams.get('epitopeEnd') || ''; 1781 1782 const targetUrl = `https://www.ptglab.com/mapping/index.html?pdbName=${encodeURIComponent(pdbName)}&epitopeStart=${epitopeStart}&epitopeEnd=${epitopeEnd}`; 1783 1784 if (targetUrl && pdbName) { 1785 window.open(targetUrl, '_blank'); 1786 } else { 1787 } 1788 } catch (error) { 1789 console.error("跳转失败ï¼", error); 1790 } 1791 } 1792 1793 function isSingleGeneWord(input) { 1794 let s = input.trim(); 1795 1796 // ç»ä¸æ¿æ¢ ç¹æ®æ¨ªæ â æ 忍ªæ ï¼æ¯æ 26975â1âAPï¼ 1797 s = s.replace(/\u2011/g, '-'); 1798 1799 // 1. åºç¡æ ¡éªï¼é空 + ä» å«åæ³å符ï¼åæ¯/æ°å/-/_ï¼ 1800 const notEmpty = s !== ''; 1801 const validChars = /^[A-Za-z0-9_\-]+$/.test(s); 1802 const basicValid = notEmpty && validChars; 1803 1804 // 2. æé¤ææå¼è¯æ±ï¼ä¸åºå大å°åï¼ 1805 const lowerS = s.toLowerCase(); 1806 const greetings = ['hi', 'hello']; 1807 const isGreet = greetings.includes(lowerS); 1808 1809 // æç»è¿å 1810 return basicValid && !isGreet; 1811 } 1812 async function sendMessage() { 1813 isShowReview = "" 1814 const displayValue = $(".able-intro").css("display"); 1815 // 夿ï¼å¦æä¸æ¯display: noneï¼å设置为display: none
1816 if (displayValue !== "none") { 1817 $(".able-intro").css("display", "none"); 1818 } 1819 const userText = userInput.val().trim(); 1820 1821 const attachedUploadInfo = pendingUploadedFileInfo; 1822 if ((!userText && !attachedUploadInfo) || isLoading) return; 1823 if (pendingFileUploading) { 1824 showError(language === 'Chinese' ? 'æä»¶æ£å¨ä¸ä¼ ï¼è¯·ç¨å...' : 'File is uploading, please wait...'); 1825 return; 1826 } 1827 if (pendingFileAttachment && !attachedUploadInfo) { 1828 showError(language === 'Chinese' ? 'æä»¶ä¸ä¼ 失败ï¼è¯·éæ°éæ©æä»¶ã' : 'File upload failed, please select again.'); 1829 return; 1830 } 1831 if (isLoading) return; 1832 if (userText === '/clear') { 1833 clearMessages(); 1834 userInput.val(''); 1835 autoResizeUserInput(); 1836 return; 1837 } 1838 const hasMessage = $('#messagesContainer .message').length > 0; 1839 if (isSingleGeneWord(userText) && !hasMessage) { 1840 top.location.href = `https://www.ptglab.com/results?q=${userText}`; 1841 return 1842 } 1843 1844 // æ£æ¥æ¯å¦æ¯éæ°çææ¨¡å¼ 1845 let isRegenerating = window.isRegenerating || false; 1846 let regenerateMessageId = window.regenerateMessageId; 1847 let regenerateDialogId = window.regenerateDialogId; 1848 let regenerateRoundId = window.regenerateRoundId; 1849 1850 // æ¸ é¤éæ°çææ è®° 1851 window.isRegenerating = false; 1852 window.regenerateMessageId = null; 1853 window.regenerateDialogId = null; 1854 window.regenerateRoundId = null; 1855 let messageId; 1856 if (!isRegenerating) { 1857 // å¼å§æ°å¯¹è¯æ¶ï¼ç§»é¤ææåå²å¯¹è¯çéç½®å¾æ 1858 removeAllResetIcons(); 1859 1860 const savedPreviewUrl = pendingFilePreviewUrl; 1861 pendingFilePreviewUrl = null; 1862 const savedUploadInfo = attachedUploadInfo; 1863 addUserMessage(userText, savedUploadInfo ? { 1864 name: savedUploadInfo.name, 1865 isImage: savedUploadInfo.isImage, 1866 previewUrl: savedPreviewUrl 1867 } : null); 1868 userInput.val(''); 1869 autoResizeUserInput(); 1870 clearPendingFile(false); 1871 messageId = addBotMessage("æ£å¨æèä½ çé®é¢..."); 1872 isLoading = true; 1873 } else { 1874 messageId = regenerateMessageId; 1875 isLoading = true; 1876 } 1877 1878 //addUserMessage(userText); 1879 //userInput.val(''); 1880 //const messageId = addBotMessage("æ£å¨æèä½ çé®é¢..."); 1881 //isLoading = true; 1882 $('#sendBtn').addClass('loading'); 1883 $('#sendBtn').off('click').on('click', function () { 1884 // $(".able-intro").hide() 1885 if (isLoading) { 1886 abortRequest(true); 1887 isLoading = false; 1888 $('#sendBtn').removeClass('loading'); 1889 $('#sendBtn').off('click').on('click', sendMessage); 1890 } 1891 }); 1892 1893 // ãå ³é®ä¿®æ¹3ãåå§åå½åæ¶æ¯çç¼å²åºï¼think/content/isLoadingï¼ 1894 responseBuffers[messageId] = { 1895 segments: [], 1896 lastSegmentType: '', 1897 isLoading: false, 1898 lastType: "", 1899 toolContentBuffer: '', // ãæ°å¢ãç¼åä¸å®æ´çå·¥å ·æ ç¾å 容 1900 renderOrder: [], // ãæ°å¢ãè®°å½æ¸²æé¡ºåºï¼{ type: 'group'|'reasoning'|'tool', index: number } 1901 taskSegments: [], // ãæ°å¢ã任塿®µå表 1902 nonContentGroups: [], // ãæ°å¢ãécontentç»å表 1903 hasReviewed: false, // ãæ°å¢ãæ¯å¦å·²å®¡æ ¸ 1904 afterTaskDoneNoValidContent: false // ãæ°å¢ãtask_done忝å¦è¿æ²¡æéå°ææcontent 1905 , renderedMermaidCache: {} 1906 , pendingMermaidKeys: {} 1907 , pendingFormatGroupIds: {} // å¾ æ¸²æçç»ï¼task åºç° / task_done / å« markdown ç content æ¶æ è®° 1908 }; 1909 responseCompleted[messageId] = false;
1910 auditLoadingStates[messageId] = false; // åå§åå®¡æ ¸å è½½ç¶æ 1911 record_MCP = ""; 1912 userChat.push({ content: userText, round_id: `${messageId}` }); 1913 const apiUrl = 'https://aipublic.ptglab.com/llm/v2/product/chat'; 1914 // abortRequest(); 1915 abortController = new AbortController(); 1916 const signal = abortController.signal; 1917 // æå»ºè¯·æ±ä½ï¼æ ¹æ®æ¯å¦æ¯éæ°çææ¨¡å¼ä½¿ç¨ä¸åçåæ° 1918 let requestBody; 1919 1920 if (!localStorage.getItem('chat_uuid') && !uuid) { 1921 // ç¨ await æ¿ä»£ .thenï¼ç¡®ä¿ id èµå¼ç» uuid åï¼åæ§è¡åç»ä»£ç 1922 const id = await generateSafeNumericId(); 1923 uuid = id; 1924 // å¯éï¼åæ¶åå ¥ localStorageï¼é¿å 䏿¬¡éå¤çæ 1925 localStorage.setItem('chat_uuid', id); 1926 } 1927 1928 1929 let topHref, topTitle; 1930 try { 1931 // å°è¯è·åé¡¶å±çªå£ç屿§ï¼è·¨åæ¶ä¼æéï¼ 1932 topHref = window.top.location.href; 1933 topTitle = window.top.document.title; 1934 } catch (e) {
1935 // è·¨åæ¥éæ¶ï¼é级为å½å页é¢ç屿§ 1936 topHref = window.self.location.href; // å½å页é¢URL 1937 topTitle = window.self.document.title; // å½å页颿 é¢ 1938 console.warn('è·¨åæ æ³è®¿é®é¡¶å±çªå£å±æ§ï¼å·²é级为å½å页é¢å¼:', e); 1939 } 1940 const dialogId = isRegenerating 1941 ? regenerateDialogId 1942 : (localStorage.getItem('chat_uuid') ? localStorage.getItem('chat_uuid') : uuid); 1943 1944 1945 if (isRegenerating) { 1946 // éæ°çææ¨¡å¼ï¼ä½¿ç¨æå®çdialog_idåround_idï¼æ·»å regenerateæ è®° 1947 requestBody = JSON.stringify({ 1948 messages: [{ role: "user", content: userText }], 1949 dialog_id: regenerateDialogId, 1950 round_id: `${regenerateRoundId}`, 1951 stream: true, 1952 lang: languageInfo?.primary, 1953 use_context: true, 1954 prompt_template: "", 1955 timestamp: new Date().getTime(), 1956 country: isMobile ? "Mobile United States" : "United States", 1957 regenerate: true, // æ·»å éæ°çææ è®° 1958 current_url: topHref, 1959 current_title: topTitle 1960 }); 1961 1962 } else if (attachedUploadInfo) { 1963 // ç¹å»åéï¼ä½¿ç¨éæ©æä»¶æ¶å·²ä¸ä¼ ç url è° chat 1964 requestBody = JSON.stringify(buildFileChatRequestBody(dialogId, userText, attachedUploadInfo, topHref, topTitle,messageId)); 1965 ReviewID = `${messageId}`; 1966 } else { 1967 // æ£å¸¸æ¨¡å¼ï¼ä½¿ç¨æ°çåæ° 1968 requestBody = JSON.stringify({ 1969 messages: [{ role: "user", content: userText }], 1970 dialog_id: localStorage.getItem('chat_uuid') ? localStorage.getItem('chat_uuid') : uuid, 1971 round_id: `${messageId}`, 1972 stream: true, 1973 lang: languageInfo?.primary, 1974 use_context: true, 1975 prompt_template: "", 1976 timestamp: new Date().getTime(), 1977 language: userLanguage, 1978 country: isMobile ? "Mobile United States" : "United States", 1979 current_url: topHref, 1980 current_title: topTitle 1981 }); 1982 ReviewID = `${messageId}` 1983 } 1984 $(".end-message-btn").hide() 1985 fetch(apiUrl, { 1986 method: 'POST', 1987 headers: { 'Content-Type': 'application/json' }, 1988 body: requestBody, 1989 signal: signal 1990 }) 1991 .then(response => { 1992 if (!response.ok) { 1993 throw new Error(`HTTPé误ï¼ç¶æç : ${response.status}`); 1994 } 1995 const reader = response.body.getReader(); 1996 const decoder = new TextDecoder(); 1997 record_MCP = ""; 1998 1999 function read() { 2000 reader.read().then(({ done, value }) => { 2001 if (done) { 2002 try { 2003 isShowReview = 'Reviewed' 2004 const $messageDom = $(`#${messageId}`); 2005 updateStatusIndicator($messageDom, "") 2006 // ãå ³é®ä¿®æ¹4ãååºç»æï¼æ è®°å è½½å®æï¼ç§»é¤ææå è½½å¨ 2007 const buffer = responseBuffers[messageId]; 2008 if (buffer) buffer.isLoading = false; 2009 $('.loading-indicator, .load-more-indicator').remove(); 2010 2011 completeResponse(messageId); 2012 let $megID = $(`#${messageId}`); 2013 // æ£æµå½åæ¶æ¯ç.content-bodyä¸ç.pdb龿¥å¹¶å¤ç3Dæ¾ç¤º 2014 // åªæå¨æ²¡æç°æ3D容卿¶æå¤çPDB龿¥ï¼é¿å éå¤å建 2015 if (messageId) { 2016 const $message = $(`#${messageId}`); 2017 const hasExistingContainers = $message.find('.pdb-3d-container').length > 0 || 2018 $message.data('has-3d-containers') === true; 2019 if (!hasExistingContainers
2019) { 2020 checkAndProcessPdbLinks(messageId); 2021 } 2022 } else { 2023 console.error('messageIdä¸ºç©ºï¼æ æ³æ£æ¥PDB龿¥'); 2024 } 2025 // å¨.content-bodyææ«å°¾æ·»å å¤å¶å¾æ 2026 addCopyIconToContent(messageId); 2027 // 为æåä¸è½®å¯¹è¯æ·»å éç½®å¾æ 2028 addResetIconToLastMessage(messageId); 2029 2030 // 夿URLæ¯å¦ä¸ºå¾ç 2031 function isImageUrl(url, extensions) { 2032 const lowerUrl = url.toLowerCase(); 2033 return extensions.some(ext => lowerUrl.endsWith(ext)); 2034 } 2035 2036 // ä»URLæåæä»¶å 2037 function getFileName(url) { 2038 const parts = url.split('/'); 2039 return parts[parts.length - 1] || 'å¾ç'; 2040 } 2041 const imageExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp', '.svg']; 2042 2043 $('a:not(:has(img))').each(function () { 2044 const $link = $(this); 2045 if ($link.find('img').length > 0) { 2046 return 2047 } 2048 const href = $link.attr('href'); 2049 2050 // æ£æ¥é¾æ¥æ¯å¦ä¸ºå¾ç 2051 if (href && isImageUrl(href, imageExtensions)) { 2052 2053 // å建å¾çå ç´ 2054 const $img = $('<img>', { 2055 'src': href, 2056 'alt': 'å¾çé¢è§: ' + getFileName(href), 2057 'data-jq-auto-added': 'true' 2058 }).css({ 2059 'vertical-align': 'middle', 2060 'border-radius': '3px' 2061 }); 2062 let imagesToLoad = 1; // æ¬æ¬¡å¾ªç¯åªæ·»å 1å¼ å¾ç 2063 // æ·»å å°é¾æ¥æ«å°¾ 2064 $link.append($img); 2065 2066 2067 $img.on('load error', function () { 2068 imagesToLoad--; // ä¸å¼ å¾çå è½½å®æï¼æ 论æåå¤±è´¥ï¼ 2069 2070 // 彿æå¾çé½å¤ç宿¯åæ§è¡æ»å¨ 2071 if (imagesToLoad === 0) { 2072 setTimeout(() => { 2073 scrollToBottom(true); 2074 }, 300); 2075 } 2076 }); 2077 } 2078 }); 2079 2080 2081 $('.thinkHtml a[href="@ptglab-live-chat"]').each(function () { 2082 const $link = $(this); 2083 // ç§»é¤ href 屿§ï¼é²æ¢ç¹å»è·³è½¬ 2084 $link.removeAttr('href'); 2085 // æ·»å class 2086 $link.addClass('open-live-chat'); 2087 // ç¡®ä¿ææ¬æ¯ Live Chat 2088 $link.text('Live Chat'); 2089 }); 2090 2091 // å¤ççº¯ææ¬å½¢å¼ç @ptglab-live-chat 2092 $megID.find('.thinkHtml').each(function () { 2093 const $thinkHtml = $(this); 2094 2095 // ä¿å3D容å¨ï¼é¿å è¢«æ¸ ç©º 2096 const $pdbContainers = $thinkHtml.find('.pdb-3d-container').detach(); 2097 2098 let htmlContent = $thinkHtml.html(); 2099 2100 // 1. å å¤çææå å«@ptglab-live-chatçaæ ç¾ï¼
2100å®å ¨ç§»é¤aæ ç¾ 2101 // ä½¿ç¨æ´ç²¾ç¡®çæ£åï¼ç¡®ä¿æè·æ´ä¸ªaæ ç¾å¹¶æ¿æ¢ä¸ºspan 2102 htmlContent = htmlContent.replace( 2103 /<a\s+[^>]*?>([^<]*?)@ptglab-live-chat([^<]*?)<\/a>/gi, 2104 function (match, prefix, suffix) { 2105 // ä¿ç@ååçå 容ï¼ç¨spanå 裹æ´ä¸ªç»æ 2106 return `<span class="open-live-chat">${prefix}Live Chat${suffix}</span>`; 2107 } 2108 ); 2109 2110 // 2. å¤ççº¯ææ¬å½¢å¼ç@ptglab-live-chat 2111 htmlContent = htmlContent.replace( 2112 /([^<>\s])?@ptglab-live-chat([^<>\s])?/g, 2113 function (match, prefix, suffix) { 2114 // ä¿çå¯è½åå¨çååå符ï¼é¿å ç ´åä¸ä¸æ 2115 const pre = prefix || ''; 2116 const suf = suffix || ''; 2117 return `${pre}<span class="open-live-chat">Live Chat</span>${suf}`; 2118 } 2119 ); 2120 2121 // 3. æ¸ çå¯è½æ®çç空aæ ç¾ 2122 htmlContent = htmlContent.replace(/<a\s+[^>]*?><\/a>/gi, ''); 2123 2124 // 4. ãæ°å¢ãå¤ç<end_message>æ ç¾ï¼è½¬æ¢ä¸ºå¯ç¹å»æé® 2125 // å å¤çå¯è½è¢«è½¬ä¹çæ ç¾ 2126 htmlContent = htmlContent.replace(/<end_message>/gi, '<end_message>'); 2127 htmlContent = htmlContent.replace(/<\/end_message>/gi, '</end_message>'); 2128 2129 htmlContent = htmlContent.replace( 2130 /<end_message>([\s\S]*?)<\/end_message>/gi, 2131 function (match, content) { 2132 const questionText = content.trim(); 2133 if (!questionText) return ''; 2134 return `<button class="end-message-btn" data-question="${questionText.replace(/"/g, '"')}">${questionText}</button>`; 2135 } 2136 ); 2137 // æ´æ°å 容 2138 $thinkHtml.html(htmlContent); 2139 // æ¢å¤3Då®¹å¨ 2140 if ($pdbContainers.length > 0) { 2141 $thinkHtml.append($pdbContainers); 2142 // console.log('æ¢å¤3D容å¨å¨thinkHtmlå¤çä¸:', $pdbContainers.length, '个'); 2143 } 2144 }); 2145 2146 // æµå¼ç»æï¼æ¸²ææåä¸ä¸ªä»å¨æ´»è·çç» + reasoning/tool 段 2147 const doneBuffer = responseBuffers[messageId]; 2148 const $segmentsContainer = $messageDom.find('.segments-container'); 2149 if ($segmentsContainer.length) { 2150 reinjectCachedMermaidInContainer(messageId, doneBuffer, $segmentsContainer); 2151 formatAllNonContentGroupsAtStreamEnd(messageId, doneBuffer, $segmentsContainer); 2152 formatOrphanSegmentGroups(messageId, $segmentsContainer); 2153 sweepUnrenderedMermaid(messageId, $segmentsContainer); 2154 } 2155 2156 $(".end-message-btn").addClass("visible"); 2157 2158 $(document).off('click', 'a[data-original-href]', handleLinkClick); 2159 $(document).on('click', 'a[data-original-href]', handleLinkClick); 2160 2161 $('.open-live-chat') 2162 .off('click') // ç§»é¤ææå·²ç»å®çclickäºä»¶ 2163 .click(function () { 2164 const data = { 2165 type: 'fromChild', 2166 message: 'msg', 2167 status: 'success' 2168 }; 2169 window.parent.postMessage(data, '*'); 2170 }); 2171 2172 //æååç»å®ä¸ä¸ ç¹å»å±å¼äºä»¶ 2173 $('#messagesContainer') 2174 .off('click', '.think-header') // ç§»é¤å·²æçäºä»¶å¤ç 2175 .on('click', '.think-header', function (e) { // éæ°ç»å®äºä»¶ 2176 e.stopPropagation(); 2177 const $container = $(this).closest('.segment-group'); 2178 const $contentBody = $container.find('.think-content-body'); 2179 const $toggleIcon = $container.find('.think-toggle-icon'); 2180 $contentBody.slideToggle(300, function () { 2181 $toggleIcon.toggleClass('fa-chevron-down fa-chevron-right'); 2182 }); 2183 }); 2184 2185 $(document) 2186 .off('click', '.end-message-btn') 2187 .on('click', '.end-message-btn', function (e) { 2188 e.preventDefault(); 2189 // e.stopPropagation(); 2190 // const questionText = $(this).data('question'); 2191 const questionText = $(this).text(); 2192 if (questionText) { 2193 // å°é®é¢å¡«å ¥è¾å ¥æ¡
2194 $('#userInput').val(questionText); 2195 // å¯éï¼èªå¨åéæ¶æ¯ 2196 $('#sendBtn').click(); 2197 2198 } 2199 }); 2200 2201 // å¨.content-bodyææ«å°¾æ·»å å¤å¶å¾æ 2202 addCopyIconToContent(messageId); 2203 setTimeout(() => { 2204 // let $messagehideThinking = $(`#${messageId}`); 2205 // $messagehideThinking.find('.hideThinking').removeClass('hideThinking'); 2206 scrollToBottom(true); 2207 }, 300); 2208 return; 2209 } 2210 catch (doneError) { 2211 console.error('over:', doneError); 2212 updateSendButtonState(false); // å¯ç¨åéæé® 2213 reportErrorToApi(uuid, `${messageId}`, `${doneError.message}, msg:Error in done phase`,) 2214 } finally { // æ°å¢ï¼ç¡®ä¿ç¶ææ è®° 2215 responseCompleted[messageId] = true; 2216 return; 2217 } 2218 } 2219 2220 const chunk = decoder.decode(value, { stream: true }); 2221 2222 // MCPæ ç¾å¤çï¼ä¿çåé»è¾ï¼ 2223 if (chunk.includes('"type":"tool_response"')) { 2224 console.log('"type":"tool_response"ï¼åå¤è¯·æ±æ¥å£'); 2225 // fetchMcpHostResult(uuid, messageId); 2226 } 2227 if (chunk.includes('/tool_call')) { 2228 record_MCP += chunk; 2229 const hasTag1 = record_MCP.includes('</think>') && record_MCP.includes('</MCP_HOST_TASK>') 2230 2231 if (hasTag1) { 2232 record_MCP = ""; 2233 const $messageContainer = $(`#${messageId}`); 2234 if (!$messageContainer.length) return; 2235 const $taskTag = $messageContainer.find('mcp_host_task:has(span.incomplete-placeholder)'); 2236 if ($taskTag.length) { 2237 const loaderId = `loader-${messageId}-${$taskTag.index()}`; 2238 const $existingLoading = $(`#${loaderId}`); 2239 if (!$existingLoading.length) { 2240 const $loading = $(`<div id="${loaderId}" class="loading-indicator ml-1 inline-block"></div>`) 2241 .css('color', 'dimgray') 2242 .html('<i class="fa fa-spinner fa-spin"></i>'); 2243 $taskTag.after($loading); 2244 } 2245 } 2246 } 2247 } 2248 2249 // ãå ³é®ä¿®æ¹5ãå¤çSSE chunkï¼åå²å¤dataè¡+容éè§£æï¼ 2250 // const processedChunks = processSseChunk(chunk); 2251 // log('[ååºæ°æ®å]', processedChunks); 2252 // updateBotMessage(messageId, processedChunks); 2253 2254 try { 2255 const processedChunks = processSseChunk(chunk); 2256 updateBotMessage(messageId, processedChunks); 2257 } catch (codeError) { 2258 updateSendButtonState(false); // å¯ç¨åéæé® 2259 reportErrorToApi(uuid, `${messageId}`, `${codeError},msg:Error processing AI response data`) 2260 console.error('error:', codeError); 2261 return; // ä¸åç»§ç»è¯»å 2262 } 2263 2264 read(); 2265 }).catch(error => { 2266 console.error('读åååºæ¶åºé:', error); 2267 isLoading = false; 2268 $('#sendBtn').removeClass('loading'); 2269 $('#sendBtn').off('click').on('click', sendMessage); 2270 $('.loading-indicator, .load-more-indicator').remove(); 2271 hideAuditLoading(messageId); 2272 updateSendButtonState(false); // å¯ç¨åéæé® 2273 // ãæ°å¢ãç§»å¨ç«¯ç½ç»é误å¤ç 2274 const isNetworkError = error.message && (error.message.includes('network') || error.message.includes('Failed to fetch')); 2275 if (error.name === 'AbortError') { 2276 showError(language === 'Chinese' ? '请æ±å·²åæ¶' : 'Request cancelled'); 2277 // reportErrorToApi(uuid, `${messageId}`, `${error},msg:Request cancelled`) 2278 } else if (isMobile && isNetworkError) { 2279 // ç§»å¨ç«¯ç½ç»é误ï¼ä¸æ¾ç¤ºé误ï¼èæ¯æ¢å¤å¯¹è¯ 2280 2281 // æ è®°è¿æ¥å¨éèæ¶æå¼ 2282 if (!isPageVisible) { 2283 // connectionLostWhileHidden = true; 2284 } else { 2285 // 妿页é¢ä»ç¶å¯è§ä½è¿æ¥æå¼ï¼ç«å³æ¢å¤ 2286 $(`#${messageId}`).remove(); // ç§»é¤æªå®æçæ¶æ¯ 2287 showConversationRestoreLoading(); // æ¾ç¤ºæ¢å¤å è½½ 2288 2289 setTimeout(() => { 2290 if (uuid) { 2291 loadConversationHistory(uuid); 2292 } else { 2293 hideConversationRestoreLoading(); 2294 showError(language === 'Chinese' ? 'æ±æï¼æ æ³æ¢å¤ä¼è¯ã' : 'Sorry, unable to restore conversation.'); 2295 } 2296 }, 500); 2297 } 2298 reportErrorToApi(uuid, `${messageId}`, `${error},msg:Mobile network error - attempting recovery`) 2299 } else { 2300 showError(language === 'Chinese' ? 'æ±æï¼è¯·æ±å¤çè¿ç¨ä¸åºç°é误ã' : 'Sorry, there was an error during the request processing.'); 2301 reportErrorToApi(uuid, `${messageId}`, `${error},msg:Sorry, there was an error during the request processing.`) 2302 } 2303 }); 2304 } 2305 read(); 2306 }) 2307 .catch(error => { 2308 console.error('Fetch请æ±åºé:', error); 2309 isLoading = false; 2310 $('#sendBtn').removeClass('loading'); 2311 $('#sendBtn').off('click').on('click', sendMessage); 2312 hideAuditLoading(messageId); 2313 updateSendButtonState(false); // å¯ç¨åéæé® 2314 2315 2316 // ãæ°å¢ãææºç«¯è¿æ¥ä¸æå¤çï¼æ£æµæ¯å¦ä¸ºç½ç»éè¯¯ä¸æ¯ç§»å¨è®¾å¤ 2317 const isMobile = isMobileDevice(); 2318 const isNetworkError = error.name === 'TypeError' || error.message.includes('Failed to fetch') || error.message.includes('NetworkError'); 2319 2320 if (isMobile && isNetworkError && messageId) { 2321 // ææºç«¯ç½ç»ä¸æï¼å°è¯æ¢å¤ä¼è¯è䏿¯æ¾ç¤ºé误 2322 console.log('ææºç«¯æ£æµå°ç½ç»ä¸æï¼å°è¯æ¢å¤ä¼è¯...'); 2323 $(`#${messageId}`).remove(); // ç§»é¤æªå®æçæ¶æ¯ 2324 showConversationRestoreLoading(); // æ¾ç¤ºæ¢å¤å è½½æç¤º 2325 2326 // å»¶è¿åå°è¯å è½½åå²ä¼è¯ 2327 setTimeout(() => { 2328 if (uuid) { 2329 loadConversationHistory(uuid); 2330 } else { 2331 hideConversationRestoreLoading(); 2332 showError(language === 'Chinese' ? 'æ±æï¼æ æ³æ¢å¤ä¼è¯ã' : 'Sorry, unable to restore conversation.'); 2333 } 2334 }, 500); 2335 } else if (error.name !== 'AbortError') { 2336 showError(language === 'Chinese' ? 'æ±æï¼æ æ³è¿æ¥å°AIæå¡ã' : 'Sorry, unable to connect to AI service.'); 2337 $(`#${messageId}`).remove() 2338 reportErrorToApi(uuid, `${messageId}`, `${error},msg:Sorry, unable to connect to AI service.`) 2339 } else { 2340 showError(language === 'Chinese' ? '请æ±å·²åæ¶' : 'Request cancelled'); 2341 // reportErrorToApi(uuid, `${messageId}`, `${error},msg:Request cancelled`) 2342 } 2343 }); 2344 } 2345 2346 // ãå ³é®ä¿®æ¹6ãä¿®å¤SSE chunkå¤çï¼åå²å¤dataè¡+åæ¡è§£æå®¹é 2347 function processSseChunk(chunk) { 2348 // 1. å ææ¢è¡ç¬¦åå²ï¼è§£å³å¤dataè¡æ··æé®é¢ï¼ 2349 const allLines = typeof chunk === 'string' ? chunk.split(/\r?\n/) : []; 2350 2351 return allLines 2352 .map(line => line.trim()) // æ¸ çç©ºç½ 2353 .filter(line => line.startsWith('data: ')) // åªä¿çdataè¡ 2354 .map(dataLine => { 2355 try { 2356 const jsonStr = dataLine.slice(6).trim(); // ç§»é¤"data: " 2357 if (!jsonStr) return null; 2358 const parsed = JSON.parse(jsonStr); 2359 // éªè¯chunkç»æï¼å¿ é¡»ætypeåcontentï¼ 2360 if (!parsed.type || typeof parsed.content !== 'string') { 2361 console.warn('æ æçchunkç»æ:', parsed);
2362 return null; 2363 } 2364 // å¤çè½¬ä¹æ¢è¡ 2365 // parsed.content = parsed.content.replace(/\\n/g, '\n'); 2366 parsed.content = parsed.content.replace(/\\n/g, '\n').replace(/\\"/g, '"'); 2367 return parsed; 2368 } catch (e) { 2369 // åæ¡è§£æå¤±è´¥ä¸é»ææ´ä½ 2370 console.error('è§£æåæ¡chunk失败:', e, 'å 容:', dataLine.slice(6)); 2371 return null; 2372 } 2373 }) 2374 .filter(Boolean); // è¿æ»¤æ æchunk 2375 } 2376 2377 // æ·»å ç¨æ·æ¶æ¯ï¼ä¿çåé»è¾ï¼ 2378 function addUserMessage(text, attachment) { 2379 let spanText = language === 'Chinese' ? 'ä½ ' : 'U'; 2380 let attachmentHtml = ''; 2381 if (attachment) { 2382 if (attachment.isImage && attachment.previewUrl) { 2383 attachmentHtml = '<div class="user-attachment"><img src="' + attachment.previewUrl + '" alt="' + attachment.name + '"><span>' + attachment.name + '</span></div>'; 2384 } else { 2385 const iconClass = attachment.name.toLowerCase().endsWith('.pdf') ? 'fa-file-pdf-o' : 'fa-file-o'; 2386 attachmentHtml = '<div class="user-attachment"><i class="fa ' + iconClass + '"></i><span>' + attachment.name + '</span></div>'; 2387 } 2388 } 2389 const messageContent = attachmentHtml + (text ? '<div>
2389' + text + '</div>' : ''); 2390 2391 const messageHtml = ` 2392 <div class="message user-message message-appear"> 2393 <div class="flex items-start justify-end space-x-3" style="margin-left:45px"> 2394 <div class="bg-primary/10 rounded-lg shadow-sm p-4 max-w-[90%] sm:max-w-[80%] md:max-w-[75%] border-r-4 border-primary aifontSize"> 2395 <div class="prose max-w-none markdown-preview"> 2396 ${messageContent} 2397 </div> 2398 </div> 2399 <div class="mobileUser w-8 h-8 rounded-full bg-gray-200 flex items-center justify-center text-gray-600 text-sm font-bold flex-shrink-0 shadow-sm">${spanText}</div> 2400 </div> 2401 </div> 2402 `; 2403 messagesContainer.append(messageHtml); 2404 scrollToBottom(true); 2405 } 2406 2407 // æ·»å AIä¸´æ¶æ¶æ¯ï¼ä¿çåé»è¾ï¼ 2408 function addBotMessage(text) { 2409 const messageId = generateUniqueId(); 2410 const messageHtml = ` 2411 <div class="message bot-message message-appear" id="${messageId}"> 2412 <div class="flex items-start"> 2413 <div class="w-8 h-8 avatar-container rounded-full overflow-hidden shadow-md flex-shrink-0"> 2414 <img src="/img/Able-Profile-Picture.jpg" alt="AI婿头å" class="w-full h-full object-cover"> 2415 </div> 2416 <div class="mobilePadding bg-white rounded-lg shadow-md p-4 max-w-[90%] sm:max-w-[80%] md:max-w-[75%] border-l-4 border-primary" style="width:100%;overflow:auto;min-height: 10px;position: relative;padding-bottom: 40px;"> 2417 <!-- prose容å¨ï¼æ¿æ¢åææ ·å¼ä¸ºwidth:100% --> 2418 <div class="prose max-w-none markdown-preview" style="width:100%;"> 2419 <!-- thinkHtmlï¼æ°å¢segments-containeråå ç´ --> 2420 <div class="thinkHtml markdown-body"> 2421 <div class="segments-container"></div> 2422 </div> 2423 </div> 2424 <!-- showThinkinglogoï¼ç§»åºprose容å¨ï¼æ°å¢ç»å¯¹å®ä½æ ·å¼ --> 2425 <div class="showThinkinglogo" style="position: absolute;bottom: 10px;left: 70px;transform: translateX(-50%);"> 2426 <sp
2426an class="thinking-text"> 2427 <span>T</span><span>h</span><span>i</span><span>n</span><span>k</span><span>ing</span> 2428 </span> 2429 <span class="dot"></span> 2430 <span class="dot"></span> 2431 <span class="dot"></span> 2432 </div> 2433 </div> 2434 </div> 2435 </div> 2436 `; 2437 messagesContainer.append(messageHtml); 2438 scrollToBottom(true); 2439 return messageId; 2440 } 2441 2442 2443 // æ§è¡doneåæ¯çææé»è¾ 2444 function executeDoneLogic(messageId, isReviewMode = false) { 2445 // ãå ³é®ä¿®æ¹4ãååºç»æï¼æ è®°å è½½å®æï¼ç§»é¤ææå è½½å¨ 2446 // const buffer = responseBuffers[messageId]; 2447 // if (buffer) buffer.isLoading = false; 2448 // $('.loading-indicator, .load-more-indicator').remove(); 2449 // completeResponse(messageId); 2450 2451 // è·åå½åæ¶æ¯çjQuery对象 2452 let $message = $('#' + messageId); 2453 2454 // æ£æµå½åæ¶æ¯ç.content-bodyä¸ç.pdb龿¥å¹¶å¤ç3Dæ¾ç¤º 2455 // åªæå¨æ²¡æç°æ3D容å¨çæ åµä¸æéæ°çæ 2456 2457 $message.find('a:not([target="_blank"])').attr('target', '_blank'); 2458 // å¾çå è½½é»è¾ 2459 function loadImages() { 2460 // 夿URLæ¯å¦ä¸ºå¾ç 2461 function isImageUrl(url, extensions) { 2462 const lowerUrl = url.toLowerCase(); 2463 return extensions.some(ext => lowerUrl.endsWith(ext)); 2464 } 2465 2466 // ä»URLæåæä»¶å 2467 function getFileName(url) { 2468 const parts = url.split('/'); 2469 return parts[parts.length - 1] || 'å¾ç'; 2470 } 2471 const imageExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp', '.svg']; 2472 2473 $message.find('a:not(:has(img))').each(function () { 2474 const $link = $(this); 2475 const href = $link.attr('href'); 2476 2477 // æ£æ¥é¾æ¥æ¯å¦ä¸ºå¾ç 2478 if (href && isImageUrl(href, imageExtensions)) { 2479 // å建å¾çå ç´ 2480 const $img = $('<img>', { 2481 'src': href, 2482 'alt': 'å¾çé¢è§: ' + getFileName(href), 2483 'data-jq-auto-added': 'true' 2484 }).css({ 2485 'vertical-align': 'middle', 2486 'border-radius': '3px' 2487 }); 2488 let imagesToLoad = 1; // æ¬æ¬¡å¾ªç¯åªæ·»å 1å¼ å¾ç 2489 // æ·»å å°é¾æ¥æ«å°¾ 2490 $link.append($img); 2491 2492 $img.on('load error', function () { 2493 imagesToLoad--; // ä¸å¼ å¾çå è½½å®æï¼æ 论æåå¤±è´¥ï¼ 2494 2495 // 彿æå¾çé½å¤ç宿¯åæ§è¡æ»å¨ 2496 if (imagesToLoad === 0) { 2497 setTimeout(() => { 2498 scrollToBottom(true); 2499 }, 300); 2500 } 2501 }); 2502 } 2503 }); 2504 } 2505
2506 // 妿æ¯review模å¼ï¼å»¶è¿æ§è¡å¾çå è½½ï¼çå¾ å 容å®å ¨æ¸²æ 2507 if (isReviewMode) { 2508 setTimeout(() => { 2509 loadImages(); 2510 // åªæ¥æ¾å½åæ¶æ¯ç.content-bodyä¸ç.pdb龿¥ 2511 const $pdbLinks = $message.find('.content-body a[href*=".pdb"]'); 2512 if ($pdbLinks.length > 0) { 2513 // 为æ¯ä¸ªPDB龿¥å¤çhref屿§ 2514 $pdbLinks.each(function () { 2515 const $link = $(this); 2516 const originalHref = $link.attr('href'); 2517 2518 // ç§»é¤åå§href屿§ï¼å°å ¶å¼ä¿åå°data-original-hrefä¸ 2519 $link.removeAttr('href'); 2520 $link.attr('data-original-href', originalHref); 2521 }); 2522 } // åªæ¥æ¾å½åæ¶æ¯ç.content-bodyä¸ç.pdb龿¥ 2523 $(document).off('click', 'a[data-original-href]', handleLinkClick); 2524 $(document).on('click', 'a[data-original-href]', handleLinkClick); 2525 $message.find('.thinkHtml').each(function () { 2526 const $thinkHtml = $(this); 2527 let htmlContent = $thinkHtml.html(); 2528 htmlContent = htmlContent.replace(/<end_message>/gi, '<end_message>'); 2529 htmlContent = htmlContent.replace(/<\/end_message>/gi, '</end_message>'); 2530 2531 htmlContent = htmlContent.replace( 2532 /<end_message>([\s\S]*?)<\/end_message>/gi, 2533 function (match, content) { 2534 const questionText = content.trim(); 2535 if (!questionText) return ''; 2536 return `<button class="end-message-btn" data-question="${questionText.replace(/"/g, '"')}">${questionText}</button>`; 2537 } 2538 ); 2539 // æ´æ°å 容 2540 $thinkHtml.html(htmlContent); 2541 2542 }) 2543 2544 }, 500); // å»¶è¿500msçå¾ å 容渲æ 2545 2546 } else { 2547 loadImages(); 2548 } 2549 2550 $message.find('.thinkHtml a[href="@ptglab-live-chat"]').each(function () { 2551 const $link = $(this); 2552 // ç§»é¤ href 屿§ï¼é²æ¢ç¹å»è·³è½¬ 2553 $link.removeAttr('href'); 2554 // æ·»å class 2555 $link.addClass('open-live-chat'); 2556 // ç¡®ä¿ææ¬æ¯ Live Chat 2557 $link.text('Live Chat'); 2558 }); 2559 2560 // å¤ççº¯ææ¬å½¢å¼ç @ptglab-live-chat 2561 $message.find('.thinkHtml').each(function () { 2562 const $thinkHtml = $(this); 2563 let htmlContent = $thinkHtml.html(); 2564 // 1. å å¤çææå å«@ptglab-live-chatçaæ ç¾ï¼å®å ¨ç§»é¤aæ ç¾ 2565 // ä½¿ç¨æ´ç²¾ç¡®çæ£åï¼ç¡®ä¿æè·æ´ä¸ªaæ ç¾å¹¶æ¿æ¢ä¸ºspan 2566 htmlContent = htmlContent.replace( 2567 /<a\s+[^>]*?>([^<]*?)@ptglab-live-chat([^<]*?)<\/a>/gi, 2568 function (match, prefix, suffix) { 2569 // ä¿ç@ååçå 容ï¼ç¨spanå 裹æ´ä¸ªç»æ 2570 return `<span class="open-live-chat">${prefix}Live Chat${suffix}</span>`; 2571 } 2572 ); 2573 2574 // 2. å¤ççº¯ææ¬å½¢å¼ç@ptglab-live-chat 2575 htmlContent = htmlContent.replace( 2576 /([^<>\s])?@ptglab-live-chat([^<>\s])?/g, 2577 function (match, prefix, suffix) { 2578 // ä¿çå¯è½åå¨çååå符ï¼é¿å ç ´åä¸ä¸æ 2579 const pre = prefix || ''; 2580 const suf = suffix || ''; 2581 return `${pre}<span class="open-live-chat">Live Chat</span>${suf}`; 2582 } 2583 ); 2584 2585 // 3. æ¸ çå¯è½æ®ççé误æ ç¾ï¼å¦ç©ºaæ ç¾ï¼ 2586 htmlContent = htmlContent.replace(/<a\s+[^>]*?><\/a>/gi, ''); 2587 // æ´æ°å 容 2588 $thinkHtml.html(htmlContent); 2589 2590 }); 2591 2592 2593 2594 $message.find('.open-live-chat') 2595 .off('click') // ç§»é¤ææå·²ç»å®çclickäºä»¶ 2596 .click(function () { 2597 const data = { 2598 type: 'fromChild', 2599 message: 'msg', 2600 status: 'success' 2601 }; 2602 window.parent.postMessage(data, '*'); 2603 }); 2604 2605 //æååç»å®ä¸ä¸ ç¹å»å±å¼äºä»¶ 2606 $message 2607 .off('click', '.think-header') // ç§»é¤å·²æçäºä»¶å¤ç 2608 .on('click', '.think-header', function (e) { // éæ°ç»å®äºä»¶ 2609 e.stopPropagation(); 2610 const $container = $(this).closest('.segment-group'); 2611 const $contentBody = $container.find('.think-content-body'); 2612 const $toggleIcon = $container.find('.think-toggle-icon'); 2613 $contentBody.slideToggle(300, function () { 2614 $toggleIcon.toggleClass('fa-chevron-down fa-chevron-right'); 2615 }); 2616 2617 }); 2618 2619 //æååç»å®ä¸ä¸ ç¹å»å±å¼äºä»¶ 2620 $message 2621 .off('click', '.think-header') // ç§»é¤å·²æçäºä»¶å¤ç 2622 .on('click', '.think-header', function (e) { // éæ°ç»å®äºä»¶ 2623 e.stopPropagation(); 2624 const $container = $(this).closest('.segment-group'); 2625 const $contentBody = $container.find('.think-content-body'); 2626 const $toggleIcon = $container.find('.think-toggle-icon'); 2627 $contentBody.slideToggle(300, function () { 2628 $toggleIcon.toggleClass('fa-chevron-down fa-chevron-right'); 2629 }); 2630 }); 2631 2632 $message 2633 .off('click', '.tool-header') // ç§»é¤å·²æçäºä»¶å¤ç 2634 .on('click', '.tool-header', function (e) { // éæ°ç»å®äºä»¶ 2635 const $header = $(this); 2636 const $segmentGroup = $header.closest('.segment-group'); 2637 const segmentIndex = $segmentGroup.index(); 2638 const currentMessageId = $segmentGroup.closest('.message').attr('id'); 2639 2640 // è·å对åºçsegmentæ°æ® 2641 const buffer = responseBuffers[currentMessageId]; 2642 if (!buffer || !buffer.segments[segmentIndex]) return; 2643 2644 // 忢æå ç¶æ 2645 const segment = buffer.segments[segmentIndex]; 2646 segment.isCollapsed = !segment.isCollapsed; 2647 2648 // æ´æ°UI 2649 $header.next('.tool-content-body').toggleClass('hidden', segment.isCollapsed); 2650 $header.find('.tool-toggle-icon') 2651 .toggleClass('fa-chevron-down', !segment.isCollapsed) 2652 .toggleClass('fa-chevron-right', segment.isCollapsed); 2653 }); 2654 setTimeout(() => { 2655 scrollToBottom(true); 2656 }, 300); 2657 } 2658 2659 2660 // ãæç»ä¼åçãæ´æ°AIæ¶æ¯ï¼æ¯æä» contenté空ç¬ç«å±ç¤ºï¼ä¿çææåæåè½ + é»è®¤å±å¼think-
2660header/think-content-body 2661 function updateBotMessage(messageId, chunks) { 2662 let buffer = responseBuffers[messageId]; 2663 if (!buffer) return; 2664 2665 // ========== å ³é®ä¿®å¤1ï¼æååå§åDOMæ ¸å¿å®¹å¨ï¼ä¸ä¾èµä»»ä½å ¶ä»chunkç±»åï¼ ========== 2666 const $message = $(`#${messageId}`); 2667 if ($message.length) { 2668 // æ£æ¥æ¯å¦å·²åå¨å¤´å容å¨ï¼é¿å éå¤å建 2669 let $avatarContainer = $message.find('.avatar-container, .w-8.h-8.rounded-full.overflow-hidden'); 2670 if (!$avatarContainer.length) { 2671 // åªå¨æ²¡æå¤´å容卿¶æå建 2672 $avatarContainer = $(` 2673 <div class="avatar-container" style="width: 32px; height: 32px; border-radius: 50%; overflow: hidden; flex-shrink-0; margin-right: 12px;"> 2674 <img src="/img/Able-Profile-Picture.jpg" alt="AI婿头å" class="w-full h-full object-cover"> 2675 </div> 2676 `); 2677 } 2678 2679 // å 容容å¨ï¼ç¡®ä¿å¿ åå¨ï¼é»è®¤éèThinkingå¨ç»ï¼ 2680 let $thinkingWrapper = $message.find('.bg-white.rounded-lg.shadow-md.border-l-4.border-primary'); 2681 if (!$thinkingWrapper.length) { 2682 $thinkingWrapper = $(` 2683 <div class="mobilePadding bg-white rounded-lg shadow-md p-4 max-w-[90%] sm:max-w-[80%] md:max-w-[75%] border-l-4 border-primary" style="width:100%;overflow:auto;min-height: 10px;position: relative;padding-bottom: 40px;"> 2684 <div class="prose max-w-none markdown-preview" style="width:100%;"> 2685 <div class="thinkHtml markdown-body"></div> 2686 </div> 2687 <div class="showThinkinglogo hidden" style="position: absolute;bottom: 10px;left: 70px;transform: translateX(-50%);"> 2688 <span class="thinking-text"><span>T</span><span>h</span><span>i</span><span>n</span><span>k</span><span>i</span><span>n</span><span>g</span></span> 2689 <span class="dot"></span><span class="dot"></span><span class="dot"></span> 2690 </div> 2691 </div> 2692 `); 2693 } 2694 2695 // åªå¨éè¦æ¶æå»ºææ´æ°DOMç»æï¼å¤´å+å 容容å¨ï¼ 2696 if (!$message.find('.avatar-container, .w-8.h-8.rounded-full.overflow-hidden').length || !$thinkingWrapper.length) { 2697 $message.empty().css({ display: 'flex', 'align-items': 'flex-start' }); 2698 $message.append($avatarContainer).append($thinkingWrapper); 2699 } 2700 } 2701 2702 // ========== ç¼ååå§åï¼åæé»è¾ä¿çï¼ ========== 2703 if (!Array.isArray(buffer.taskSegments)) buffer.taskSegments = []; 2704 if (!Array.isArray(buffer.nonContentGroups)) { 2705 buffer.nonContentGroups = []; // { id, segments, groupContent, isCollapsed, isActive } 2706 } 2707 if (!Array.isArray(buffer.segments)) buffer.segments = []; // ãæ°å¢ãåå§åsegments 2708 if (!Array.isArray(buffer.renderOrder)) buffer.renderOrder = []; // ãæ°å¢ãåå§årenderOrder 2709 if (buffer.lastSegmentType === undefined) buffer.lastSegmentType = ''; 2710 if (buffer.toolContentBuffer === undefined) buffer.toolContentBuffer = ''; 2711 if (buffer.isLoading === undefined) buffer.isLoading = false; 2712 if (buffer.hasReviewed === undefined) buffer.hasReviewed = false; 2713 if (buffer.afterTaskDoneNoValidContent === undefined) buffer.afterTaskDoneNoValidContent = false; 2714 2715 2716 // ========== å ³é®ä¿®æ¹1ï¼å®ä¹éåºæ å¿ + æ¿æ¢forEach为for循ç¯ï¼æ¯æå¼ºå¶ä¸æï¼ ========== 2717 let shouldExit = false; // æ è®°æ¯å¦éè¦éåºæ´ä¸ªå½æ° 2718 const chunkList = Array.isArray(chunks) ? chunks : [chunks]; 2719
2720 // ç¨forå¾ªç¯æ¿ä»£forEachï¼æ¯æbreak䏿 2721 for (let i = 0; i < chunkList.length; i++) { 2722 const chunk = chunkList[i]; 2723 if (!chunk || shouldExit) break; // å·²æ è®°éåºåç´æ¥ä¸æå¾ªç¯ 2724 2725 switch (chunk.type) { 2726 case 'review': 2727 buffer.hasReviewed = true; 2728 if (!auditLoadingStates[messageId]) { 2729 const $thinkContainer = $(`#${messageId}`).find('.markdown-preview'); 2730 if ($thinkContainer.length > 0) { 2731 showAuditLoading(messageId); 2732 // showContentAuditLoading(messageId); 2733 } 2734 } 2735 updateSendButtonState(true); 2736 executeDoneLogic(messageId, true); 2737 isShowReview = "Reviewing" 2738 break; // æ¿æ¢return为breakï¼ä» ç»æ¢å½åcase 2739 case 'retract': 2740 isShowReview = "Reviewing" 2741 updateSendButtonState(false); 2742 hideAuditLoading(messageId); 2743 // hideContentAuditLoading(messageId); // <-- æ°å¢ 2744 2745 if (buffer.hasReviewed) { 2746 // --- å®¡æ ¸åæ¤åï¼åªæ¸ é¤å 容ï¼contentï¼ï¼ä¿çä¸ä¸æ --- 2747 // ... (è¿é¨åé»è¾ä¸å) 2748 buffer.toolContentBuffer = ''; 2749 buffer.segments.forEach(seg => { 2750 if (seg.type === 'reasoning') seg.content = ''; 2751 }); 2752 buffer.nonContentGroups.forEach(group => { 2753 group.groupContent = ''; 2754 }); 2755 buffer.isLoading = false; 2756 buffer.lastSegmentType = 'retracted_after_review'; 2757 } else { 2758 // --- å®¡æ ¸åæ¤åï¼éç½®ææç¼ååDOM --- 2759 2760 // 1. éç½®æææ°æ®ç¼å 2761 buffer.taskSegments = []; 2762 buffer.nonContentGroups = []; 2763 buffer.segments = []; 2764 buffer.renderOrder = []; // ãæ°å¢ãæ¸ é¤æ¸²æé¡ºåº 2765 buffer.toolContentBuffer = ''; 2766 buffer.lastSegmentType = ''; 2767 buffer.afterTaskDoneNoValidContent = false; // ãæ°å¢ãéç½®æ è®° 2768 buffer.isLoading = false; 2769 2770 // 2. ãä¿®æ¹ãéç½®DOMç»æï¼ä½ä¿çç¶ææç¤ºå¨ 2771 const $message = $(`#${messageId}`); 2772 if ($message.length) { 2773 // --- å¼å§ä¿®æ¹ --- 2774 2775 // æ¥éª¤1: ä¿åå·²æçç¶ææç¤ºå¨ 2776 let $statusIndicator = null; 2777 const $existingIndicator = $message.find('.status-indicator'); 2778 if ($existingIndicator.length > 0) { 2779 $statusIndicator = $existingIndicator.detach(); // 使ç¨detach()ä¿çå ç´ ä»¥ä¾¿åç»æ¢å¤ 2780 } 2781 2782 // æ£æ¥æ¯å¦å·²åå¨å¤´å容å¨ï¼é¿å éå¤å建 2783 let $avatarContainer = $message.find('.avatar-container, .w-8.h-8.rounded-full.overflow-hidden'); 2784 if (!$avatarContainer.length) { 2785 $avatarContainer = $(` 2786 <div class="avatar-container" style="width: 32px; height: 32px; border-radius: 50%; overflow: hidden; flex-shrink: 0; margin-right: 12px;"> 2787 <img src="/img/Able-Profile-Picture.jpg" alt="AI婿头å" class="w-full h-full object-cover"> 2788 </div> 2789 `); 2790 } 2791 const $newContent = $(` 2792 <div class="mobilePadding bg-white rounded-lg shadow-md p-4 max-w-[90%] sm:max-w-[80%] md:max-w-[75%] border-l-4 border-primary" style="width:100%;overflow:auto;min-height: 120px;position: relative;padding-bottom: 40px;"> 2793 <div class="prose max-w-none markdown-preview" style="width:100%;"><div class="thinkHtml markdown-body"></div></div> 2794 <div class="showThinkinglogo" style="position: absolute;bottom: 10px;left: 70px;transform: translateX(-50%);"> 2795 <sp
2795an class="thinking-text"><span>T</span><span>h</span><span>i</span><span>n</span><span>k</span><span>i</span><span>n</span><span>g</span></span> 2796 <span class="dot"></span><span class="dot"></span><span class="dot"></span> 2797 </div> 2798 </div> 2799 `); 2800 $message.empty().css({ display: 'flex', 'align-items': 'flex-start' }); 2801 2802 // æ¥éª¤2: å æ·»å ç¶ææç¤ºå¨ï¼å¦æåå¨ï¼ï¼åæ·»å å ¶ä»å 容 2803 if ($statusIndicator) { 2804 $message.append($statusIndicator); 2805 } 2806 2807 $message.append($avatarContainer).append($newContent); 2808 $('.load-more-indicator').remove(); 2809 2810 // --- ä¿®æ¹ç»æ --- 2811 } 2812 } 2813 2814 buffer.hasReviewed = false; 2815 break; // æ¿æ¢return为breakï¼ä» ç»æ¢å½åcase 2816 case 'success': 2817 isShowReview = "Reviewed" 2818 updateSendButtonState(false); 2819 hideAuditLoading(messageId); 2820 // hideContentAuditLoading(messageId); // <-- æ°å¢ 2821 shouldExit = true; // æ è®°éè¦éåºæ´ä¸ªå½æ° 2822 break; // 䏿switchï¼ç¶åfor循ç¯ä¼å shouldExit=true䏿 2823 default: 2824 // å ¶ä»ç±»åçchunkç»§ç»å¤ç 2825 break; 2826 } 2827 2828 // ========== éswitchçchunkå¤çé»è¾ï¼åæé»è¾ä¿çï¼ ========== 2829 if (shouldExit) break; // å·²æ è®°éåºåä¸æå¾ªç¯ 2830 2831 const isContentChunk = chunk.type === 'content'; 2832 const isNonContentChunk = !isContentChunk && ['task', 'task_done', 'reasoning_content', 'tool'].includes(chunk.type); 2833 2834 // contentåºç°æ¶ãä¸åæå ãä¸ä¸ä¸ªæ´»è·écontentç»ï¼ä¿®æ¹ç¹1ï¼ 2835 if (isContentChunk) { 2836 // å ¼å®¹çï¼æ¿ä»£ findLastï¼å®ç°âæ¾æåä¸ä¸ªisActive为trueçgroupâ 2837 // 1. å æ ¡éªæ°ç»æææ§ï¼é¿å éæ°ç»è°ç¨æ¹æ³æ¥é 2838 // 2. slice() å¤å¶æ°ç» â reverse() å转 â find() æ¾ç¬¬ä¸ä¸ªï¼çä»·äºåæ°ç»çæåä¸ä¸ª 2839 const lastActiveGroup = Array.isArray(buffer.nonContentGroups) 2840 ? buffer.nonContentGroups.slice().reverse().find(group => group.isActive) 2841 : null; 2842 2843 if (lastActiveGroup) { 2844 // æ£æ¥è¯¥ç»æ¯å¦æ task 2845 const hasTaskInGroup = lastActiveGroup.segments.some(seg => seg.type === 'task'); 2846 2847 // æ£æ¥contentæ¯å¦ä¸ºææå 容ï¼ä¸æ¯ç©ºç½åç¬¦ï¼ 2848 const hasValidContent = chunk.content && chunk.content.trim() !== ''; 2849 2850 // 妿éå°ææcontentï¼éç½®æ è®° 2851 if (hasValidContent) { 2852 buffer.afterTaskDoneNoValidContent = false; 2853 2854 // 妿ä¹åæ¯task_doneåçç¶æï¼ç°å¨éå°ææcontentï¼åå建æ°ç» 2855 if (buffer.lastSegmentType === 'task_done' || buffer.lastSegmentType === 'reasoning_content') { 2856 // æå å½åæ´»è·ç» 2857 if (hasTaskInGroup) { 2858 lastActiveGroup.isCollapsed = true; 2859 } 2860 // å°è¯¥ç»æ è®°ä¸ºéæ´»è·ï¼åç»ä¼å建æ°ç» 2861 lastActiveGroup.isActive = false; 2862 } 2863 } else { 2864 // 空contentï¼ä¸æ¹åafterTaskDoneNoValidContentç¶æ 2865 // 妿ä¸å¨task_doneåçé¶æ®µï¼æåé»è¾å¤ç 2866 if (!buffer.afterTaskDoneNoValidContent) { 2867 if (hasTaskInGroup) { 2868 lastActiveGroup.isCollapsed = true; 2869 } 2870 lastActiveGroup.isActive = false; 2871 } 2872 } 2873 // 妿æ¯task_doneåç空contentï¼afterTaskDoneNoValidContent=trueï¼ï¼ä¸åä»»ä½å¤çï¼ç»§ç»ä½¿ç¨å½åæ´»è·ç» 2874 } 2875 } 2876 2877 // å¤çreasoning_content 2878 if (chunk.type === 'reasoning_content') { 2879 const $message = $(`#${messageId}`); 2880 if (!$message.length) continue; // ç¨continueæ¿ä»£returnï¼ä» è·³è¿å½åè¿ä»£ 2881 2882 // é建DOMï¼ä¿çThinkingå¨ç»ï¼ 2883 // æ£æ¥æ¯å¦å·²åå¨å¤´å容å¨ï¼é¿å éå¤å建 2884 let $avatarContainer = $message.find('.avatar-container, .w-8.h-8.rounded-full.overflow-hidden'); 2885 if (!$avatarContainer.length) { 2886 $avatarContainer = $(` 2887 <div class="avatar-container" style="width: 32px; height: 32px; border-radius: 50%; overflow: hidden; flex-shrink: 0; margin-right: 12px;"> 2888 <img src="/img/Able-Profile-Picture.jpg" alt="AI婿头å" class="w-full h-full object-cover"> 2889 </div> 2890 `); 2891 } 2892 const $thinkingWrapper = $(` 2893 <div class="mobilePadding bg-white rounded-lg shadow-md p-4 max-w-[90%] sm:max-w-[80%] md:max-w-[75%] border-l-4 border-primary" style="width:100%;overflow:auto;min-height: 10px;position: relative;padding-bottom: 40px;"> 2894 <div class="prose max-w-none markdown-preview" style="width:100%;"><div class="thinkHtml markdown-body"></div></div> 2895 <div class="showThinkinglogo" style="position: absolute;bottom: 10px;left: 70px;transform: translateX(-50%);">
2896 <span class="thinking-text"><span>T</span><span>h</span><span>i</span><span>n</span><span>k</span><span>i</span><span>n</span><span>g</span></span> 2897 <span class="dot"></span><span class="dot"></span><span class="dot"></span> 2898 </div> 2899 </div> 2900 `); 2901 $message.empty().css({ display: 'flex', 'align-items': 'flex-start' }); 2902 $message.append($avatarContainer).append($thinkingWrapper); 2903 2904 // ä¿ç3Då®¹å¨ 2905 const $thinkHtml = $thinkingWrapper.find('.thinkHtml'); 2906 const pdbContainersHtml = $thinkHtml.find('.pdb-3d-container').map(() => this.outerHTML).get(); 2907 $thinkHtml.empty(); 2908 if (pdbContainersHtml.length > 0) $thinkHtml.append(pdbContainersHtml.join('')); 2909 2910 // 空å å®¹ç»æ¢ 2911 if (!chunk.content || chunk.content.trim() === '') { 2912 buffer.lastSegmentType = 'reasoning_content'; 2913 buffer.isLoading = false; 2914 continue; // ç¨continueæ¿ä»£returnï¼ä» è·³è¿å½åè¿ä»£ 2915 } 2916 2917 // åå§å/è¿½å æèå 容 2918 if (buffer.segments.length === 0) { 2919 buffer.segments[0] = { type: 'reasoning', think: chunk.content, content: '', isCollapsed: false }; 2920 // è®°å½æ¸²æé¡ºåº 2921 buffer.renderOrder.push({ type: 'reasoning', index: 0 }); 2922 } else { 2923 const reasoningSeg = buffer.segments.find(seg => seg.type === 'reasoning') || buffer.segments[0]; 2924 reasoningSeg.think += chunk.content; 2925 } 2926 2927 buffer.lastSegmentType = 'reasoning_content'; 2928 buffer.isLoading = false; 2929 } 2930 // å¤çtask 2931 else if (chunk.type === 'task') { 2932 $(`#${messageId}`).find('.showThinkinglogo').hide(); 2933 const taskParts = chunk.content.split('|').map(part => part.trim()); 2934 const taskContent = taskParts[1] || chunk.content; 2935 2936 const newTaskSeg = { 2937 type: 'task', 2938 originalContent: chunk.content, 2939 taskContent: taskContent, 2940 isLoading: true, 2941 isCompleted: false 2942 }; 2943 2944 // å建/è·åæ´»è·écontentç» 2945 let activeGroup = buffer.nonContentGroups.find(group => group.isActive); 2946 if (!activeGroup) { 2947 activeGroup = { 2948 id: `non-content-group-${Date.now()}-${buffer.nonContentGroups.length}`, 2949 segments: [], 2950 groupContent: '', 2951 isCollapsed: false, 2952 isActive: true 2953 }; 2954 buffer.nonContentGroups.push(activeGroup); 2955 // è®°å½æ¸²æé¡ºåº 2956 buffer.renderOrder.push({ type: 'group', index: buffer.nonContentGroups.length - 1 }); 2957 } 2958 activeGroup.segments.push(newTaskSeg); 2959 buffer.taskSegments.push(newTaskSeg); 2960 // ä»»å¡åºç°å³æ è®°å½åç»å¾ 渲æï¼ä¸çä¸ä¸ä¸ªä»»å¡ï¼ 2961 markGroupForFormat(buffer, activeGroup.id, 'task'); 2962 2963 buffer.lastSegmentType = 'task'; 2964 buffer.afterTaskDoneNoValidContent = false; // éç½®æ è®° 2965 buffer.isLoading = false; 2966 2967 2968 // 妿å½åæ´»è·ç»çä»»å¡å表æ¯éèçï¼åå±å¼å® 2969 const $message = $(`#${messageId}`); 2970 // æ¾å°å½åæ´»è·ç»å¯¹åºç unified-task-group 2971 const $activeTaskGroup = $message.find(`.unified-task-group[data-group-id="${activeGroup.id}"]`); 2972 if ($activeTaskGroup.length > 0) { 2973 const $taskContentBody = $activeTaskGroup.find('.think-content-body'); 2974 if ($taskContentBody.css('display') === 'none') { 2975 $taskContentBody.show(); 2976 // åæ¶æ´æ°åæ¢å¾æ 2977 const $toggleIcon = $activeTaskGroup.find('.think-toggle-icon'); 2978 $toggleIcon.removeClass('fa-chevron-right').addClass('fa-chevron-down'); 2979 // æ´æ°ç»çæå ç¶æ 2980 activeGroup.isCollapsed = false; 2981 } 2982 } 2983 } 2984 // å¤çtask_done 2985 else if (chunk.type === 'task_done') { 2986 // æ´æ°taskç¶æ
2987 buffer.taskSegments.forEach(seg => { 2988 if (seg.originalContent === chunk.content) { 2989 seg.isLoading = false; 2990 seg.isCompleted = true; 2991 } 2992 }); 2993 buffer.nonContentGroups.forEach(group => { 2994 group.segments.forEach(seg => { 2995 if (seg.type === 'task' && seg.originalContent === chunk.content) { 2996 seg.isLoading = false; 2997 seg.isCompleted = true; 2998 markGroupForFormat(buffer, group.id, 'task_done'); 2999 } 3000 }); 3001 }); 3002 3003 buffer.lastSegmentType = 'task_done'; 3004 buffer.afterTaskDoneNoValidContent = true; // æ è®°task_doneåè¿æ²¡æéå°ææcontent 3005 buffer.isLoading = false; 3006 } 3007 // ========== å ³é®ä¿®å¤2ï¼contentå¤çï¼éç©ºå¿ åå¨ï¼ ========== 3008 else if (chunk.type === 'content') { 3009 $(`#${messageId}`).find('.showThinkinglogo').hide(); 3010 const hasToolTag = chunk.content.includes('<tool_call>') || chunk.content.includes('</tool_call>'); 3011 const isToolRelated = hasToolTag || buffer.toolContentBuffer; 3012 const shouldCollapse = buffer.lastSegmentType === 'reasoning_content'; 3013 3014 // å¤çå·¥å ·ç±»åcontent 3015 if (isToolRelated) { 3016 buffer.toolContentBuffer += chunk.content; 3017 // 循ç¯åå²å®æ´å·¥å ·è°ç¨ 3018 while (true) { 3019 const closeIdx = buffer.toolContentBuffer.indexOf('</tool_call>'); 3020 const openIdx = buffer.toolContentBuffer.lastIndexOf('<tool_call>', closeIdx); 3021 if (openIdx === -1 || closeIdx === -1) break; 3022 3023 const toolContent = buffer.toolContentBuffer.slice(openIdx, closeIdx + 13); 3024 // ä¿®æ¹ç¹2ï¼å·¥å ·æ®µé»è®¤å±å¼ï¼åé»è¾ï¼isCollapsed: true â falseï¼ 3025 buffer.segments.push({ type: 'tool', toolContent: toolContent, isCollapsed: false }); 3026 buffer.renderOrder.push({ type: 'tool', index: buffer.segments.length - 1 }); 3027 buffer.toolContentBuffer = buffer.toolContentBuffer.slice(closeIdx + 13); 3028 } 3029 3030 buffer.lastSegmentType = buffer.toolContentBuffer ? 'tool_content_pending' : 'tool_content'; 3031 // ãä¸åæå ãæè段ï¼ä¿®æ¹ç¹3ï¼ 3032 if (shouldCollapse) { 3033 const reasoningSeg = buffer.segments.find(seg => seg.type === 'reasoning'); 3034 if (reasoningSeg) reasoningSeg.isCollapsed = false; // åé»è¾ï¼true â false 3035 } 3036 continue; // ç¨continueæ¿ä»£returnï¼ä» è·³è¿å½åè¿ä»£ 3037 } 3038 3039 // å¤çæ®écontentï¼éç©ºå¿ åå¨ï¼ 3040 // å ¼å®¹çï¼æ¿æ¢findLastï¼ä¿çå夿é»è¾ï¼ä¸ä¸ä¿®æ¹åæ°ç» 3041 const targetGroup = Array.isArray(buffer.nonContentGroups) 3042 ? buffer.nonContentGroups.slice().reverse().find(group => 3043 // å®å ¨å¤ç¨åæç夿æ¡ä»¶ï¼é»è¾ä¸å 3044 group.isActive || (group.isActive === false && group.segments.length > 0) 3045 ) 3046 : null; // éæ°ç»æ¶è¿ånullï¼é¿å åç»é»è¾æ¥é 3047 3048 if (targetGroup) { 3049 targetGroup.groupContent += chunk.content; 3050 // æ£æå« markdown 代ç åæ¶åæ è®°ï¼mermaid å¨ groupContent éï¼ 3051 if (chunk.content.includes('```')) { 3052 markGroupForFormat(buffer, targetGroup.id, 'content-markdown'); 3053 } 3054 } else { 3055 // æ 对åºç»ï¼ä¸»å¨å建reasoning段åå¨ 3056 let reasoningSeg = buffer.segments.find(seg => seg.type === 'reasoning'); 3057 if (!reasoningSeg) { 3058 reasoningSeg = { type: 'reasoning', think: '',
3058content: '', isCollapsed: false }; 3059 buffer.segments.push(reasoningSeg); 3060 // è®°å½æ¸²æé¡ºåº 3061 buffer.renderOrder.push({ type: 'reasoning', index: buffer.segments.length - 1 }); 3062 } 3063 // ä¿®æ¹ç¹4ï¼æ®écontentæ¶ä¸åæå æè段ï¼åé»è¾ï¼true â falseï¼ 3064 if (shouldCollapse) reasoningSeg.isCollapsed = false; 3065 reasoningSeg.content += chunk.content; 3066 } 3067 3068 buffer.lastSegmentType = 'content'; 3069 } 3070 } 3071 3072 // ========== å ³é®ä¿®æ¹2ï¼å¤æéåºæ å¿ï¼è¥ä¸ºtrueåç´æ¥ç»æ¢å½æ° ========== 3073 if (shouldExit) { 3074 return; // ç»æ¢æ´ä¸ªupdateBotMessage彿°ï¼ä¸åæ§è¡åç»ä»£ç 3075 } 3076 3077 // ========== å ³é®ä¿®å¤3ï¼DOM渲æï¼contentéç©ºå¿ å±ç¤ºï¼ ========== 3078 const $messageDom = $(`#${messageId}`); 3079 const $thinkingWrapperDom = $messageDom.find('.bg-white.rounded-lg.shadow-md.border-l-4.border-primary'); 3080 const $thinkContainerDom = $thinkingWrapperDom.find('.thinkHtml'); 3081 if (!$messageDom.length || !$thinkingWrapperDom.length || !$thinkContainerDom.length) return; 3082 3083 // ä¿ç3Då®¹å¨ 3084 const pdbContainersHtml = $thinkContainerDom.find('.pdb-3d-container').map(() => this.outerHTML).get(); 3085 $thinkContainerDom.empty();
3086 const $combinedContainer = $('<div class="segments-container"></div>'); 3087 3088 3089 // ========== æç § renderOrder çé¡ºåºæ¸²æææå 容 ========== 3090 buffer.renderOrder.forEach(item => { 3091 if (item.type === 'group') { 3092 // 渲æ nonContentGroup 3093 const group = buffer.nonContentGroups[item.index]; 3094 if (!group) return; 3095 3096 // æ¶éå½åç»çæætask 3097 let groupTasksHtml = ''; 3098 let hasTaskInGroup = false; 3099 3100 group.segments.forEach((taskSeg) => { 3101 if (taskSeg.type !== 'task') return; 3102 hasTaskInGroup = true; 3103 const statusIcon = taskSeg.isLoading 3104 ? `<i class="fa fa-spinner fa-spin ml-2" style="color: dimgray;"></i>` 3105 : (taskSeg.isCompleted ? `<i class="fa fa-check ml-2" style="color: #28a745;"></i>` : ''); 3106 3107 groupTasksHtml += ` 3108 <div class="task-segment mb-2"> 3109 <div class="task-content-body aifontSize" style="display:flex; align-items:center; background-color:#f8f9fa; border-radius:8px; padding:12px 16px; box-shadow:0 1px 3px rgba(0,0,0,0.05);color:#333;"> 3110 <div class="left" style="flex-grow: 1; line-height: 1.5;"> 3111 ${mdParser.render(taskSeg.taskContent || '')} 3112 </div> 3113 <div class="right" style="margin-left: 12px;"> 3114 ${statusIcon} 3115 </div> 3116 </div> 3117 </div> 3118 `; 3119 }); 3120 3121 // 妿å½åç»ætaskï¼æ¸²æä¸ä¸ªç¬ç«çæå æ¡ 3122 if (hasTaskInGroup) { 3123 const groupCollapsed = group.isCollapsed !== undefined ? group.isCollapsed : false; 3124 3125 const unifiedTaskHtml = ` 3126 <div class="segment-group unified-task-group" data-type="unified-task" data-group-id="${group.id}" data-group-index="${item.index}"> 3127 <div class="think-header flex items-center cursor-pointer text-sm text-gray-700"> 3128 <i class="fa ${groupCollapsed ? 'fa-chevron-right' : 'fa-chevron-down'} think-toggle-icon mr-1"></i> 3129 <span style="font-weight:bold;">${language === 'Chinese' ? 'ä»»å¡å表' : 'Task List'}</span> 3130 </div> 3131 <div class="think-content-body text-gray-600 text-sm aifontSize" style="display: ${groupCollapsed ? 'none' : 'block'};"> 3132 ${groupTasksHtml} 3133 </div> 3134 </div> 3135 `; 3136 $combinedContainer.append(unifiedTaskHtml); 3137 } 3138 3139 // 渲æå½åç»ç groupContentï¼å¦ææï¼ 3140 const groupContentHtml = group.groupContent && group.groupContent.trim() !== '' ? ` 3141 <div class="content-body pl-3 mt-1"> 3142 <div class="content-rendered text-gray-800 aifontSize" style=""> 3143 ${mdParser.render(group.groupContent)} 3144 </div> 3145 </div> 3146 ` : ''; 3147 3148 if (groupContentHtml) { 3149 const contentGroupHtml = ` 3150 <div class="segment-group" data-group-id="${group.id}" data-group-index="${item.index}" data-type="content"> 3151 ${groupContentHtml} 3152 </div> 3153 `; 3154 $combinedContainer.append(contentGroupHtml); 3155 } 3156 } else if (item.type === 'reasoning') { 3157 // 渲æ reasoning segment 3158 const segment = buffer.segments[item.index]; 3159 if (!segment) return; 3160 3161 const isReasoning = (segment.type === 'reasoning' || !segment.type); 3162 const hasThink = segment.think && segment.think.trim() !== ''; 3163 const hasContent = segment.content && segment.content.trim() !== ''; 3164 if (!isReasoning || (!hasThink && !hasContent)) return; 3165 3166 const thinkHtml = ` 3167 <div class="segment-group" data-segment-index="${item.index}" data-type="reasoning"> 3168 ${hasThink ? ` 3169 <div class="think-header hideThinking flex items-center cursor-pointer text-sm text-gray-700 mb-1" data-index="${item.index}"> 3170 <i class="fa ${segment.isCollapsed ? 'fa-chevron-right' : 'fa-chevron-down'} think-toggle-icon mr-1"></i> 3171 <sp
3171an style="font-weight:bold;">${language === 'Chinese' ? 'æèè¿ç¨' : 'Thinking'}</span> 3172 </div> 3173 <div class="think-content-body ${segment.isCollapsed ? 'hidden' : ''} py-1 text-gray-600 text-sm aifontSize" style=""> 3174 ${buildThinkContent(segment.think || '')} 3175 </div> 3176 ` : ''} 3177 ${hasContent ? ` 3178 <div class="content-body pl-3 mt-1"> 3179 ${mdParser.render(segment.content)} 3180 </div> 3181 ` : ''} 3182 </div> 3183 `; 3184 $combinedContainer.append(thinkHtml); 3185 } else if (item.type === 'tool') { 3186 // 渲æ tool segment 3187 const segment = buffer.segments[item.index]; 3188 if (!segment || segment.type !== 'tool') return; 3189 3190 const toolHtml = ` 3191 <div class="segment-group"> 3192 <div class="tool-header flex items-center cursor-pointer text-sm text-black-600 mb-1"> 3193 <i class="fa ${segment.isCollapsed ? 'fa-chevron-right' : 'fa-chevron-down'} tool-toggle-icon mr-1"></i> 3194 <span style="font-weight:bold;">${language === 'Chinese' ? 'å·¥å ·è°ç¨' : 'Tool Call'}</span> 3195 </div> 3196 <div class="tool-content-body ${segment.isCollapsed ? 'hidden' : ''} pl-3 py-1 aifontSize" style="color:gray;"> 3197 ${mdParser.render(segment.toolContent || '')} 3198 </div> 3199 </div> 3200 `; 3201 $combinedContainer.append(toolHtml); 3202 } 3203 }); 3204 3205 3206 3207 3208 // <div class="status-indicator pl-3 py-1 text-sm" style="background-color: #f8f9fa; border-radius: 4px; margin-top: 4px; margin-bottom: 4px;"> 3209 // <span class="status-icon mr-1"></span> 3210 // <span class="status-text"></span> 3211 // </div> 3212 // ========== æ°å¢ï¼å°ç¶ææç¤ºå¨æå ¥å°æåä¸ä¸ª .think-content-body çæ«å°¾ ========== 3213 const statusIndicatorHtml = ` 3214 <div class="status-indicator pl-3 py-1 text-sm" style="background-color: #f8f9fa; border-radius: 4px; margin-top: 4px; margin-bottom: 4px; display: none;text-align:center"> 3215 <span class="status-text"></span> 3216 <span class="status-icon ml-1"></span> 3217 </div> 3218 `; 3219 // æ¾å° $combinedContainer 䏿åä¸ä¸ª .think-content-body å ç´ ï¼å¹¶å°ç¶ææç¤ºå¨è¿½å å°å ¶å 鍿«å°¾ 3220 $combinedContainer.find('.think-content-body').last().append(statusIndicatorHtml); 3221 // æå 交äºäºä»¶ï¼åæé»è¾ä¿çï¼ 3222 $combinedContainer.on('click', '.think-header', function (e) { 3223 e.stopPropagation(); 3224 const $header = $(this); 3225 const $group = $header.closest('.segment-group'); 3226 const type = $group.data('type'); 3227 const idx = $header.data('index'); 3228 const groupIdx = $header.data('group-index'); 3229 3230 let targetItem, targetContent; 3231 if (type === 'reasoning') { 3232 targetItem = buffer.segments[idx]; 3233 targetContent = $group.find('.think-content-body'); 3234 } else if (type === 'task') { 3235 targetItem = buffer.nonContentGroups[groupIdx]; 3236 targetContent = $group.find('.think-content-body'); 3237 } else if (type === 'unified-task') { 3238 // å¤çç»ä¸task容å¨çæå 3239 const groupId = $group.data('group-id'); 3240 const groupIdx = $group.data('group-index'); 3241 targetContent = $group.find('.think-content-body'); 3242
3243 // è·å对åºç group 对象 3244 const targetGroup = buffer.nonContentGroups[groupIdx]; 3245 if (!targetGroup) return; 3246 3247 // 忢æå ç¶æ 3248 targetGroup.isCollapsed = !targetGroup.isCollapsed; 3249 3250 if (targetGroup.isCollapsed) { 3251 targetContent.hide(); 3252 $header.find('.think-toggle-icon') 3253 .removeClass('fa-chevron-down') 3254 .addClass('fa-chevron-right'); 3255 } else { 3256 targetContent.show(); 3257 $header.find('.think-toggle-icon') 3258 .removeClass('fa-chevron-right') 3259 .addClass('fa-chevron-down'); 3260 } 3261 return; 3262 } 3263 if (!targetItem || !targetContent) return; 3264 3265 targetItem.isCollapsed = !targetItem.isCollapsed; 3266 targetContent.toggleClass('hidden', targetItem.isCollapsed); 3267 $header.find('.think-toggle-icon') 3268 .removeClass('fa-chevron-down fa-chevron-right') 3269 .addClass(targetItem.isCollapsed ? 'fa-chevron-right' : 'fa-chevron-down'); 3270 }); 3271 3272 $combinedContainer.on('click', '.tool-header', function (e) { 3273 e.stopPropagation(); 3274 const $header = $(this); 3275 const $group = $header.closest('.segment-group'); 3276 const $content = $group.find('.tool-content-body'); 3277 const isCollapsed = $content.hasClass('hidden'); 3278 3279 $content.toggleClass('hidden', !isCollapsed); 3280 $header.find('.tool-toggle-icon') 3281 .removeClass('fa-chevron-down fa-chevron-right') 3282 .addClass(!isCollapsed ? 'fa-chevron-right' : 'fa-chevron-down'); 3283 }); 3284 3285 // æè½½å 容+æ¢å¤3Då®¹å¨ 3286 $thinkContainerDom.append($combinedContainer); 3287 if (pdbContainersHtml.length > 0) { 3288 $thinkContainerDom.append(pdbContainersHtml.join('')); 3289 $messageDom.data('has-3d-containers', true); 3290 3291 // éæ°æ¸²æ3D模å 3292 setTimeout(() => { 3293 $thinkContainerDom.find('.pdb-3d-container').each(function () { 3294 const $container = $(this); 3295 const containerId = $container.attr('id'); 3296 const $molContainer = $container.find('.mol-container'); 3297 if ($molContainer.length && (!$molContainer[0].viewer || $molContainer.is(':empty'))) { 3298 $molContainer.html(` 3299 <div class="text-center text-white p-4"> 3300 <i class="fa fa-spinner fa-spin fa-2x mb-2"></i> 3301 <p>loading 3D model...</p> 3302 </div> 3303 `); 3304 const fileName = extractFileNameFromContainerId(containerId); 3305 if (fileName) loadPdbAndRender3D({ fileName: fileName }, messageId, null); 3306 } 3307 }); 3308 }, 100); 3309 } 3310 3311 // å è½½æç¤ºå¨æ§å¶ 3312 if (buffer.isLoading) { 3313 const $lastGroup = $combinedContainer.find('.segment-group').last(); 3314 if (!$lastGroup.find('.load-more-indicator').length) { 3315 $lastGroup.append(` 3316 <div class="load-more-indicator mt-1 block text-center" style="color: dimgray;"> 3317 <i class="fa fa-spinner fa-spin"></i> 3318 </div> 3319 `); 3320 } 3321 } else { 3322 $('.load-more-indicator').remove(); 3323 } 3324 3325 // 龿¥ _blank 宿¶å ï¼mermaid å¨ task åºç° / task_done / å« ``` ç content æ¶æ¸²æ 3326 $combinedContainer.find('a:not([target="_blank"])').attr('target', '_blank'); 3327 reinjectCachedMermaidInContainer(messageId, buffer, $combinedContainer); 3328 formatPendingGroups(messageId, buffer, $combinedContainer); 3329 3330 updateStatusIndicator($messageDom, buffer); 3331 scrollToBottom(true); 3332 } 3333 // ãå ³é®å é¤ãåºå¼æ§çæ è®°å¤ç彿°ï¼ä¸å使ç¨ï¼ 3334 // function processThinkContent(chunk, messageId) { ... } 3335 // function processMarkContent(chunk) { ... } 3336
3337 // æå»ºæèå 容ï¼ä¿çåæ ¼å¼é»è¾ï¼ 3338 function buildThinkContent(thinkText) { 3339 if (!thinkText) return ''; 3340 // 1. å 转ä¹ç¹æ®å符ï¼é²æ¢XSSï¼ 3341 const escapedText = $('<div>').text(thinkText).html(); 3342 // 2. åç¨mdParserè§£æMarkdownæ ¼å¼ï¼åç»æå å®¹ä¿æä¸è´ï¼ 3343 return mdParser.render(escapedText); 3344 } 3345 3346 // ãå ³é®ä¿®æ¹8ãåºå¼æ§çMarkdownæå»ºå½æ°ï¼ç´æ¥ç¨mdParser.renderï¼ 3347 // function buildMarkdownContent() { ... } 3348 3349 // æ´æ°åéæé®ç¶æï¼ä¿çåé»è¾ï¼ 3350 function updateSendButtonState(isLoading) { 3351 const sendBtn = $('#sendBtn'); 3352 const sendIcon = sendBtn.find('.send-icon'); 3353 const loadingIcon = sendBtn.find('.loading-icon'); 3354 if (isLoading) { 3355 sendIcon.addClass('hidden'); 3356 loadingIcon.removeClass('hidden'); 3357 sendBtn.attr('disabled', 'disabled'); 3358 } else { 3359 sendIcon.removeClass('hidden'); 3360 loadingIcon.addClass('hidden'); 3361 sendBtn.removeAttr('disabled'); 3362 } 3363 } 3364 3365 // æ è®°ååºå®æï¼ä¿çåé»è¾ï¼è¡¥å ç¼å²åºç¶æï¼ 3366 function completeResponse(messageId) { 3367 responseCompleted[messageId] = true; 3368 const buffer = responseBuffers[messageId]; 3369 if (buffer) { 3370 buffer.isLoading = false; 3371 // ãæ°å¢ãå¤çæªå®æçå·¥å ·ç¼åï¼è¥ææªå®æ´çå·¥å ·å 容ï¼ä½ä¸ºæ®écontentå¤ç 3372 if (buffer.toolContentBuffer) { 3373 buffer.segments.push({ 3374 type: 'reasoning', 3375 think: '', 3376 content: buffer.toolContentBuffer, // ä¸å®æ´çå·¥å ·å 容ä½ä¸ºæ®éææ¬ 3377 isCollapsed: false 3378 }); 3379 buffer.toolContentBuffer = ''; 3380 } 3381 // åæé»è¾ï¼å¤çæåä¸ç»çæå ç¶æ 3382 if (buffer.segments.length > 0) { 3383 const lastSegment = buffer.segments[buffer.segments.length - 1]; 3384 if ((lastSegment.type === 'reasoning' && lastSegment.think && !lastSegment.content) || !lastSegment.type) { 3385 lastSegment.isCollapsed = true; 3386 } else if (lastSegment.type === 'tool') { 3387 lastSegment.isCollapsed = true; 3388 } 3389 } 3390 } 3391 3392 // ãæ°å¢ãéèå®¡æ ¸å è½½æç¤º 3393 hideAuditLoading(messageId); 3394 isLoading = false; 3395 $('#sendBtn').removeClass('loading'); 3396 $('#sendBtn').off('click').on('click', sendMessage); 3397 $('.load-more-indicator').remove(); 3398 // éæ°æ¸²æä»¥åºç¨æç»ç¶æ 3399 //updateBotMessage(messageId, []); 3400 } 3401 3402 // æ¾ç¤ºéè¯¯æ¶æ¯ï¼ä¿çåé»è¾ï¼ 3403 function showError(message) { 3404 const messageId = generateUniqueId(); 3405 const messageHtml = ` 3406 <div class="message bot-message message-appear" id="${messageId}"> 3407 <div class="flex items-start space-x-3"> 3408 <div class="w-8 h-8 rounded-full bg-red-500 flex items-center justify-center text-white text-sm font-bold flex-shrink-0 shadow-md">!</div> 3409 <div class="bg-red-50 rounded-lg shadow-md p-4 max-w-[90%] sm:max-w-[80%] md:max-w-[75%] border-l-4 border-red-500"> 3410 <div class="prose max-w-none"> 3411 <p class="text-red-600">${message}</p> 3412 </div> 3413 </div> 3414 </div> 3415 </div> 3416 `; 3417 messagesContainer.append(messageHtml); 3418 scrollToBottom(true); 3419 } 3420 3421 // æ¸ ç©ºæ¶æ¯ï¼ä¿çåé»è¾ï¼ 3422 function clearMessages() { 3423 $('#sendBtn').prop('disabled', false); 3424 let spanHtm = language === 'Chinese' ? 'ä½ å¥½ï¼ææ¯ä½ çAIå©æï¼æä»ä¹å¯ä»¥å¸®å©ä
3424½ çåï¼' : 'Hello! I am your AI assistant, can I help you?'; 3425 fetch(`https://aipublic.ptglab.com/llm/v1/product/clear-chat-context/${uuid}`, { method: 'GET' }) 3426 .then(data => { /* 空å¤ç */ }); 3427 messagesContainer.children().not('.able-intro').remove(); 3428 // ãæ°å¢ãæ¸ çå®¡æ ¸å è½½ç¶æ 3429 Object.keys(auditLoadingStates).forEach(key => delete auditLoadingStates[key]); 3430 rebuildChatRoundIds.clear(); 3431 3432 // ãæ°å¢ãæ¸ é¤localStorageä¸çuuidï¼çææ°çuuid 3433 // localStorage.removeItem('chat_uuid'); 3434 generateSafeNumericId() 3435 .then(id => { 3436 uuid = id 3437 localStorage.setItem('chat_uuid', uuid); 3438 // åç»é»è¾ 3439 }) 3440 // ãæ°å¢ãæ¾ç¤ºä»ç»é¡µé¢ 3441 $(".able-intro").css("display", "block"); 3442 } 3443 3444 // æºè½æ»å¨å°åºé¨ï¼ä¿çåé»è¾ï¼ 3445 function scrollToBottom(force = false) { 3446 // 1. ä¿æDOMå ç´ è·åé»è¾ï¼æ éæ¹ï¼ 3447 const container = $('#messagesContainer')[0]; 3448 if (!container) return; 3449 3450 // 2. 计ç®ãåç¡®çåºé¨ä½ç½®ãï¼å ³é®ä¿®å¤ï¼ 3451 const scrollHeight = container.scrollHeight; 3452 const clientHeight = container.clientHeight; 3453 const maxScrollTop = scrollHeight - clientHeight; // çæ£çâæåºé¨âä½ç½® 3454 3455 // 3. ç®å夿ï¼å¼ºå¶æ»å¨ æ ç¨æ·æªæå¨æ»ç¦»åºé¨ï¼éå¼ç¼©å°å°5pxï¼åå°è¯¯å¤ï¼ 3456 const scrollTop = container.scrollTop; 3457 const isAtBottom = scrollTop >= maxScrollTop - 5; // éå¼ä»20æ¹ä¸º5ï¼æ´çµæ 3458 3459 // 4. æ»å¨å°åç¡®çåºé¨ä½ç½®ï¼èéscrollHeightï¼ 3460 if (force || isAtBottom) { 3461 container.scrollTo({ 3462 top: maxScrollTop, // ç¨è®¡ç®åºçâçæ£åºé¨âæ¿æ¢scrollHeight 3463 behavior: 'auto' // 宿¶è¾åºåºæ¯ï¼æsmoothæ¹ä¸ºautoï¼é¿å æ»å¨å¨ç»å»¶è¿ 3464 }); 3465 } 3466 } 3467 3468 // çæå个åå¤çå¯ä¸IDï¼ä¿çåé»è¾ï¼ 3469 function generateUniqueId() { 3470 return Date.now().toString(); 3471 } 3472 3473 /** 3474* æ¾ç¤ºå®¡æ ¸å è½½æç¤º 3475* @param {string} messageId - æ¶æ¯ID 3476*/ 3477 function showAuditLoading(messageId) { 3478 const $message = $(`#${messageId}`); 3479 if (!$message.length) return; 3480 3481 const $thinkContainer = $message.find('.markdown-preview'); 3482 if (!$thinkContainer.length) return; 3483 3484 // æ£æ¥æ¯å¦å·²ç»æ¾ç¤ºå®¡æ ¸å è½½æç¤º 3485 if ($thinkContainer.find('.audit-loading').length > 0) return; 3486 3487 // å¨ææ³¨å ¥CSSæ ·å¼ç¡®ä¿å¨ç»çæ 3488 if (!$('#audit-animation-styles').length) { 3489 $('head').append(` 3490 <style id="audit-animation-styles"> 3491 .audit-loading { 3492 animation: audit-glow 2s ease-in-out infinite !important; 3493 text-align: center !important; 3494 margin: 10px auto !important; 3495 max-width: 200px !important; 3496 padding: 12px 16px !important; 3497 } 3498 .audit-loading .audit-text { 3499 animation: audit-pulse 1.5s ease-in-out infinite !important; 3500 color: #4b5563 !important; 3501 font-weight: 500 !important; 3502 } 3503 .audit-loading .audit-text::after { 3504 content: '...'; 3505 animation: audit-dots 1.5s infinite !important; 3506 display: inline-block !important; 3507 } 3508 @keyframes audit-glow { 3509 0%, 100% { 3510 box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); 3511 border-color: #e
35110e7ff; 3512 background: #f8fafc; 3513 } 3514 50% { 3515 box-shadow: 0 4px 8px rgba(59, 130, 246, 0.1); 3516 border-color: #c7d2fe; 3517 background: #f1f5f9; 3518 } 3519 } 3520 @keyframes audit-pulse { 3521 0%, 100% { 3522 opacity: 0.7; 3523 } 3524 50% { 3525 opacity: 1; 3526 } 3527 } 3528 @keyframes audit-dots { 3529 0%, 20% { 3530 opacity: 0; 3531 } 3532 50% { 3533 opacity: 1; 3534 } 3535 80%, 100% { 3536 opacity: 0; 3537 } 3538 } 3539 </style> 3540 `); 3541 } 3542 3543 const auditLoadingHtml = ` 3544 <div class="audit-loading" id="audit-loading-${messageId}"> 3545 <div class="audit-loading-content"> 3546 <div class="audit-spinner"></div> 3547 <div class="audit-text"> 3548 ${language === 'Chinese' ? 'å®¡æ ¸ä¸' : 'Reviewing'} 3549 </div> 3550 </div> 3551 </div> 3552 `; 3553 3554 3555 $thinkContainer.append(auditLoadingHtml);
3556 auditLoadingStates[messageId] = true; 3557 scrollToBottom(true); 3558 } 3559 3560 function updateStatusIndicator($messageDom, buffer) { 3561 const $statusContainer = $messageDom.find('.status-indicator'); 3562 const $statusIcon = $statusContainer.find('.status-icon'); 3563 const $statusText = $statusContainer.find('.status-text'); 3564 3565 // const isAuditing = auditLoadingStates[$messageDom.attr('id')] || false; 3566 3567 if (isShowReview === 'Reviewed') { 3568 $statusContainer.show(); 3569 $statusContainer.css('border-left', '3px solid #28a745'); 3570 // ãä¿®æ¹ãå è®¾ç½®ææ¬ï¼åè®¾ç½®å¾æ 3571 $statusText.text(language === 'Chinese' ? 'å®¡æ ¸æå' : 'Reviewed '); 3572 $statusIcon.html('<i class="fa fa-check" style="color: #28a745;"></i>'); 3573 $statusText.css('color', '#155724'); 3574 } else if (isShowReview === 'Reviewing') { 3575 $statusContainer.show(); 3576 $statusContainer.css('border-left', '3px solid #ffc107'); 3577 // ãä¿®æ¹ãå è®¾ç½®ææ¬ï¼åè®¾ç½®å¾æ 3578 $statusText.text(language === 'Chinese' ? 'å®¡æ ¸ä¸' : 'Reviewing'); 3579 $statusIcon.html('<i class="fa fa-spinner fa-spin" style="color: #ffc107;"></i>'); 3580 $statusText.css('color', '#856404'); 3581 } 3582 } 3583 /** 3584 * éèå®¡æ ¸å è½½æç¤º 3585 * @param {string} messageId - æ¶æ¯ID 3586 */ 3587 function hideAuditLoading(messageId) { 3588 const $auditLoading = $(`#audit-loading-${messageId}`); 3589 if ($auditLoading.length) { 3590 $auditLoading.addClass('audit-loading-fade-out'); 3591 setTimeout(() => { 3592 $auditLoading.remove(); 3593 }, 500); 3594 auditLoadingStates[messageId] = false; 3595 } 3596 } 3597 3598 3599 // éç¥åå°ç¨æ·ä¸»å¨ä¸æäºå½åå¯¹è¯ 3600 function notifyCancelChat(dialogId) { 3601 const id = dialogId || localStorage.getItem('chat_uuid') || uuid; 3602 if (!id) return; 3603 fetch(`https://aipublic.ptglab.com/llm/v1/product/cancel-chat/${id}`, { method: 'GET' }) 3604 .catch(err => console.warn('cancel-chat failed:', err)); 3605 } 3606 3607 // 䏿¢å½å请æ±ï¼ä¿çåé»è¾ï¼ 3608 function abortRequest(notifyServer, dialogId) { 3609 if (abortController) { 3610 abortController.abort(); 3611 } 3612 if (notifyServer) { 3613 notifyCancelChat(dialogId); 3614 } 3615 } 3616 3617 // æ¥å¿è®°å½å½æ°ï¼ä¿çåé»è¾ï¼ 3618 function log(...args) { 3619 if (enableLogging) { 3620 // console.log(...args); 3621 } 3622 } 3623 3624 3625 /** 3626 * å¨.content-bodyææ«å°¾æ·»å å¤å¶å¾æ 3627 * @param {string} messageId - æ¶æ¯ID 3628 */ 3629 function addCopyIconToContent(messageId) { 3630 const $message = $(`#${messageId}`); 3631 if (!$message.length) { 3632 console.log("Message not found:", messageId); 3633 return; 3634 } 3635 3636 // åªæ¥æ¾æåä¸ä¸ª.content-bodyå®¹å¨ 3637 const $contentBody = $message.find('.content-body').last(); 3638 3639 if (!$contentBody.length) { 3640 return; 3641 } 3642 3643 // æ£æ¥æ¯å¦å·²ç»æ·»å äºå¤å¶å¾æ 3644 if ($contentBody.find('.copy-icon-container').length > 0) { 3645 return; // è·³è¿å·²æ·»å ç 3646 } 3647 3648 // å建å¤å¶å导åºå¾æ å®¹å¨ 3649 const copyIconHtml = ` 3650 <div class="copy-icon-container"> 3651 <div class="copy-icon" title="${language === 'Chinese' ? 'å¤å¶Markdownå 容' : 'Copy Markdown Content'}"> 3652 <i class="fa fa-copy"></i> 3653 </div> 3654 <div class="export-pdf-icon" title="${language === 'Chinese' ? '导åºä¸ºPDF' : 'Export as PDF'}"> 3655 <i class="fa fa-file-pdf-o"></i> 3656 </div> 3657 </div> 3658 `; 3659 3660 // æ·»å å°content-bodyæ«å°¾ 3661 $contentBody.append(copyIconHtml); 3662 3663 // ç»å®ç¹å»äºä»¶ - 使ç¨äºä»¶å§æç¡®ä¿äºä»¶ä¸è¢«è¦ç 3664 const $copyIcon = $contentBody.find('.copy-icon'); 3665 3666 // ç§»é¤ææå¯è½çäºä»¶çå¬å¨ 3667 $copyIcon.off('click copy click.copyIcon'); 3668 3669 // ç»å®æ°çç¹å»äºä»¶ 3670 $copyIcon.on('click.copyIcon', function (e) { 3671 e.preventDefault(); 3672 e.stopPropagation(); 3673 copyMarkdownContent($contentBody, $copyIcon); 3674 }); 3675 3676 // ç»å®å¯¼åºPDFæé®ç¹å»äºä»¶ 3677 const $exportIcon = $contentBody.find('.export-pdf-icon'); 3678 3679 if ($exportIcon.length > 0) { 3680 $exportIcon.off('click export click.exportPdfIcon'); 3681 $exportIcon.on('click.exportPdfIcon', function (e) { 3682 e.preventDefault(); 3683 e.stopPropagation();
3684 exportContentAsPDF($contentBody, $exportIcon); 3685 }); 3686 3687 // æµè¯ç¹å»äºä»¶æ¯å¦ç»å®æå 3688 } else { 3689 console.error("Export PDF icon not found!"); 3690 } 3691 } 3692 /** 3693 * å¤å¶Markdownå 容å°åªè´´æ¿ 3694 * @param {jQuery} $contentBody - å å®¹å®¹å¨ 3695 * @param {jQuery} $copyIcon - å¤å¶å¾æ 3696 */ 3697 function copyMarkdownContent($contentBody, $copyIcon) { 3698 try { 3699 // è·ååå§HTMLå 容 3700 const htmlContent = $contentBody.html(); 3701 3702 // ç§»é¤å¤å¶å¾æ 容å¨ï¼é¿å å¤å¶æ¶å å«å¤å¶æé® 3703 const tempContent = htmlContent.replace(/<div class="copy-icon-container">[\s\S]*?<\/div>/g, ''); 3704 3705 // å建临æ¶divæ¥è§£æHTML 3706 const $tempDiv = $('<div>').html(tempContent); 3707 3708 // ç§»é¤3D容å¨ï¼å 为3D容å¨ä¸åºè¯¥è¢«å¤å¶ 3709 $tempDiv.find('.pdb-3d-container').remove(); 3710
3711 // 转æ¢HTML为Markdownæ ¼å¼ 3712 const markdownContent = htmlToMarkdown($tempDiv.html()); 3713 3714 // å¤å¶å°åªè´´æ¿ 3715 if (navigator.clipboard && window.isSecureContext) { 3716 navigator.clipboard.writeText(markdownContent).then(() => { 3717 showCopySuccess($copyIcon); 3718 }).catch(err => { 3719 console.error('å¤å¶å¤±è´¥:', err); 3720 fallbackCopyTextToClipboard(markdownContent, $copyIcon); 3721 }); 3722 } else { 3723 // éçº§æ¹æ¡ 3724 fallbackCopyTextToClipboard(markdownContent, $copyIcon); 3725 } 3726 } catch (error) { 3727 console.error('å¤å¶è¿ç¨ä¸åºé:', error); 3728 showCopyError($copyIcon); 3729 } 3730 } 3731 3732 /** 3733 * é级å¤å¶æ¹æ¡ 3734 * @param {string} text - è¦å¤å¶çææ¬ 3735 * @param {jQuery} $copyIcon - å¤å¶å¾æ 3736 */ 3737 function fallbackCopyTextToClipboard(text, $copyIcon) { 3738 const textArea = document.createElement('textarea'); 3739 textArea.value = text; 3740 textArea.style.position = 'fixed'; 3741 textArea.style.left = '-999999px'; 3742 textArea.style.top = '-999999px'; 3743 document.body.appendChild(textArea); 3744 textArea.focus(); 3745 textArea.select(); 3746 3747 try { 3748 const successful = document.execCommand('copy'); 3749 if (successful) { 3750 showCopySuccess($copyIcon); 3751 } else { 3752 showCopyError($copyIcon); 3753 } 3754 } catch (err) { 3755 console.error('é级å¤å¶å¤±è´¥:', err); 3756 showCopyError($copyIcon); 3757 } finally { 3758 document.body.removeChild(textArea); 3759 } 3760 } 3761 3762 /** 3763 * æ¾ç¤ºå¤å¶æåç¶æ 3764 * @param {jQuery} $copyIcon - å¤å¶å¾æ 3765 */ 3766 function showCopySuccess($copyIcon) { 3767 $copyIcon.addClass('copied'); 3768 $copyIcon.find('i').remove(); 3769 3770 // 2ç§åæ¢å¤åç¶ 3771 setTimeout(() => { 3772 $copyIcon.removeClass('copied'); 3773 $copyIcon.html('<i class="fa fa-copy"></i>'); 3774 }, 2000); 3775 } 3776 3777 /** 3778 * æ¾ç¤ºå¤å¶éè¯¯ç¶æ 3779 * @param {jQuery} $copyIcon - å¤å¶å¾æ 3780 */ 3781 function showCopyError($copyIcon) { 3782 const originalHtml = $copyIcon.html(); 3783 $copyIcon.html('<i class="fa fa-exclamation-triangle"></i>'); 3784 $copyIcon.css('background-color', '#ef4444'); 3785 $copyIcon.css('border-color', '#dc2626'); 3786 $copyIcon.css('color', 'white'); 3787 3788 // 1ç§åæ¢å¤åç¶ 3789 setTimeout(() => { 3790 $copyIcon.html(originalHtml); 3791 $copyIcon.css('background-color', ''); 3792 $copyIcon.css('border-color', ''); 3793 $copyIcon.css('color', ''); 3794 }, 1000); 3795 } 3796 3797 /** 3798 * å°HTML转æ¢ä¸ºMarkdownæ ¼å¼ 3799 * @param {string} html - HTMLå 容 3800 * @returns {string} Markdownå 容 3801 */ 3802 function htmlToMarkdown(html) { 3803 // å建临æ¶divæ¥è§£æHTML 3804 const $temp = $('<div>').html(html); 3805 3806 // å¤çåç§HTMLæ ç¾è½¬æ¢ä¸ºMarkdown 3807 $temp.find('h1').each(function () { 3808 $(this).replaceWith(`# ${$(this).text()}\n\n`); 3809 }); 3810 3811 $temp.find('h2').each(function () { 3812 $(this).replaceWith(`## ${$(this).text()}\n\n`); 3813 }); 3814 3815 $temp.find('h3').each(function () { 3816 $(this).replaceWith(`### ${$(this).text()}\n\n`); 3817 }); 3818 3819 $temp.find('h4').each(function () { 3820 $(this).replaceWith(`#### ${$(this).text()}\n\n`); 3821 }); 3822 3823 $temp.find('h5').each(function () { 3824 $(this).replaceWith(`##### ${$(this).text()}\n\n`); 3825 }); 3826 3827 $temp.find('h6').each(function () { 3828 $(this).replaceWith(`###### ${$(this).text()}\n\n`); 3829 }); 3830 3831 $temp.find('strong, b').each(function () { 3832 const text = $(this).text().trim(); 3833 if (text) { 3834 $(this).replaceWith(`**${text}**`); 3835 } else { 3836 $(this).remove(); 3837 } 3838 }); 3839 3840 $temp.find('em, i').each(function () { 3841 const text = $(this).text().trim(); 3842 if (text) { 3843 $(this).replaceWith(`*${text}*`); 3844 } else { 3845 $(this).remove(); 3846 } 3847 }); 3848 3849 $temp.find('code').each(function () { 3850 $(this).replaceWith(`\`${$(this).text()}\``); 3851 }); 3852 3853 $temp.find('pre').each(function () { 3854 const code = $(this).text(); 3855 $(this).replaceWith(`\n\`\`\`\n${code}\n\`\`\`\n\n`); 3856 }); 3857 3858 $temp.find('ul').each(function () { 3859 const $ul = $(this); 3860 let markdown = ''; 3861 $ul.find('li').each(function () { 3862 const text = $(this).text().trim(); 3863 if (text) { 3864 markdown += `- ${text}\n`; 3865 } 3866 }); 3867 if (markdown) { 3868 markdown = '\n' + markdown + '\n'; 3869 } 3870 $ul.replaceWith(markdown); 3871 }); 3872 3873 $temp.find('ol').each(function () { 3874 const $ol = $(this); 3875 let markdown = ''; 3876 $ol.find('li').each(function (index) { 3877 const text = $(this).text().trim(); 3878 if (text) { 3879 markdown += `${index + 1}. ${text}\n`; 3880 } 3881 }); 3882 if (markdown) { 3883 markdown = '\n' + markdown + '\n'; 3884 } 3885 $ol.replaceWith(markdown); 3886 }); 3887 3888 $temp.find('a').each(function () { 3889 const href = $(this).attr('href'); 3890 const text = $(this).text(); 3891 $(this).replaceWith(`[${text}](${href})`); 3892 }); 3893 3894 $temp.find('blockquote').each(function () { 3895 const text = $(this).text(); 3896 $(this).replaceWith(`> ${text}\n\n`); 3897 }); 3898 3899 $temp.find('hr').each(function () { 3900 $(this).replaceWith('\n---\n\n'); 3901 }); 3902 3903 $temp.find('p').each(function () { 3904 const text = $(this).text().trim(); 3905 if (text) { 3906 $(this).replaceWith(`${text}\n\n`); 3907 } else { 3908 $(this).remove(); 3909 } 3910 }); 3911 3912 // è·åæç»ææ¬å¹¶æ¸ ç 3913 let markdown = $temp.text(); 3914 3915 // æ¸ çå¤ä½ç空è¡ï¼ä¸åï¼å ¼å®¹ææç¯å¢ï¼ 3916 markdown = markdown.replace(/\n{3,}/g, '\n\n'); 3917 3918 // æ¸ çå¤ä½çæå·ï¼è¿ç»çæå·ï¼ 3919 // 1. æ¿æ¢ 3 个å以ä¸è¿ç»æå·ä¸º **ï¼ä¸åï¼ 3920 markdown = markdown.replace(/\*{3,}/g, '**'); 3921 // 2. æ¿æ¢ "æå·åæ¥ç©ºæ ¼" çåºæ¯ï¼ç¨æè·ç»æ¿ä»£æ£åå è¡æè¨ï¼å ¼å®¹ä
3921½çæ¬æµè§å¨ï¼ 3922 markdown = markdown.replace(/\*{2,}(\s)/g, '**$1'); 3923 // 3. æ¿æ¢ "ç©ºæ ¼åæ¥ 2 个å以䏿å·" çåºæ¯ï¼ä¸åï¼ 3924 markdown = markdown.replace(/(\s)\*{2,}/g, '$1**'); 3925 3926 // æ¸ ç空æå·å¯¹ï¼ä¸åï¼ 3927 markdown = markdown.replace(/\*\*\s*\*\*/g, ''); 3928 markdown = markdown.replace(/\*\s*\*/g, ''); 3929 3930 // æ¸ çé¦å°¾ç©ºç½ï¼ä¸åï¼ 3931 markdown = markdown.trim(); 3932 3933 return markdown; 3934 } 3935 3936 /** 3937 * 导åºå 容为PDF 3938 * @param {jQuery} $contentBody - å å®¹å®¹å¨ 3939 * @param {jQuery} $exportIcon - 导åºå¾æ 3940 */ 3941 function exportContentAsPDF($contentBody, $exportIcon) { 3942 3943 try { 3944 // æ¾ç¤ºå¯¼åºä¸ç¶æ 3945 $exportIcon.addClass('exporting'); 3946 $exportIcon.find('i').remove(); 3947 3948 // å建临æ¶å®¹å¨ç¨äºPDFå¯¼åº 3949 const $tempContainer = $('<
3949div>').css({ 3950 position: 'absolute', 3951 left: '-9999px', // ç§»å°å±å¹å¤é¿å éªç 3952 top: '-9999px', 3953 width: '800px', 3954 backgroundColor: 'white', 3955 padding: '10px', // åå°å è¾¹è·é¿å æªæ 3956 fontFamily: 'Arial, sans-serif', 3957 fontSize: '14px', 3958 lineHeight: '1.6', // éä¸çè¡é« 3959 color: '#333', 3960 boxShadow: 'none', // ç§»é¤é´å½±é¿å é¢å¤ç©ºé´ 3961 borderRadius: '0', // ç§»é¤åè§ 3962 visibility: 'visible', 3963 opacity: 1, 3964 zIndex: 9999, 3965 overflow: 'visible', 3966 margin: '0', 3967 border: 'none' 3968 }); 3969 3970 // å¤å¶å 容å°ä¸´æ¶å®¹å¨ 3971 const originalContent = $contentBody.clone(); 3972 3973 // ç§»é¤å¤å¶åå¯¼åºæé® 3974 originalContent.find('.copy-icon-container').remove(); 3975 3976 // ç§»é¤3D容å¨ï¼å ä¸ºæ æ³å¨PDF䏿¸²æï¼ 3977 originalContent.find('.pdb-3d-container').remove(); 3978 3979 // ä¿ç徿 åè¡¨æ ¼ï¼åªå¤çå¾ç龿¥ 3980 originalContent.find('a[href*=".jpg"], a[href*=".jpeg"], a[href*=".png"], a[href*=".gif"], a[href*=".webp"], a[href*=".svg"]').each(function () { 3981 const $link = $(this); 3982 const href = $link.attr('href'); 3983 const fileName = href.split('/').pop() || 'å¾ç'; 3984 // å建åºå®å®½åº¦çå¾çå ä½ç¬¦ 3985 $link.replaceWith(` 3986 <div class="image-placeholder"> 3987 <div> 3988 <i class="fa fa-image" style="font-size: 28px; margin-bottom: 8px; display: block;"></i> 3989 <div style="font-weight: bold; margin-bottom: 4px;">å¾ç: ${fileName}</div> 3990 <div style="font-size: 12px; color: #6c757d;">400Ã250px</div> 3991 </div> 3992 </div> 3993 `); 3994 }); 3995 3996 // å¤çå ¶ä»é¾æ¥ï¼ä½ä¿ç徿 3997 originalContent.find('a').not('a[href*=".jpg"], a[href*=".jpeg"], a[href*=".png"], a[href*=".gif"], a[href*=".webp"], a[href*=".svg"]').each(function () { 3998 const $link = $(this); 3999 const href = $link.attr('href'); 4000 const text = $link.text(); 4001 // 妿龿¥å å«å¾æ ï¼ä¿ç徿 4002 if ($link.find('i, .fa, .icon').length > 0) { 4003 const iconHtml = $link.find('i, .fa, .icon').prop('outerHTML'); 4004 $link.replaceWith(`${iconHtml} ${text} (${href})`); 4005 } else { 4006 $link.replaceWith(`${text} (${href})`); 4007 } 4008 }); 4009 4010 // å¤ç Live Chat å ç´ ï¼ç¡®ä¿å¨PDF䏿£ç¡®æ¾ç¤º 4011 const liveChatElements = originalContent.find('.open-live-chat'); 4012 4013 liveChatElements.each(function () { 4014 const $liveChat = $(this); 4015 const text = $liveChat.text(); 4016 // ä¿ç Live Chat ææ¬ï¼ä½ç§»é¤ç¹æ®æ ·å¼ç±» 4017 $liveChat.replaceWith(`<span style="color: #007bff; font-weight: bold;">${text}</span>`); 4018 }); 4019 4020 // æ·»å å 容å°ä¸´æ¶å®¹å¨ 4021 $tempContainer.append(originalContent); 4022 $('body').append($tempContainer); 4023 // è°è¯ï¼æ£æ¥å 容æ¯å¦æ£ç¡®æ·»å 4024 const tempHtml = $tempContainer.html(); 4025 const tempText = $tempContainer.text(); 4026 4027 // æ£æ¥æ¯å¦ææªéåçæ ç¾ 4028 const openTags = (tempHtml.match(/</g) || []).length; 4029 const closeTags = (tempHtml.match(/>/g) || []).length; 4030 4031 4032 // ç´æ¥ä½¿ç¨html2canvas + jsPDFæ¹æ¡ï¼æ´å¯é ï¼ 4033 const html2canvasLib = window.html2canvas || html2canvas; 4034 const jsPDFLib = window.jsPDF || window.jspdf || jsPDF; 4035 4036 // 妿jsPDFæ¯å¯¹è±¡ï¼å°è¯è·åæé 彿° 4037 const jsPDFConstructor = (typeof jsPDFLib === 'object' && jsPDFLib.jsPDF) ? jsPDFLib.jsPDF : jsPDFLib; 4038 4039 4040 if (typeof html2canvasLib !== 'undefined' && typeof jsPDFConstructor !== 'undefined') { 4041 // çå¾ DOMå®å ¨æ¸²æ - 对äºé¿å 容å¢å çå¾ æ¶é´ 4042 setTimeout(() => { 4043 // 强å¶éæ°è®¡ç®é«åº¦ - æ¸ é¤ä»»ä½ç¼åçé«åº¦å¼ 4044 $tempContainer[0].style.height = 'auto'; // éç½®é«åº¦ 4045 $tempContainer[0].style.minHeight = 'auto'; // éç½®æå°é«åº¦ 4046 4047 // å¼ºå¶æµè§å¨éæ°è®¡ç®å¸å± 4048 const forceReflow = $tempContainer[0].offsetHeight; 4049 4050 // è·åå®é é«åº¦ - 夿¬¡æµéç¡®ä¿åç¡® 4051 let actualHeight = Math.max( 4052 $tempContainer[0].scrollHeight, 4053 $tempContainer[0].offsetHeight, 4054 $tempContainer[0].clientHeight 4055 ); 4056 4057 // 忬¡æµéï¼ç¡®ä¿é«åº¦æ¯ææ°ç 4058 actualHeight = Math.max(actualHeight, $tempContainer[0].scrollHeight); 4059 4060 // 忬¡æ£æ¥HTMLå 容æ¯å¦å®æ´ 4061 const finalHtml = $tempContainer.html(); 4062 const finalText = $tempContainer.text(); 4063 4064 html2canvasLib($tempContainer[0], { 4065 scale: 2, // æé«ç¼©æ¾ä»¥è·å¾æ´æ¸ æ°çå¾å 4066 useCORS: true, 4067 allowTaint: true, 4068 backgroundColor: '#ffffff', 4069 width: 800, 4070 height: actualHeight + 1000, // å¢å æ´å¤ç¼å²ç¡®ä¿é¿å 容宿´ 4071 logging: false, 4072 imageTimeout: 20000, // å¢å å¾çå è½½è¶ æ¶æ¶é´ 4073 scrollX: 0, 4074 scrollY: 0, 4075 onclone: function (clonedDoc) { 4076 // ç¡®ä¿å éçææ¡£ä¸æ ·å¼æ£ç¡®åºç¨ 4077 const clonedContainer = clonedDoc.querySelector('div'); 4078 if (clonedContainer) { 4079 clonedContainer.style.fontFamily = 'Arial, sans-serif'; 4080 clonedContainer.style.fontSize = '14px'; 4081 clonedContainer.style.lineHeight = '1.6'; 4082 clonedContainer.style.color = '#333'; 4083 clonedContainer.style.padding = '10px'; 4084 clonedContainer.style.margin = '0'; 4085 clonedContainer.style.width = '800px'; 4086 clonedContainer.style.backgroundColor = 'white'; 4087 clonedContainer.style.boxSizing = 'border-box'; 4088 } 4089 4090 // ç¡®ä¿å¾æ å使£ç¡®å 载并æ¹åæ ·å¼ 4091 const style = clonedDoc.createElement('style'); 4092 style.textContent = ` 4093 @import url('https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css'); 4094 * { box-sizing: border-box; } 4095 .fa, .icon { font-family: FontAwesome !important; } 4096 table { 4097 border-collapse: collapse; 4098 width: 100%; 4099 margin: 10px 0; 4100 font-size: 13px; 4101 } 4102 th, td { 4103 border: 1px solid #ddd; 4104 padding: 8px; 4105 text-align: left; 4106 vertical-align: top; 4107 line-height: 1.5; 4108 } 4109 th { 4110 background-color: #f8f9fa; 4111 font-weight: bold; 4112 color: #495057; 4113 } 4114 p { 4115 margin: 8px 0; 4116 line-height: 1.7; 4117 text-align: justify;
4118 } 4119 h1, h2, h3, h4, h5, h6 { 4120 margin: 15px 0 8px 0; 4121 line-height: 1.3; 4122 font-weight: bold; 4123 } 4124 img { 4125 max-width: 100%; 4126 height: auto; 4127 margin: 0; 4128 padding: 0; 4129 display: block; 4130 } 4131 .image-placeholder { 4132 margin: 0; 4133 padding: 0; 4134 background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); 4135 border: 2px solid #dee2e6; 4136 border-radius: 8px; 4137 width: 400px; 4138 height: 250px; 4139 display: flex; 4140 align-items: center; 4141 justify-content: center; 4142 text-align: center; 4143 font-size: 16px; 4144 color: #495057; 4145 box-shadow: 0 2px 8px rgba(0,0,0,0.1); 4146 flex-direction: column; 4147 4148 position: relative; 4149 } 4150 .image-placeholder i { 4151 font-size: 32px !important; 4152 margin-bottom: 10px !important; 4153 display: block !important; 4154 color: #007bff !important; 4155 } 4156 .image-placeholder > div { 4157 display: flex; 4158 flex-direction: column; 4159 align-items: center; 4160 justify-content: center; 4161 height: 100%; 4162 } 4163 .content-body { 4164 padding: 0; 4165 margin: 0; 4166 } 4167 .message-content { 4168 margin: 2px 0; 4169 padding: 0; 4170 } 4171 .open-live-chat { 4172 color: #007bff !important; 4173 font-weight: bold !important; 4174 text-decoration: none !important; 4175 } 4176 `; 4177 clonedDoc.head.appendChild(style); 4178 } 4179 }).then(canvas => { 4180 // æ£æ¥canvasæ¯å¦å å«å®æ´å 容 4181 const canvasDataUrl = canvas.toDataURL('image/jpeg', 1.0); 4182 // 忬¡æ£æ¥åå§HTMLå 容 4183 const postCanvasHtml = $tempContainer.html(); 4184 const postCanvasText = $tempContainer.text(); 4185 // å建PDF 4186 const pdf = new jsPDFConstructor('p', 'mm', 'a4'); 4187 const imgWidth = 210; // A4宽度 (mm) 4188 const pageHeight = 295; // A4é«åº¦ (mm) 4189 const bottomMargin = 20; // åºé¨è¾¹è· (mm) 4190 const effectivePageHeight = pageHeight - bottomMargin; // ææé¡µé¢é«åº¦ 4191 const imgHeight = (canvas.height * imgWidth) / canvas.width; 4192 // 计ç®éè¦å¤å°é¡µ - 使ç¨å®æ´é¡µé¢é«åº¦ï¼ä½å 容åºåèèåºé¨è¾¹è· 4193 const totalPages = Math.ceil(imgHeight / pageHeight); 4194 // æ·»å å 容宿´æ§æ£æ¥ 4195 const contentText = $tempContainer.text(); 4196 if (totalPages === 1) { 4197 // å页å 容 - 使ç¨é«è´¨éPNG 4198 pdf.addImage(canvas.toDataURL('image/jpeg', 0.95), 'JPEG', 0, 0, imgWidth, imgHeight); 4199 // pdf.addImage(canvas.toDataURL('image/png', 1.0), 'PNG', 0, 0, imgWidth, imgHeight); 4200 } else { 4201 // å¤é¡µå 容ï¼ä½¿ç¨ç®åçå页é»è¾ - åå°å页次æ°é使ªæé£é© 4202 let currentY = 0; 4203 let pageCount = 0; 4204 4205 // 计ç®åç´ å°æ¯«ç±³çè½¬æ¢æ¯ä¾ 4206 const pixelsPerMm = canvas.width / imgWidth; 4207 // ä½¿ç¨æ´å¤§ç页é¢é«åº¦æ¥å®¹çº³æ´å¤å 容ï¼åå°å页 4208 const pageHeightPx = pageHeight * pixelsPerMm; 4209 4210 while (currentY < canvas.height) { 4211 if (pageCount > 0) { 4212 pdf.addPage(); 4213 } 4214 4215 // 计ç®å½å页çé«åº¦ - ç®åé»è¾ï¼ç´æ¥ä½¿ç¨é¡µé¢é«åº¦ 4216 const remainingHeight = canvas.height - currentY; 4217 let sourceHeight = Math.min(remainingHeight, pageHeightPx); 4218 4219 // 计ç®å¨PDFä¸çä½ç½®åé«åº¦ 4220 const targetHeight = sourceHeight / pixelsPerMm; 4221 4222 // å建å½å页çcanvas 4223 const pageCanvas = document.createElement('canvas'); 4224 const pageCtx = pageCanvas.getContext('2d'); 4225 pageCanvas.width = canvas.width; 4226 pageCanvas.height = sourceHeight; 4227 4228 // ç»å¶å½å页çå 容 4229 pageCtx.drawImage(canvas, 0, currentY, canvas.width, sourceHeight, 0, 0, canvas.width, sourceHeight); 4230 4231 // æ·»å å°PDF - 使ç¨JPEGé«è´¨éæ ¼å¼ 4232 pdf.addImage(pageCanvas.toDataURL('image/jpeg', 0.95), 'JPEG', 0, 0, imgWidth, targetHeight); 4233 4234 // æ´æ°å½åä½ç½® 4235 currentY += sourceHeight; 4236 pageCount++; 4237 4238 // 鲿¢æ éå¾ªç¯ 4239 if (pageCount > 50) { 4240 console.error("åé¡µå¾ªç¯æ¬¡æ°è¿å¤ï¼å¼ºå¶éåº"); 4241 break; 4242 } 4243 } 4244 4245 if (canvas.height - currentY > 10) { 4246 console.warn(`è¦åï¼è¿æ ${canvas.height - currentY}px å 容æªå¤çï¼`); 4247 } 4248 } 4249 4250 // ä¿åPDF 4251 pdf.save(`content-export-${Date.now()}.pdf`); 4252 4253 // æ¸ ç临æ¶å®¹å¨ 4254 $tempContainer.remove(); 4255 4256 // æ¾ç¤ºæåç¶æ 4257 showExportSuccess($exportIcon); 4258 4259 }).catch(error => { 4260 console.error('html2canvas失败:', error); 4261 $tempContainer.remove(); 4262 showExportError($exportIcon); 4263 }); 4264 }, 3000); // ç»æ´å¤æ¶é´æ¸²æï¼ç¡®ä¿å 容å®å ¨å è½½ 4265 } else { 4266 console.error('html2canvasæjsPDFåºæªå è½½'); 4267 $tempContainer.remove(); 4268 showExportError($exportIcon); 4269 } 4270 4271 } catch (error) { 4272 console.error('导åºPDFè¿ç¨ä¸åºé:', error); 4273 showExportError($exportIcon); 4274 } 4275 } 4276 4277 4278 /** 4279 * æ¾ç¤ºå¯¼åºæåç¶æ 4280 * @param {jQuery} $exportIcon - 导åºå¾æ 4281 */ 4282 function showExportSuccess($exportIcon) { 4283 $exportIcon.removeClass('exporting').addClass('success'); 4284 4285 // 2ç§åæ¢å¤åç¶ 4286 setTimeout(() => { 4287 $exportIcon.removeClass('success'); 4288 $exportIcon.html('<i class="fa fa-file-pdf-o"></i>'); 4289 }, 2000); 4290 } 4291 4292 /** 4293 * æ¾ç¤ºå¯¼åºéè¯¯ç¶æ 4294 * @param {jQuery} $exportIcon - 导åºå¾æ 4295 */ 4296 function showExportError($exportIcon) { 4297 const originalHtml = $exportIcon.html(); 4298 $exportIcon.removeClass('exporting').html('<i class="fa fa-exclamation-triangle"></i>'); 4299 $exportIcon.css('background-color', '#ef4444'); 4300 $exportIcon.css('border-color', '#dc2626'); 4301 $exportIcon.css('color', 'white'); 4302 4303 // 1ç§åæ¢å¤åç¶ 4304 setTimeout(() => { 4305 $exportIcon.html(originalHtml); 4306 $exportIcon.css('background-color', ''); 4307 $exportIcon.css('border-color', ''); 4308 $exportIcon.css('color', ''); 4309 }, 1000); 4310 } 4311 4312 /** 4313* éæ°çæååº 4314* @param {jQuery} $resetIcon - éç½®å¾æ 4315* @param {string} messageId - æ¶æ¯ID 4316*/ 4317 function regenerateResponse($resetIcon, messageId) { 4318 try { 4319 // æ¾ç¤ºéç½®ä¸ç¶æ 4320 $resetIcon.addClass('resetting'); 4321 $resetIcon.find('i').remove(); 4322 4323 // è·åå½åæ¶æ¯çå 容 4324 const $message = $(`#${messageId}`); 4325 if (!$message.length) { 4326 console.error('æªæ¾å°æ¶æ¯:', messageId); 4327 showResetError($resetIcon); 4328 return; 4329 } 4330
4331 // è·åç¨æ·è¾å ¥å 容ï¼ä»ç¨æ·æ¶æ¯ä¸è·åï¼ 4332 const $userMessage = $message.prev('.user-message'); 4333 if (!$userMessage.length) { 4334 console.error('æªæ¾å°å¯¹åºçç¨æ·æ¶æ¯'); 4335 showResetError($resetIcon); 4336 return; 4337 } 4338 4339 const userText = $userMessage.find('.markdown-preview').text().trim(); 4340 if (!userText) { 4341 console.error('æ æ³è·åç¨æ·è¾å ¥å 容'); 4342 showResetError($resetIcon); 4343 return; 4344 } 4345 4346 // æ¸ ç©ºå½åAIæ¶æ¯å 容 4347 $message.find('.thinkHtml').empty(); 4348 $message.find('.markHtml').empty(); 4349 4350 // è®¾ç½®éæ°çææ è®° 4351 window.isRegenerating = true; 4352 window.regenerateMessageId = messageId; 4353 window.regenerateDialogId = uuid; 4354 window.regenerateRoundId = messageId; 4355 4356 // è®¾ç½®ç¨æ·è¾å ¥å¹¶ç«å³æ¸ 空ï¼ç¡®ä¿æ¥å£è½æ£å¸¸è°ç¨ä½è¾å ¥æ¡ä¸æ¾ç¤º 4357 userInput.val(userText); 4358 sendMessage(); 4359 userInput.val(''); // ç«å³æ¸ 空è¾å ¥æ¡ 4360 autoResizeUserInput(); 4361 // æ¾ç¤ºæåç¶æ 4362 setTimeout(() => { 4363 $resetIcon.removeClass('resetting').addClass('success'); 4364 $resetIcon.find('i').remove(); 4365 4366 // 2ç§åæ¢å¤åç¶ 4367 setTimeout(() => { 4368 $resetIcon.removeClass('success'); 4369 $resetIcon.html('<i class="fa fa-refresh"></i>'); 4370 }, 2000); 4371 }, 1000); 4372 4373 } catch (error) { 4374 console.error('éæ°çæååºå¤±è´¥:', error); 4375 showResetError($resetIcon); 4376 } 4377 } 4378 4379 4380 /** 4381 * æ¾ç¤ºéç½®éè¯¯ç¶æ 4382 * @param {jQuery} $resetIcon - éç½®å¾æ 4383 */ 4384 function showResetError($resetIcon) { 4385 const originalHtml = $resetIcon.html(); 4386 $resetIcon.removeClass('resetting').html('<i class="fa fa-exclamation-triangle"></i>'); 4387 $resetIcon.css('background-color', '#ef4444'); 4388 $resetIcon.css('border-color', '#dc2626'); 4389 $resetIcon.css('color', 'white'); 4390 4391 // 1ç§åæ¢å¤åç¶ 4392 setTimeout(() => { 4393 $resetIcon.html(originalHtml); 4394 $resetIcon.css('background-color', ''); 4395 $resetIcon.css('border-color', ''); 4396 $resetIcon.css('color', ''); 4397 }, 1000); 4398 } 4399 4400 /** 4401 * 为æåä¸è½®å¯¹è¯æ·»å éç½®å¾æ 4402 * @param {string} messageId - æ¶æ¯ID 4403 */ 4404 function addResetIconToLastMessage(messageId) { 4405 // å ç§»é¤ææåå²å¯¹è¯ä¸çéç½®å¾æ 4406 removeAllResetIcons(); 4407 4408 const $message = $(`#${messageId}`); 4409 if (!$message.length) { 4410 console.log("Message not found:", messageId); 4411 return; 4412 } 4413 4414 // æ¥æ¾ææ.content-bodyå®¹å¨ 4415 const $contentBodies = $message.find('.content-body'); 4416 4417 $contentBodies.each(function () { 4418 const $contentBody = $(this); 4419 4420 // æ£æ¥æ¯å¦å·²ç»æéç½®å¾æ 4421 if ($contentBody.find('.reset-icon').length > 0) { 4422 return; // è·³è¿å·²æ·»å ç 4423 } 4424 4425 // å¨å¤å¶å¾æ å颿·»å éç½®å¾æ 4426 const $copyIconContainer = $contentBody.find('.copy-icon-container'); 4427 if ($copyIconContainer.length > 0) { 4428 const resetIconHtml = ` 4429 <div class="reset-icon" title="Regenerate" data-message-id="${messageId}"> 4430 <i class="fa fa-refresh"></i> 4431 </div> 4432 `; 4433 $copyIconContainer.prepend(resetIconHtml); 4434 4435 } 4436 }); 4437 } 4438 4439 /** 4440 * ç§»é¤ææåå²å¯¹è¯ä¸çéç½®å¾æ 4441 */ 4442 function removeAllResetIcons() { 4443 // ç§»é¤æææ¶æ¯ä¸çéç½®å¾æ 4444 $('.reset-icon').remove(); 4445 } 4446 4447 4448 4449 /** 4450* æ£æµå¹¶å¤çå½åæ¶æ¯ç.content-bodyä¸ç.pdb龿¥ 4451* @param {string} messageId - æ¶æ¯ID 4452*/ 4453 function checkAndProcessPdbLinks(messageId) { 4454 const $message = $(`#${messageId}`); 4455 if (!$message.length) { 4456 console.warn('æªæ¾å°æ¶æ¯å®¹å¨:', messageId); 4457 return; 4458 } 4459 4460 // 为å½åæ¶æ¯åå§åå¤çç¶æ 4461 if (!processedPdbLinks.has(messageId)) { 4462 processedPdbLinks.set(messageId, new Set()); 4463 } 4464 const messageProcessedLinks = processedPdbLinks.get(messageId); 4465 4466 // åªæ¥æ¾å½åæ¶æ¯ç.content-bodyä¸ç.pdb龿¥ 4467 const $pdbLinks = $message.find('.content-body a[href*=".pdb"]'); 4468 4469 if ($pdbLinks.length > 0) { 4470 // 为æ¯ä¸ªPDB龿¥å¤çhref屿§ 4471 $pdbLinks.each(function () { 4472 const $link = $(this); 4473 const originalHref = $link.attr('href'); 4474 4475 // ç§»é¤åå§href屿§ï¼å°å ¶å¼ä¿åå°data-original-hrefä¸ 4476 $link.removeAttr('href'); 4477 $link.attr('data-original-href', originalHref); 4478 }); 4479 // æ¶éææPDB龿¥æ°æ®ï¼ææä»¶ååç» 4480 const pdbDataMap = new Map(); // fileName -> { epitopes: [], originalUrls: [] } 4481 4482 $pdbLinks.each(function (index) { 4483 const $link = $(this); 4484 const href = $link.attr('data-original-href'); 4485 4486 // æ£æ¥å½åæ¶æ¯æ¯å¦å·²ç»å¤çè¿è¿ä¸ªé¾æ¥ 4487 if (messageProcessedLinks.has(href)) { 4488 return; 4489 } 4490 4491 if (href && href.includes('.pdb')) { 4492 const pdbData = parsePdbUrl(href); 4493 if (pdbData && pdbData.fileName) { 4494 // console.log('æåè§£æPDBæ°æ®:', pdbData); 4495 // æ è®°è¯¥é¾æ¥å¨å½åæ¶æ¯ä¸å·²å¤ç 4496 messageProcessedLinks.add(href); 4497 4498 // ææä»¶ååç»æ¶éæ°æ® 4499 if (!pdbDataMap.has(pdbData.fileName)) { 4500 pdbDataMap.set(pdbData.fileName, { 4501 fileName: pdbData.fileName, 4502 epitopes: [], 4503 originalUrls: [] 4504 }); 4505 } 4506 4507 const fileData = pdbDataMap.get(pdbData.fileName); 4508 fileData.originalUrls.push(href); 4509 4510 // 妿æepitopeä¿¡æ¯ï¼æ·»å å°åè¡¨ä¸ 4511 if (pdbData.epitopeStart && pdbData.epitopeEnd) { 4512 fileData.epitopes.push({ 4513 start: pdbData.epitopeStart, 4514 end: pdbData.epitopeEnd, 4515 originalUrl: href 4516 }); 4517 } 4518 } else {
4519 console.warn('æ æ³è§£æPDB龿¥:', href, 'è§£æç»æ:', pdbData); 4520 } 4521 } else { 4522 console.warn('龿¥ä¸å å«.pdbæä¸ºç©º:', href); 4523 } 4524 }); 4525 4526 // 为æ¯ä¸ªå¯ä¸çPDBæä»¶æ¸²æ3Dç»æ 4527 pdbDataMap.forEach((fileData, fileName) => { 4528 // æ¾å°ç¬¬ä¸ä¸ªç¸å ³ç龿¥å ç´ ä½ä¸ºæå ¥ä½ç½® 4529 const firstLink = $pdbLinks.filter(function () { 4530 const href = $(this).attr('data-original-href'); 4531 if (href && href.includes('.pdb')) { 4532 const pdbData = parsePdbUrl(href); 4533 return pdbData && pdbData.fileName === fileName; 4534 } 4535 return false; 4536 }).first(); 4537 4538 loadPdbAndRender3D(fileData, messageId, firstLink.length > 0 ? firstLink : null); 4539 }); 4540 } else { 4541 // console.log('å½åæ¶æ¯ä¸æªæ¾å°ä»»ä½PDB龿¥'); 4542 } 4543 } 4544 4545 /** 4546 * è§£æPDB URLï¼å°æ´ä¸ªURLä½ä¸ºfileNameåæ° 4547 * @param {string} url - PDB龿¥ 4548 * @returns {Object} å å«fileNameç对象 4549 */ 4550 function parsePdbUrl(url) { 4551 try { 4552 // æ£æ¥URLæ¯å¦ææ 4553 if (!url || typeof url !== 'string') { 4554 console.warn('æ æçURL:', url); 4555 return null; 4556 } 4557 4558 // è§£æURLåæ° 4559 const urlObj = new URL(url); 4560 const fileName = urlObj.pathname; // è·åè·¯å¾é¨åä½ä¸ºfileName 4561 const epitopeStart = urlObj.searchParams.get('epitopeStart'); 4562 const epitopeEnd = urlObj.searchParams.get('epitopeEnd'); 4563 4564 const result = { 4565 fileName: fileName, 4566 originalUrl: url 4567 }; 4568 4569 // 妿æepitopeåæ°ï¼æ·»å å°ç»æä¸ 4570 if (epitopeStart && epitopeEnd) { 4571 result.epitopeStart = parseInt(epitopeStart); 4572 result.epitopeEnd = parseInt(epitopeEnd); 4573 // console.log('è§£æå°epitopeåºæ®µ:', { start: result.epitopeStart, end: result.epitopeEnd }); 4574 } 4575 4576 // console.log('è§£æPDB URLæå:', result); 4577 return result; 4578 } catch (error) { 4579 // console.error('è§£æPDB URL失败:', error, 'URL:', url); 4580 return null; 4581 } 4582 } 4583 4584 /** 4585 * ä»å®¹å¨ID䏿åæä»¶å 4586 * @param {string} containerId - 容å¨IDï¼æ ¼å¼ï¼pdb3d-${messageId}-${linkHash} 4587 * @returns {string|null} æä»¶å 4588 */ 4589 function extractFileNameFromContainerId(containerId) { 4590 try { 4591 // 容å¨IDæ ¼å¼ï¼pdb3d-${messageId}-${linkHash} 4592 // æä»¬éè¦ä»linkHashä¸è§£ç åºåå§çæä»¶å 4593 const parts = containerId.split('-'); 4594 if (parts.length >= 3) { 4595 const linkHash = parts.slice(2).join('-'); 4596 // å°è¯è§£ç base64ç¼ç çhash 4597 try { 4598 const decoded = atob(linkHash); 4599 const fileName = decodeURIComponent(decoded); 4600 // console.log('ä»å®¹å¨IDæåæä»¶å:', fileName); 4601 return fileName; 4602 } catch (e) { 4603 // console.warn('æ æ³è§£ç 容å¨IDä¸çæä»¶å:', containerId); 4604 return null; 4605 } 4606 } 4607 return null; 4608 } catch (error) { 4609 console.error('æåæä»¶å失败:', error, 'containerId:', containerId); 4610 return null; 4611 } 4612 } 4613 4614 /**
4615 * å è½½PDBæä»¶å¹¶æ¸²æ3Dç»æ 4616 * @param {Object} pdbData - PDBæ°æ®å¯¹è±¡ï¼æ¯æå¤ä¸ªepitopeåºæ®µ 4617 * @param {string} messageId - æ¶æ¯ID 4618 * @param {jQuery} $linkElement - PDB龿¥å ç´ ï¼å¯éï¼ 4619 */ 4620 function loadPdbAndRender3D(pdbData, messageId, $linkElement) { 4621 // éªè¯è¾å ¥åæ° 4622 if (!pdbData || !pdbData.fileName) { 4623 console.error('loadPdbAndRender3D: æ æçpdbDataåæ°', pdbData); 4624 return; 4625 } 4626 4627 if (!messageId) { 4628 console.error('loadPdbAndRender3D: 缺å°messageIdåæ°'); 4629 return; 4630 } 4631 4632 const { fileName, epitopes = [] } = pdbData; 4633 4634 // çæåºäºé¾æ¥çå¯ä¸å®¹å¨ID 4635 const linkHash = btoa(encodeURIComponent(fileName)).replace(/[^a-zA-Z0-9]/g, '').substring(0, 8); 4636 const containerId = `pdb3d-${messageId}-${linkHash}`; 4637 4638 const $message = $(`#${messageId}`); 4639 4640 // æ£æ¥æ¶æ¯å®¹å¨æ¯å¦åå¨ 4641 if (!$message.length) { 4642 console.error('loadPdbAndRender3D: æªæ¾å°æ¶æ¯å®¹å¨', messageId); 4643 return; 4644 } 4645 4646 // æ£æ¥æ¯å¦å·²ç»åå¨ç¸åç3Då®¹å¨ 4647 if ($message.find(`#${containerId}`).length > 0) { 4648 // console.log('3D容å¨å·²åå¨ï¼è·³è¿å建:', containerId); 4649 return; 4650 } 4651 4652 // é¢å¤æ£æ¥ï¼å¦æå·²ç»åå¨ç¸åæä»¶åç容å¨ï¼ä¹è·³è¿å建 4653 const existingContainers = $message.find('.pdb-3d-container'); 4654 for (let i = 0; i < existingContainers.length; i++) { 4655 const existingId = $(existingContainers[i]).attr('id'); 4656 if (existingId && existingId.includes(fileName.replace(/[^a-zA-Z0-9]/g, ''))) { 4657 // console.log('ç¸åæä»¶åç3D容å¨å·²åå¨ï¼è·³è¿å建:', fileName); 4658 return; 4659 } 4660 } 4661 4662 const containerHtml = ` 4663 <div id="${containerId}" class="pdb-3d-container mt-3"> 4664 <div class="mol-container" style="width: 100%; height: 400px; background: black; border-radius: 4px;"></div> 4665 </div> 4666 `; 4667 4668 4669 4670 // å°3D容卿·»å å°æå®ä½ç½® 4671 if ($linkElement && $linkElement.length) { 4672 // å¨é¾æ¥åé¢æå ¥3Då®¹å¨ 4673 $linkElement.after(containerHtml); 4674 // æ è®°è¯¥é¾æ¥å·²æ¸²æ 4675 $linkElement.data('pdb-rendered', true); 4676 } else { 4677 // å¦ææ²¡ææå®é¾æ¥å ç´ ï¼æ·»å å°content-bodyæ«å°¾ 4678 $message.find('.content-body').append(containerHtml); 4679 } 4680 4681 // æ è®°æ¶æ¯å·²æ3Då®¹å¨ 4682 $message.data('has-3d-containers', true); 4683 4684 // æ¾ç¤ºå è½½ç¶æ 4685 $(`#${containerId} .mol-container`).html(` 4686 <div class="text-center text-white p-4"> 4687 <i class="fa fa-spinner fa-spin fa-2x mb-2"></i> 4688 <p>loading 3D model...</p> 4689 </div> 4690 `); 4691 4692 4693 // 请æ±PDBæä»¶ï¼å°æ´ä¸ªURLä½ä¸ºfileNameåæ° 4694 fetch(`https://www.ptglab.com/api/pdbfile?fileName=${encodeURIComponent(fileName)}`) 4695 .then(response => { 4696 if (!response.ok) {
4697 throw new Error(`HTTPé误ï¼ç¶æç : ${response.status}`); 4698 } 4699 return response.text(); 4700 }) 4701 .then(pdbData => { 4702 if (!pdbData || pdbData.trim() === '') { 4703 throw new Error('PDBæä»¶å 容为空'); 4704 } 4705 4706 // æ·»å çæå»¶è¿ï¼ç¡®ä¿DOMå ç´ å®å ¨åå¤å¥½ 4707 setTimeout(() => { 4708 // ä¼ éepitopesä¿¡æ¯ç»æ¸²æå½æ° 4709 const epitopesInfo = epitopes.length > 0 ? epitopes : null; 4710 renderPdbStructure(containerId, pdbData, epitopesInfo); 4711 }, 200); 4712 }) 4713 .catch(error => { 4714 console.error('å è½½PDBæä»¶å¤±è´¥:', error); 4715 $(`#${containerId} .mol-container`).html(` 4716 <div class="text-center text-white p-4"> 4717 <i class="fa fa-exclamation-triangle fa-2x mb-2"></i> 4718 <p>Error</p> 4719 <p class="small">${error.message}</p> 4720 <button class="btn btn-sm btn-outline-light mt-2" onclick="retryLoadPdb('${containerId}', '${fileName}', ${epitopes.length > 0 ? JSON.stringify(epitopes) : 'null'})"> 4721 <i class="fa fa-refresh"></i> try 4722 </button> 4723 </div> 4724 `); 4725 }); 4726 } 4727 4728 /** 4729 * 渲æPDBç»æå°3Då®¹å¨ 4730 * @param {string} containerId - 容å¨ID 4731 * @param {string} pdbData - PDBæä»¶æ°æ® 4732 * @param {Array} epitopesInfo - epitopeåºæ®µæ°ç»ï¼æ¯ä¸ªå ç´ å å«startãendãoriginalUrl 4733 */ 4734 function renderPdbStructure(containerId, pdbData, epitopesInfo = null) { 4735 try { 4736 // åå§å3Dmolæ¥çå¨ 4737 const element = document.querySelector(`#${containerId} .mol-container`); 4738 if (!element) { 4739 console.error('æªæ¾å°3D容å¨å ç´ :', containerId); 4740 return; 4741 } 4742 4743 // æ£æ¥å®¹å¨æ¯å¦å·²ç»è¢«å ¶ä»3D模åå ç¨ 4744 if (element.viewer) { 4745 console.log('容å¨å·²æviewerå®ä¾ï¼è·³è¿æ¸²æ:', containerId); 4746 return; 4747 } 4748 4749 // console.log(element.innerHTML, "12212"); 4750 // æ¸ ç©ºå®¹å¨å 容ï¼ç¡®ä¿å¹²åçç¶æ 4751 element.innerHTML = ''; 4752 4753 const config = { backgroundColor: 'black' }; 4754 const viewer = $3Dmol.createViewer(element, config); 4755 4756 // å°viewerå®ä¾ä¿åå°å®¹å¨ä¸ï¼ä»¥ä¾¿åç»æä½ 4757 element.viewer = viewer; 4758 // æ·»å PDB模å 4759 viewer.addModel(pdbData, 'pdb'); 4760 4761 // 设置é»è®¤æ ·å¼ 4762 viewer.setStyle( 4763 { chain: 'A' }, 4764 { cartoon: { color: '#f7f7f7', width: 1.2 } } 4765 ); 4766 4767 // 妿æepitopeåºæ®µä¿¡æ¯ï¼æ·»å é«äº®æ¾ç¤º 4768 if (epitopesInfo && epitopesInfo.length > 0) { 4769 // å®ä¹ä¸åé¢è²ç¨äºåºåå¤ä¸ªåºæ®µ 4770 const colors = ['#ff0000', '#00ff00', '#0000ff', '#ffff00', '#ff00ff', '#00ffff']; 4771 // 为æ¯ä¸ªepitopeåºæ®µè®¾ç½®ä¸åé¢è²çé«äº® 4772 epitopesInfo.forEach((epitope, index) => { 4773 const color = colors[index % colors.length]; 4774 // 为å½åepitopeåºæ®µè®¾ç½®é«äº® 4775 viewer.setStyle( 4776 { 4777 chain: 'A', 4778 resi: Array.from({ length: epitope.end - epitope.start + 1 }, (_, i) => epitope.start + i) 4779 }, 4780 { 4781 cartoon: { color: color, width: 2.0 }, // ä¸åé¢è²ï¼æ´ç² 4782 stick: { color: color, radius: 0.3 } // ä¸åé¢è²çæ£æ¨¡å 4783 } 4784 ); 4785 }); 4786 4787 // è§£æPDBæ°æ®ï¼æ¾å°å ·ä½åååæ 4788 const lines = pdbData.split('\n'); 4789 const atomMap = new Map(); // å卿æååçåæ ä¿¡æ¯ 4790 4791 // æ¶éææåååæ 4792 for (const line of lines) { 4793 if (line.startsWith('ATOM')) { 4794 const resSeq = parseInt(line.substring(22, 26).trim(), 10); 4795 const resName = line.substring(17, 20).trim(); // æ®åºåç§° 4796 const x = parseFloat(line.substring(30, 38).trim()); 4797 const y = parseFloat(line.substring(38, 46).trim()); 4798 const z = parseFloat(line.substring(46, 54).trim()); 4799 4800 atomMap.set(resSeq, { x, y, z, resSeq, resName }); 4801 } 4802 } 4803 4804 // 为æ¯ä¸ªepitopeåºæ®µæ·»å æ ç¾ 4805 epitopesInfo.forEach((epitope, index) => { 4806 const color = colors[index % colors.length]; 4807 4808 // æ·»å åºæ®µæ ç¾ 4809 const centerResidue = Math.floor((epitope.start + epitope.end) / 2); 4810 viewer.addLabel( 4811 `Epitope ${index + 1}: ${epitope.start}-${epitope.end}`, 4812 { 4813 position: { chain: 'A', resi: centerResidue }, 4814 backgroundColor: color, 4815 fontColor: 'white', 4816 fontSize: 12, 4817 borderThickness: 1, 4818 borderColor: 'white',
4819 showBackground: true 4820 } 4821 ); 4822 4823 // æ·»å èµ·å§åç»æä½ç½®çæ ç¾ 4824 const startAtom = atomMap.get(epitope.start); 4825 const endAtom = atomMap.get(epitope.end); 4826 4827 if (startAtom) { 4828 viewer.addLabel( 4829 `${startAtom.resName} ${startAtom.resSeq}`, 4830 { 4831 position: { x: startAtom.x, y: startAtom.y, z: startAtom.z }, 4832 fontColor: color, 4833 fontSize: 14, 4834 backgroundOpacity: 0, 4835 font: "Arial Bold" 4836 } 4837 ); 4838 } 4839 4840 if (endAtom) { 4841 viewer.addLabel( 4842 `${endAtom.resName} ${endAtom.resSeq}`, 4843 { 4844 position: { x: endAtom.x, y: endAtom.y, z: endAtom.z }, 4845 fontColor: color, 4846 fontSize: 14, 4847 backgroundOpacity: 0, 4848 font: "Arial Bold" 4849 } 4850 ); 4851 } 4852 }); 4853 } 4854 4855 // è®¾ç½®èæ¯è²å¹¶æ¸²æ 4856 viewer.setBackgroundColor('black'); 4857 viewer.zoomTo(); 4858 viewer.render(); 4859 // æ·»å æè½¬æ§å¶ 4860 add3DControls(viewer, containerId); 4861 4862 } catch (error) { 4863 console.error('渲æ3Dç»æå¤±è´¥:', error, 'containerId:', containerId); 4864 const element = document.querySelector(`#${containerId} .mol-container`); 4865 if (element) { 4866 element.innerHTML = ` 4867 <div class="text-center text-white p-4"> 4868 <i class="fa fa-exclamation-triangle"></i> 4869 <p>渲æ3Dç»æå¤±è´¥: ${error.message}</p> 4870 <button class="btn btn-sm btn-outline-light mt-2" onclick="retryLoadPdb('${containerId}', '${pdbData}', null)"> 4871 <i class="fa fa-refresh"></i> éè¯ 4872 </button> 4873 </div> 4874 `; 4875 } 4876 } 4877 } 4878 4879 /** 4880 * æ·»å 3Dæ§å¶åè½ 4881 * @param {Object} viewer - 3Dmolæ¥çå¨å®ä¾ 4882 * @param {string} containerId - 容å¨ID 4883 */ 4884 function add3DControls(viewer, containerId) { 4885 const container = document.querySelector(`#${containerId} .mol-container`); 4886 let isDragging = false; 4887 let lastX = 0; 4888 let lastY = 0; 4889 4890 // é¼ æ æ§å¶ 4891 container.addEventListener('mousedown', function (event) { 4892 isDragging = true; 4893 lastX = event.clientX; 4894 lastY = event.clientY; 4895 }); 4896 4897 container.addEventListener('mousemove', function (event) { 4898 if (isDragging) { 4899 const dx = event.clientX - lastX; 4900 const dy = event.clientY - lastY; 4901 viewer.rotate(dy * 0.5, dx * 0.5); 4902 lastX = event.clientX; 4903 lastY = event.clientY; 4904 } 4905 }); 4906 4907 container.addEventListener('mouseup', function () { 4908 isDragging = false; 4909 }); 4910 4911 // æ»è½®ç¼©æ¾ 4912 container.addEventListener('wheel', function (event) { 4913 event.preventDefault(); 4914 const scale = event.deltaY > 0 ? 0.9 : 1.1; 4915 viewer.zoom(scale); 4916 }); 4917 } 4918 4919 /** 4920 * 忢3Dè§å¾æ¾ç¤º/éè 4921 * @param {string} containerId - 容å¨ID 4922 */ 4923 function toggle3DView(containerId) { 4924 const container = document.getElementById(containerId); 4925 if (container) { 4926 if (container.style.display === 'none') { 4927 container.style.display = 'block'; 4928 } else { 4929 container.style.display = 'none'; 4930 } 4931 } 4932 } 4933 4934 /** 4935 * éç½®3Dè§å¾ 4936 * @param {string} containerId - 容å¨ID 4937 */ 4938 function reset3DView(containerId) { 4939 const container = document.querySelector(`#${containerId} .mol-container`); 4940 if (container && container.viewer) { 4941 container.viewer.zoomTo(); 4942 container.viewer.render(); 4943 } 4944 } 4945 4946 /** 4947 * éè¯å è½½PDBæä»¶ 4948 * @param {string} containerId - 容å¨ID 4949 * @param {string} fileName - æä»¶å 4950 * @param {Array} epitopesInfo - epitopeåºæ®µæ°ç» 4951 */ 4952 function retryLoadPdb(containerId, fileName, epitopesInfo = null) { 4953 // æ¾ç¤ºå è½½ç¶æ 4954 $(`#${containerId} .mol-container`).html(` 4955 <div class="text-center text-white p-4"> 4956 <i class="fa fa-spinner fa-spin fa-2x mb-2"></i> 4957 <p>loading...</p> 4958 </div> 4959 `); 4960 4961 // éæ°è¯·æ±PDBæä»¶ 4962 fetch(`https://www.ptglab.com/api/pdbfile?fileName=${encodeURIComponent(fileName)}`) 4963 .then(response => { 4964 if (!response.ok) {
4965 throw new Error(`HTTPé误ï¼ç¶æç : ${response.status}`); 4966 } 4967 return response.text(); 4968 }) 4969 .then(pdbData => { 4970 if (!pdbData || pdbData.trim() === '') { 4971 throw new Error('PDBæä»¶å 容为空'); 4972 } 4973 renderPdbStructure(containerId, pdbData, epitopesInfo); 4974 }) 4975 .catch(error => { 4976 console.error('éè¯å è½½PDBæä»¶å¤±è´¥:', error); 4977 $(`#${containerId} .mol-container`).html(` 4978 <div class="text-center text-white p-4"> 4979 <i class="fa fa-exclamation-triangle fa-2x mb-2"></i> 4980 <p>error</p> 4981 <p class="small">${error.message}</p> 4982 <button class="btn btn-sm btn-outline-light mt-2" onclick="retryLoadPdb('${containerId}', '${fileName}', ${epitopesInfo ? JSON.stringify(epitopesInfo) : 'null'})"> 4983 <i class="fa fa-refresh"></i> try again 4984 </button> 4985 </div> 4986 `); 4987 }); 4988 } 4989 // äºä»¶çå¬ï¼ä¿çåé»è¾ï¼ 4990 $('#attachFileBtn').on('click', function () { 4991 if (isLoading) return; 4992 $('#fileInput').click(); 4993 }); 4994 $('#fileInput').on('change', function (e) { 4995 const file = e.target.files && e.target.files[0]; 4996 if (!file) return; 4997 handleFileSelect(file); 4998 }); 4999 5000 let fileDropCounter = 0; 5001 const $chatInputWrapper = $('#chatInputWrapper'); 5002 const $chatInputDropzone = $('#chatInputDropzone'); 5003 $chatInputWrapper.on('dragenter', function (e) { 5004 e.preventDefault(); 5005 e.stopPropagation(); 5006 if (isLoading) return; 5007 fileDropCounter++; 5008 $chatInputDropzone.addClass('drag-over'); 5009 }); 5010 $chatInputWrapper.on('dragleave', function (e) { 5011 e.preventDefault(); 5012 e.stopPropagation(); 5013 fileDropCounter--; 5014 if (fileDropCounter <= 0) { 5015 fileDropCounter = 0; 5016 $chatInputDropzone.removeClass('drag-over'); 5017 } 5018 }); 5019 $chatInputWrapper.on('dragover', function (e) { 5020 e.preventDefault(); 5021 e.stopPropagation(); 5022 }); 5023 $chatInputWrapper.on('drop', function (e) { 5024 e.preventDefault(); 5025 e.stopPropagation(); 5026 fileDropCounter = 0; 5027 $chatInputDropzone.removeClass('drag-over'); 5028 const file = getFileFromDataTransfer(e.originalEvent.dataTransfer); 5029 attemptAttachFile(file, false); 5030 }); 5031 5032 $('#chatInputWrapper').on('paste', function (e) { 5033 const file = getFileFromClipboardEvent(e); 5034 if (!file) return; 5035 e.preventDefault(); 5036 attemptAttachFile(file, true); 5037 }); 5038 5039 5040 $('#removeFileBtn').on('click', function () { 5041 clearPendingFile(); 5042 userInput.focus(); 5043 }); 5044 // è¾å ¥æ¡éå 容å¢é«ï¼æå¤ 5 è¡ååºç°æ»å¨æ¡ 5045 function autoResizeUserInput() { 5046 const el = userInput[0]; 5047 if (!el) return; 5048 el.style.height = 'auto'; 5049 const maxHeight = parseFloat(window.getComputedStyle(el).maxHeight) || el.scrollHeight; 5050 el.style.height = Math.min(el.scrollHeight, maxHeight) + 'px'; 5051 } 5052 userInput.on('input', autoResizeUserInput); 5053 5054 // äºä»¶çå¬ï¼ä¿çåé»è¾ï¼ 5055 sendBtn.on('click', sendMessage); 5056 userInput.on('keydown', function (e) { 5057 if (e.which === 13 && !e.shiftKey) { 5058 e.preventDefault(); 5059 sendMessage(); 5060 } 5061 }); 5062 clearBtn.on('click', clearMessages); 5063 $(document).on('keydown', function (e) { 5064 if (e.key === 'Escape' && isLoading) {
5065 abortRequest(true); 5066 isLoading = false; 5067 $('#sendBtn').removeClass('loading'); 5068 $('#sendBtn').off('click').on('click', sendMessage); 5069 $('.loading-indicator, .load-more-indicator').remove(); 5070 } 5071 }); 5072 userInput.focus(); 5073 5074 5075 // ç嬿¥èªç¶é¡µé¢ç ableChat æ¶æ¯ï¼æç´¢ç»æé¡µ "Chat with Able" æé®ï¼ 5076 window.addEventListener('message', function (event) { 5077 var data = event.data; 5078 if (data && data.type === 'ableChat' && data.keyword) { 5079 console.log(data.keyword, "data.keyword"); 5080 5081 // å å»¶è¿300msï¼è®© focusInput çæ¶æ¯å å¤çå® 5082 setTimeout(function () { 5083 // çåå²è®°å½å è½½å®æåååéï¼é¿å 被 renderConversationHistory æ¸ ç©º 5084 function waitAndSend(keyword, retries) { 5085 var stillLoading = $('#restoreLoading').length > 0 || $('#sendBtn').prop('disabled'); 5086 if (stillLoading && retries > 0) { 5087 setTimeout(function () { waitAndSend(keyword, retries - 1); }, 300); 5088 return; 5089 } 5090 // å è½½å®æï¼æè¶ æ¶ï¼ï¼æ§è¡åé 5091 $(".able-intro").css("display", "none"); 5092 $('#userInput').val(keyword); 5093 $('#sendBtn').prop('disabled', false); 5094 isLoading = false; 5095 sendMessage(); 5096 } 5097 5098 // æå¤ç 30 次 à 300ms = 9ç§ 5099 waitAndSend(data.keyword, 30); 5100 }, 300); 5101 } 5102 }); 5103 5104 5105 function collapseThinkContent(messageId) { 5106 const $message = $(`#${messageId}`); 5107 const $thinkContent = $message.find('.think-content-body'); 5108 if ($thinkContent.length) { 5109 $thinkContent.slideUp(300, function () { 5110 $thinkContent.addClass('think-collapsed'); 5111 $message.find('.think-toggle-icon') 5112 .removeClass('fa-chevron-down') 5113 .addClass('fa-chevron-right'); 5114 }); 5115 } 5116 } 5117 5118 // ãå ³é®å é¤ãåºå¼æ§çæèå 容å¤ç彿°ï¼ä¸å使ç¨ï¼ 5119 // function displayThinkContet(messageId) { ... } 5120 5121 // MCPç¸å ³å½æ°ï¼ä¿çåé»è¾ï¼ 5122 function fetchMcpHostResult(uuid, messageId) { 5123 fetch(`https://aipublic.ptglab.com/llm/v1/mcp/result-list/${uuid}`, { method: 'GET' }) 5124 .then(response => { 5125 if (!response.ok) throw new Error(`HTTPé误ï¼ç¶æç : ${response.status}`); 5126 return response.json(); 5127 }) 5128 .then(data => { 5129 handleApiData(messageId, data); 5130 resultList = data?.data; 5131 // if (data?.data && Array.isArray(data.data) && data.data.length > 0) { 5132 // const $rightPanel = $('#rightPanel'); 5133 // if (!$rightPanel.hasClass('slide-in')) { 5134 // // $('#openRight').trigger('click'); 5135 // $('#rightPanel').removeClass('right-panel slide-out').addClass('slide-in'); 5136 // $('#app').removeClass('mx-auto').removeClass("max-w-7xl").css('width', '75%'); 5137 // } 5138 // setTimeout(() => scrollToBottom(true), 300); 5139 // } 5140 }) 5141 .catch(error => console.error('请æ±MCP_HOST_RESULTæ¥å£å¤±è´¥:', error)); 5142 } 5143 5144 function handleApiData(messageId, apiData, userText) { 5145 if (!Array.isArray(apiData?.data)) { 5146 console.warn('æ°æ®æ ¼å¼é误:', apiData); 5147 return; 5148 } 5149 messageGroups.clear(); 5150 collapseStates.clear(); 5151 apiData.data.forEach(item => { 5152 const roundId = item.round_id; 5153 const parsedArgs = parseArgs(item.args); 5154 const messageItem = { ...item, parsedArgs }; 5155 if (messageGroups.has(roundId)) { 5156 messageGroups.get(roundId).messages.push(messageItem); 5157 } else { 5158 messageGroups.set(roundId, { roundId, messages: [messageItem], count: 1 }); 5159 collapseStates.set(roundId, roundId !== messageId); 5160 } 5161 }); 5162 renderGroupedContent(); 5163 } 5164 5165 function renderGroupedContent() { 5166 const $container = $('#scrollableContent'); 5167 $container.empty(); 5168 messageGroups.forEach((group, roundId) => { 5169 const { messages } = group; 5170 const isCollapsed = collapseStates.get(roundId); 5171 const targetItem = userChat.find(item => item.round_id === roundId); 5172 const content = targetItem?.content; 5173 const $group = $(` 5174 <div class="group-wrapper border border-gray-200 rounded-lg mb-4"> 5175 <div class="group-header flex justify-between items-center p-3 bg-gray-50 cursor-pointer"> 5176 <h3 class="font-semibold tooltip-trigger" data-full-content="${content || ''}"> 5177 ${content || 'æªç¥è¯·æ±'} 5178 </h3> 5179 <i class="fa ${isCollapsed ? 'fa-chevron-down' : 'fa-chevron-up'}"></i> 5180 </div> 5181 <div class="group-content ${isCollapsed ? 'hidden' : ''} p-3 border-t"> 5182 </div> 5183 </div> 5184 `); 5185 const $content = $group.find('.group-content');
5186 messages.forEach((msg) => { 5187 const $message = $(` 5188 <div class="message-item border border-gray-100 rounded mb-3" data-id=${msg.id}> 5189 <div class="args-container"></div> 5190 </div> 5191 `); 5192 renderArgs($message.find('.args-container'), msg.parsedArgs, msg.tool); 5193 $content.append($message); 5194 }); 5195 $group.find('.group-header').on('click', function () { 5196 const newState = !isCollapsed; 5197 collapseStates.set(roundId, newState); 5198 $(this).find('i').toggleClass('fa-chevron-down fa-chevron-up'); 5199 $(this).next('.group-content').toggleClass('hidden'); 5200 }); 5201 const $headerTitle = $group.find('.tooltip-trigger'); 5202 $headerTitle 5203 .off('touchstart touchend touchleave mouseenter mouseleave') 5204 .on('touchstart', function (e) { e.preventDefault(); showTooltip(this); }) 5205 .on('touchend touchleave', hideTooltip) 5206 .on('mouseenter', () => showTooltip($headerTitle[0])) 5207 .on('mouseleave', hideTooltip); 5208 $container.append($group); 5209 }); 5210 $('#scrollableContent') 5211 .off('click', '.message-item') 5212 .on('click', '.message-item', clickTab); 5213 } 5214 5215 function clickTab() { 5216 const queryString = $(this).data("id"); 5217 const matchingItems = resultList.filter(item => item.id == queryString); 5218 const args = matchingItems.length > 0 ? matchingItems[0].args : ""; 5219 let jumpParams = convertJsonToQuery(args); 5220 window.open(`https://ptgcn.com/results?g=&${jumpParams}`, '_blank'); 5221 } 5222 5223 function parseArgs(argsStr) { 5224 try { 5225 return argsStr ? JSON.parse(argsStr) : {}; 5226 } catch (e) { 5227 console.error('è§£æargs失败:', e); 5228 return { error: 'è§£æå¤±è´¥' }; 5229 } 5230 } 5231 5232 function renderArgs($container, argsObj, toolName) { 5233 if (toolName) { 5234 const $toolInfo = $(` 5235 <div class="p-2 text-sm font-medium border-b border-gray-100"> 5236 Tool: ${toolName} 5237 </div> 5238 `); 5239 $container.append($toolInfo); 5240 } 5241 if (!argsObj || !Object.keys(argsObj).length) { 5242 $container.html('<div class="text-sm text-gray-500 p-2">æ åæ°</div>'); 5243 return; 5244 } 5245 const $panel = $(` 5246 <div class="p-2 text-sm"> 5247 ${Object.entries(argsObj).map(([k, v]) => ` 5248 <div><span class="font-medium">${k}:</span> ${Array.isArray(v) ? v.join(', ') : v}</div> 5249 `).join('')} 5250 </div> 5251 `); 5252 $container.append($panel); 5253 } 5254 5255 function convertJsonToQuery(jsonStr) { 5256 try { 5257 const data = JSON.parse(jsonStr); 5258 const queryParams = []; 5259 function processArray(key, values) {
5260 values.forEach(value => queryParams.push(`${encodeURIComponent(key)}=${encodeURIComponent(value)}`)); 5261 } 5262 function processValue(key, value) { 5263 queryParams.push(`${encodeURIComponent(key)}=${encodeURIComponent(value)}`); 5264 } 5265 Object.entries(data).forEach(([key, value]) => { 5266 Array.isArray(value) ? processArray(key, value) : processValue(key, value); 5267 }); 5268 return queryParams.join('%26'); 5269 } catch (error) { 5270 console.error('JSONè§£æé误:', error); 5271 return ''; 5272 } 5273 } 5274 5275 5276 function normalizeMermaidCode(raw) { 5277 let code = (raw || '').trim(); 5278 code = code.replace(/>/g, '>').replace(/</g, '<'); 5279 code = code.replace(/^graph LR/gm, 'graph TB'); 5280 return code; 5281 } 5282 5283 // æµå¼è¿ç¨ä¸ mermaid 代ç å¯è½ä¸å®æ´ï¼ä¸å®æ´æ¶ä¸æ¸²æã䏿¾ç¤º error 5284 function isMermaidCodeLikelyComplete(code) { 5285 if (!code || !code.trim()) return false; 5286 const lines = code.split('\n').map(l => l.trim()).filter(Boolean); 5287 if (lines.length < 2) return false; 5288 const lastLine = lines[lines.length - 1]; 5289 if (/(-->|---|-\.-|==>|:::|\[|\(|\{|--)$/.test(lastLine)) return false; 5290 if (/^[A-Za-z0-9_]+\s*(-->|---|-\.-)/.test(lastLine) && !/(-->|---|-\.-).+[A-Za-z0-9_\[\(]/.test(lastLine)) { 5291 return false; 5292 } 5293 return true; 5294 } 5295 5296 function restoreMermaidSourceNode(preNode, code) { 5297 if (!preNode || !preNode.parentNode) return; 5298 const restorePre = document.createElement('pre'); 5299 const restoreCode = document.createElement('code'); 5300 restoreCode.className = 'language-mermaid'; 5301 restoreCode.textContent = code; 5302 restorePre.appendChild(restoreCode); 5303 preNode.parentNode.replaceChild(restorePre, preNode); 5304 } 5305 5306 function applyGroupLinkFormatting($group) { 5307 if (!$group || !$group.length) return; 5308 $group.find('a:not([target="_blank"])').attr('target', '_blank'); 5309 } 5310 5311 // DOM å ¨éé建åï¼æå·²ç¼åç SVG è´´åå»ï¼ä¸è§¦å mermaid.renderï¼ 5312 function reinjectCachedMermaidInContainer(messageId, buffer, $container) { 5313 if (!buffer?.renderedMermaidCache || !$container?.length) return; 5314 const targetDom = $container[0]; 5315 targetDom.querySelectorAll('pre:not([data-mermaid-rendered="success"]) > code.language-mermaid').forEach(codeEl => { 5316 const cacheKey = normalizeMermaidCode(codeEl.textContent); 5317 const svg = buffer.renderedMermaidCache[cacheKey]; 5318 if (!svg) return; 5319 const originalPreEl = codeEl.parentElement; 5320 if (!originalPreEl || originalPreEl.tagName !== 'PRE') return; 5321 const cachedPreEl = document.createElement('pre'); 5322 cachedPreEl.className = 'mermaid'; 5323 cachedPreEl.dataset.mermaidRendered = 'success'; 5324 cachedPreEl.dataset.mermaidCode = cacheKey; 5325 cachedPreEl.innerHTML = svg; 5326 originalPreEl.parentNode.replaceChild(cachedPreEl, originalPreEl); 5327 }); 5328 } 5329 5330 // æµå¼ç»æï¼è¡¥æ«ä»æªæ¸²æç mermaid 5331 function sweepUnrenderedMermaid(messageId, $container) { 5332 if (!$container?.length) return; 5333 $container.find('.segment-group').each(function () { 5334 if (this.querySelector('pre:not([data-mermaid-rendered="success"]) > code.language-mermaid')) { 5335 initMermaidRender(this, { messageId, skipIncomplete: false }); 5336 } 5337 }); 5338 } 5339 5340 function markGroupForFormat(buffer, groupId, reason) { 5341 if (!buffer || !groupId) return; 5342 if (!buffer.pendingFormatGroupIds) buffer.pendingFormatGroupIds = {}; 5343 buffer.pendingFormatGroupIds[groupId] = reason || 'unknown'; 5344 } 5345 5346 function formatPendingGroups(messageId, buffer, $container) { 5347 if (!buffer?.pendingFormatGroupIds || !$container?.length) return; 5348 const pendingIds = Object.keys(buffer.pendingFormatGroupIds); 5349 if (!pendingIds.length) return; 5350 5351 pendingIds.forEach(groupId => { 5352 const reason = buffer.pendingFormatGroupIds[groupId]; 5353 const $groups = $container.find(`.segment-group[data-group-id="${groupId}"]`); 5354 if (!$groups.length) { 5355 return; 5356 } 5357 5358 5359 $groups.each(function () { 5360 applyGroupLinkFormatting($(this)); 5361 initMermaidRender(this, { messageId, skipIncomplete: false }); 5362 }); 5363 5364 delete buffer.pendingFormatGroupIds[groupId]; 5365 }); 5366 } 5367 5368 function formatAllNonContentGroupsAtStreamEnd(messageId, buffer, $container) { 5369 if (!buffer?.nonContentGroups?.length) return; 5370 buffer.nonContentGroups.forEach(group => { 5371 markGroupForFormat(buffer, group.id, 'stream-end'); 5372 }); 5373 formatPendingGroups(messageId, buffer, $container); 5374 } 5375 5376 // reasoning / tool çæ data-group-id çæ®µï¼ä» 卿µå¼ç»ææ¶æ¸²æ 5377 function formatOrphanSegmentGroups(messageId, $container) { 5378 if (!$container?.length) return; 5379 const $orphans = $container.find('.segment-group:not([data-group-id])'); 5380 if (!$orphans.length) return; 5381 5382 5383 $orphans.each(function () { 5384 applyGroupLinkFormatting($(this)); 5385 initMermaidRender(this, { messageId, skipIncomplete: false }); 5386 }); 5387 } 5388 5389 //å±ç¤º flow chart 5390 function initMermaidRender(container = document, options = {}) { 5391 const { messageId = null, skipIncomplete = false } = options; 5392 const buffer = messageId ? responseBuffers[messageId] : null; 5393 // æ ¸å¿é»è¾å è£¹å¨ try-catch ä¸ï¼æè·ææå¼å¸¸ 5394 try {
5395 // 2. ç»ä¸è½¬æ¢ä¸ºåçDOMï¼å ¼å®¹jQuery对象/åçDOMï¼ï¼éå®éæ©å¨ä½ç¨å 5396 const targetDom = container instanceof jQuery ? container[0] : container; 5397 5398 // é²å¾¡æ§å¤æï¼ç¡®ä¿ targetDom æ¯ææDOMå ç´ ï¼é¿å querySelectorAll æ¥é 5399 if (!targetDom || !(targetDom instanceof HTMLElement)) { 5400 return; // æåéåºï¼ä¸å½±ååç» 5401 } 5402 5403 // å·²æå渲æçè·³è¿ï¼æªæ¸²æç code.language-mermaid æå¤ç 5404 const mermaidCodeElements = targetDom.querySelectorAll('pre:not([data-mermaid-rendered="success"]) > code.language-mermaid'); 5405 5406 5407 5408 // å ³é®ä¿®æ¹1ï¼å¦ææ²¡æéè¦æ¸²æçmermaidå ç´ ï¼ç´æ¥éåº 5409 if (mermaidCodeElements.length === 0) { 5410 return; 5411 } 5412 5413 // å卿°å建ç mermaid èç¹ï¼ç¨äºåç»æå¨æ¸²æ 5414 const newMermaidNodes = []; 5415 5416 // 3. éåæ¯ä¸ªå ç´ ï¼å 宿èç¹æ¹é ï¼æåå¤çèç¹ç»æï¼åç»æ¸²æç¨ï¼ 5417 mermaidCodeElements.forEach(codeEl => { 5418 // å å±ä¹å¢å è½»éé²å¾¡ï¼é¿å å个å ç´ å¤çå¤±è´¥å¯¼è´æ´ä¸ªéå䏿 5419 try { 5420 // 3.1 è·åç¶èç¹ï¼åå§ç pre æ ç¾ï¼ 5421 const originalPreEl = codeEl.parentElement; 5422 if (!originalPreEl || originalPreEl.tagName !== 'PRE') return; 5423 if (originalPreEl.dataset.mermaidRendered === 'success') return; 5424 5425 const mermaidCode = normalizeMermaidCode(codeEl.textContent); 5426 const cacheKey = mermaidCode; 5427 5428 // å½ä¸ç¼åï¼ç´æ¥æ³¨å ¥ SVGï¼ä¸åè°ç¨ mermaid.render 5429 if (buffer && buffer.renderedMermaidCache && buffer.renderedMermaidCache[cacheKey]) { 5430 const cachedPreEl = document.createElement('pre'); 5431 cachedPreEl.className = 'mermaid'; 5432 cachedPreEl.dataset.mermaidRendered = 'success'; 5433 cachedPreEl.dataset.mermaidCode = cacheKey; 5434 cachedPreEl.innerHTML = buffer.renderedMermaidCache[cacheKey]; 5435 originalPreEl.parentNode.replaceChild(cachedPreEl, originalPreEl); 5436 return; 5437 } 5438 5439 // æµå¼æªç»æä¸ä»£ç ä¸å®æ´ï¼ä¸å¨ DOMï¼ç䏿¬¡ chunk 5440 if (skipIncomplete && !isMermaidCodeLikelyComplete(mermaidCode)) { 5441 return; 5442 } 5443 5444 // åä¸ä»£ç æ£å¨å¼æ¥æ¸²æä¸ï¼è·³è¿ 5445 if (buffer && buffer.pendingMermaidKeys && buffer.pendingMermaidKeys[cacheKey]) { 5446 return; 5447 } 5448 5449 // 3.3 å建æ°ç pre æ ç¾ï¼Mermaid è¯å«ç容å¨ï¼ 5450 const newPreEl = document.createElement('pre'); 5451 newPreEl.className = 'mermaid'; 5452 newPreEl.dataset.mermaidCode = mermaidCode; 5453 newPreEl.dataset.mermaidRendered = 'pending'; 5454 newMermaidNodes.push({ preNode: newPreEl, cacheKey }); 5455 5456 if (buffer) { 5457 if (!buffer.pendingMermaidKeys) buffer.pendingMermaidKeys = {}; 5458 buffer.pendingMermaidKeys[cacheKey] = true; 5459 } 5460 5461 // 3.4 æ¿æ¢åæèç¹ï¼å 餿§ç preï¼æå ¥æ°ç preï¼ 5462 originalPreEl.parentNode.replaceChild(newPreEl, originalPreEl); 5463 } catch (innerError) { 5464 // å个å ç´ å¤ç失败ï¼ä» æå°æ¥å¿ï¼ç»§ç»å¤çä¸ä¸ä¸ªå ç´ 5465 } 5466 }); 5467 5468 if (newMermaidNodes.length === 0) { 5469 return; 5470 } 5471 5472 5473 // å ³é®ä¿®æ¹2ï¼å¨æå è½½mermaid JSæä»¶ç彿° 5474 function loadMermaidScript() { 5475 return new Promise((resolve, reject) => { 5476 // æ£æ¥æ¯å¦å·²å è½½è¿mermaidèæ¬ï¼
5476é¿å éå¤å è½½ 5477 const existingScript = document.querySelector('script[src="/scripts/mermaid.min.js"]'); 5478 if (existingScript) { 5479 // 妿已å è½½ï¼ç´æ¥æ£æ¥window.mermaidæ¯å¦åå¨ 5480 if (window.mermaid) { 5481 resolve(); 5482 } else { 5483 // èæ¬å·²æå ¥ä½è¿æ²¡å è½½å®æï¼çå¬å è½½äºä»¶ 5484 existingScript.onload = resolve; 5485 existingScript.onerror = reject; 5486 } 5487 return; 5488 } 5489 5490 // å建æ°çscriptæ ç¾ 5491 const script = document.createElement('script'); 5492 script.src = '/scripts/mermaid.min.js'; 5493 script.type = 'text/javascript'; 5494 script.async = true; // 弿¥å è½½ï¼ä¸é»å¡é¡µé¢ 5495 5496 // å è½½æååè° 5497 script.onload = () => { 5498 if (window.mermaid) { 5499 resolve(); 5500 } else { 5501 reject(new Error('Mermaid not find mermaid')); 5502 } 5503 }; 5504 5505 // å 载失败åè° 5506 script.onerror = () => { 5507 reject(new Error('ï¼/scripts/mermaid.min.js')); 5508 }; 5509 5510 // æå ¥å°headæ ç¾ä¸ï¼ä¹å¯ä»¥æå ¥å°bodyï¼ 5511 document.head.appendChild(script); 5512 }); 5513 } 5514 5515 // å ³é®ä¿®æ¹3ï¼å è½½èæ¬åååå§å并渲æ 5516 loadMermaidScript() 5517 .then(() => { 5518 // 4. åå§å/éæ°æ¸²æ Mermaid 5519 mermaid.initialize({ 5520 startOnLoad: false, // æå¨è§¦å渲æï¼é¿å éå¤ 5521 theme: 'default', 5522 flowchart: { nodeSpacing: 15, rankSpacing: 20 } 5523 }); 5524 5525 // 5. æå¨æ¸²ææ¯ä¸ªæ°å建ç mermaid èç¹ 5526 newMermaidNodes.forEach(async (item, index) => { 5527 const { preNode, cacheKey } = item; 5528 try { 5529 const code = preNode.dataset.mermaidCode || cacheKey; 5530 const graphId = `mermaid-graph-${Date.now()}-${index}`; 5531 const { svg } = await mermaid.render(graphId, code); 5532 if (!preNode.isConnected) { 5533 if (buffer) { 5534 if (!buffer.renderedMermaidCache) buffer.renderedMermaidCache = {}; 5535 buffer.renderedMermaidCache[cacheKey] = svg; 5536 if (buffer.pendingMermaidKeys) delete buffer.pendingMermaidKeys[cacheKey]; 5537 } 5538 return; 5539 } 5540 preNode.innerHTML = svg; 5541 preNode.dataset.mermaidRendered = 'success'; 5542 if (buffer) { 5543 if (!buffer.renderedMermaidCache) buffer.renderedMermaidCache = {}; 5544 buffer.renderedMermaidCache[cacheKey] = svg; 5545 if (buffer.pendingMermaidKeys) delete buffer.pendingMermaidKeys[cacheKey]; 5546 } 5547 } catch (renderError) { 5548 if (buffer && buffer.pendingMermaidKeys) delete buffer.pendingMermaidKeys[cacheKey]; 5549 if (preNode.isConnected) { 5550 restoreMermaidSourceNode(preNode, preNode.dataset.mermaidCode || cacheKey); 5551 } 5552 console.warn('[mermaid] render failï¼ä¿çæºç 䏿¾ç¤º error', cacheKey.slice(0, 60), renderError.message); 5553 } 5554 }); 5555 }) 5556 .catch((loadError) => {
5557 newMermaidNodes.forEach(item => { 5558 const { preNode, cacheKey } = item; 5559 if (buffer && buffer.pendingMermaidKeys) delete buffer.pendingMermaidKeys[cacheKey]; 5560 if (preNode.isConnected) { 5561 restoreMermaidSourceNode(preNode, preNode.dataset.mermaidCode || cacheKey); 5562 } 5563 }); 5564 console.error('[mermaid] script load fail', loadError); 5565 }); 5566 5567 } catch (outerError) { 5568 console.error('Mermaid errorï¼', outerError); 5569 } 5570 // 彿°æ§è¡å®æ¯ï¼æ 论æ¯å¦æ¥éï¼ï¼åç»æ¹æ³å¯æ£å¸¸æ§è¡ 5571 } 5572 5573 5574 $(document).ready(function () { 5575 5576 // å ¨å±äºä»¶å§æ - ä½ä¸ºå¤ç¨æ¹æ¡å¤çå¤å¶å¾æ ç¹å» 5577 $(document).on('click', '.copy-icon', function (e) { 5578 e.preventDefault(); 5579 e.stopPropagation(); 5580 const $copyIcon = $(this); 5581 const $contentBody = $copyIcon.closest('.content-body'); 5582 if ($contentBody.length > 0) { 5583 copyMarkdownContent($contentBody, $copyIcon); 5584 } else { 5585 console.log("No content-body found for global handler"); 5586 } 5587 }); 5588 5589 // å ¨å±äºä»¶å§æ - å¤ç导åºPDFæé®ç¹å» 5590 $(document).on('click', '.export-pdf-icon', function (e) { 5591 e.preventDefault(); 5592 e.stopPropagation(); 5593 const $exportIcon = $(this); 5594 const $contentBody = $exportIcon.closest('.content-body'); 5595 if ($contentBody.length > 0) { 5596 exportContentAsPDF($contentBody, $exportIcon); 5597 } else { 5598 } 5599 }); 5600 5601 // å ¨å±äºä»¶å§æ - å¤çéç½®æé®ç¹å» 5602 $(document).on('click', '.reset-icon', function (e) { 5603 e.preventDefault(); 5604 e.stopPropagation(); 5605 const $resetIcon = $(this); 5606 const messageId = $resetIcon.data('message-id'); 5607 if (messageId) { 5608 regenerateResponse($resetIcon, messageId); 5609 } else { 5610 console.error("No messageId found for reset handler"); 5611 } 5612 }); 5613 5614 $(window).on('message', function (event) { 5615 // // å®å ¨éªè¯ï¼åªå¤çæ¥èªä¿¡ä»»ååçæ¶æ¯ 5616 const trustedOrigins = ['https://www.ptglab.com', 'http://www.ptglab.com', 'https://aipublic.ptglab.com', 'https://ptglab.com', 'http://ptglab.com', 'https://ptglab.my.site.com']; // æ¿æ¢ä¸ºå®é ç¶é¡µé¢åå 5617 if (!trustedOrigins.includes(event.originalEvent.origin)) { 5618 console.warn('æç»å¤çæ¥èªæªç¥ååçæ¶æ¯:', event.originalEvent.origin); 5619 return; 5620 } 5621 5622 // è§£ææ¶æ¯å 容 5623 const message = event.originalEvent.data; 5624 if (message.action === 'showAbleIntro') { 5625 // æ§è¡æ¾ç¤ºæä½ï¼å°.able-intro设置为display: block 5626 clearMessages() 5627 $('.able-intro').css('display', 'block'); 5628 // å¯ä»¥æ·»å å ¶ä»éè¦æ§è¡çæä½ 5629 } 5630 }); 5631 5632 5633 $(".uuid").text(uuid) 5634 if (languageInfo?.primary?.startsWith('zh')) { 5635 language = 'Chinese'; 5636 $(".titleLanguage").text('æºè½å¯¹è¯å©æ'); 5637 $(".clearLanguage").text("æ¸ ç©ºå¯¹è¯"); 5638 $(".welcomeLanguage").text("ä½ å¥½ï¼ææ¯ä½ çAIå©æï¼æä»ä¹å¯ä»¥å¸®å©ä½ çåï¼"); 5639 // $(".inputClearLanguage").text('è¾å ¥ /clear å¯å¿«éæ¸ ç©ºå¯¹è¯'); 5640 $('#userInput').attr('placeholder', '请è¾å ¥é®é¢'); 5641 $(".canText").text('åèå 容'); 5642 } else { 5643 language = 'English'; 5644 $(".titleLanguage").text('AI conversation assistant'); 5645 $(".clearLanguage").text("Clear the conversation"); 5646 $(".welcomeLanguage").text("Hello! I am your AI assistant, can I help you?"); 5647 // $(".inputClearLanguage").text('Disclaimer: Able AI is experimental and still learning how to provide the best assistance. It may occasionally generate incorrect or incomplete responses. Please do not rely solely on its recommendations when making purchasing decisions or designing experiments.');
5648 $('#userInput').attr('placeholder', 'Ask Able to find you the perfect product'); 5649 $(".canText").text('Reference content'); 5650 } 5651 5652 $("#openRight").click(function () { 5653 $('#rightPanel').removeClass('right-panel slide-out').addClass('slide-in'); 5654 $('#app').removeClass('mx-auto').removeClass("max-w-7xl").css('width', '75%'); 5655 }); 5656 5657 $("#closeBtn").click(function () { 5658 $('#app').addClass('mx-auto').addClass("max-w-7xl").css('width', '100%'); 5659 $('#rightPanel').removeClass('slide-in').addClass('slide-out'); 5660 setTimeout(function () { 5661 $('#rightPanel').removeClass('slide-out').addClass('right-panel'); 5662 }, 300); 5663 }); 5664 5665 5666 $('.open-live-chat').click(function () { 5667 const data = { 5668 type: 'fromChild', 5669 message: 'msg', 5670 status: 'success' 5671 }; 5672 window.parent.postMessage(data, '*'); 5673 }); 5674 }); 5675 5676 5677 //主è¦ç¹å»å¯¹è¯æ°ä¼ å ¥åæ°åå¨å¯¹è¯æ¶é´ 5678 document.addEventListener('DOMContentLoaded', function () { 5679 // çå¬ç¶çªå£çæ¶æ¯ 5680 window.addEventListener('message', function (event) { 5681 // å®å ¨éªè¯ï¼åªå¤çæ¥èªä¿¡ä»»ååçæ¶æ¯ 5682 const trustedOrigins = ['https://www.ptglab.com', 'http://www.ptglab.com', 'https://aipublic.ptglab.com', 'https://ptglab.com', 'http://ptglab.com', 'https://ptglab.my.site.com']; 5683 const isValidMessage = 5684 event.data && 5685 (event.data.type === 'ableChat' || event.data.action === 'focusInput'); 5686 5687 if (trustedOrigins.includes(event.origin) && isValidMessage) { 5688 // 1. å®ä¹åºç¡å¸¸éï¼å½åæ¸ æ°ï¼æ³¨éåç¡®ï¼ 5689 const currentTimestamp = Date.now(); // å½åæ¶é´æ³ï¼æ¯«ç§ï¼ 5690 const timestampKey = 'chat_uuid_timestamp'; // åºåæ¶é´æ³åå¨key 5691 const chatUuidKey = 'chat_uuid'; // chat_uuidåå¨key 5692 const oneHour = 1 * 60 * 60 * 1000; // 1å°æ¶çæ¯«ç§æ° 5693 5694 // 2. è·åæ¬å°åå¨çåºåæ¶é´æ³ 5695 const lastTimestamp = localStorage.getItem(timestampKey); 5696 5697 // 3. æ ¸å¿é»è¾ï¼ 5698 // - æ åºåæ¶é´ï¼åå§ååºåï¼ä¸ºåç»å¤æååå¤ï¼ 5699 // - æåºåæ¶é´ï¼ä» 1å°æ¶å 䏿¸ é¤ï¼å ¶ä½å ¨é¨æ¸ é¤ 5700 if (!lastTimestamp) { 5701 // åºæ¯1ï¼æ åºåæ¶é´ï¼é¦æ¬¡è®¿é®/忏 é¤ï¼â åå§ååºåæ¶é´ 5702 localStorage.setItem(timestampKey, currentTimestamp.toString()); 5703 } else { 5704 // åºæ¯2ï¼æåºåæ¶é´ â 夿æ¯å¦å¨1å°æ¶å 5705 const lastTime = parseInt(lastTimestamp, 10); // è§£æåºåæ¶é´ä¸ºæ°å 5706 5707 // æ ¸å¿å¤æï¼åªè¦ä¸å¨1å°æ¶å ï¼è¶ æ¶/æ¶é´åæ¨ï¼ï¼å°±æ¸ é¤ 5708 if (!(currentTimestamp >= lastTime && currentTimestamp < lastTime + oneHour)) { 5709 localStorage.removeItem(chatUuidKey); // æ¸ é¤chat_uuid 5710 localStorage.removeItem(timestampKey); // æ¸ é¤åºåæ¶é´æ³ 5711 localStorage.setItem(timestampKey, currentTimestamp.toString()); 5712 } 5713 // 1å°æ¶å ï¼ä¸æ§è¡ä»»ä½æä½ï¼ä¿çæ°æ® 5714 } 5715 5716 5717 5718 /** æ£æ¥å¹¶çæ chat_uuid */ 5719 const storedUuid = localStorage.getItem('chat_uuid'); 5720 5721 5722 // å¤ç focusInput æä½ 5723 if (event.data && event.data.action === 'focusInput') { 5724 $('#userInput').focus(); 5725 } 5726 5727 if (!storedUuid) { 5728 // 妿 localStorage 䏿²¡æ chat_uuidï¼éæ°çæä¸ä¸ªå¹¶åå¨ 5729 // const newUuid = generateSafeNumericId(); 5730 messagesContainer.children().not('.able-intro').remove(); 5731 // ãæ°å¢ãæ¸ çå®¡æ ¸å è½½ç¶æ
5732 Object.keys(auditLoadingStates).forEach(key => delete auditLoadingStates[key]); 5733 rebuildChatRoundIds.clear(); 5734 5735 // ãæ°å¢ãæ¸ é¤localStorageä¸çuuidï¼çææ°çuuid 5736 // localStorage.removeItem('chat_uuid'); 5737 generateSafeNumericId() 5738 .then(id => { 5739 uuid = id 5740 localStorage.setItem('chat_uuid', uuid); 5741 // åç»é»è¾ 5742 }) 5743 // ãæ°å¢ãæ¾ç¤ºä»ç»é¡µé¢ 5744 $(".able-intro").css("display", "block"); 5745 } else { 5746 messagesContainer.children().not('.able-intro').remove(); 5747 // 妿éè¦å è½½åå²è®°å½ 5748 if (event.data && event.data.loadHistory && event.data.dialogId) { 5749 const dialogId = event.data.dialogId; 5750 const storedId = localStorage.getItem('chat_uuid') || dialogId; 5751 // å¼å§æ¾ç¤ºå è½½æç¤º 5752 showConversationRestoreLoading(); 5753 loadConversationHistory(storedId); 5754 } 5755 // å ¼å®¹æ§çæ¶æ¯æ ¼å¼ï¼loadConversationHistory actionï¼ 5756 if (event.data && event.data.action === 'loadConversationHistory') { 5757 const dialogId = event.data.dialogId || event.data.dialog_id; 5758 const storedId = localStorage.getItem('chat_uuid') || dialogId; 5759 if (storedId) { 5760 // å¼å§æ¾ç¤ºå è½½æç¤º 5761 showConversationRestoreLoading(); 5762 // æ¸ çä¹åçæ¶æ¯å ç´ ï¼é¿å éå¤å å 5763 loadConversationHistory(storedId); 5764 } 5765 } 5766 } 5767 } else { 5768 console.warn('æç»å¤çæ¥èªæªç¥ååçæ¶æ¯:', event.origin); 5769 } 5770 }); 5771 5772 function isMobileDevice() { 5773 // ç²¾åå¹é ææºè®¾å¤çUAæ£åï¼æé¤å¹³æ¿/PCçï¼ 5774 // æ©å±UAæ£åï¼æ°å¢å¾®ä¿¡/QQæµè§å¨ãå®å/è¹æç§»å¨ç«¯æ è¯ãå½äº§æµè§å¨å ³é®è¯ 5775 const mobileUA = /Android|iPhone|iPod|BlackBerry|IEMobile|Opera Mini|Windows Phone|Mobile|MicroMessenger|QQBrowser|MiuiBrowser|UCWEB/i.test(navigator.userAgent); 5776 // æ£æµiPad/å¹³æ¿ï¼å¯éï¼å¦éå å«å¹³æ¿ï¼å é¤è¿è¡å¤æï¼ 5777 const isTablet = /iPad|Macintosh/.test(navigator.userAgent) && 'ontouchend' in document; 5778 const result = mobileUA && !isTablet; 5779 return result; 5780 } 5781 5782 // 宿´ä½¿ç¨ç¤ºä¾ï¼ä¿çåææ ·å¼æå ¥é»è¾ï¼ 5783 if (isMobileDevice()) { 5784 const style = document.createElement('style'); 5785 style.type = 'text/css'; 5786 const mobileStyles = ` 5787 #messagesContainer { 5788 max-height: calc(100vh - 155px); 5789 } 5790 .mobilePadding { 5791 padding: 0.35rem; 5792 } 5793 .content-body { 5794 padding-left: 0.2rem; 5795 } 5796 .mobilePadding .segments-container { 5797 font-size: 0.85rem; 5798 } 5799 .aifontSize { 5800 font-size: 0.85rem; 5801 } 5802 .avatar-container { 5803 margin-right: 6px !important; 5804 } 5805 .mobileUser { 5806 margin-left: 6px !important; 5807 } 5808 .showThinkinglogo { 5809 padding: 0px !important; 5810 } 5811 .able-intro{ 5812 margin-top:0px; 5813 } 5814 `; 5815 style.innerHTML = mobileStyles; 5816 document.head.appendChild(style); 5817 console.log('mobile'); 5818 } 5819 5820 }); 5821 5822 /** 5823 * æ¢å¤å¯¹è¯ SSE æµæ§å¶ 5824 */ 5825 let restoreStreamAbortController = null; 5826 let activeRestoreDialogId = null; 5827 5828 /** 5829 * è®°å½å·²æ¸²æçææ°æ¶æ¯IDï¼ç¨äºå»é 5830 */ 5831 const renderedMessageIds = new Set(); 5832 5833 /** 5834 * æ¾ç¤ºå¯¹è¯æ¢å¤ä¸çå è½½æç¤ºå¹¶ç¦ç¨åéæé® 5835 */ 5836 function showConversationRestoreLoading() { 5837 // æ£æ¥æ¯å¦å·²åå¨ 5838 if ($('#restoreLoading').length > 0) { 5839 return; 5840 } 5841 5842 const loadingHtml = ` 5843 <div id="restoreLoading" class="flex items-center justify-center py-4"> 5844 <div class="flex flex-col items-center gap-2"> 5845 <i class="fa fa-spinner fa-spin" style="font-size: 24px; color: #00ae9e;"></i> 5846 <p class="text-sm text-gray-500">Restoring conversation...</p> 5847 </div> 5848 </div> 5849 `; 5850 messagesContainer.append(loadingHtml); 5851 5852 // ç¦ç¨åéæé® 5853 $('#sendBtn').prop('disabled', true); 5854 } 5855 5856 /** 5857 * éèå¯¹è¯æ¢å¤ä¸çå è½½æç¤ºå¹¶å¯ç¨åéæé® 5858 */ 5859 function hideConversationRestoreLoading() { 5860 $('#restoreLoading').remove(); 5861 5862 // å¯ç¨åéæé® 5863 $('#sendBtn').prop('disabled', false); 5864 } 5865 5866 /** 5867 * å 载对è¯åå²è®°å½ 5868 * @param {string} dialogId - 对è¯ID 5869 */ 5870 function loadConversationHistory(dialogId) { 5871 stopRestoreStream(); 5872 fetch(`https://aipublic.ptglab.com/llm/v1/product/rebuild-chat/${dialogId}`, { 5873 method: 'GET', 5874 headers: { 5875 'Content-Type': 'application/json' 5876 } 5877 }) 5878 .then(response => { 5879 if (!response.ok) {
5880 throw new Error(`HTTPé误ï¼ç¶æç : ${response.status}`); 5881 } 5882 return response.json(); 5883 }) 5884 .then(data => { 5885 if (data.dialog_expired == true) { 5886 generateSafeNumericId() 5887 .then(id => { 5888 uuid = id 5889 localStorage.setItem('chat_uuid', uuid); 5890 // åç»é»è¾ 5891 }) 5892 isLoading = false; 5893 $('#sendBtn').removeClass('loading'); 5894 $('.loading-indicator, .load-more-indicator').remove(); 5895 updateSendButtonState(false); // å¯ç¨åéæé® 5896 hideConversationRestoreLoading() 5897 $(".able-intro").css("display", "block"); 5898 5899 $('#sendBtn').off('click').on('click', sendMessage); 5900 return 5901 } else { 5902 5903 if (data && data.data && data.data.length == 0) { 5904 // 妿 data ä¸ºç©ºä¸ finished 为 trueï¼å±ç¤ºä»ç»é¡µé¢ 5905 const isEmpty = !data || !data.data || (Array.isArray(data.data) && data.data.length === 0); 5906 if (isEmpty && data && data.finished === true) { 5907 hideConversationRestoreLoading(); 5908 $(".able-intro").css("display", "block"); 5909 } 5910 //return 5911 } 5912 5913 if (data && data.ok && data.data && Array.isArray(data.data) && data.data.length > 0) { 5914 // æ£æ¥æ¯å¦å å«ReviewIDçround_idï¼é¿å é夿¸²æ 5915 const hasReviewId = data.data.some(message => message.round_id == ReviewID); 5916 5917 // åå¨rebuild-chatæ¥å£è¿åçææround_id 5918 data.data.forEach(message => { 5919 if (message.round_id) { 5920 rebuildChatRoundIds.add(message.round_id); 5921 } 5922 }); 5923 // if (!hasReviewId) { 5924 // éèä»ç»åºå 5925 $(".able-intro").css("display", "none"); 5926 // æ¸²ææ¶æ¯ 5927 //renderConversationHistory(data.data); 5928 5929 // æ¸²ææ¶æ¯ï¼æªå®ææ¶ä¿çæåä¸è½®ç± SSE è¾åºï¼ä¸æ¸²æå ¶ assistant é¨åï¼ 5930 const willStreamLastRound = data.finished === false; 5931 renderConversationHistory(data.data, { 5932 skipLastRoundAssistant: willStreamLastRound 5933 }); 5934 5935 5936 // è®°å½å·²æ¸²æçæ¶æ¯ID 5937 data.data.forEach(message => { 5938 if (message.message_id) { 5939 renderedMessageIds.add(message.message_id); 5940 } 5941 }); 5942 // } else { 5943 // console.log("æ£æµå°ReviewIDï¼è·³è¿é夿¸²æ:", ReviewID); 5944 // } 5945 } else if (data && data.ok && data.data && data.data.length === 0) { 5946 // 妿 data æ¯ç©ºæ°ç»ï¼ä¿æä»ç»é¡µé¢æ¾ç¤º 5947 } 5948 5949 // æªå®ææ¶éè¿ SSE ç»§ç»è¾åºæåä¸è½®ï¼åå²è½®æ¬¡å·²ç± rebuild-chat 渲æï¼ 5950 if (data && data.finished === false) { 5951 updateSendButtonState(false); 5952 const lastRoundId = (data.data && data.data.length > 0) 5953 ? getLastRoundId(data.data) 5954 : null; 5955 startChatLastRoundStream(dialogId, lastRoundId); 5956 } else { 5957 // finished 为 true æå ¶ä»æ åµï¼éèå è½½æç¤ºå¹¶å¯ç¨åéæé® 5958 hideConversationRestoreLoading(); 5959 5960 // 妿 data ä¸ºç©ºä¸ finished 为 trueï¼å±ç¤ºä»ç»é¡µé¢ 5961 const isEmpty = !data || !data.data || (Array.isArray(data.data) && data.data.length === 0); 5962 if (isEmpty && data && data.finished === true) { 5963 $(".able-intro").css("display", "block"); 5964 } 5965 } 5966 } 5967 5968 }) 5969 .catch(error => { 5970 console.error('å 载对è¯åå²å¤±è´¥:', error); 5971 // é误æ¶ä¹è¦éèå è½½æç¤º 5972 hideConversationRestoreLoading(); 5973 }); 5974 } 5975 5976 5977 /** 5978 * åæ¶æ¯åè¡¨ä¸æåä¸è½®ç round_id 5979 * @param {Array} messages 5980 */ 5981 function getLastRoundId(messages) { 5982 if (!messages || !messages.length) return null; 5983 const last = messages[messages.length - 1]; 5984 return last.round_id != null ? String(last.round_id) : null; 5985 } 5986 5987 function initResponseBufferForMessage(messageId) { 5988 responseBuffers[messageId] = { 5989 segments: [], 5990 lastSegmentType: '', 5991 isLoading: false, 5992 lastType: '', 5993 toolContentBuffer: '', 5994 renderOrder: [], 5995 taskSegments: [], 5996 nonContentGroups: [], 5997 hasReviewed: false, 5998 afterTaskDoneNoValidContent: false, 5999 renderedMermaidCache: {}, 6000 pendingMermaidKeys: {}, 6001 pendingFormatGroupIds: {} 6002 }; 6003 responseCompleted[messageId] = false;
6004 auditLoadingStates[messageId] = false; 6005 } 6006 6007 /** 6008 * æ¢å¤æªå®æå¯¹è¯ï¼è°ç¨ chat-last-round-sse æµå¼ç»åæåä¸è½® 6009 * å¨ rebuild-chat 已渲æçåå²ä¹åï¼åç¬è¿½å 䏿¡ bot æ¶æ¯ç¨äºæµå¼è¾åº 6010 * @param {string} dialogId - 对è¯ID 6011 * @param {string|null} lastRoundId - æªå®æè½®ç round_id 6012 */ 6013 function startChatLastRoundStream(dialogId, lastRoundId) { 6014 if (activeRestoreDialogId === dialogId && restoreStreamAbortController) { 6015 return; 6016 } 6017 stopRestoreStream(); 6018 6019 activeRestoreDialogId = dialogId; 6020 $('#restoreLoading').remove(); 6021 6022 const messageId = addBotMessage(language === 'Chinese' ? 'æ£å¨æ¢å¤åç...' : 'Resuming response...'); 6023 if (lastRoundId) { 6024 $(`#${messageId}`).attr('data-round-id', lastRoundId); 6025 ReviewID = lastRoundId; 6026 } 6027 6028 initResponseBufferForMessage(messageId); 6029 record_MCP = ''; 6030 isLoading = true; 6031 $('#sendBtn').addClass('loading'); 6032 $('#sendBtn').off('click').on('click', function () { 6033 if (isLoading) { 6034 stopRestoreStream(); 6035 abortRequest(true, dialogId); 6036 isLoading = false; 6037 $('#sendBtn').removeClass('loading'); 6038 hideConversationRestoreLoading(); 6039 $('#sendBtn').off('click').on('click', sendMessage); 6040 } 6041 }); 6042 6043 restoreStreamAbortController = new AbortController(); 6044 const signal = restoreStreamAbortController.signal; 6045 const apiUrl = 'https://aipublic.ptglab.com/llm/v2/product/chat-last-round-sse'; 6046 6047 fetch(apiUrl, { 6048 method: 'POST', 6049 headers: { 'Content-Type': 'application/json' }, 6050 body: JSON.stringify({ dialog_id: dialogId }), 6051 signal: signal 6052 }) 6053 .then(response => { 6054 if (!response.ok) { 6055 throw new Error(`HTTPé误ï¼ç¶æç : ${response.status}`); 6056 } 6057 const reader = response.body.getReader(); 6058 const decoder = new TextDecoder(); 6059 6060 function read() { 6061 reader.read().then(({ done, value }) => { 6062 if (activeRestoreDialogId !== dialogId) { 6063 return; 6064 } 6065 6066 if (done) { 6067 try { 6068 isShowReview = 'Reviewed'; 6069 const $messageDom = $(`#${messageId}`); 6070 updateStatusIndicator($messageDom, ''); 6071 const buffer = responseBuffers[messageId]; 6072 if (buffer) buffer.isLoading = false; 6073 $('.loading-indicator, .load-more-indicator').remove(); 6074 6075 completeResponse(messageId); 6076 const hasExistingContainers = $messageDom.find('.pdb-3d-container').length > 0 || 6077 $messageDom.data('has-3d-containers') === true; 6078 if (!hasExistingContainers) { 6079 checkAndProcessPdbLinks(messageId); 6080 } 6081 addCopyIconToContent(messageId); 6082 addResetIconToLastMessage(messageId); 6083 executeDoneLogic(messageId); 6084 6085 $messageDom.find('.thinkHtml').each(function () { 6086 const $thinkHtml = $(this); 6087 const $pdbContainers = $thinkHtml.find('.pdb-3d-container').detach(); 6088 let htmlContent = $thinkHtml.html(); 6089 htmlContent = htmlContent.replace(/<end_message>/gi, '<end_message>'); 6090 htmlContent = htmlContent.replace(/<\/end_message>/gi, '</end_message>'); 6091 htmlContent = htmlContent.replace(
6092 /<end_message>([\s\S]*?)<\/end_message>/gi, 6093 function (match, content) { 6094 const questionText = content.trim(); 6095 if (!questionText) return ''; 6096 return `<button class="end-message-btn" data-question="${questionText.replace(/"/g, '"')}">${questionText}</button>`; 6097 } 6098 ); 6099 $thinkHtml.html(htmlContent); 6100 if ($pdbContainers.length > 0) { 6101 $thinkHtml.append($pdbContainers); 6102 } 6103 }); 6104 6105 const restoreDoneBuffer = responseBuffers[messageId]; 6106 const $restoreSegments = $messageDom.find('.segments-container'); 6107 if ($restoreSegments.length) { 6108 reinjectCachedMermaidInContainer(messageId, restoreDoneBuffer, $restoreSegments); 6109 formatAllNonContentGroupsAtStreamEnd(messageId, restoreDoneBuffer, $restoreSegments); 6110 formatOrphanSegmentGroups(messageId, $restoreSegments); 6111 sweepUnrenderedMermaid(messageId, $restoreSegments); 6112 } 6113 6114 $(".end-message-btn").addClass("visible"); 6115 hideConversationRestoreLoading(); 6116 stopRestoreStream(); 6117 updateSendButtonState(false); 6118 setTimeout(() => scrollToBottom(true), 300); 6119 } catch (doneError) { 6120 console.error('æ¢å¤æµå¼è¾åºç»æå¤ç失败:', doneError); 6121 hideConversationRestoreLoading(); 6122 stopRestoreStream(); 6123 updateSendButtonState(false); 6124 } finally { 6125 responseCompleted[messageId] = true; 6126 } 6127 return; 6128 } 6129 6130 const chunk = decoder.decode(value, { stream: true }); 6131 6132 if (chunk.includes('/tool_call')) { 6133 record_MCP += chunk; 6134 const hasTag1 = record_MCP.includes('</think>') && record_MCP.includes('</MCP_HOST_TASK>'); 6135 if (hasTag1) { 6136 record_MCP = ''; 6137 const $messageContainer = $(`#${messageId}`); 6138 if (!$messageContainer.length) return; 6139 const $taskTag = $messageContainer.find('mcp_host_task:has(span.incomplete-placeholder)'); 6140 if ($taskTag.length) { 6141 const loaderId = `loader-${messageId}-${$taskTag.index()}`; 6142 const $existingLoading = $(`#${loaderId}`); 6143 if (!$existingLoading.length) { 6144 const $loading = $(`<div id="${loaderId}" class="loading-indicator ml-1 inline-block"></div>`) 6145 .css('color', 'dimgray') 6146 .html('<i class="fa fa-spinner fa-spin"></i>'); 6147 $taskTag.after($loading); 6148 } 6149 } 6150 } 6151 } 6152 6153 try { 6154 const processedChunks = processSseChunk(chunk);
6155 updateBotMessage(messageId, processedChunks); 6156 } catch (codeError) { 6157 console.error('æ¢å¤æµå¼è¾åºè§£æå¤±è´¥:', codeError); 6158 hideConversationRestoreLoading(); 6159 stopRestoreStream(); 6160 updateSendButtonState(false); 6161 return; 6162 } 6163 6164 read(); 6165 }).catch(error => { 6166 if (error.name === 'AbortError') { 6167 return; 6168 } 6169 console.error('è¯»åæ¢å¤æµå¼ååºå¤±è´¥:', error); 6170 isLoading = false; 6171 $('#sendBtn').removeClass('loading'); 6172 $('#sendBtn').off('click').on('click', sendMessage); 6173 hideConversationRestoreLoading(); 6174 stopRestoreStream(); 6175 updateSendButtonState(false); 6176 }); 6177 } 6178 read(); 6179 }) 6180 .catch(error => { 6181 if (error.name === 'AbortError') { 6182 return; 6183 } 6184 console.error('æ¢å¤æµå¼è¯·æ±å¤±è´¥:', error); 6185 isLoading = false; 6186 $('#sendBtn').removeClass('loading'); 6187 $('#sendBtn').off('click').on('click', sendMessage); 6188 hideConversationRestoreLoading(); 6189 stopRestoreStream(); 6190 updateSendButtonState(false); 6191 }); 6192 } 6193 6194 /** 6195 * 忢æ¢å¤å¯¹è¯ç SSE æµ 6196 */ 6197 function stopRestoreStream() { 6198 if (restoreStreamAbortController) { 6199 restoreStreamAbortController.abort(); 6200 restoreStreamAbortController = null; 6201 } 6202 activeRestoreDialogId = null; 6203 } 6204 6205 // 页é¢å¸è½½æ¶ä¸æ¢æ¢å¤æµ 6206 window.addEventListener('beforeunload', function () { 6207 stopRestoreStream(); 6208 }) 6209 6210 /** 6211 * 追å 对è¯åå²ï¼æ°å¢çæ¶æ¯ï¼ 6212 * @param {Array} messages - æ¶æ¯æ°ç» 6213 */ 6214 function appendConversationHistory(messages) { 6215 // å ç§»é¤å è½½æç¤ºï¼ä¸´æ¶ç§»é¤ï¼ç¨åéæ°æ·»å ï¼ 6216 const $restoreLoading = $('#restoreLoading'); 6217 $restoreLoading.detach(); 6218 6219 // æround_idåç»æ¶æ¯ï¼å æ¬useråassistantï¼ 6220 const messagesByRound = {}; 6221 6222 messages.forEach(message => { 6223 const roundId = message.round_id || 'default'; 6224 6225 if (!messagesByRound[roundId]) { 6226 messagesByRound[roundId] = { 6227 userMessages: [], 6228 assistantMessages: [] 6229 }; 6230 } 6231 6232 if (message.role === 'user' || message.sender === 'ä½ ' || message.sender?.toLowerCase() === 'user') { 6233 messagesByRound[roundId].userMessages.push(message); 6234 } else if (message.role === 'assistant' || message.sender === 'AI' || message.sender?.toLowerCase() === 'ai') { 6235 messagesByRound[roundId].assistantMessages.push(message); 6236 } 6237 }); 6238 6239 // æ¸²ææ¯ä¸ªroundçæ¶æ¯ï¼è¿½å 模å¼ï¼ 6240 Object.keys(messagesByRound).forEach(roundId => { 6241 const roundData = messagesByRound[roundId]; 6242 6243 // å æ¸²æç¨æ·æ¶æ¯ï¼å¦ææå¤ä¸ªï¼åå¹¶æ¾ç¤ºï¼ 6244 if (roundData.userMessages.length > 0) { 6245 // åªæ¾ç¤ºç¬¬ä¸ä¸ªç¨æ·æ¶æ¯çå 容 6246 addUserMessageFromHistory(roundData.userMessages[0]); 6247 } 6248 6249 // 渲æAIæ¶æ¯ç»ï¼åå¹¶æä¸ä¸ªæ¶æ¯ï¼ 6250 if (roundData.assistantMessages.length > 0) { 6251 addBotMessageGroupFromHistory(roundData.assistantMessages); 6252 } 6253 }); 6254 6255 // éæ°æ·»å å è½½æç¤ºå°åºé¨ 6256 if ($restoreLoading.length > 0) { 6257 messagesContainer.append($restoreLoading); 6258 } 6259 6260 // æ§è¡åå¤çæä½ 6261 executeMessagePostProcessing(); 6262 6263 // 为æ°è¿½å çAIæ¶æ¯æ·»å å¤å¶å¾æ 6264 const $allBotMessages = messagesContainer.find('.bot-message'); 6265 $allBotMessages.each(function () { 6266 const messageId = $(this).attr('id'); 6267 if (messageId) { 6268 // å ç§»é¤å¯è½åå¨çéå¤å¾æ 6269 $(this).find('.copy-icon-container').remove(); 6270 addCopyIconToContent(messageId); 6271 } 6272 }); 6273 6274 // ç§»é¤ææéç½®å¾æ ï¼åªä¿çæåä¸ä¸ª 6275 removeAllResetIcons(); 6276 const $lastBotMessage = messagesContainer.find('.bot-message').last(); 6277 if ($lastBotMessage.length > 0) { 6278 const lastMessageId = $lastBotMessage.attr('id'); 6279 if (lastMessageId) { 6280 $lastBotMessage.find('.end-message-btn').css('display', 'inline-block'); 6281 addResetIconToLastMessage(lastMessageId); 6282 } 6283 } 6284 6285 // æ»å¨å°åºé¨ 6286 setTimeout(() => { 6287 scrollToBottom(true); 6288 }, 300); 6289 } 6290 6291 /** 6292 * 渲æå¯¹è¯åå² 6293 * @param {Array} messages - æ¶æ¯æ°ç» 6294 */ 6295 function renderConversationHistory(messages, options) { 6296 options = options || {}; 6297 const skipLastRoundAssistant = options.skipLastRoundAssistant === true; 6298 const lastRoundId = skipLastRoundAssistant ? getLastRoundId(messages) : null; 6299 6300 // å æ¸ ç©ºç°ææ¶æ¯ï¼ä¿çä»ç»é¡µé¢åå è½½æç¤ºï¼ 6301 messagesContainer.children().not('.able-intro, #restoreLoading').remove(); 6302 6303 messagesContainer.find('.able-intro').hide(); 6304 6305 // æround_idåç»æ¶æ¯ï¼å æ¬useråassistantï¼ 6306 const messagesByRound = {}; 6307
6308 messages.forEach(message => { 6309 const roundId = message.round_id || 'default'; 6310 6311 if (!messagesByRound[roundId]) { 6312 messagesByRound[roundId] = { 6313 userMessages: [], 6314 assistantMessages: [] 6315 }; 6316 } 6317 6318 if (message.role === 'user' || message.sender === 'ä½ ' || message.sender?.toLowerCase() === 'user') { 6319 messagesByRound[roundId].userMessages.push(message); 6320 } else if (message.role === 'assistant' || message.sender === 'AI' || message.sender?.toLowerCase() === 'ai') { 6321 messagesByRound[roundId].assistantMessages.push(message); 6322 } 6323 }); 6324 6325 // æ¸²ææ¯ä¸ªroundçæ¶æ¯ 6326 Object.keys(messagesByRound).forEach(roundId => { 6327 const roundData = messagesByRound[roundId]; 6328 const isLastStreamingRound = skipLastRoundAssistant && lastRoundId != null 6329 && String(roundId) === String(lastRoundId); 6330 6331 // å æ¸²æç¨æ·æ¶æ¯ï¼å¦ææå¤ä¸ªï¼åå¹¶æ¾ç¤ºï¼ 6332 if (roundData.userMessages.length > 0) { 6333 // åªæ¾ç¤ºç¬¬ä¸ä¸ªç¨æ·æ¶æ¯çå 容 6334 addUserMessageFromHistory(roundData.userMessages[0]); 6335 } 6336 6337 // 渲æAIæ¶æ¯ç»ï¼æåä¸è½®æªå®ææ¶è·³è¿ï¼ç± chat-last-round-sse æµå¼è¾åºï¼ 6338 if (roundData.assistantMessages.length > 0 && !isLastStreamingRound) { 6339 addBotMessageGroupFromHistory(roundData.assistantMessages); 6340 } 6341 }); 6342 6343 // ç¡®ä¿å è½½æç¤ºå¨æåºé¨ 6344 const $restoreLoading = $('#restoreLoading'); 6345 if ($restoreLoading.length > 0) { 6346 $restoreLoading.appendTo(messagesContainer); 6347 } 6348 6349 // æ§è¡åå¤çæä½ 6350 executeMessagePostProcessing(); 6351 6352 // 为ææAIæ¶æ¯æ·»å å¤å¶å¾æ 6353 const $allBotMessages = messagesContainer.find('.bot-message'); 6354 $allBotMessages.each(function () { 6355 const messageId = $(this).attr('id'); 6356 if (messageId) { 6357 addCopyIconToContent(messageId); 6358 } 6359 }); 6360 6361 // ç§»é¤ææåå²å¯¹è¯çéç½®å¾æ ï¼åªä¿çæåä¸ä¸ª 6362 removeAllResetIcons(); 6363 const $lastBotMessage = messagesContainer.find('.bot-message').last(); 6364 if ($lastBotMessage.length > 0) { 6365 const lastMessageId = $lastBotMessage.attr('id'); 6366 if (lastMessageId) { 6367 $lastBotMessage.find('.end-message-btn').css('display', 'inline-block'); 6368 addResetIconToLastMessage(lastMessageId); 6369 } 6370 } 6371 6372 // æ»å¨å°åºé¨ 6373 setTimeout(() => { 6374 // éè¦ä¼ å ¥å ·ä½çDOMå ç´ ï¼å 为initMermaidRender伿£æ¥HTMLElementç±»å 6375 initMermaidRender(messagesContainer); 6376 scrollToBottom(true); 6377 }, 300); 6378 } 6379 6380 /** 6381 * ä»åå²è®°å½æ·»å ç¨æ·æ¶æ¯ 6382 * @param {string} content - æ¶æ¯å 容 6383 */ 6384 /** 6385 * ä»åå²è®°å½æ·»å ç¨æ·æ¶æ¯ 6386 * @param {Object} message - æ¶æ¯å¯¹è±¡ï¼å« contentãuser_input_multi_contentãdialog_idï¼ 6387 */ 6388 function addUserMessageFromHistory(message) { 6389 const content = message.content || ''; 6390 const multiText = (message.user_input_multi_content || []) 6391 .filter(item => item.type === 'text' && item.text) 6392 .map(item => item.text) 6393 .join(''); 6394 const allText = content + multiText; 6395 6396 // æ£æ¥æ¯å¦å å«tool_callæ ç¾ï¼å¦æå å«åè·³è¿æ¤æ¶æ¯ 6397 if (allText && allText.includes('<tool_call>') && allText.includes('</tool_call>')) { 6398 return; 6399 } 6400 6401 const messageBodyHtml = buildUserHistoryMessageContent(message); 6402 if (!messageBodyHtml.trim()) { 6403 return; 6404 } 6405 6406 let spanText = language === 'Chinese' ? 'ä½ ' : 'U'; 6407 const messageHtml = ` 6408 <div class="message user-message message-appear"> 6409 <div class="flex items-start justify-end space-x-3" style="margin-left:45px"> 6410 <div class="bg-primary/10 rounded-lg shadow-sm p-4 max-w-[90%] sm:max-w-[80%] md:max-w-[75%] border-r-4 border-primary aifontSize"> 6411 <div class="prose max-w-none markdown-preview"> 6412 ${messageBodyHtml} 6413 </div> 6414 </div> 6415 <div class="mobileUser w-8 h-8 rounded-full bg-gray-200 flex items-center justify-center text-gray-600 text-sm font-bold flex-shrink-0 shadow-sm">${spanText}</div> 6416 </div> 6417 </div> 6418 `; 6419 messagesContainer.append(messageHtml); 6420 } 6421 6422 /** 6423 * ä»åå²è®°å½æ·»å AIæ¶æ¯ç»ï¼åå¹¶åround_idçå¤ä¸ªæ¶æ¯ï¼ 6424 * @param {Array} messageGroup - åround_idçå¤ä¸ªæ¶æ¯å¯¹è±¡ 6425 */ 6426 function addBotMessageGroupFromHistory(messageGroup) { 6427 const messageId = generateUniqueId(); 6428 let segmentsHtml = ''; 6429 let segmentIndex = 0; 6430 6431 // è·åround_idï¼ä»ç¬¬ä¸ä¸ªæ¶æ¯ä¸è·åï¼ 6432 const roundId = messageGroup.length >
6432 0 ? messageGroup[0].round_id : ''; 6433 6434 messageGroup.forEach(message => { 6435 const hasReasoning = message.reasoning_content && message.reasoning_content.trim() !== ''; 6436 const hasContent = message.content && message.content.trim() !== ''; 6437 6438 // å¦ææ²¡æå 容乿²¡ææèå 容ï¼è·³è¿ 6439 if (!hasReasoning && !hasContent) { 6440 return; 6441 } 6442 6443 // æ£æ¥contentæ¯å¦æ¯JSONæ ¼å¼ï¼å®¡æ ¸ç»æï¼æå å«tool_call 6444 let contentHtml = ''; 6445 let isJsonContent = false; 6446 let isToolCall = false; 6447 6448 if (hasContent) { 6449 // æ£æ¥æ¯å¦å å«tool_callæ ç¾ 6450 if (message.content.includes('<tool_call>') && message.content.includes('</tool_call>')) { 6451 // å å«tool_callæ ç¾ï¼æ 记为tool_callï¼ä½ä¸è®¾ç½®contentHtmlï¼ä¸æ¾ç¤ºcontentï¼ 6452 isToolCall = true; 6453 contentHtml = ''; // tool_callæ¶ä¸æ¾ç¤ºcontent 6454 } else { 6455 // æ£æ¥æ¯å¦æ¯çº¯JSONæ ¼å¼ï¼å®¡æ ¸ç»æï¼ 6456 try { 6457 const parsed = JSON.parse(message.content); 6458 if (typeof parsed === 'object') { 6459 contentHtml = `<pre style="background: #f5f5f5; padding: 10px; border-radius: 5px; overflow-x: auto;"><code>${message.content}</code></pre>`; 6460 isJsonContent = true; 6461 } 6462 } catch (e) { 6463 // 䏿¯JSONï¼æ£å¸¸å¤ç 6464 contentHtml = mdParser.render(message.content); 6465 } 6466 } 6467 } 6468 6469 // 为æ¯ä¸ªsegmentå建HTML 6470 let segmentHtml = ''; 6471 6472 if (hasReasoning && hasContent && !isJsonContent && !isToolCall) { 6473 // ææèè¿ç¨åå 容 6474 segmentHtml = ` 6475 <div class="segment-group mb-1" data-segment-index="${segmentIndex}"> 6476 <div class="think-header hideThinking flex items-center cursor-pointer text-sm text-gray-700 mb-1" data-index="${segmentIndex}"> 6477 <i class="fa fa-chevron-right think-toggle-icon mr-1"></i> 6478 <span style="font-weight:bold;">${language === 'Chinese' ? 'æèè¿ç¨' : 'Thinking'}</span> 6479 </div> 6480 <div class="think-content-body hidden pl-3 py-1 text-gray-600 text-sm aifontSize" style=""> 6481 ${mdParser.render(message.reasoning_content)} 6482 </div> 6483 <div class="content-body pl-3 mt-1"> 6484 ${contentHtml} 6485 </div> 6486 </div> 6487 `; 6488 } else if (hasReasoning && !hasContent) { 6489 // åªææèè¿ç¨ 6490 segmentHtml = ` 6491 <div class="segment-group mb-1" data-segment-index="${segmentIndex}"> 6492 <div class="think-header hideThinking flex items-center cursor-pointer text-sm text-gray-700 mb-1" data-index="${segmentIndex}"> 6493 <i class="fa fa-chevron-right think-toggle-icon mr-1"></i> 6494 <sp
6494an style="font-weight:bold;">${language === 'Chinese' ? 'æèè¿ç¨' : 'Thinking'}</span> 6495 </div> 6496 <div class="think-content-body hidden pl-3 py-1 text-gray-600 text-sm aifontSize" style=""> 6497 ${mdParser.render(message.reasoning_content)} 6498 </div> 6499 </div> 6500 `; 6501 } else if (hasReasoning && isToolCall) { 6502 // ææèè¿ç¨ä¸æ¯tool_callï¼åªæ¾ç¤ºthinkingï¼ä¸æ¾ç¤ºcontent 6503 segmentHtml = ` 6504 <div class="segment-group mb-1" data-segment-index="${segmentIndex}"> 6505 <div class="think-header hideThinking flex items-center cursor-pointer text-sm text-gray-700 mb-1" data-index="${segmentIndex}"> 6506 <i class="fa fa-chevron-right think-toggle-icon mr-1"></i> 6507 <span style="font-weight:bold;">${language === 'Chinese' ? 'æèè¿ç¨' : 'Thinking'}</span> 6508 </div> 6509 <div class="think-content-body hidden pl-3 py-1 text-gray-600 text-sm aifontSize" style=""> 6510 ${mdParser.render(message.reasoning_content)} 6511 </div> 6512 </div> 6513 `; 6514 } else if (hasContent && (isJsonContent || isToolCall)) { 6515 // åªæå 容䏿¯JSONæ ¼å¼ï¼tool_callçæ åµå·²å¨ä¸é¢å¤çï¼ 6516 segmentHtml = ` 6517 ${hasReasoning ? ` 6518 <div class="segment-group mb-1" data-segment-index="${segmentIndex++}"> 6519 <div class="think-header hideThinking flex items-center cursor-pointer text-sm text-gray-700 mb-1" data-index="${segmentIndex - 1}"> 6520 <i class="fa fa-chevron-right think-toggle-icon mr-1"></i> 6521 <span style="font-weight:bold;">${language === 'Chinese' ? 'æèè¿ç¨' : 'Thinking'}</span> 6522 </div> 6523 <div class="think-content-body hidden pl-3 py-1 text-gray-600 text-sm aifontSize" style=""> 6524 ${mdParser.render(message.reasoning_content)} 6525 </div> 6526 </div> 6527 ` : ''} 6528 <div class="segment-group mb-1" data-segment-index="${hasReasoning ? segmentIndex : segmentIndex}"> 6529 <div class="content-body pl-3 mt-1"> 6530 ${contentHtml} 6531 </div> 6532 </div> 6533 `; 6534 if (hasReasoning) segmentIndex++; 6535 } else if (hasContent) { 6536 // åªæå 容 6537 segmentHtml = ` 6538 <div class="segment-group mb-1" data-segment-index="${segmentIndex}"> 6539 <div class="content-body pl-3 mt-1"> 6540 ${contentHtml} 6541 </div> 6542 </div> 6543 `; 6544 } 6545 6546 segmentsHtml += segmentHtml; 6547 segmentIndex++; 6548 }); 6549 6550 if (!segmentsHtml) { 6551 return; // æ²¡æææå 容ï¼ä¸æ¸²æ 6552 } 6553 6554 const messageHtml = ` 6555 <div class="message bot-message message-appear" id="${messageId}
6555" data-round-id="${roundId}"> 6556 <div class="flex items-start"> 6557 <div class="avatar-container w-8 h-8 rounded-full overflow-hidden shadow-md flex-shrink-0"> 6558 <img src="/img/Able-Profile-Picture.jpg" alt="AI婿头å" class="w-full h-full object-cover"> 6559 </div> 6560 <div class="mobilePadding bg-white rounded-lg shadow-md p-4 max-w-[90%] sm:max-w-[80%] md:max-w-[75%] border-l-4 border-primary" style="width:100%;overflow:auto"> 6561 <div class="prose max-w-none markdown-preview"> 6562 <div class="thinkHtml markdown-body"> 6563 <div class="segments-container"> 6564 ${segmentsHtml} 6565 </div> 6566 </div> 6567 </div> 6568 </div> 6569 </div> 6570 </div> 6571 `; 6572 6573 messagesContainer.append(messageHtml); 6574 6575 // ç»å®æå äºä»¶ 6576 $(`#${messageId} .think-header`).on('click', function (e) { 6577 e.stopPropagation(); 6578 const $container = $(this).closest('.segment-group'); 6579 const $contentBody = $container.find('.think-content-body'); 6580 const $toggleIcon = $container.find('.think-toggle-icon'); 6581 $contentBody.slideToggle(300, function () { 6582 $toggleIcon.toggleClass('fa-chevron-down fa-chevron-right'); 6583 }); 6584 }); 6585 } 6586 6587 /** 6588 * ä»åå²è®°å½æ·»å AIæ¶æ¯ï¼å·²åºå¼ï¼æ¹ç¨addBotMessageGroupFromHistoryï¼ 6589 * @param {Object} message - æ¶æ¯å¯¹è±¡ï¼å å«contentåreasoning_content 6590 */ 6591 function addBotMessageFromHistory(message) { 6592 const messageId = generateUniqueId(); 6593 const hasReasoning = message.reasoning_content && message.reasoning_content.trim() !== ''; 6594 const hasContent = message.content && message.content.trim() !== ''; 6595 6596 // è·åround_id 6597 const roundId = message.round_id || ''; 6598 6599 // å¦ææ²¡æå 容åæèå 容ï¼è·³è¿ 6600 if (!hasReasoning && !hasContent) { 6601 return; 6602 } 6603 6604 // æ£æ¥contentæ¯å¦æ¯JSONæ ¼å¼ï¼å®¡æ ¸ç»æï¼æå å«tool_call 6605 let contentHtml = ''; 6606 let isJsonContent = false; 6607 let isToolCall = false; 6608 6609 if (hasContent) { 6610 // æ£æ¥æ¯å¦å å«tool_callæ ç¾ 6611 if (message.content.includes('<tool_call>') && message.content.includes('</tool_call>')) { 6612 // å å«tool_callæ ç¾ï¼æ 记为tool_callï¼ä½ä¸è®¾ç½®contentHtmlï¼ä¸æ¾ç¤ºcontentï¼ 6613 isToolCall = true; 6614 contentHtml = ''; // tool_callæ¶ä¸æ¾ç¤ºcontent 6615 } else { 6616 // æ£æ¥æ¯å¦æ¯çº¯JSONæ ¼å¼ï¼å®¡æ ¸ç»æï¼ 6617 try { 6618 const parsed = JSON.parse(message.content); 6619 if (typeof parsed === 'object') { 6620 contentHtml = `<pre style="background: #f5f5f5; padding: 10px; border-radius: 5px; overflow-x: auto;"><code>${message.content}</code></pre>`; 6621 isJsonContent = true; 6622 } 6623 } catch (e) { 6624 // 䏿¯JSONï¼æ£å¸¸å¤ç 6625 contentHtml = mdParser.render(message.content); 6626 } 6627 } 6628 } 6629 6630 // æå»ºæ¶æ¯HTML 6631 let segmentsHtml = ''; 6632 6633 if (hasReasoning && hasContent && !isJsonContent && !isToolCall) { 6634 // ææèè¿ç¨åå 容 6635 segmentsHtml = ` 6636 <div class="segments-container"> 6637 <div class="segment-group mb-1" data-segment-index="0"> 6638 <div class="think-header hideThinking flex items-center cursor-pointer text-sm text-gray-700 mb-1" data-index="0"> 6639 <i class="fa fa-chevron-right think-toggle-icon mr-1"></i> 6640 <sp
6640an style="font-weight:bold;">${language === 'Chinese' ? 'æèè¿ç¨' : 'Thinking'}</span> 6641 </div> 6642 <div class="think-content-body hidden pl-3 py-1 text-gray-600 text-sm aifontSize" style=""> 6643 ${mdParser.render(message.reasoning_content)} 6644 </div> 6645 <div class="content-body pl-3 mt-1"> 6646 ${contentHtml} 6647 </div> 6648 </div> 6649 </div> 6650 `; 6651 } else if (hasReasoning && !hasContent) { 6652 // åªææèè¿ç¨ 6653 segmentsHtml = ` 6654 <div class="segments-container"> 6655 <div class="segment-group mb-1" data-segment-index="0"> 6656 <div class="think-header hideThinking flex items-center cursor-pointer text-sm text-gray-700 mb-1" data-index="0"> 6657 <i class="fa fa-chevron-right think-toggle-icon mr-1"></i> 6658 <span style="font-weight:bold;">${language === 'Chinese' ? 'æèè¿ç¨' : 'Thinking'}</span> 6659 </div> 6660 <div class="think-content-body hidden pl-3 py-1 text-gray-600 text-sm aifontSize" style=""> 6661 ${mdParser.render(message.reasoning_content)} 6662 </div> 6663 </div> 6664 </div> 6665 `; 6666 } else if (hasReasoning && isToolCall) { 6667 // ææèè¿ç¨ä¸æ¯tool_callï¼åªæ¾ç¤ºthinkingï¼ä¸æ¾ç¤ºcontent 6668 segmentsHtml = ` 6669 <div class="segments-container"> 6670 <div class="segment-group mb-1" data-segment-index="0"> 6671 <div class="think-header hideThinking flex items-center cursor-pointer text-sm text-gray-700 mb-1" data-index="0"> 6672 <i class="fa fa-chevron-right think-toggle-icon mr-1"></i> 6673 <span style="font-weight:bold;">${language === 'Chinese' ? 'æèè¿ç¨' : 'Thinking'}</span> 6674 </div> 6675 <div class="think-content-body hidden pl-3 py-1 text-gray-600 text-sm aifontSize" style=""> 6676 ${mdParser.render(message.reasoning_content)} 6677 </div> 6678 </div> 6679 </div> 6680 `; 6681 } else if (hasContent && (isJsonContent || isToolCall)) { 6682 // åªæå 容䏿¯JSONæ ¼å¼ï¼tool_callçæ åµå·²å¨ä¸é¢å¤çï¼ 6683 segmentsHtml = ` 6684 <div class="segments-container"> 6685 ${hasReasoning ? ` 6686 <div class="segment-group mb-1" data-segment-index="0"> 6687 <div class="think-header hideThinking flex items-center cursor-pointer text-sm text-gray-700 mb-1" data-index="0"> 6688 <i class="fa fa-chevron-right think-toggle-icon mr-1"></i> 6689 <sp
6689an style="font-weight:bold;">${language === 'Chinese' ? 'æèè¿ç¨' : 'Thinking'}</span> 6690 </div> 6691 <div class="think-content-body hidden pl-3 py-1 text-gray-600 text-sm aifontSize" style=""> 6692 ${mdParser.render(message.reasoning_content)} 6693 </div> 6694 </div> 6695 ` : ''} 6696 <div class="segment-group mb-1" data-segment-index="${hasReasoning ? '1' : '0'}"> 6697 <div class="content-body pl-3 mt-1"> 6698 ${contentHtml} 6699 </div> 6700 </div> 6701 </div> 6702 `; 6703 } else if (hasContent) { 6704 // åªæå 容 6705 segmentsHtml = contentHtml; 6706 } 6707 6708 const messageHtml = ` 6709 <div class="message bot-message message-appear" id="${messageId}" data-round-id="${roundId}"> 6710 <div class="flex items-start"> 6711 <div class="w-8 h-8 rounded-full overflow-hidden shadow-md flex-shrink-0"> 6712 <img src="/img/Able-Profile-Picture.jpg" alt="AI婿头å" class="w-full h-full object-cover"> 6713 </div> 6714 <div class="mobilePadding bg-white rounded-lg shadow-md p-4 max-w-[90%] sm:max-w-[80%] md:max-w-[75%] border-l-4 border-primary" style="width:100%;overflow:auto"> 6715 <div class="prose max-w-none markdown-preview"> 6716 <div class="thinkHtml markdown-body"> 6717 ${segmentsHtml} 6718 </div> 6719 </div> 6720 </div> 6721 </div> 6722 </div> 6723 `; 6724 6725 messagesContainer.append(messageHtml); 6726 6727 // ç»å®æå äºä»¶ 6728 $(`#${messageId} .think-header`).on('click', function (e) { 6729 e.stopPropagation(); 6730 const $container = $(this).closest('.segment-group'); 6731 const $contentBody = $container.find('.think-content-body'); 6732 const $toggleIcon = $container.find('.think-toggle-icon'); 6733 $contentBody.slideToggle(300, function () { 6734 $toggleIcon.toggleClass('fa-chevron-down fa-chevron-right'); 6735 }); 6736 }); 6737 } 6738 6739 6740 /** 6741 * æ§è¡æ¶æ¯çåå¤çæä½ 6742 * å æ¬ï¼å¤ç龿¥ãPDBãLive Chatãç»å®äºä»¶ç 6743 */ 6744 function executeMessagePostProcessing() { 6745 // å¤ç龿¥å±æ§ 6746 $('.thinkHtml a').attr('target', '_blank'); 6747 // 夿URLæ¯å¦ä¸ºå¾ç 6748 function isImageUrl(url, extensions) { 6749 const lowerUrl = url.toLowerCase(); 6750 return extensions.some(ext => lowerUrl.endsWith(ext)); 6751 } 6752 6753 // ä»URLæåæä»¶å 6754 function getFileName(url) { 6755 const parts = url.split('/'); 6756 return parts[parts.length - 1] || 'å¾ç'; 6757 } 6758 const imageExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp', '.svg']; 6759 6760 // å¤çå¾ç龿¥ 6761 $('a:not(:has(img))').each(function () { 6762 const $link = $(this); 6763 if ($link.find('img').length > 0) { 6764 return; 6765 } 6766 const href = $link.attr('href'); 6767 6768 // æ£æ¥é¾æ¥æ¯å¦ä¸ºå¾ç 6769 if (href && isImageUrl(href, imageExtensions)) { 6770 // å建å¾çå ç´ 6771 const $img = $('<img>', { 6772 'src': href, 6773 'alt': 'å¾çé¢è§: ' + getFileName(href), 6774 'data-jq-auto-added': 'true' 6775 }).css({ 6776 'vertical-align': 'middle', 6777 'border-radius': '3px' 6778 }); 6779 6780 $link.append($img); 6781 6782 $img.on('load error', function () { 6783 setTimeout(() => { 6784 scrollToBottom(true); 6785 }, 300); 6786 }); 6787 } 6788 }); 6789 6790 // å¤çLive Chat龿¥ 6791 $('.thinkHtml a[href="@ptglab-live-chat"]').each(function () { 6792 const $link = $(this); 6793 $link.removeAttr('href'); 6794 $link.addClass('open-live-chat'); 6795 $link.text('Live Chat'); 6796 }); 6797 6798 // å¤ççº¯ææ¬å½¢å¼ç @ptglab-live-chat 6799 $('.thinkHtml').each(function () { 6800 const $thinkHtml = $(this); 6801 6802 // ä¿å3D容å¨ï¼é¿å è¢«æ¸ ç©º 6803 const $pdbContainers = $thinkHtml.find('.pdb-3d-container').detach(); 6804 6805 let htmlContent = $thinkHtml.html(); 6806 6807 // 1. å å¤çææå å«@ptglab-live-chatçaæ ç¾ 6808 htmlContent = htmlContent.replace( 6809 /<a\s+[^>]*?>([^<]*?)@ptglab-live-chat([^<]*?)<\/a>/gi, 6810 function (match, prefix, suffix) { 6811 return `<span class="open-live-chat">${prefix}Live Chat${suffix}</span>`; 6812 } 6813 ); 6814 6815 // 2. å¤ççº¯ææ¬å½¢å¼ç@ptglab-live-chat 6816 htmlContent = htmlContent.replace( 6817 /([^<>\s])?@ptglab-live-chat([^<>\s])?/g, 6818 function (match, prefix, suffix) { 6819 const pre = prefix || ''; 6820 const suf = suffix || ''; 6821 return `${pre}<span class="open-live-chat">Live Chat</span>${suf}`; 6822 } 6823 ); 6824 6825 // 3. æ¸ çå¯è½æ®ççé误æ ç¾ 6826 htmlContent = htmlContent.replace(/<a\s+[^>]*?><\/a>/gi, ''); 6827 6828 // æ´æ°å 容 6829 $thinkHtml.html(htmlContent); 6830 6831 // æ¢å¤3Då®¹å¨ 6832 if ($pdbContainers.length > 0) {
6833 $thinkHtml.append($pdbContainers); 6834 } 6835 }); 6836 6837 // ç»å®Live Chatç¹å»äºä»¶ 6838 $('.open-live-chat') 6839 .off('click') 6840 .click(function () { 6841 const data = { 6842 type: 'fromChild', 6843 message: 'msg', 6844 status: 'success' 6845 }; 6846 window.parent.postMessage(data, '*'); 6847 }); 6848 6849 // ç»å®æèæå äºä»¶ 6850 $('#messagesContainer') 6851 .off('click', '.think-header') 6852 .on('click', '.think-header', function (e) { 6853 e.stopPropagation(); 6854 const $container = $(this).closest('.segment-group'); 6855 const $contentBody = $container.find('.think-content-body'); 6856 const $toggleIcon = $container.find('.think-toggle-icon'); 6857 $contentBody.slideToggle(300, function () { 6858 $toggleIcon.toggleClass('fa-chevron-down fa-chevron-right'); 6859 }); 6860 }); 6861 6862 const $pdbLinks = $('.content-body a[href*=".pdb"]'); 6863 if ($pdbLinks.length > 0) { 6864 // 为æ¯ä¸ªPDB龿¥å¤çhref屿§ 6865 $pdbLinks.each(function () { 6866 const $link = $(this); 6867 const originalHref = $link.attr('href'); 6868 6869 // ç§»é¤åå§href屿§ï¼å°å ¶å¼ä¿åå°data-original-hrefä¸ 6870 $link.removeAttr('href'); 6871 $link.attr('data-original-href', originalHref); 6872 }); 6873 } 6874 // åªæ¥æ¾å½åæ¶æ¯ç.content-bodyä¸ç.pdb龿¥ 6875 setTimeout(() => { 6876 initMermaidRender(); 6877 $(document).off('click', 'a[data-original-href]', handleLinkClick); 6878 $(document).on('click', 'a[data-original-href]', handleLinkClick); 6879 }, 300) 6880 6881 } 6882 6883 6884 6885 6886 6887 function reportErrorToApi(dialogId, roundId, errorMsg) { 6888 6889 let topHref; 6890 try { 6891 // å°è¯è·åé¡¶å±çªå£ç屿§ï¼è·¨åæ¶ä¼æéï¼ 6892 topHref = window.top.location.href; 6893 } catch (e) {
6894 // è·¨åæ¥éæ¶ï¼é级为å½å页é¢ç屿§ 6895 topTitle = window.self.document.title; // å½å页颿 é¢ 6896 console.warn('è·¨åæ æ³è®¿é®é¡¶å±çªå£å±æ§ï¼å·²é级为å½å页é¢å¼:', e); 6897 } 6898 6899 // 1. æé ErrorReport æ ¼å¼çåæ°ï¼ä¸¥æ ¼å¹é åç«¯è¦æ±ï¼ 6900 const errorReport = { 6901 dialog_id: dialogId || '', // 对è¯IDï¼ç¨ä½ 代ç éçå ¨å±uuidï¼ 6902 round_id: roundId || '', // 轮次IDï¼è天çmessageIdï¼ 6903 msg: errorMsg || 'æªç¥é误', // é误æç¤º 6904 current_url: topHref 6905 }; 6906 6907 6908 // 2. è°ç¨éè¯¯ä¸æ¥æ¥å£ï¼ç¬ç«fetchï¼ä¸å½±ååæé»è¾ï¼ 6909 fetch('https://aipublic.ptglab.com/llm/v1/report-error', { 6910 method: 'POST', 6911 headers: { 'Content-Type': 'application/json' }, 6912 body: JSON.stringify(errorReport) // 转JSONå符串 6913 }).catch(reportErr => { 6914 // 䏿¥å¤±è´¥åªææ¥å¿ï¼ä¸å½±å主æµç¨ 6915 console.error('éè¯¯ä¸æ¥æ¥å£è°ç¨å¤±è´¥:', reportErr); 6916 }); 6917 } 6918 6919 </script>
6919 6920</body> 6921</html>
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.