1<!DOCTYPE html> 2<html lang="en"> 3<head> 4 <meta charset="UTF-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 <title>Liquid AI LFM 2.5 - Supercharged Edge Assistant</title> 7 <!-- Premium Fonts --> 8 <link rel="preconnect" href="https://fonts.googleapis.com"> 9 <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> 10 <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&family=Outfit:wght@300;400;500;600;700;800&display=swap" rel="stylesheet"> 11 <!-- FontAwesome Icons --> 12 <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> 13 <!-- Prism.js Tomorrow Theme for premium code highlighting --> 14 <link href="https://cdn.jsdelivr.net/npm/[email protected]/themes/prism-tomorrow.min.css" rel="stylesheet" /> 15 16 <style> 17 :root { 18 --bg-primary: #faf9f6; /* Clean warm off-white */ 19 --bg-secondary: #ffffff; /* Pure white for panels, cards */ 20 --bg-tertiary: #f4f3f0; /* Soft warm grey for boundaries / active items */ 21 --text-primary: #000000; /* True corporate black */ 22 --text-secondary: #636366; /* Subtle warm medium grey */ 23 --glass-bg: #ffffff; 24 --glass-border: #e2e1df; /* Clean warm borders */ 25 --glass-border-hover: #000000; 26 --radius-lg: 20px; /* Modern card rounding */ 27 --radius-md: 14px; 28 --radius-sm: 10px; 29 --accent-teal: #10b981; /* Crisp mint green for success/connected state */ 30 } 31 32 * { 33 margin: 0; 34 padding: 0; 35 box-sizing: border-box; 36 font-family: 'Outfit', sans-serif; 37 scroll-behavior: smooth; 38 } 39 40 body { 41 background-color: var(--bg-primary); 42 color: var(--text-primary); 43 overflow: hidden; 44 height: 100vh; 45 display: flex; 46 } 47 48 /* --- Scrollbar styling --- */ 49 ::-webkit-scrollbar {
50 width: 6px; 51 height: 6px; 52 } 53 ::-webkit-scrollbar-track { 54 background: rgba(0, 0, 0, 0.01); 55 } 56 ::-webkit-scrollbar-thumb { 57 background: rgba(0, 0, 0, 0.1); 58 border-radius: 10px; 59 } 60 ::-webkit-scrollbar-thumb:hover { 61 background: #000000; 62 } 63 64 /* --- Main Layout --- */ 65 .app-container { 66 display: flex; 67 width: 100%; 68 height: 100vh; 69 position: relative; 70 } 71 72 /* --- Sidebar Panel --- */ 73 aside { 74 width: 320px; 75 background-color: var(--bg-secondary); 76 border-right: 1px solid var(--glass-border); 77 display: flex; 78 flex-direction: column; 79 height: 100%; 80 z-index: 10; 81 transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); 82 } 83 84 .sidebar-header { 85 padding: 20px 24px; 86 border-bottom: 1px solid var(--glass-border); 87 display: flex; 88 align-items: center; 89 justify-content: space-between; 90 } 91 92 .brand { 93 display: flex; 94 align-items: center; 95 gap: 8px; 96 } 97 98 .new-chat-btn { 99 background: #000000; 100 border: none; 101 border-radius: 9999px; 102 color: #ffffff; 103 padding: 12px 24px; 104 margin: 16px 24px 8px; 105 cursor: pointer; 106 display: flex; 107 align-items: center; 108 justify-content: center; 109 gap: 10px; 110 font-weight: 600; 111 font-size: 14px; 112 transition: all 0.2s ease; 113 } 114 115 .new-chat-btn:hover { 116 background: #222222; 117 transform: translateY(-1px); 118 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12); 119 } 120 121 /* --- Settings Panel --- */ 122 .sidebar-sections { 123 flex: 1; 124 overflow-y: auto; 125 padding: 8px 24px 24px; 126 display: flex; 127 flex-direction: column; 128 gap: 20px; 129 } 130 131 .section-title { 132 font-size: 11px; 133 font-weight: 700; 134 text-transform: uppercase; 135 letter-spacing: 1.5px; 136 color: var(--text-secondary); 137 margin-bottom: 8px; 138 display: flex; 139 align-items: center; 140 gap: 8px; 141 } 142 143 .sidebar-card { 144 background: var(--bg-secondary); 145 border: 1px solid var(--glass-border); 146 border-radius: var(--radius-md); 147 padding: 16px; 148 display: flex; 149 flex-direction: column; 150 gap: 14px; 151 } 152 153 .setting-group { 154 display: flex; 155 flex-direction: column; 156 gap: 6px; 157 } 158 159 .setting-label { 160 font-size: 12px; 161 font-weight: 600; 162 color: var(--text-secondary); 163 display: flex; 164 justify-content: space-between; 165 } 166 167 .setting-val { 168 color: #000000; 169 font-weight: 700; 170 font-family: 'JetBrains Mono', monospace; 171 } 172 173 .setting-slider { 174 -webkit-appearance: none; 175 width: 100%; 176 height: 4px; 177 border-radius: 5px; 178 background: var(--bg-tertiary); 179 outline: none; 180 cursor: pointer; 181 } 182 183 .setting-slider::-webkit-slider-thumb { 184 -webkit-appearance: none; 185 appearance: none; 186 width: 14px; 187 height: 14px; 188 border-radius: 50%; 189 background: #000000; 190 transition: transform 0.1s ease; 191 } 192 193 .setting-slider::-webkit-slider-thumb:hover { 194 transform: scale(1.25); 195 } 196 197 .form-select { 198 width: 100%; 199 background-color: var(--bg-tertiary); 200 border: 1px solid var(--glass-border); 201 border-radius: var(--radius-sm); 202 color: var(--text-primary); 203 padding: 8px 12px; 204 outline: none; 205 font-size: 13.5px; 206 cursor: pointer; 207 transition: border-color 0.2s ease; 208 } 209 210 .form-select:focus { 211 border-color: #000000; 212 } 213 214 .form-textarea { 215 width: 100%; 216 background-color: var(--bg-tertiary); 217 border: 1px solid var(--glass-border); 218 border-radius: var(--radius-sm); 219 color: var(--text-primary); 220 padding: 10px; 221 outline: none; 222 font-size: 13px; 223 resize: none; 224 height: 80px; 225 line-height: 1.4; 226 transition: border-color 0.2s ease; 227 } 228 229 .form-textarea:focus { 230 border-color: #000000; 231 } 232 233 /* --- Chat History List --- */ 234 .chat-list { 235 display: flex; 236 flex-direction: column; 237 gap: 6px; 238 max-height: 160px; 239 overflow-y: auto; 240 padding-right: 4px; 241 } 242 243 .chat-item { 244 display: flex; 245 align-items: center; 246 justify-content: space-between; 247 padding: 8px 12px; 248 border-radius: var(--radius-sm); 249 cursor: pointer; 250 background: transparent; 251 transition: all 0.2s ease; 252 font-size: 13.5px; 253 border: 1px solid transparent; 254 color: var(--text-secondary); 255 } 256 257 .chat-item:hover { 258 background: var(--bg-tertiary); 259 color: var(--text-primary); 260 } 261 262 .chat-item.active { 263 background: var(--bg-tertiary); 264 border-color: var(--glass-border); 265 color: #000000; 266 font-weight: 600; 267 } 268 269 .chat-item-title { 270 white-space: nowrap; 271 overflow: hidden; 272 text-overflow: ellipsis; 273 max-width: 200px; 274 pointer-events: none; 275 } 276 277 .delete-chat-btn { 278 background: none; 279 border: none; 280 color: #c1c1c4; 281 cursor: pointer; 282 transition: color 0.2s ease; 283 } 284 285 .delete-chat-btn:hover { 286 color: #ef4444; 287 } 288 289 /* --- Main Content Panel --- */ 290 main { 291 flex: 1; 292 display: flex; 293 flex-direction: column; 294 height: 100%; 295 position: relative; 296 } 297 298 /* --- Header Navigation & Tabs --- */ 299 .main-header { 300 height: 72px; 301 background: #ffffff; 302 border-bottom: 1px solid var(--glass-border); 303 display: flex; 304 align-items: center; 305 justify-content: space-between; 306 padding: 0 32px; 307 z-index: 9; 308 } 309 310 .hamburger { 311 display: none; 312 cursor: pointer; 313 font-size: 20px; 314 color: var(--text-secondary); 315 transition: color 0.2s ease; 316 } 317 318 .hamburger:hover { 319 color: var(--text-primary); 320 } 321 322 /* Top header brand elements */ 323 .header-brand-section { 324 display: flex; 325 align-items: center; 326 gap: 16px; 327 } 328 329 .header-nav-links { 330 display: flex; 331 gap: 28px; 332 align-items: center; 333 } 334 335 .header-nav-links a { 336 color: var(--text-primary); 337 text-decoration: none; 338 font-size: 12px; 339 font-weight: 600; 340 letter-spacing: 0.8px; 341 opacity: 0.65; 342 transition: opacity 0.2s ease; 343 text-transform: uppercase; 344 } 345 346 .header-nav-links a:hover { 347 opacity: 1; 348 } 349 350 .nav-tabs { 351 display: flex; 352 gap: 4px; 353 background: var(--bg-tertiary); 354 padding: 4px; 355 border-radius: 9999px; 356 border: 1px solid var(--glass-border); 357 } 358 359 .tab-btn { 360 background: transparent; 361 border: none; 362 color: var(--text-secondary); 363 font-weight: 600; 364 font-size: 13px; 365 padding: 6px 14px; 366 border-radius: 9999px; 367 cursor: pointer; 368 display: flex; 369 align-items: center; 370 gap: 6px; 371 transition: all 0.2s ease; 372 } 373 374 .tab-btn.active { 375 background: #000000; 376 color: #ffffff; 377 box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08); 378 } 379 380 .tab-btn:hover:not(.active) { 381 color: var(--text-primary); 382 background: rgba(0, 0, 0, 0.03); 383 } 384 385 .status-container { 386 display: flex; 387 align-items: center; 388 gap: 8px; 389 font-size: 12px; 390 font-weight: 500; 391 color: var(--text-secondary); 392 } 393 394 .status-dot { 395 width: 7px; 396 height: 7px; 397 border-radius: 50%; 398 background-color: #10b981; 399 position: relative; 400 } 401 402 .status-dot::after { 403 content: ''; 404 position: absolute; 405 width: 100%; 406 height: 100%; 407 border-radius: 50%; 408 background: inherit; 409 animation: ping 1.5s infinite; 410 } 411 412 /* --- Tab View Panels --- */ 413 .tab-panel { 414 display: none; 415 flex: 1; 416 flex-direction: column; 417 overflow: hidden; 418 position: relative; 419 } 420 421 .tab-panel.active { 422 display: flex; 423 } 424 425 /* --- Chat Assistant View --- */ 426 .chat-history-scroll { 427 flex: 1; 428 overflow-y: auto; 429 padding: 32px 32px 100px; 430 display: flex; 431 flex-direction: column; 432 gap: 24px; 433 } 434 435 /* Centered minimal welcome view */ 436 .welcome-view { 437 max-width: 600px; 438 width: 100%; 439 margin: auto; 440 display: flex; 441 flex-direction: column; 442 align-items: center; 443 justify-content: center; 444 gap: 16px; 445 padding: 80px 24px 120px; 446 text-align: center; 447 animation: slide-up 0.4s cubic-bezier(0.4, 0, 0.2, 1); 448 } 449 450 .welcome-main-title { 451 font-size: 32px; 452 font-weight: 700; 453 color: #000000; 454 letter-spacing: -0.8px; 455 } 456 457 .welcome-main-subtitle { 458 font-size: 14px; 459 line-height: 1.5; 460 color: var(--text-secondary); 461 max-width: 440px; 462 margin-bottom: 8px; 463 } 464 465 .starters-minimal { 466 display: flex; 467 flex-wrap: wrap; 468 gap: 8px; 469 justify-content: center; 470 width: 100%; 471 } 472 473 .starter-pill { 474 background: #ffffff; 475 border: 1px solid #e2e1df; 476 padding: 8px 16px; 477 border-radius: 9999px; 478 font-size: 13px; 479 font-weight: 500; 480 color: var(--text-primary); 481 cursor: pointer; 482 transition: all 0.2s ease; 483 } 484 485 .starter-pill:hover { 486 background: #000000; 487 color: #ffffff; 488 border-color: #000000; 489 transform: translateY(-1px); 490 } 491 492 /* --- Chat Bubbles --- */ 493 .message-row { 494 display: flex; 495 width: 100%; 496 animation: slide-up 0.3s cubic-bezier(0.4, 0, 0.2, 1); 497 } 498 499 .message-row.user-row { 500 justify-content: flex-end; 501 } 502 503 .message-bubble { 504 max-width: 80%; 505 padding: 16px 20px; 506 border-radius: var(--radius-lg); 507 position: relative; 508 line-height: 1.6; 509 font-size: 15px; 510 } 511 512 .user-row .message-bubble { 513 background: #f4f3f0; 514 border: 1px solid #dcdad7; 515 border-bottom-right-radius: 4px; 516 color: var(--text-primary); 517 } 518 519 .assistant-row .message-bubble { 520 background: #ffffff; 521 border: 1px solid var(--glass-border); 522 border-bottom-left-radius: 4px; 523 color: var(--text-primary); 524 display: flex; 525 flex-direction: column; 526 gap: 12px; 527 box-shadow: 0 4px 20px rgba(0, 0, 0, 0.02); 528 } 529 530 /* --- Collapsible Thinking Accordion --- */ 531 .thinking-accordion { 532 background: #faf9f6; 533 border: 1px solid #e2e1df; 534 border-radius: var(--radius-md); 535 overflow: hidden; 536 transition: all 0.3s ease; 537 border-left: 3px solid #000000; 538 } 539 540 .thinking-header { 541 padding: 12px 16px; 542 display: flex; 543 align-items: center; 544 justify-content: space-between; 545 cursor: pointer; 546 user-select: none; 547 font-size: 13px; 548 font-weight: 600; 549 color: #000000; 550 transition: background-color 0.2s ease; 551 } 552 553 .thinking-header:hover { 554 background-color: var(--bg-tertiary); 555 } 556 557 .thinking-status { 558 display: flex; 559 align-items: center; 560 gap: 8px; 561 } 562 563 .thinking-status i.spinner-icon { 564 animation: spin 1.5s linear infinite; 565 } 566 567 .thinking-status i.check-icon { 568 color: #10b981; 569 } 570 571 .thinking-body { 572 padding: 16px; 573 font-size: 13px; 574 color: var(--text-secondary); 575 border-top: 1px solid #e2e1df; 576 line-height: 1.6; 577 background: #ffffff; 578 white-space: pre-wrap; 579 font-family: 'JetBrains Mono', monospace; 580 } 581 582 .thinking-accordion.collapsed .thinking-body { 583 display: none; 584 } 585 586 .chevron-icon { 587 transition: transform 0.2s ease; 588 } 589 590 .thinking-accordion.collapsed .chevron-icon { 591 transform: rotate(-90deg); 592 } 593 594 /* --- Text Output Styling (Markdown) --- */ 595 .message-content { 596 word-break: break-word; 597 } 598 599 .message-content p { 600 margin-bottom: 12px; 601 } 602 603 .message-content p:last-child { 604 margin-bottom: 0; 605 } 606 607 .message-content strong { 608 color: #000000; 609 font-weight: 600; 610 } 611 612 .message-content ul, .message-content ol { 613 margin-left: 20px; 614 margin-bottom: 12px; 615 } 616 617 .message-content li { 618 margin-bottom: 4px; 619 } 620 621 .message-content code { 622 font-family: 'JetBrains Mono', monospace; 623 background-color: var(--bg-tertiary); 624 padding: 3px 6px; 625 border-radius: var(--radius-sm); 626 font-size: 13px; 627 color: #a11; /* warm burgundy classic light theme style */ 628 border: 1px solid #e2e1df; 629 } 630 631 /* --- Code Block Wrapper --- */ 632 .code-block-wrapper { 633 position: relative; 634 margin: 16px 0; 635 border-radius: var(--radius-md); 636 overflow: hidden; 637 border: 1px solid #e2e1df; 638 background-color: #1c1b18; /* Premium warm charcoal */ 639 } 640 641 .code-block-header { 642 display: flex; 643 justify-content: space-between; 644 align-items: center; 645 background-color: #151412; 646 padding: 10px 16px; 647 border-bottom: 1px solid rgba(255, 255, 255, 0.04); 648 font-size: 11.5px; 649 color: #a3a3a3; 650 } 651 652 .code-block-lang { 653 text-transform: uppercase; 654 font-family: 'JetBrains Mono', monospace; 655 font-weight: 700; 656 color: #faf9f6; 657 } 658 659 .copy-code-btn { 660 background: none; 661 border: none; 662 color: #a3a3a3; 663 cursor: pointer; 664 transition: color 0.2s ease; 665 display: flex; 666 align-items: center; 667 gap: 6px; 668 font-siz
668e: 11.5px; 669 } 670 671 .copy-code-btn:hover { 672 color: #ffffff; 673 } 674 675 .message-content pre { 676 margin: 0 !important; 677 padding: 16px !important; 678 background-color: transparent !important; 679 overflow-x: auto; 680 } 681 682 .message-content pre code { 683 font-size: 13px; 684 background: none; 685 border: none; 686 padding: 0; 687 color: #f5f5f7; 688 font-family: 'JetBrains Mono', monospace; 689 } 690 691 .message-content blockquote { 692 border-left: 3px solid #000000; 693 padding-left: 16px; 694 margin: 16px 0; 695 color: var(--text-secondary); 696 font-style: italic; 697 } 698 699 .message-content table { 700 width: 100%; 701 border-collapse: collapse; 702 margin: 16px 0; 703 font-size: 14px; 704 } 705 706 .message-content th, .message-content td { 707 border: 1px solid var(--glass-border); 708 padding: 10px 12px; 709 text-align: left; 710 } 711 712 .message-content th { 713 background-color: var(--bg-tertiary); 714 color: #000000; 715 font-weight: 600; 716 } 717 718 .message-content tr:nth-child(even) { 719 background-color: rgba(0, 0, 0, 0.01); 720 } 721 722 /* --- Footer Input Bar --- */ 723 .footer-input-bar { 724 position: absolute; 725 bottom: 0; 726 left: 0; 727 width: 100%; 728 padding: 24px 32px 32px; 729 background: linear-gradient(to top, var(--bg-primary) 70%, transparent); 730 pointer-events: none; 731 } 732 733 .input-wrapper { 734 max-width: 800px; 735 margin: auto; 736 background: #ffffff; 737 border: 1px solid #dcdad7; 738 border-radius: var(--radius-lg); 739 padding: 8px 14px; 740 display: flex; 741 align-items: center; 742 gap: 12px; 743 box-shadow: 0 10px 30px rgba(0, 0, 0, 0.04); 744 pointer-events: auto; 745 transition: border-color 0.25s ease, box-shadow 0.25s ease; 746 } 747 748 .input-wrapper:focus-within { 749 border-color: #000000; 750 box-shadow: 0 10px 30px rgba(0, 0, 0, 0.06), 0 0 0 1px #000000; 751 } 752 753 .chat-textarea { 754 flex: 1; 755 background: transparent; 756 border: none; 757 outline: none; 758 color: var(--text-primary); 759 resize: none; 760 height: 44px; 761 min-height: 44px; 762 max-height: 200px; 763 padding: 10px 4px; 764 font-size: 15px; 765 line-height: 1.5; 766 transition: height 0.1s ease; 767 } 768 769 .action-btn-circle { 770 width: 40px; 771 height: 40px; 772 border-radius: 50%; 773 border: none; 774 display: flex; 775 align-items: center; 776 justify-content: center; 777 cursor: pointer; 778 transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); 779 } 780 781 .send-btn { 782 background: #000000; 783 color: #ffffff; 784 box-shadow: none; 785 } 786 787 .send-btn:hover { 788 transform: scale(1.05); 789 background: #222222; 790 } 791 792 .stop-btn { 793 background: #fee2e2; 794 color: #ef4444; 795 border: 1px solid #fca5a5; 796 display: none; 797 } 798 799 .stop-btn:hover { 800 background: #fecaca; 801 } 802 803 .char-counter { 804 font-size: 11px; 805 color: var(--text-secondary); 806 font-family: 'JetBrains Mono', monospace; 807 align-self: flex-end; 808 margin-bottom: 8px; 809 user-select: none; 810 } 811 812 /* --- Tool Sandbox View Panel --- */ 813 .sandbox-container { 814 display: grid; 815 grid-template-columns: 340px 1fr; 816 height: calc(100vh - 72px); 817 overflow: hidden; 818 } 819 820 .sandbox-control { 821 border-right: 1px solid var(--glass-border); 822 padding: 24px; 823 background: var(--bg-secondary); 824 display: flex; 825 flex-direction: column; 826 gap: 20px; 827 overflow-y: auto; 828 } 829 830 .sandbox-interactive { 831 display: flex; 832 flex-direction: column; 833 overflow: hidden; 834 position: relative; 835 height: 100%; 836 } 837 838 .tool-chip-list { 839 display: flex; 840 flex-direction: column; 841 gap: 10px; 842 } 843 844 .tool-chip { 845 border: 1px solid var(--glass-border); 846 background: var(--bg-secondary); 847 border-radius: var(--radius-md); 848 padding: 12px; 849 display: flex; 850 flex-direction: column; 851 gap: 6px; 852 transition: all 0.2s ease; 853 } 854 855 .tool-chip:hover { 856 border-color: #000000; 857 background: var(--bg-tertiary); 858 } 859 860 .tool-chip-header { 861 display: flex; 862 align-items: center; 863 justify-content: space-between; 864 } 865 866 .tool-name-badge { 867 font-family: 'JetBrains Mono', monospace; 868 font-size: 12px; 869 color: #000000; 870 background: var(--bg-tertiary); 871 padding: 3px 8px; 872 border-radius: var(--radius-sm); 873 border: 1px solid var(--glass-border); 874 font-weight: 600; 875 } 876 877 .tool-chip-desc { 878 font-size: 12px; 879 color: var(--text-secondary); 880 line-height: 1.4; 881 } 882 883 /* --- Intercepted Tool Bubble --- */ 884 .tool-call-card { 885 background: #ffffff; 886 border: 1px solid var(--glass-border); 887 border-radius: var(--radius-lg); 888 padding: 20px; 889 margin: 16px 0; 890 display: flex; 891 flex-direction: column; 892 gap: 12px; 893 border-left: 4px solid #000000; 894 box-shadow: 0 4px 20px rgba(0, 0, 0, 0.02); 895 } 896 897 .tool-call-header { 898 display: flex; 899 align-items: center; 900 gap: 10px; 901 font-weight: 700; 902 color: #000000; 903 font-size: 14px; 904 } 905 906 .tool-call-code { 907 font-family: 'JetBrains Mono', monospace; 908 background-color: #1c1b18; 909 padding: 12px; 910 border-radius: var(--radius-md); 911 border: 1px solid #e2e1df; 912 color: #f1f5f9; 913 font-size: 13px; 914 overflow-x: auto; 915 } 916 917 .tool-result-section { 918 display: flex; 919 flex-direction: column; 920 gap: 8px; 921 border-top: 1px solid var(--glass-border); 922 padding-top: 14px; 923 } 924 925 .tool-result-title { 926 font-size: 11px; 927 font-weight: 700; 928 text-transform: uppercase; 929 letter-spacing: 0.5px; 930 color: var(--text-secondary); 931 } 932 933 .tool-result-input { 934 width: 100%; 935 background-color: #f4f3f0; 936 border: 1px solid var(--glass-border); 937 border-radius: var(--radius-sm); 938 color: #000000; 939 font-family: 'JetBrains Mono', monospace; 940 font-size: 13px; 941 padding: 10px; 942 outline: none; 943 } 944 945 .tool-result-input:focus { 946 border-color: #000000; 947 } 948 949 .tool-execute-btn { 950 background: #000000; 951 color: white; 952 border: none; 953 border-radius: 9999px; 954 padding: 10px 20px; 955 font-weight: 600; 956 font-size: 13px; 957 cursor: pointer; 958 align-self: flex-start; 959 display: flex; 960 align-items: center; 961 gap: 8px; 962 transition: all 0.2s ease; 963 } 964 965 .tool-execute-btn:hover { 966 background: #222222; 967 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); 968 transform: translateY(-1px); 969 } 970 971 .tool-executed-badge { 972 background: var(--bg-tertiary); 973 color: #000000; 974 border: 1px solid var(--glass-border); 975 padding: 6px 12px; 976 border-radius: 9999px; 977 font-size: 12px; 978 font-weight: 600; 979 display: flex; 980 align-items: center; 981 gap: 6px; 982 align-self: flex-start; 983 } 984 985 /* --- Keyframe Animations --- */ 986 @keyframes spin { 987 0% { transform: rotate(0deg); } 988 100% { transform: rotate(360deg); } 989 } 990 991 @keyframes float { 992 0% { transform: translateY(0px); } 993 50% { transform: translateY(-10px); } 994 100% { transform: translateY(0px); } 995 } 996 997 @keyframes ping { 998 75%, 100% { 999 transform: scale(2.5); 1000 opacity: 0; 1001 } 1002 } 1003 1004 @keyframes slide-up { 1005 from { 1006 opacity: 0; 1007 transform: translateY(12px); 1008 } 1009 to { 1010 opacity: 1; 1011 transform: translateY(0); 1012 } 1013 } 1014 1015 /* --- Media Queries --- */ 1016 @media (max-width: 900px) { 1017 aside { 1018 position: absolute; 1019 left: -320px; 1020 height: 100%; 1021 box-shadow: 10px 0 30px rgba(0, 0, 0, 0.1); 1022 } 1023 1024 aside.open { 1025 left: 0; 1026 } 1027 1028 .hamburger { 1029 display: block; 1030 } 1031 1032 .sandbox-container { 1033 grid-template-columns: 1fr; 1034 } 1035 1036 .sandbox-control { 1037 display: none; 1038 } 1039 1040 /* --- Custom mobile clean design --- */ 1041 .header-nav-links { 1042 display: none !important; 1043 } 1044 1045 .main-header { 1046 padding: 0 16px; 1047 height: 60px; 1048 } 1049 1050 .status-container span { 1051 display: none !important; /* Hide text, keep green dot */ 1052 } 1053 1054 .status-container { 1055 gap: 0; 1056 } 1057 1058 .nav-tabs { 1059 gap: 2px; 1060 padding: 2px; 1061 } 1062 1063 .tab-btn { 1064 font-size: 11px; 1065 padding: 5px 10px; 1066 gap: 4px; 1067 } 1068 1069 .welcome-view { 1070 grid-template-columns: 1fr !important; 1071 gap: 24px !important; 1072 padding: 24px 16px 120px !important; 1073 text-align: center !important; 1074 } 1075 1076 .welcome-left { 1077 align-items: center !important; 1078 text-align: center !important; 1079 } 1080 1081 .welcome-main-title { 1082 font-size: 28px !important; 1083 letter-spacing: -0.6px !important; 1084 text-align: center !important; 1085 } 1086 1087 .welcome-main-subtitle { 1088 font-size: 13.5px !important; 1089 text-align: center !important; 1090 max-width: 100% !important; 1091 } 1092 1093 .welcome-action-buttons { 1094 justify-content: center !important; 1095 width: 100% !important; 1096 } 1097 1098 .btn-pill { 1099 padding: 10px 20px !important; 1100 font-size: 13px !important; 1101 } 1102
1103 .starters-grid { 1104 grid-template-columns: 1fr !important; 1105 gap: 10px !important; 1106 width: 100% !important; 1107 } 1108 1109 .starter-card { 1110 padding: 12px 16px !important; 1111 } 1112 1113 .welcome-right { 1114 margin-top: 16px !important; 1115 width: 100% !important; 1116 display: flex !important; 1117 justify-content: center !important; 1118 } 1119 1120 .partnership-card { 1121 padding: 20px !important; 1122 max-width: 100% !important; 1123 gap: 20px !important; 1124 border-radius: 16px !important; 1125 } 1126 1127 .partner-divider { 1128 height: 36px !important; 1129 } 1130 1131 .partnership-logos { 1132 gap: 16px !important; 1133 padding: 8px 0 !important; 1134 } 1135 1136 .partner-logo img { 1137 height: 36px !important; 1138 } 1139 1140 .partnership-footer { 1141 font-size: 12px !important; 1142 } 1143 1144 .chat-history-scroll { 1145 padding: 16px 16px 90px !important; 1146 gap: 16px !important; 1147 } 1148 1149 .message-bubble { 1150 max-width: 92% !important; 1151 padding: 12px 16px !important; 1152 font-size: 14px !important; 1153 } 1154 1155 .footer-input-bar { 1156 padding: 12px 12px 16px !important; 1157 } 1158 1159 .input-wrapper { 1160 padding: 6px 10px !important; 1161 gap: 8px !important; 1162 border-radius: 14px !important; 1163 } 1164 1165 .chat-textarea { 1166 height: 38px !important; 1167 min-height: 38px !important; 1168 font-size: 14px !important; 1169 padding: 8px 2px !important; 1170 } 1171 1172 .char-counter { 1173 display: none !important; 1174 } 1175 } 1176 1177 /* --- Google-style Minimalist Chat Redesign --- */ 1178 .chat-empty .chat-history-scroll { 1179 display: none; 1180 } 1181 1182 .chat-empty .landing-container { 1183 display: flex; 1184 } 1185 1186 .chat-empty .footer-input-bar { 1187 position: relative; 1188 padding: 0 24px 60px; 1189 background: transparent; 1190 width: 100%; 1191 max-width: 680px; 1192 margin: 0 auto; 1193 pointer-events: auto; 1194 } 1195 1196 .chat-empty .footer-input-bar .input-wrapper { 1197 box-shadow: 0 12px 36px rgba(0, 0, 0, 0.05); 1198 border: 1px solid #dcdad7; 1199 border-radius: 28px; 1200 } 1201 1202 /* --- Landing Welcome Container --- */ 1203 .landing-container { 1204 display: none; 1205 flex-direction: column; 1206 align-items: center; 1207 justify-content: center; 1208 flex: 1; 1209 width: 100%; 1210 max-width: 680px; 1211 margin: auto; 1212 padding: 60px 24px 20px; 1213 text-align: center; 1214 gap: 32px; 1215 animation: slide-up 0.4s cubic-bezier(0.4, 0, 0.2, 1); 1216 } 1217 1218 .landing-brand { 1219 display: flex; 1220 flex-direction: column; 1221 align-items: center; 1222 gap: 16px; 1223 } 1224 1225 .landing-logo { 1226 width: 64px; 1227 height: 64px; 1228 border-radius: 16px; 1229 box-shadow: 0 8px 24px rgba(0, 0, 0, 0.06); 1230 background: #ffffff; 1231 padding: 2px; 1232 border: 1px solid #e2e1df; 1233 animation: float 5s ease-in-out infinite; 1234 } 1235 1236 .landing-title { 1237 font-size: 36px; 1238 font-weight: 700; 1239 color: #000000; 1240 letter-spacing: -1px; 1241 } 1242 1243 .landing-subtitle { 1244 font-size: 15px; 1245 color: var(--text-secondary); 1246 max-width: 480px; 1247 line-height: 1.5; 1248 } 1249 1250 .landing-starters { 1251 display: flex; 1252 flex-wrap: wrap; 1253 gap: 10px; 1254 justify-content: center; 1255 width: 100%; 1256 } 1257 1258 .starter-pill { 1259 background: #ffffff; 1260 border: 1px solid #dcdad7; 1261 padding: 10px 18px; 1262 border-radius: 9999px; 1263 font-size: 13.5px; 1264 font-weight: 500; 1265 color: var(--text-primary); 1266 cursor: pointer; 1267 transition: all 0.2s ease; 1268 display: flex; 1269 align-items: center; 1270 gap: 8px; 1271 } 1272 1273 .starter-pill i { 1274 color: var(--text-secondary); 1275 font-size: 12px; 1276 } 1277 1278 .starter-pill:hover { 1279 background: #000000; 1280 color: #ffffff; 1281 border-color: #000000; 1282 transform: translateY(-1px); 1283 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); 1284 } 1285 1286 .starter-pill:hover i { 1287 color: #ffffff; 1288 } 1289 1290 /* --- Collapsible Sidebar Settings Footer --- */ 1291 .sidebar-footer { 1292 border-top: 1px solid var(--glass-border); 1293 background: var(--bg-secondary); 1294 padding: 16px 24px; 1295 } 1296 1297 .developer-toggle { 1298 display: flex; 1299 align-items: center; 1300 justify-content: space-between; 1301 cursor: pointer; 1302 font-size: 13px; 1303 font-weight: 600; 1304 color: var(--text-secondary); 1305 user-select: none; 1306 transition: color 0.2s ease; 1307 } 1308 1309 .developer-toggle:hover { 1310 color: var(--text-primary); 1311 } 1312 1313 .dev-chevron { 1314 transition: transform 0.2s ease; 1315 } 1316 1317 .developer-content { 1318 max-height: 500px; 1319 overflow-y: auto; 1320 transition: max-height 0.3s ease, opacity 0.3s ease; 1321 opacity: 1; 1322 display: flex; 1323 flex-direction: column; 1324 gap: 12px; 1325 padding-top: 12px; 1326 } 1327 1328 .developer-content.collapsed { 1329 max-height: 0; 1330 opacity: 0; 1331 overflow: hidden; 1332 padding-top: 0; 1333 pointer-events: none; 1334 } 1335 1336 /* --- Elegant Toggle Switch --- */ 1337 .switch { 1338 position: relative; 1339 display: inline-block; 1340 width: 36px; 1341 height: 20px; 1342 } 1343 1344 .switch input { 1345 opacity: 0; 1346 width: 0; 1347 height: 0; 1348 } 1349 1350 .slider.round { 1351 position: absolute; 1352 cursor: pointer; 1353 top: 0; 1354 left: 0; 1355 right: 0; 1356 bottom: 0; 1357 background-color: var(--bg-tertiary); 1358 border: 1px solid var(--glass-border); 1359 transition: .2s; 1360 border-radius: 34px; 1361 } 1362 1363 .slider.round:before { 1364 position: absolute; 1365 content: ""; 1366 height: 14px; 1367 width: 14px; 1368 left: 2px; 1369 bottom: 2px; 1370 background-color: #000000; 1371 transition: .2s; 1372 border-radius: 50%; 1373 } 1374 1375 input:checked + .slider { 1376 background-color: #000000; 1377 border-color: #000000; 1378 } 1379 1380 input:checked + .slider:before { 1381 transform: translateX(16px); 1382 background-color: #ffffff; 1383 } 1384 1385 /* --- Footer Disclaimer --- */ 1386 .input-footer-note { 1387 font-size: 11px; 1388 color: var(--text-secondary); 1389 text-align: center; 1390 margin-top: 8px; 1391 font-weight: 400; 1392 pointer-events: auto; 1393 letter-spacing: 0.2px; 1394 } 1395 </style> 1396</head> 1397<body> 1398 1399 <div class="app-container"> 1400 <!-- Sidebar Configurator --> 1401 <aside id="sidebar"> 1402 <div class="sidebar-header"> 1403 <div class="brand"> 1404 <img src="https://cdn-avatars.huggingface.co/v1/production/uploads/61b8e2ba285851687028d395/EsTgVtnM2IqVRKgPdfqcB.png" alt="Liquid AI Logo" style="width: 28px; height: 28px; border-radius: 6px; object-fit: contain; flex-shrink: 0;">
1405 <span style="font-family: 'Inter', sans-serif; font-size: 16px; font-weight: 700; color: #000000; letter-spacing: -0.3px;">Liquid AI</span> 1406 <span style="font-family:'JetBrains Mono',monospace; font-size:10px; background:#f4f3f0; padding:2px 6px; border-radius:4px; color:#636366; font-weight:500; margin-left: 4px;">LFM 2.5</span> 1407 </div> 1408 <button class="delete-chat-btn" onclick="toggleSidebar()" style="font-size: 16px;"><i class="fa-solid fa-chevron-left"></i></button> 1409 </div> 1410 1411 <button class="new-chat-btn" onclick="createNewChat()"> 1412 <i class="fa-solid fa-plus"></i> New Conversation 1413 </button> 1414 1415 <!-- Scrollable Conversations List --> 1416 <div class="sidebar-sections" style="flex: 1; overflow-y: auto;"> 1417 <div class="setting-group" style="height: 100%; display: flex; flex-direction: column;"> 1418 <span class="section-title"><i class="fa-solid fa-clock-rotate-left"></i> Recent Conversations</span> 1419 <div class="chat-list" id="chat-history-list" style="flex: 1; max-height: none;"> 1420 <!-- Loaded dynamically --> 1421 </div> 1422 </div> 1423 </div> 1424 1425 <!-- Collapsible Settings Panel locked at bottom --> 1426 <div class="sidebar-footer"> 1427 <div class="developer-toggle" onclick="toggleDeveloperSettings()"> 1428 <span><i class="fa-solid fa-sliders"></i> Developer Settings</span> 1429 <i class="fa-solid fa-chevron-down dev-chevron"></i> 1430 </div> 1431 <div class="developer-content collapsed" id="developer-settings-content"> 1432 <!-- Sandbox Toggle --> 1433 <div class="setting-group" style="flex-direction: row; justify-content: space-between; align-items: center; margin-top: 10px;"> 1434 <span class="setting-label">Enable Agent Sandbox</span> 1435 <label class="switch"> 1436 <input type="checkbox" id="sandbox-mode-toggle" onchange="toggleSandboxMode(this.checked)"> 1437 <span class="slider round"></span> 1438 </label> 1439 </div> 1440 1441 <!-- Temperature --> 1442 <div class="setting-group" style="margin-top: 10px;"> 1443 <div class="setting-label"> 1444 <span>Temperature</span> 1445 <span class="setting-val" id="temp-val">0.2</span> 1446 </div> 1447 <input type="range" class="setting-slider" id="temp-slider" min="0" max="1.5" step="0.05" value="0.2" oninput="updateParam('temp', this.value)"> 1448 </div> 1449 1450 <!-- Top-K --> 1451 <div class="setting-group" style="margin-top: 10px;"> 1452 <div class="setting-label"> 1453 <span>Top-K</span> 1454 <span class="setting-val" id="topk-val">80</span> 1455 </div> 1456 <input type="range" class="setting-slider" id="topk-slider" min="1" max="200" step="1" value="80" oninput="updateParam('topk', this.value)"> 1457 </div> 1458 1459 <!-- Repetition Penalty --> 1460 <div class="setting-group" style="margin-top: 10px;"> 1461 <div class="setting-label"> 1462 <span>Repetition Penalty</span> 1463 <span class="setting-val" id="rep-val">1.05</span> 1464 </div> 1465 <input type="range" class="setting-slider" id="rep-slider" min="1" max="1.5" step="0.01" value="1.05" oninput="updateParam('rep', this.value)"> 1466 </div> 1467 1468 <!-- Max Tokens --> 1469 <div class="setting-group" style="margin-top: 10px;"> 1470 <div class="setting-label">
1471 <span>Max Tokens</span> 1472 <span class="setting-val" id="tokens-val">2048</span> 1473 </div> 1474 <input type="range" class="setting-slider" id="tokens-slider" min="256" max="4096" step="128" value="2048" oninput="updateParam('tokens', this.value)"> 1475 </div> 1476 1477 <!-- Persona Selection --> 1478 <div class="setting-group" style="margin-top: 10px;"> 1479 <span class="setting-label">System Persona</span> 1480 <select class="form-select" id="persona-select" onchange="applyPersona(this.value)" style="margin-top: 4px;"> 1481 <option value="general">Default Personal Assistant</option> 1482 <option value="math">Reasoning & Logic Solver</option> 1483 <option value="coder">Software Engineer</option> 1484 <option value="translator">Multilingual Translator</option> 1485 <option value="custom">Custom System Prompt</option> 1486 </select> 1487 <textarea class="form-textarea" id="system-prompt" style="margin-top: 8px;" oninput="saveCurrentChatSettings()" placeholder="Enter custom instructions..."></textarea> 1488 </div> 1489 </div> 1490 </div> 1491 </aside> 1492 <!-- Main Body Area --> 1493 <main> 1494 <!-- Navigation Header --> 1495 <div class="main-header"> 1496 <div class="header-brand-section"> 1497 <i class="fa-solid fa-bars hamburger" onclick="toggleSidebar()"></i> 1498 <div class="brand" style="margin-left: 8px;"> 1499 <img src="https://cdn-avatars.huggingface.co/v1/production/uploads/61b8e2ba285851687028d395/EsTgVtnM2IqVRKgPdfqcB.png" alt="Liquid AI Logo" style="width: 24px; height: 24px; border-radius: 5px; object-fit: contain; flex-shrink: 0;"> 1500 <span style="font-family: 'Inter', sans-serif; font-size: 15px; font-weight: 700; color: #000000; letter-spacing: -0.3px; margin-left: 6px;">Liquid AI</span> 1501 </div> 1502 </div> 1503 1504 <div style="display: flex; align-items: center; gap: 16px;"> 1505 <div class="nav-tabs" style="display: none;"> 1506 <button class="tab-btn active" id="tab-chat" onclick="switchTab('chat')"> 1507 <i class="fa-solid fa-message"></i> Chat 1508 </button> 1509 <button class="tab-btn" id="tab-sandbox" onclick="switchTab('sandbox')"> 1510 <i class="fa-solid fa-laptop-code"></i> Developer Sandbox 1511 </button> 1512 </div> 1513 <div class="status-container"> 1514 <div class="status-dot"></div> 1515 <span id="connection-status">Connecting...</span> 1516 </div> 1517 </div> 1518 </div> 1519 1520 <!-- Tab View 1: Chat Assistant --> 1521 <div class="tab-panel active chat-empty" id="panel-chat"> 1522 <!-- Chat Messages Scroll Area --> 1523 <div class="chat-history-scroll" id="chat-history-scroll"> 1524 <!-- Rendered messages will go here --> 1525 </div> 1526 1527 <!-- Centered Landing Layout (only visible when chat-empty is active) --> 1528 <div class="landing-container" id="landing-container"> 1529 <div class="landing-brand"> 1530 <img src="https://cdn-avatars.huggingface.co/v1/production/uploads/61b8e2ba285851687028d395/EsTgVtnM2IqVRKgPdfqcB.png" alt="Liquid AI Logo" class="landing-logo"> 1531 <h1 class="landing-title">How can I help today?</h1> 1532 <p class="landing-subtitle">LFM 2.5 delivers supercharged, private edge-native intelligence.</p> 1533 </div> 1534 1535 <!-- Suggestions/Starter Pills --> 1536 <div class="landing-starters"> 1537 <button class="starter-pill" onclick="loadStarter('Explain the concept of quantum computing in simple terms for a child.')"> 1538 <i class="fa-solid fa-lightbulb"></i> Explain Quantum Computing 1539 </button> 1540 <button class="starter-pill" onclick="loadStarter('Write a clean Python script to find all prime numbers up to a given limit.')"> 1541 <i class="fa-solid fa-code"></i> Python Primes Script 1542 </button> 1543 <button class="starter-pill" onclick="loadStarter('Write a short, engaging sci-fi story about a rogue robot in 3 sentences.')"> 1544 <i class="fa-solid fa-pen-nib"></i> Write Sci-Fi Story 1545 </button> 1546 </div> 1547 </div> 1548 1549 <!-- Unified Input Footer --> 1550 <div class="footer-input-bar" id="chat-input-bar"> 1551 <div class="input-wrapper"> 1552 <textarea class="chat-textarea" id="chat-input" placeholder="Ask LFM 2.5 anything..." rows="1" onkeydown="handleInputKeyDown(event)"></textarea> 1553 <button class="action-btn-circle send-btn" id="send-button" onclick="submitUserPrompt()"> 1554 <i class="fa-solid fa-arrow-up"></i> 1555 </button> 1556 <button class="action-btn-circle stop-btn" id="stop-button" onclick="cancelActiveGeneration()"> 1557 <i class="fa-solid fa-stop"></i> 1558 </button> 1559 </div> 1560 <div class="input-footer-note"> 1561 LFM 2.5 may produce inaccurate information. Direct, secure edge-native intelligence. 1562 </div> 1563 </div> 1564 </div> 1565 1566 <!-- Tab View 2: Tool Sandbox --> 1567 <div class="tab-panel" id="panel-sandbox"> 1568 <div class="sandbox-container"> 1569 <!-- Sandbox sidebar controls --> 1570 <div class="sandbox-control">
1571 <span class="section-title"><i class="fa-solid fa-briefcase"></i> Active Tools</span> 1572 <p style="font-size: 13px; color: var(--text-secondary); line-height: 1.4;"> 1573 LFM2.5 natively outputs Pythonic function calls between <code><|tool_call_start|></code> and <code><|tool_call_end|></code> special tokens. 1574 </p> 1575 <div class="tool-chip-list"> 1576 <div class="tool-chip"> 1577 <div class="tool-chip-header"> 1578 <span class="tool-name-badge">get_candidate_status</span> 1579 </div> 1580 <div class="tool-chip-desc">Retrieves the recruitment status of a candidate. Argument: <code>candidate_id (str)</code>.</div> 1581 </div> 1582 <div class="tool-chip"> 1583 <div class="tool-chip-header"> 1584 <span class="tool-name-badge">get_weather</span> 1585 </div> 1586 <div class="tool-chip-desc">Queries live weather conditions. Arguments: <code>location (str)</code>.</div> 1587 </div> 1588 <div class="tool-chip"> 1589 <div class="tool-chip-header"> 1590 <span class="tool-name-badge">search_products</span> 1591 </div> 1592 <div class="tool-chip-desc">Searches local inventories. Argument: <code>query (str)</code>.</div> 1593 </div> 1594 </div> 1595 <span class="section-title"><i class="fa-solid fa-circle-question"></i> Sample Prompts</span> 1596 <div class="starter-card" style="padding: 12px;" onclick="loadSandboxStarter('What is the current status of candidate ID 12345?')"> 1597 <div class="starter-card-title" style="font-size:13.5px;"><i class="fa-solid fa-user-tag"></i> Candidate Status</div> 1598 <div class="starter-card-desc" style="font-size:11.5px;">Triggers get_candidate_status call.</div> 1599 </div> 1600 <div class="starter-card" style="padding: 12px;" onclick="loadSandboxStarter('Can you check if it is raining in Seattle right now?')"> 1601 <div class="starter-card-title" style="font-size:13.5px;"><i class="fa-solid fa-cloud-sun"></i> Weather Check</div> 1602 <div class="starter-card-desc" style="font-size:11.5px;">Triggers get_weather call.</div> 1603 </div> 1604 </div> 1605 1606 <!-- Sandbox Interactive Chat --> 1607 <div class="sandbox-interactive"> 1608 <div class="chat-history-scroll" id="sandbox-history-scroll"> 1609 <!-- Sandbox Welcome Message --> 1610 <div class="welcome-view" id="sandbox-welcome-view"> 1611 <img src="https://cdn-avatars.huggingface.co/v1/production/uploads/61b8e2ba285851687028d395/EsTgVtnM2IqVRKgPdfqcB.png" alt="Liquid AI" style="width: 56px; height: 56px; border-radius: 12px; margin-bottom: 8px;"> 1612 <h1 class="welcome-main-title">Agentic Sandbox</h1> 1613 <p class="welcome-main-subtitle"> 1614 Test the native function-calling capabilities of LFM 2.5. 1615 </p> 1616 1617 <div class="starters-minimal"> 1618 <button class="starter-pill" onclick="loadSandboxStarter('What is the current status of candidate ID 12345?')">Candidate Status</button> 1619 <button class="starter-pill" onclick="loadSandboxStarter('Can you check if it is raining in Seattle right now?')">Weather Check</button> 1620 </div> 1621 </div> 1622 </div> 1623 1624 <!-- Sandbox Input Bar --> 1625 <div class="footer-input-bar"> 1626 <div class="input-wrapper"> 1627 <textarea class="chat-textarea" id="sandbox-input" placeholder="Type a prompt to trigger a tool call..." rows="1" onkeydown="handleSandboxInputKeyDown(event)"></textarea> 1628 <button class="action-btn-circle send-btn" style="background: linear-gradient(135deg, var(--accent-teal), var(--accent-glow));" id="sandbox-send-button" onclick="submitSandboxPrompt()"> 1629 <i class="fa-solid fa-play"></i> 1630 </button> 1631 <button class="action-btn-circle stop-btn" id="sandbox-stop-button" onclick="cancelActiveGeneration()"> 1632 <i class="fa-solid fa-stop"></i> 1633 </button> 1634 </div> 1635 </div> 1636 </div> 1637 </div> 1638 </div> 1639 </main> 1640 </div> 1641 1642 <!-- Scripts: Marked.js, Prism.js, and Gradio JS Client --> 1643
1643<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
1643 1644
1644<script src="https://cdn.jsdelivr.net/npm/[email protected]/components/prism-core.min.js"></script>
1644 1645
1645<script src="https://cdn.jsdelivr.net/npm/[email protected]/plugins/autoloader/prism-autoloader.min.js"></script>
1645 1646 1647
1647<script type="module"> 1648 import { client } from "https://cdn.jsdelivr.net/npm/@gradio/client/dist/index.min.js"; 1649 1650 // Global State variables 1651 let gradioClient = null; 1652 let activeJob = null; 1653 let chats = []; 1654 let activeChatId = null; 1655 1656 // Tool Sandbox execution state 1657 let sandboxHistory = []; 1658 let sandboxActiveJob = null; 1659 1660 // Custom personas definitions 1661 const personas = { 1662 general: "You are a helpful assistant trained by Liquid AI. Be clear, precise, and highly constructive.", 1663 math: "You are a specialized reasoning and logical deduction solver trained by Liquid AI. Deeply analyze all problems step-by-step. Always reason thoroughly inside <think>...</think> tags before writing your final verified solution.", 1664 coder: "You are an expert software engineer trained by Liquid AI. Write highly optimized, well-documented, clean code following best practices. Focus on readability and execution performance.", 1665 translator: "You are an expert translator. Help the user translate phrases perfectly, retaining nuances, local idioms, and stylistic traits in multiple target languages." 1666 }; 1667 1668 // UI Initializer 1669 window.addEventListener('DOMContentLoaded', async () => { 1670 initApp(); 1671 await connectGradio(); 1672 }); 1673 1674 // Toggle Sidebar view 1675 window.toggleSidebar = function() { 1676 const sidebar = document.getElementById('sidebar'); 1677 sidebar.classList.toggle('open'); 1678 } 1679 1680 // Toggle collapsible Developer settings panel in sidebar 1681 window.toggleDeveloperSettings = function() { 1682 const content = document.getElementById('developer-settings-content'); 1683 const chevron = document.querySelector('.dev-chevron'); 1684 content.classList.toggle('collapsed'); 1685 if (chevron) { 1686 chevron.style.transform = content.classList.contains('collapsed') ? 'rotate(0deg)' : 'rotate(180deg)'; 1687 } 1688 } 1689 1690 // Toggle visibility of sandbox tab in navigation header 1691 window.toggleSandboxMode = function(enabled) { 1692 const tabs = document.querySelector('.nav-tabs'); 1693 if (tabs) { 1694 tabs.style.display = enabled ? 'flex' : 'none'; 1695 } 1696 if (!enabled) { 1697 switchTab('chat'); 1698 } 1699 } 1700 1701 // Initialize state, localstorage, and setup UI handlers 1702 function initApp() { 1703 // Load chats from LocalStorage 1704 const storedChats = localStorage.getItem('lfm25_chats'); 1705 if (storedChats) { 1706 chats = JSON.parse(storedChats); 1707 } 1708 1709 if (chats.length === 0) { 1710 createNewChat(); 1711 } else { 1712 activeChatId = chats[0].id; 1713 renderChatList(); 1714 loadActiveChat(); 1715 } 1716 1717 // Setup input listener to autosize height 1718 const chatInput = document.getElementById('chat-input'); 1719 chatInput.addEventListener('input', () => { 1720 chatInput.style.height = 'auto'; 1721 chatInput.style.height = (chatInput.scrollHeight) + 'px'; 1722 }); 1723 1724 const sandboxInput = document.getElementById('sandbox-input'); 1725 sandboxInput.addEventListener('input', () => { 1726 sandboxInput.style.height = 'auto'; 1727 sandboxInput.style.height = (sandboxInput.scrollHeight) + 'px'; 1728 }); 1729 } 1730 1731 // Connect to the backend Gradio Server 1732 async function connectGradio() { 1733 const statusText = document.getElementById('connection-status'); 1734 const dot = document.querySelector('.status-dot'); 1735 try { 1736 // Connect directly to backend host origin 1737 gradioClient = await client(window.location.origin); 1738 statusText.innerText = "Backend Connected"; 1739 dot.style.backgroundColor = "var(--accent-teal)"; 1740 dot.style.boxShadow = "0 0 8px var(--accent-teal)"; 1741 } catch (err) { 1742 console.error("Gradio connection failed:", err); 1743 statusText.innerText = "Demo Mode (Mock Offline)"; 1744 dot.style.backgroundColor = "#eab308"; 1745 dot.style.boxShadow = "0 0 8px #eab308"; 1746 } 1747 } 1748 1749 // Tab selection logic 1750 window.switchTab = function(tabName) { 1751 document.querySelectorAll('.tab-btn').forEach(btn => btn.classList.remove('active')); 1752 document.querySelectorAll('.tab-panel').forEach(panel => panel.classList.remove('active')); 1753 1754 if (tabName === 'chat') { 1755 document.getElementById('tab-chat').classList.add('active'); 1756 document.getElementById('panel-chat').classList.add('active'); 1757 } else { 1758 document.getElementById('tab-sandbox').classList.add('active'); 1759 document.getElementById('panel-sandbox').classList.add('active'); 1760 } 1761 } 1762 1763 /* --- Chat history management --- */ 1764 window.createNewChat = function() { 1765 const newChat = { 1766 id: 'chat_' + Date.now(),
1767 title: 'New Discussion', 1768 history: [], 1769 systemPrompt: personas.general, 1770 selectedPersona: 'general', 1771 params: { 1772 temperature: 0.2, 1773 top_k: 80, 1774 repetition_penalty: 1.05, 1775 max_new_tokens: 2048 1776 } 1777 }; 1778 chats.unshift(newChat); 1779 activeChatId = newChat.id; 1780 saveChatsToStorage(); 1781 renderChatList(); 1782 loadActiveChat(); 1783 } 1784 1785 function saveChatsToStorage() { 1786 localStorage.setItem('lfm25_chats', JSON.stringify(chats)); 1787 } 1788 1789 function renderChatList() { 1790 const list = document.getElementById('chat-history-list'); 1791 list.innerHTML = ''; 1792 chats.forEach(chat => { 1793 const item = document.createElement('div'); 1794 item.className = `chat-item ${chat.id === activeChatId ? 'active' : ''}`; 1795 item.onclick = () => selectChat(chat.id); 1796 1797 item.innerHTML = ` 1798 <span class="chat-item-title"><i class="fa-regular fa-message" style="margin-right:8px;"></i>${chat.title}</span> 1799 <button class="delete-chat-btn" onclick="event.stopPropagation(); deleteChat('${chat.id}')"><i class="fa-solid fa-trash-can"></i></button> 1800 `; 1801 list.appendChild(item); 1802 }); 1803 } 1804 1805 window.selectChat = function(chatId) { 1806 activeChatId = chatId; 1807 renderChatList(); 1808 loadActiveChat(); 1809 1810 // Close sidebar on mobile after selection 1811 if (window.innerWidth <= 900) { 1812 toggleSidebar(); 1813 } 1814 } 1815 1816 window.deleteChat = function(chatId) { 1817 chats = chats.filter(c => c.id !== chatId); 1818 if (chats.length === 0) { 1819 createNewChat(); 1820 } else { 1821 if (activeChatId === chatId) { 1822 activeChatId = chats[0].id; 1823 } 1824 saveChatsToStorage(); 1825 renderChatList(); 1826 loadActiveChat(); 1827 } 1828 } 1829 1830 function loadActiveChat() { 1831 const chat = chats.find(c => c.id === activeChatId); 1832 if (!chat) return; 1833 1834 // Load parameters into inputs 1835 document.getElementById('temp-slider').value = chat.params.temperature; 1836 document.getElementById('temp-val').innerText = chat.params.temperature; 1837 document.getElementById('topk-slider').value = chat.params.top_k; 1838 document.getElementById('topk-val').innerText = chat.params.top_k; 1839 document.getElementById('rep-slider').value = chat.params.repetition_penalty; 1840 document.getElementById('rep-val').innerText = chat.params.repetition_penalty; 1841 document.getElementById('tokens-slider').value = chat.params.max_new_tokens; 1842 document.getElementById('tokens-val').innerText = chat.params.max_new_tokens; 1843 1844 document.getElementById('persona-select').value = chat.selectedPersona; 1845 document.getElementById('system-prompt').value = chat.systemPrompt; 1846 1847 // Render Messages 1848 const scrollArea = document.getElementById('chat-history-scroll'); 1849 scrollArea.innerHTML = ''; 1850 1851 const chatPanel = document.getElementById('panel-chat'); 1852 if (chat.history.length === 0) { 1853 chatPanel.classList.add('chat-empty'); 1854 } else { 1855 chatPanel.classList.remove('chat-empty');
1856 chat.history.forEach(msg => { 1857 appendMessageBubble(msg.role, msg.content, false); 1858 }); 1859 } 1860 scrollArea.scrollTop = scrollArea.scrollHeight; 1861 } 1862 1863 window.updateParam = function(paramType, val) { 1864 const chat = chats.find(c => c.id === activeChatId); 1865 if (!chat) return; 1866 1867 if (paramType === 'temp') { 1868 chat.params.temperature = parseFloat(val); 1869 document.getElementById('temp-val').innerText = val; 1870 } else if (paramType === 'topk') { 1871 chat.params.top_k = parseInt(val); 1872 document.getElementById('topk-val').innerText = val; 1873 } else if (paramType === 'rep') { 1874 chat.params.repetition_penalty = parseFloat(val); 1875 document.getElementById('rep-val').innerText = val; 1876 } else if (paramType === 'tokens') { 1877 chat.params.max_new_tokens = parseInt(val); 1878 document.getElementById('tokens-val').innerText = val; 1879 } 1880 saveChatsToStorage(); 1881 } 1882 1883 window.applyPersona = function(personaKey) { 1884 const chat = chats.find(c => c.id === activeChatId); 1885 if (!chat) return; 1886 1887 chat.selectedPersona = personaKey; 1888 if (personaKey !== 'custom') { 1889 chat.systemPrompt = personas[personaKey]; 1890 document.getElementById('system-prompt').value = personas[personaKey]; 1891 } 1892 saveChatsToStorage(); 1893 } 1894 1895 window.saveCurrentChatSettings = function() { 1896 const chat = chats.find(c => c.id === activeChatId); 1897 if (!chat) return; 1898 chat.systemPrompt = document.getElementById('system-prompt').value; 1899 chat.selectedPersona = 'custom'; 1900 document.getElementById('persona-select').value = 'custom'; 1901 saveChatsToStorage(); 1902 } 1903 1904 /* --- Quick Prompts --- */ 1905 window.loadStarter = function(promptText) { 1906 document.getElementById('chat-input').value = promptText; 1907 document.getElementById('chat-input').dispatchEvent(new Event('input')); 1908 submitUserPrompt(); 1909 } 1910 1911 window.loadSandboxStarter = function(promptText) { 1912 document.getElementById('sandbox-input').value = promptText; 1913 document.getElementById('sandbox-input').dispatchEvent(new Event('input')); 1914 submitSandboxPrompt(); 1915 } 1916 1917 /* --- Custom Reasoning and Tool Call Parser --- */ 1918 function parseResponseText(text) { 1919 let thinking = ""; 1920 let answer = ""; 1921 let isThinking = false; 1922 let hasThought = false; 1923 1924 const thinkStartIdx = text.indexOf("<think>"); 1925 const thinkEndIdx = text.indexOf("</think>"); 1926 1927 if (thinkStartIdx !== -1) { 1928 if (thinkEndIdx !== -1) { 1929 thinking = text.substring(thinkStartIdx + 7, thinkEndIdx).trim(); 1930 answer = text.substring(thinkEndIdx + 8).trim(); 1931 hasThought = true; 1932 } else { 1933 thinking = text.substring(thinkStartIdx + 7); 1934 isThinking = true; 1935 } 1936 } else { 1937 answer = text; 1938 } 1939 1940 return { thinking, answer, isThinking, hasThought }; 1941 } 1942 1943 // Custom parser to check for pythonic tool calls 1944 function parseToolCalls(text) { 1945 const toolStart = text.indexOf("<|tool_call_start|>"); 1946 const toolEnd = text.indexOf("<|tool_call_end|>"); 1947 1948 if (toolStart !== -1) { 1949 if (toolEnd !== -1) { 1950 const toolContent = text.substring(toolStart + 19, toolEnd).trim(); 1951 const cleanAnswer = text.substring(0, toolStart).trim() + " " + text.substring(toolEnd + 17).trim(); 1952 return { hasCall: true, completed: true, callContent: toolContent, cleanAnswer }; 1953 } else { 1954 const toolContent = text.substring(toolStart + 19).trim(); 1955 const cleanAnswer = text.substring(0, toolStart).trim(); 1956 return { hasCall: true, completed: false, callContent: toolContent, cleanAnswer }; 1957 } 1958 } 1959 return { hasCall: false, completed: false, callContent: "", cleanAnswer: text }; 1960 } 1961 1962 /* --- UI Render Utilities --- */ 1963 function appendMessageBubble(role, initialContent, isStreaming = false) { 1964 const scrollArea = document.getElementById( 1965 document.getElementById('tab-chat').classList.contains('active') ? 'chat-history-scroll' : 'sandbox-history-scroll' 1966 ); 1967 1968 // Remove empty class if messages are added to standard chat 1969 const chatPanel = document.getElementById('panel-chat'); 1970 if (chatPanel && chatPanel.classList.contains('chat-empty') && scrollArea.id === 'chat-history-scroll') { 1971 chatPanel.classList.remove('chat-empty'); 1972 } 1973 const sandboxWelcome = document.getElementById('sandbox-welcome-view'); 1974 if (sandboxWelcome && sandboxWelcome.parentNode && scrollArea.id === 'sandbox-history-scroll') { 1975 sandboxWelcome.style.display = 'none'; 1976 } 1977
1978 const row = document.createElement('div'); 1979 row.className = `message-row ${role === 'user' ? 'user-row' : 'assistant-row'}`; 1980 1981 const bubble = document.createElement('div'); 1982 bubble.className = 'message-bubble'; 1983 1984 if (role === 'user') { 1985 bubble.innerText = initialContent; 1986 row.appendChild(bubble); 1987 scrollArea.appendChild(row); 1988 scrollArea.scrollTop = scrollArea.scrollHeight; 1989 return bubble; 1990 } 1991 1992 // Create structure for Assistant bubble (Reasoning Block + Answer Block) 1993 const thinkingAccordion = document.createElement('div'); 1994 thinkingAccordion.className = 'thinking-accordion collapsed'; 1995 thinkingAccordion.innerHTML = ` 1996 <div class="thinking-header" onclick="this.parentNode.classList.toggle('collapsed')"> 1997 <span class="thinking-status"> 1998 <i class="fa-solid fa-brain thinking-icon spinner-icon"></i> 1999 <span class="thinking-title-text">Thinking Process...</span> 2000 </span> 2001 <i class="fa-solid fa-chevron-down chevron-icon"></i> 2002 </div> 2003 <div class="thinking-body"></div> 2004 `; 2005 2006 const contentDiv = document.createElement('div'); 2007 contentDiv.className = 'message-content'; 2008 2009 bubble.appendChild(thinkingAccordion); 2010 bubble.appendChild(contentDiv); 2011 row.appendChild(bubble); 2012 scrollArea.appendChild(row); 2013 scrollArea.scrollTop = scrollArea.scrollHeight; 2014 2015 if (!isStreaming) { 2016 updateAssistantBubble(bubble, initialContent, false); 2017 } 2018 2019 return bubble; 2020 } 2021 2022 // Live update the elements inside assistant bubble 2023 function updateAssistantBubble(bubbleElement, rawContent, isStreaming = true) { 2024 const thinkingAccordion = bubbleElement.querySelector('.thinking-accordion'); 2025 const thinkingBody = bubbleElement.querySelector('.thinking-body'); 2026 const titleText = bubbleElement.querySelector('.thinking-title-text'); 2027 const spinnerIcon = bubbleElement.querySelector('.thinking-icon'); 2028 const contentDiv = bubbleElement.querySelector('.message-content'); 2029 const scrollArea = bubbleElement.closest('.chat-history-scroll'); 2030 2031 const parsed = parseResponseText(rawContent); 2032 2033 // Handle Thinking Trace 2034 if (parsed.thinking) { 2035 thinkingAccordion.style.display = 'block'; 2036 thinkingBody.innerText = parsed.thinking; 2037 2038 if (parsed.isThinking) { 2039 thinkingAccordion.classList.remove('collapsed'); // open while thinking 2040 titleText.innerText = "Thinking Process..."; 2041 spinnerIcon.className = "fa-solid fa-cog thinking-icon spinner-icon"; 2042 } else if (parsed.hasThought) { 2043 titleText.innerText = "Thinking complete"; 2044 spinnerIcon.className = "fa-solid fa-circle-check thinking-icon check-icon"; 2045 } 2046 } else { 2047 thinkingAccordion.style.display = 'none'; 2048 } 2049 2050 // Handle Answer content (parse markdown) 2051 if (parsed.answer) { 2052 // If in sandbox, check for tool calls 2053 const toolCheck = parseToolCalls(parsed.answer); 2054 if (toolCheck.hasCall) { 2055 contentDiv.innerHTML = marked.parse(toolCheck.cleanAnswer); 2056 2057 // Render tool card 2058 let toolCard = bubbleElement.querySelector('.tool-call-card'); 2059 if (!toolCard) { 2060 toolCard = document.createElement('div'); 2061 toolCard.className = 'tool-call-card'; 2062 bubbleElement.appendChild(toolCard); 2063 } 2064 2065 if (toolCheck.completed) { 2066 renderCompletedToolCall(toolCard, toolCheck.callContent); 2067 } else { 2068 toolCard.innerHTML = ` 2069 <div class="tool-call-header"> 2070 <i class="fa-solid fa-terminal"></i> Executing Edge Agent Tool... 2071 </div> 2072 <div class="tool-call-code">${toolCheck.callContent}</div> 2073 `; 2074 } 2075 } else { 2076 contentDiv.innerHTML = marked.parse(parsed.answer); 2077 } 2078 2079 // Add syntax highlighting & action overlays to code elements 2080 addCodeFormattingAndHighlighting(contentDiv); 2081 } else { 2082 contentDiv.innerHTML = ''; 2083 } 2084 2085 // Keep scrolling to bottom as text arrives 2086 if (isStreaming) { 2087 scrollArea.scrollTop = scrollArea.scrollHeight; 2088 } 2089 } 2090 2091 // Add prism code block syntax wrapping and Copy buttons 2092 function addCodeFormattingAndHighlighting(container) { 2093 const preElements = container.querySelectorAll('pre');
2094 preElements.forEach(pre => { 2095 if (pre.parentNode.classList.contains('code-block-wrapper')) return; 2096 2097 const code = pre.querySelector('code'); 2098 let lang = 'code'; 2099 if (code) { 2100 const classes = code.className.split(' '); 2101 const langClass = classes.find(c => c.startsWith('language-')); 2102 if (langClass) { 2103 lang = langClass.replace('language-', ''); 2104 } 2105 } 2106 2107 const wrapper = document.createElement('div'); 2108 wrapper.className = 'code-block-wrapper'; 2109 2110 const header = document.createElement('div'); 2111 header.className = 'code-block-header'; 2112 header.innerHTML = ` 2113 <span class="code-block-lang">${lang}</span> 2114 <button class="copy-code-btn" onclick="copyCode(this)"> 2115 <i class="fa-regular fa-clipboard"></i> Copy 2116 </button> 2117 `; 2118 2119 pre.parentNode.insertBefore(wrapper, pre); 2120 wrapper.appendChild(header); 2121 wrapper.appendChild(pre); 2122 2123 if (code) { 2124 Prism.highlightElement(code); 2125 } 2126 }); 2127 } 2128 2129 window.copyCode = function(button) { 2130 const wrapper = button.closest('.code-block-wrapper'); 2131 const code = wrapper.querySelector('code'); 2132 if (code) { 2133 navigator.clipboard.writeText(code.innerText).then(() => { 2134 button.innerHTML = `<i class="fa-solid fa-check" style="color:var(--accent-teal)"></i> Copied!`; 2135 setTimeout(() => { 2136 button.innerHTML = `<i class="fa-regular fa-clipboard"></i> Copy`; 2137 }, 2000); 2138 }); 2139 } 2140 } 2141 2142 /* --- Mocking the Tool Sandbox Executions --- */ 2143 function renderCompletedToolCall(cardElement, callText) { 2144 if (cardElement.dataset.executed === "true") return; 2145 2146 // Format: get_candidate_status(candidate_id="12345") 2147 // Parse name and arguments 2148 const nameMatch = callText.match(/^([a-zA-Z0-9_]+)\((.*)\)$/); 2149 let toolName = "Unknown Tool"; 2150 let toolArgs = callText; 2151 2152 if (nameMatch) { 2153 toolName = nameMatch[1]; 2154 toolArgs = nameMatch[2]; 2155 } 2156 2157 // Create prefilled mock values based on predicted tools 2158 let mockResult = "{}"; 2159 if (toolName === "get_candidate_status") { 2160 mockResult = `[{"candidate_id": "12345", "status": "Interview Scheduled", "position": "Clinical Research Associate", "date": "2023-11-20"}]`; 2161 } else if (toolName === "get_weather") { 2162 mockResult = `{"location": "Seattle", "temperature": "14°C", "condition": "Overcast Rain", "humidity": "82%"}`; 2163 } else if (toolName === "search_products") { 2164 mockResult = `[{"item": "Liquid Edge Tensor-B", "stock": 42, "price": "$899"}]`; 2165 } 2166 2167 cardElement.innerHTML = ` 2168 <div class="tool-call-header" style="color:var(--accent-teal)"> 2169 <i class="fa-solid fa-circle-play"></i> Tool Call Triggered: <span style="font-family:'JetBrains Mono'">${toolName}</span> 2170 </div> 2171 <div class="tool-call-code">${callText}</div> 2172 <div class="tool-result-section"> 2173 <span class="tool-result-title">Configure Sandbox Output Result:</span> 2174 <input type="text" class="tool-result-input" id="sandbox-res-input" value='${mockResult}'> 2175 <button class="tool-execute-btn" onclick="executeMockTool(this, '${toolName}')"> 2176 <i class="fa-solid fa-gears"></i> Return Payload to Model 2177 </button> 2178 </div> 2179 `; 2180 } 2181 2182 // Return tool results back to chat loop 2183 window.executeMockTool = async function(button, toolName) { 2184 const card = button.closest('.tool-call-card'); 2185 const resultInput = card.querySelector('#sandbox-res-input'); 2186 const resultPayload = resultInput.value; 2187 2188 // Mark as executed 2189 card.dataset.executed = "true"; 2190 2191 // Replace execute section with a static badge 2192 const section = card.querySelector('.tool-result-section'); 2193 section.innerHTML = ` 2194 <div class="tool-executed-badge"> 2195 <i class="fa-solid fa-clipboard-check"></i> Executed Result returned successfully. 2196 </div> 2197 <div class="tool-call-code" style="color:var(--accent-secondary); margin-top:8px;">${resultPayload}</div> 2198 `; 2199 2200 // Append mock result to backend payload context 2201 sandboxHistory.push({ 2202 "role": "tool", 2203 "content": resultPayload 2204 }); 2205 2206 // Start streaming the next assistant output response 2207 const bubble = appendMessageBubble('assistant', 'Synthesizing final response...', true); 2208 let fullText = ""; 2209 2210 try { 2211 toggleInputButtons(true); 2212 const chat = chats.find(c => c.id === activeChatId); 2213 const params = chat ? chat.params : { temperature: 0.2, top_k: 80, repetition_penalty: 1.05, max_new_tokens: 2048 }; 2214 2215 // Fetch next token predictions 2216 const job = gradioClient.submit("/chat", { 2217 message: "", // Empty prompt since we're continuing the tool-history loop 2218 history_json: JSON.stringify(sandboxHistory), 2219 system_prompt: `List of tools: [{"name": "get_candidate_status", "description": "Retrieves the current status of a candidate", "parameters": {"candidate_id": "string"}}
2219, {"name": "get_weather", "description": "Queries weather conditions", "parameters": {"location": "string"}}]`, 2220 temperature: params.temperature, 2221 top_k: params.top_k, 2222 repetition_penalty: params.repetition_penalty, 2223 max_new_tokens: params.max_new_tokens 2224 }); 2225 2226 activeJob = job; 2227 2228 for await (const event of job) { 2229 if (event.type === "data") { 2230 fullText = event.data[0]; 2231 updateAssistantBubble(bubble, fullText, true); 2232 } 2233 } 2234 2235 activeJob = null; 2236 toggleInputButtons(false); 2237 2238 // Add output answer to sandbox memory 2239 sandboxHistory.push({ 2240 "role": "assistant", 2241 "content": fullText 2242 }); 2243 2244 } catch (err) { 2245 console.error("Sandbox resumption failed:", err); 2246 bubble.querySelector('.message-content').innerText = "Offline Mode Mock: Synthesis complete."; 2247 toggleInputButtons(false); 2248 } 2249 } 2250 2251 /* --- Send Chat Prompt Flow --- */ 2252 window.submitUserPrompt = async function() { 2253 const inputField = document.getElementById('chat-input'); 2254 const prompt = inputField.value.trim(); 2255 if (!prompt) return; 2256 2257 // Reset Input height 2258 inputField.value = ''; 2259 inputField.style.height = '44px'; 2260 2261 const chat = chats.find(c => c.id === activeChatId); 2262 if (!chat) return; 2263 2264 // Rename discussion if it's default 2265 if (chat.history.length === 0) { 2266 chat.title = prompt.substring(0, 24) + (prompt.length > 24 ? '...' : ''); 2267 renderChatList(); 2268 } 2269 2270 // Append User message 2271 appendMessageBubble('user', prompt); 2272 2273 // Save to memory 2274 chat.history.push({"role": "user", "content": prompt}); 2275 saveChatsToStorage(); 2276 2277 // Setup streaming assistant bubble 2278 const bubble = appendMessageBubble('assistant', 'Thinking...', true); 2279 let fullText = ""; 2280 2281 if (!gradioClient) { 2282 // If server is not running locally, mock a premium response 2283 setTimeout(() => { 2284 fullText = "<think>\nUser has queried LFM2.5 locally.\nI will generate a premium welcome/offline message explaining edge intelligence.\n</think>\nHello! I am **Liquid LFM2.5**, running inside a custom **gradio.Server** environment.\n\nIt seems I am currently running in offline mock-up mode, or my local PyTorch/ZeroGPU server is still loading models on Hugging Face.\n\n### Model Key Details:\n- **Parameters**: 8.3 Billion total / 1.5 Billion active MoE structure.\n- **Architecture**: Liquid Neural Networks (LNN) + 18 Double-gated LIV convolutions + 6 GQA layers.\n- **Context**: 131,072 max length.\n\nOnce the backend completes cold-start initialization, my live inference streaming will function here!"; 2285 updateAssistantBubble(bubble, fullText, false); 2286 }, 1000); 2287 return; 2288 } 2289 2290 try { 2291 toggleInputButtons(true); 2292 2293 // Submit to gradio client 2294 const job = gradioClient.submit("/chat", { 2295 message: prompt, 2296 history_json: JSON.stringify(chat.history.slice(0, -1)), // Send preceding history 2297 system_prompt: chat.systemPrompt, 2298 temperature: chat.params.temperature, 2299 top_k: chat.params.top_k, 2300 repetition_penalty: chat.params.repetition_penalty, 2301 max_new_tokens: chat.params.max_new_tokens 2302 }); 2303 2304 activeJob = job; 2305 2306 for await (const event of job) { 2307 if (event.type === "data") { 2308 fullText = event.data[0]; 2309 updateAssistantBubble(bubble, fullText, true); 2310 } 2311 } 2312 2313 activeJob = null; 2314 toggleInputButtons(false); 2315 2316 // Add to history 2317 chat.history.push({"role": "assistant", "content": fullText}); 2318 saveChatsToStorage(); 2319 2320 } catch (err) { 2321 console.error("Inference call failed:", err); 2322 bubble.querySelector('.message-content').innerHTML = `<span style="color:#ef4444;"><i class="fa-solid fa-triangle-exclamation"></i>
2322 Inference Connection Error: ${err.message || err}</span>`; 2323 toggleInputButtons(false); 2324 } 2325 } 2326 2327 /* --- Tool Sandbox prompt submission --- */ 2328 window.submitSandboxPrompt = async function() { 2329 const inputField = document.getElementById('sandbox-input'); 2330 const prompt = inputField.value.trim(); 2331 if (!prompt) return; 2332 2333 inputField.value = ''; 2334 inputField.style.height = '44px'; 2335 2336 // Reset Sandbox context 2337 sandboxHistory = []; 2338 document.getElementById('sandbox-history-scroll').innerHTML = ''; 2339 2340 // User input 2341 appendMessageBubble('user', prompt); 2342 sandboxHistory.push({"role": "user", "content": prompt}); 2343 2344 // Start assistant response 2345 const bubble = appendMessageBubble('assistant', 'Constructing tool call...', true); 2346 let fullText = ""; 2347 2348 if (!gradioClient) { 2349 setTimeout(() => { 2350 fullText = "Checking local indexes... <|tool_call_start|>[get_candidate_status(candidate_id=\"12345\")]<|tool_call_end|>"; 2351 updateAssistantBubble(bubble, fullText, false); 2352 sandboxHistory.push({"role": "assistant", "content": fullText}); 2353 }, 1000); 2354 return; 2355 } 2356 2357 try { 2358 toggleInputButtons(true); 2359 const chat = chats.find(c => c.id === activeChatId); 2360 const params = chat ? chat.params : { temperature: 0.2, top_k: 80, repetition_penalty: 1.05, max_new_tokens: 2048 }; 2361 2362 // Build agent system prompt defining active tools 2363 const agentSystemPrompt = `List of tools: [{"name": "get_candidate_status", "description": "Retrieves the recruitment status of a candidate in the recruitment process", "parameters": {"type": "object", "properties": {"candidate_id": {"type": "string", "description": "Unique identifier for the candidate"}}, "required": ["candidate_id"]}}, {"name": "get_weather", "description": "Queries weather conditions", "parameters": {"type": "object", "properties": {"location": {"type": "string", "description": "Name of the city"}}, "required": ["location"]}}]`; 2364 2365 const job = gradioClient.submit("/chat", { 2366 message: prompt, 2367 history_json: JSON.stringify([]), // Fresh agent context 2368 system_prompt: agentSystemPrompt, 2369 temperature: params.temperature, 2370 top_k: params.top_k, 2371 repetition_penalty: params.repetition_penalty, 2372 max_new_tokens: params.max_new_tokens 2373 }); 2374 2375 activeJob = job; 2376 2377 for await (const event of job) { 2378 if (event.type === "data") { 2379 fullText = event.data[0]; 2380 updateAssistantBubble(bubble, fullText, true); 2381 } 2382 } 2383 2384 activeJob = null; 2385 toggleInputButtons(false); 2386 sandboxHistory.push({"role": "assistant", "content": fullText}); 2387 2388 } catch (err) { 2389 console.error("Sandbox initial call failed:", err); 2390 bubble.querySelector('.message-content').innerText = `Error: ${err.message || err}`; 2391 toggleInputButtons(false); 2392 } 2393 } 2394 2395 // Toggle visibility of send / stop buttons during streaming 2396 function toggleInputButtons(isGenerating) { 2397 const sendBtn = document.getElementById('send-button'); 2398 const stopBtn = document.getElementById('stop-button'); 2399 const sandboxSend = document.getElementById('sandbox-send-button'); 2400 const sandboxStop = document.getElementById('sandbox-stop-button'); 2401 2402 if (isGenerating) { 2403 if (sendBtn) sendBtn.style.display = 'none'; 2404 if (stopBtn) stopBtn.style.display = 'flex'; 2405 if (sandboxSend) sandboxSend.style.display = 'none'; 2406 if (sandboxStop) sandboxStop.style.display = 'flex'; 2407 } else { 2408 if (sendBtn) sendBtn.style.display = 'flex'; 2409 if (stopBtn) stopBtn.style.display = 'none'; 2410 if (sandboxSend) sandboxSend.style.display = 'flex'; 2411 if (sandboxStop) sandboxStop.style.display = 'none'; 2412 } 2413 } 2414 2415 window.cancelActiveGeneration = function() { 2416 if (activeJob) { 2417 activeJob.cancel(); 2418 activeJob = null; 2419 } 2420 toggleInputButtons(false); 2421 } 2422 2423 /* --- Key press triggers --- */ 2424 window.handleInputKeyDown = function(event) { 2425 if (event.key === 'Enter' && !event.shiftKey) { 2426 event.preventDefault(); 2427 submitUserPrompt(); 2428 } 2429 } 2430 2431 window.handleSandboxInputKeyDown = function(event) { 2432 if (event.key === 'Enter' && !event.shiftKey) { 2433 event.preventDefault(); 2434 submitSandboxPrompt(); 2435 } 2436 } 2437 2438 window.focusChatInput = function() { 2439 const input = document.getElementById('chat-input'); 2440 if (input) input.focus(); 2441 } 2442 2443 window.focusSandboxInput = function() { 2444 const input = document.getElementById('sandbox-input'); 2445 if (input) input.focus(); 2446 } 2447 </script>
2447 2448</body> 2449</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.