personal memory agent
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

refactor(dream): extract CSS and JS from agents.html into separate partials

Break out the large agents.html template (2961 lines) into manageable pieces:
- Created dream/templates/agents/_styles.html (1097 lines of CSS)
- Created dream/templates/agents/_scripts.html (1534 lines of JS)
- Reduced agents.html to 332 lines (main HTML structure only)

CSS and JS are server-side included via Jinja2 {% include %} directives,
so the browser still receives the same inline content (no separate HTTP
requests), but the code is now much easier to maintain and navigate.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

+2633 -2631
+2 -2631
dream/templates/agents.html
··· 3 3 {% set active = 'agents' %} 4 4 {% block head %} 5 5 <style> 6 - .container { padding: 0 1em; } 7 - 8 - /* Tab Navigation */ 9 - .tab-nav { 10 - display: flex; 11 - gap: 0.5rem; 12 - margin-bottom: 2rem; 13 - border-bottom: 2px solid #e0e0e0; 14 - padding-bottom: 0; 15 - } 16 - 17 - .tab-button { 18 - padding: 0.75rem 1.5rem; 19 - background: none; 20 - border: none; 21 - border-bottom: 3px solid transparent; 22 - color: #666; 23 - font-size: 1rem; 24 - cursor: pointer; 25 - transition: all 0.2s ease; 26 - margin-bottom: -2px; 27 - } 28 - 29 - .tab-button:hover { 30 - color: #007bff; 31 - background: #f8f9ff; 32 - } 33 - 34 - .tab-button.active { 35 - color: #007bff; 36 - border-bottom-color: #007bff; 37 - font-weight: 500; 38 - } 39 - 40 - /* Tab Content */ 41 - .tab-container { 42 - min-height: 400px; 43 - } 44 - 45 - .main-tab-content { 46 - display: none; 47 - animation: fadeIn 0.3s ease; 48 - } 49 - 50 - .main-tab-content.active { 51 - display: block; 52 - } 53 - 54 - @keyframes fadeIn { 55 - from { opacity: 0; } 56 - to { opacity: 1; } 57 - } 58 - 59 - /* Agent Cards */ 60 - .agent-cards { 61 - display: grid; 62 - grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); 63 - gap: 1rem; 64 - margin-bottom: 2rem; 65 - } 66 - 67 - .agent-card { 68 - background: white; 69 - border: 2px solid #e0e0e0; 70 - border-radius: 8px; 71 - padding: 1rem; 72 - cursor: pointer; 73 - transition: all 0.2s ease; 74 - position: relative; 75 - } 76 - 77 - .agent-card:hover { 78 - border-color: #007bff; 79 - box-shadow: 0 2px 8px rgba(0, 123, 255, 0.1); 80 - } 81 - 82 - .agent-card .chat-link { 83 - position: absolute; 84 - top: 0.5rem; 85 - right: 0.5rem; 86 - font-size: 1.5rem; 87 - text-decoration: none; 88 - opacity: 0; 89 - transition: opacity 0.2s ease; 90 - background: white; 91 - border-radius: 4px; 92 - padding: 0.2rem 0.4rem; 93 - box-shadow: 0 2px 4px rgba(0,0,0,0.1); 94 - } 95 - 96 - .agent-card:hover .chat-link { 97 - opacity: 1; 98 - } 99 - 100 - .agent-card .chat-link:hover { 101 - transform: scale(1.1); 102 - box-shadow: 0 2px 8px rgba(0,0,0,0.2); 103 - } 104 - 105 - .agent-card.selected { 106 - border-color: #007bff; 107 - background: #f8f9ff; 108 - box-shadow: 0 0 0 1px #007bff; 109 - } 110 - 111 - .agent-card.disabled { 112 - opacity: 0.6; 113 - background: #f8f8f8; 114 - } 115 - 116 - .agent-card h3 { 117 - margin: 0 0 0.5rem 0; 118 - font-size: 1.1rem; 119 - color: #333; 120 - } 121 - 122 - .agent-card p { 123 - margin: 0; 124 - color: #666; 125 - font-size: 0.9rem; 126 - line-height: 1.4; 127 - } 128 - 129 - /* Agent metadata badges */ 130 - .agent-metadata { 131 - display: flex; 132 - flex-wrap: wrap; 133 - gap: 0.3rem; 134 - margin-top: 0.5rem; 135 - } 136 - 137 - .metadata-badge { 138 - display: inline-flex; 139 - align-items: center; 140 - gap: 0.2rem; 141 - padding: 0.2rem 0.5rem; 142 - border-radius: 12px; 143 - font-size: 0.75rem; 144 - font-weight: 500; 145 - background: #f0f0f0; 146 - color: #555; 147 - } 148 - 149 - .metadata-badge.schedule { 150 - background: #e3f2fd; 151 - color: #1976d2; 152 - } 153 - 154 - .metadata-badge.priority { 155 - background: #fff3e0; 156 - color: #f57c00; 157 - } 158 - 159 - .metadata-badge.multi-domain { 160 - background: #f3e5f5; 161 - color: #7b1fa2; 162 - } 163 - 164 - .metadata-badge.tools { 165 - background: #e8f5e9; 166 - color: #388e3c; 167 - } 168 - 169 - .metadata-badge.backend { 170 - background: #fce4ec; 171 - color: #c2185b; 172 - } 173 - 174 - .metadata-badge .badge-icon { 175 - font-size: 0.9rem; 176 - } 177 - 178 - /* Create Agent Card */ 179 - .create-agent-card { 180 - background: #f8f9ff; 181 - border: 2px dashed #007bff; 182 - display: flex; 183 - flex-direction: column; 184 - align-items: center; 185 - justify-content: center; 186 - min-height: 80px; 187 - } 188 - 189 - .create-agent-card:hover { 190 - background: #e8ecff; 191 - border-style: solid; 192 - } 193 - 194 - .create-agent-card h3 { 195 - color: #007bff; 196 - margin: 0; 197 - font-size: 1.2rem; 198 - } 199 - 200 - /* Topic card with colored indicator */ 201 - .topic-indicator { 202 - display: inline-block; 203 - width: 12px; 204 - height: 12px; 205 - border-radius: 50%; 206 - margin-right: 0.5rem; 207 - vertical-align: middle; 208 - } 209 - 210 - /* Input Form */ 211 - .input-area { 212 - display: flex; 213 - flex-direction: column; 214 - gap: 1rem; 215 - margin-bottom: 2rem; 216 - } 217 - 218 - .input-area textarea { 219 - width: 100%; 220 - padding: 0.75rem; 221 - border: 1px solid #ddd; 222 - border-radius: 4px; 223 - font-family: inherit; 224 - resize: vertical; 225 - } 226 - 227 - .input-area button { 228 - padding: 0.75rem 1.5rem; 229 - background: #007bff; 230 - color: white; 231 - border: none; 232 - border-radius: 4px; 233 - cursor: pointer; 234 - font-size: 1.2rem; 235 - align-self: flex-start; 236 - } 237 - 238 - .input-area button:disabled { 239 - background: #ccc; 240 - cursor: not-allowed; 241 - } 242 - 243 - .input-area button:hover:not(:disabled) { 244 - background: #0056b3; 245 - } 246 - 247 - .input-area.busy { 248 - position: relative; 249 - } 250 - 251 - .input-area.busy::after { 252 - content: ''; 253 - position: absolute; 254 - top: 0; 255 - left: 0; 256 - right: 0; 257 - bottom: 0; 258 - background: rgba(255, 255, 255, 0.8); 259 - cursor: wait; 260 - z-index: 10; 261 - } 262 - 263 - .input-area.busy textarea, 264 - .input-area.busy button { 265 - pointer-events: none; 266 - } 267 - 268 - .planning-status { 269 - margin-top: 0.5rem; 270 - padding: 0.5rem; 271 - background: #e3f2fd; 272 - border: 1px solid #90caf9; 273 - border-radius: 4px; 274 - font-size: 0.9rem; 275 - display: none; 276 - } 277 - 278 - .planning-status.show { 279 - display: block; 280 - } 281 - 282 - /* Session History Table */ 283 - table { 284 - border-collapse: collapse; 285 - width: 100%; 286 - margin-top: 2rem; 287 - } 288 - th, td { 289 - padding: 4px 8px; 290 - border-bottom: 1px solid #ddd; 291 - white-space: nowrap; 292 - text-align: left; 293 - } 294 - th { 295 - font-weight: 600; 296 - background-color: #f8f9fa; 297 - } 298 - /* Adjust column widths */ 299 - td:nth-child(1), th:nth-child(1) { /* Status */ 300 - width: 100px; 301 - } 302 - td:nth-child(2), th:nth-child(2) { /* Started */ 303 - width: 120px; 304 - } 305 - td:nth-child(3), th:nth-child(3) { /* Runtime */ 306 - width: 80px; 307 - text-align: right; 308 - } 309 - td:nth-child(4), th:nth-child(4) { /* Model */ 310 - width: 150px; 311 - } 312 - td:nth-child(5), th:nth-child(5) { /* Agent */ 313 - width: 180px; 314 - } 315 - td:last-child { /* Prompt */ 316 - overflow: hidden; 317 - text-overflow: ellipsis; 318 - max-width: 0; 319 - } 320 - tr:hover { 321 - background-color: #f8f9fa; 322 - } 323 - tr a { 324 - color: #007bff; 325 - text-decoration: none; 326 - } 327 - tr a:hover { 328 - text-decoration: underline; 329 - } 330 - 331 - /* Status indicators */ 332 - .status-badge { 333 - display: inline-block; 334 - padding: 2px 6px; 335 - border-radius: 3px; 336 - font-size: 0.75rem; 337 - font-weight: 500; 338 - text-transform: uppercase; 339 - margin-right: 0.5rem; 340 - } 341 - 342 - .status-running { 343 - background-color: #d4edda; 344 - color: #155724; 345 - } 346 - 347 - .status-finished { 348 - background-color: #d1ecf1; 349 - color: #0c5460; 350 - } 351 - 352 - .status-error { 353 - background-color: #f8d7da; 354 - color: #721c24; 355 - } 356 - 357 - .status-interrupted { 358 - background-color: #fff3cd; 359 - color: #856404; 360 - } 361 - 362 - .status-unknown { 363 - background-color: #e2e3e5; 364 - color: #383d41; 365 - } 366 - 367 - /* Section headers for agent log */ 368 - .section-header { 369 - margin: 2rem 0 1rem 0; 370 - padding: 0.5rem 0; 371 - border-bottom: 2px solid #007bff; 372 - font-size: 1.1rem; 373 - font-weight: 500; 374 - color: #333; 375 - display: flex; 376 - align-items: center; 377 - justify-content: space-between; 378 - } 379 - 380 - .section-header .count { 381 - font-size: 0.9rem; 382 - font-weight: normal; 383 - color: #666; 384 - background: #f8f9fa; 385 - padding: 2px 8px; 386 - border-radius: 12px; 387 - } 388 - 389 - .empty-state { 390 - text-align: center; 391 - padding: 2rem; 392 - color: #666; 393 - font-style: italic; 394 - } 395 - 396 - /* Pagination */ 397 - .pagination { 398 - display: flex; 399 - justify-content: center; 400 - align-items: center; 401 - gap: 0.5rem; 402 - margin-top: 1rem; 403 - padding: 1rem; 404 - } 405 - 406 - .pagination button { 407 - padding: 0.5rem 1rem; 408 - border: 1px solid #ddd; 409 - background: white; 410 - cursor: pointer; 411 - border-radius: 4px; 412 - transition: all 0.2s ease; 413 - } 414 - 415 - .pagination button:hover:not(:disabled) { 416 - background: #007bff; 417 - color: white; 418 - border-color: #007bff; 419 - } 420 - 421 - .pagination button:disabled { 422 - opacity: 0.5; 423 - cursor: not-allowed; 424 - } 425 - 426 - .pagination .page-info { 427 - margin: 0 1rem; 428 - font-size: 0.9rem; 429 - color: #666; 430 - } 431 - 432 - /* Modal Styling (reused from entities.html) */ 433 - .modal { 434 - display: none; 435 - position: fixed; 436 - z-index: 1000; 437 - left: 0; 438 - top: 0; 439 - width: 100%; 440 - height: 100%; 441 - background-color: rgba(0,0,0,0.5); 442 - } 443 - 444 - .modal-content { 445 - background-color: white; 446 - margin: 5% auto; 447 - border-radius: 8px; 448 - width: 90%; 449 - max-width: 800px; 450 - max-height: 80vh; 451 - position: relative; 452 - box-shadow: 0 4px 6px rgba(0,0,0,0.1); 453 - overflow: hidden; 454 - } 455 - 456 - .close { 457 - color: #aaa; 458 - float: right; 459 - font-size: 28px; 460 - font-weight: bold; 461 - cursor: pointer; 462 - line-height: 1; 463 - position: absolute; 464 - top: 15px; 465 - right: 20px; 466 - background: white; 467 - z-index: 3; 468 - } 469 - 470 - .close:hover { 471 - color: #000; 472 - } 473 - 474 - .modal-header { 475 - padding: 20px 40px 15px 20px; 476 - border-bottom: 1px solid #e0e0e0; 477 - background: white; 478 - border-radius: 8px 8px 0 0; 479 - } 480 - 481 - .modal-header h3 { 482 - margin: 0; 483 - color: #333; 484 - } 485 - 486 - .modal-body { 487 - max-height: calc(80vh - 80px); 488 - overflow-y: auto; 489 - padding: 20px; 490 - } 491 - 492 - .modal-body .markdown-content h1, 493 - .modal-body .markdown-content h2, 494 - .modal-body .markdown-content h3, 495 - .modal-body .markdown-content h4, 496 - .modal-body .markdown-content h5, 497 - .modal-body .markdown-content h6 { 498 - margin-top: 1.5rem; 499 - margin-bottom: 0.75rem; 500 - color: #333; 501 - } 502 - 503 - .modal-body .markdown-content p { 504 - margin-bottom: 1rem; 505 - line-height: 1.6; 506 - } 507 - 508 - .modal-body .markdown-content ul, 509 - .modal-body .markdown-content ol { 510 - margin-bottom: 1rem; 511 - padding-left: 2rem; 512 - } 513 - 514 - .modal-body .markdown-content code { 515 - background: #f8f9fa; 516 - padding: 0.2rem 0.4rem; 517 - border-radius: 3px; 518 - font-family: 'Courier New', monospace; 519 - } 520 - 521 - .modal-body .markdown-content pre { 522 - background: #f8f9fa; 523 - padding: 1rem; 524 - border-radius: 4px; 525 - overflow-x: auto; 526 - margin-bottom: 1rem; 527 - } 528 - 529 - /* Planning Modal Tabs */ 530 - .modal-tabs { 531 - display: flex; 532 - border-bottom: 1px solid #e0e0e0; 533 - margin: -20px -20px 20px -20px; 534 - background: #f8f9fa; 535 - } 536 - 537 - .modal-tab { 538 - padding: 12px 20px; 539 - cursor: pointer; 540 - border: none; 541 - background: none; 542 - font-size: 0.9rem; 543 - color: #666; 544 - transition: all 0.2s ease; 545 - flex: 1; 546 - text-align: center; 547 - } 548 - 549 - .modal-tab:hover { 550 - background: #e9ecef; 551 - color: #333; 552 - } 553 - 554 - .modal-tab.active { 555 - color: #007bff; 556 - background: white; 557 - border-bottom: 2px solid #007bff; 558 - } 559 - 560 - .tab-content { 561 - display: none; 562 - } 563 - 564 - .tab-content.active { 565 - display: block; 566 - } 567 - 568 - .prompt-editor { 569 - width: 100%; 570 - min-height: 400px; 571 - border: 1px solid #ddd; 572 - border-radius: 4px; 573 - padding: 1rem; 574 - font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; 575 - font-size: 0.9rem; 576 - line-height: 1.4; 577 - resize: vertical; 578 - } 579 - 580 - .config-section { 581 - margin-bottom: 1.5rem; 582 - } 583 - 584 - .config-section label { 585 - display: block; 586 - margin-bottom: 0.5rem; 587 - font-weight: 500; 588 - color: #333; 589 - } 590 - 591 - .config-section select, 592 - .config-section input { 593 - width: 100%; 594 - padding: 0.5rem; 595 - border: 1px solid #ddd; 596 - border-radius: 4px; 597 - font-size: 0.9rem; 598 - } 599 - 600 - .start-agent-btn { 601 - background: #28a745; 602 - color: white; 603 - border: none; 604 - padding: 0.75rem 2rem; 605 - border-radius: 4px; 606 - cursor: pointer; 607 - font-size: 1rem; 608 - margin-top: 1rem; 609 - } 610 - 611 - .start-agent-btn:hover { 612 - background: #218838; 613 - } 614 - 615 - .start-agent-btn:disabled { 616 - background: #6c757d; 617 - cursor: not-allowed; 618 - } 619 - 620 - /* Item Modal Styles - 80% viewport */ 621 - #itemModal .modal-content { 622 - width: 80vw; 623 - height: 80vh; 624 - max-width: 1400px; 625 - margin: 2.5% auto; 626 - display: flex; 627 - flex-direction: column; 628 - } 629 - 630 - #itemModal .modal-body { 631 - flex: 1; 632 - overflow-y: auto; 633 - padding: 1.5rem; 634 - } 635 - 636 - /* Create Agent Modal Styles */ 637 - #createPersonaModal .modal-content { 638 - max-width: 700px; 639 - } 640 - 641 - #personaTitle { 642 - width: 100%; 643 - padding: 0.75rem; 644 - border: 1px solid #ddd; 645 - border-radius: 4px; 646 - font-size: 1rem; 647 - margin-bottom: 1rem; 648 - } 649 - 650 - /* Prompt content display box for create modals */ 651 - .prompt-result-box { 652 - background: #f8f9fa; 653 - border: 1px solid #dee2e6; 654 - border-radius: 4px; 655 - padding: 1rem; 656 - margin: 1rem 0; 657 - max-height: 400px; 658 - overflow-y: auto; 659 - white-space: pre-wrap; 660 - font-family: 'Monaco', 'Menlo', monospace; 661 - font-size: 0.9rem; 662 - } 663 - 664 - .modal-actions { 665 - display: flex; 666 - gap: 1rem; 667 - margin-top: 1rem; 668 - } 669 - 670 - .modal-actions button { 671 - padding: 0.75rem 1.5rem; 672 - border: none; 673 - border-radius: 4px; 674 - cursor: pointer; 675 - font-size: 1rem; 676 - } 677 - 678 - .modal-actions button:first-child { 679 - background: #28a745; 680 - color: white; 681 - } 682 - 683 - .modal-actions button:first-child:hover { 684 - background: #218838; 685 - } 686 - 687 - .modal-actions button:last-child { 688 - background: #6c757d; 689 - color: white; 690 - } 691 - 692 - .modal-actions button:last-child:hover { 693 - background: #545b62; 694 - } 695 - 696 - /* Enhanced Agent Modal Styles */ 697 - .modal-title-editable { 698 - display: inline-block; 699 - padding: 0.5rem; 700 - border-radius: 4px; 701 - cursor: pointer; 702 - transition: background 0.2s; 703 - font-size: 1.5rem; 704 - font-weight: 600; 705 - margin: 0; 706 - } 707 - 708 - .modal-title-editable:hover { 709 - background: #f0f0f0; 710 - } 711 - 712 - .modal-title-input { 713 - font-size: 1.5rem; 714 - font-weight: 600; 715 - padding: 0.5rem; 716 - border: 2px solid #007bff; 717 - border-radius: 4px; 718 - width: 100%; 719 - outline: none; 720 - } 721 - 722 - .metadata-section { 723 - background: #f8f9fa; 724 - border-radius: 8px; 725 - padding: 1rem; 726 - margin: 1rem 0; 727 - display: grid; 728 - grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); 729 - gap: 1rem; 730 - } 731 - 732 - .metadata-item { 733 - display: flex; 734 - flex-direction: column; 735 - gap: 0.25rem; 736 - } 737 - 738 - .metadata-item label { 739 - font-size: 0.85rem; 740 - color: #666; 741 - font-weight: 500; 742 - } 743 - 744 - .metadata-item input, 745 - .metadata-item select { 746 - padding: 0.5rem; 747 - border: 1px solid #ddd; 748 - border-radius: 4px; 749 - background: white; 750 - font-size: 0.95rem; 751 - } 752 - 753 - .metadata-item input[type="checkbox"] { 754 - width: auto; 755 - cursor: pointer; 756 - } 757 - 758 - .checkbox-wrapper { 759 - display: flex; 760 - align-items: center; 761 - gap: 0.5rem; 762 - padding: 0.5rem; 763 - background: white; 764 - border: 1px solid #ddd; 765 - border-radius: 4px; 766 - cursor: pointer; 767 - } 768 - 769 - .checkbox-wrapper:hover { 770 - background: #f0f0f0; 771 - } 772 - 773 - .prompt-section { 774 - position: relative; 775 - border: 1px solid #ddd; 776 - border-radius: 8px; 777 - overflow: hidden; 778 - margin: 1rem 0; 779 - } 780 - 781 - .prompt-header { 782 - display: flex; 783 - justify-content: space-between; 784 - align-items: center; 785 - padding: 0.75rem 1rem; 786 - background: #f8f9fa; 787 - border-bottom: 1px solid #ddd; 788 - } 789 - 790 - .prompt-label { 791 - font-weight: 500; 792 - color: #333; 793 - } 794 - 795 - .prompt-actions { 796 - display: flex; 797 - gap: 0.5rem; 798 - } 799 - 800 - .prompt-actions button { 801 - padding: 0.4rem 0.8rem; 802 - border: none; 803 - border-radius: 4px; 804 - cursor: pointer; 805 - font-size: 0.9rem; 806 - transition: all 0.2s; 807 - } 808 - 809 - .edit-prompt-btn { 810 - background: #007bff; 811 - color: white; 812 - } 813 - 814 - .edit-prompt-btn:hover { 815 - background: #0056b3; 816 - } 817 - 818 - .save-prompt-btn { 819 - background: #28a745; 820 - color: white; 821 - } 822 - 823 - .save-prompt-btn:hover { 824 - background: #218838; 825 - } 826 - 827 - .cancel-prompt-btn { 828 - background: #6c757d; 829 - color: white; 830 - } 831 - 832 - .cancel-prompt-btn:hover { 833 - background: #545b62; 834 - } 835 - 836 - /* Prompt section container in item modal */ 837 - .prompt-content { 838 - padding: 1rem; 839 - max-height: 500px; 840 - overflow-y: auto; 841 - background: white; 842 - } 843 - 844 - .prompt-display { 845 - white-space: pre-wrap; 846 - font-family: 'Monaco', 'Menlo', monospace; 847 - font-size: 0.9rem; 848 - line-height: 1.6; 849 - color: #333; 850 - } 851 - 852 - .prompt-editor { 853 - width: 100%; 854 - min-height: 400px; 855 - padding: 1rem; 856 - border: none; 857 - font-family: 'Monaco', 'Menlo', monospace; 858 - font-size: 0.9rem; 859 - line-height: 1.6; 860 - resize: vertical; 861 - outline: none; 862 - } 863 - 864 - .saving-indicator { 865 - display: inline-block; 866 - padding: 0.25rem 0.75rem; 867 - background: #28a745; 868 - color: white; 869 - border-radius: 4px; 870 - font-size: 0.85rem; 871 - animation: fadeInOut 2s; 872 - position: absolute; 873 - top: 0.75rem; 874 - right: 1rem; 875 - } 876 - 877 - @keyframes fadeInOut { 878 - 0% { opacity: 0; } 879 - 20% { opacity: 1; } 880 - 80% { opacity: 1; } 881 - 100% { opacity: 0; } 882 - } 883 - 884 - /* Tools Tab Styles */ 885 - .tools-container { 886 - padding: 1rem 0; 887 - } 888 - 889 - .tools-header { 890 - margin-bottom: 2rem; 891 - display: flex; 892 - flex-direction: column; 893 - gap: 1rem; 894 - } 895 - 896 - .tools-search { 897 - width: 100%; 898 - max-width: 400px; 899 - padding: 0.75rem 1rem; 900 - border: 1px solid #ddd; 901 - border-radius: 8px; 902 - font-size: 1rem; 903 - transition: border-color 0.2s; 904 - } 905 - 906 - .tools-search:focus { 907 - outline: none; 908 - border-color: #007bff; 909 - box-shadow: 0 0 0 2px rgba(0,123,255,0.1); 910 - } 911 - 912 - .pack-filters { 913 - display: flex; 914 - gap: 0.5rem; 915 - flex-wrap: wrap; 916 - } 917 - 918 - .pack-filter-btn { 919 - padding: 0.5rem 1rem; 920 - background: #f8f9fa; 921 - border: 1px solid #dee2e6; 922 - border-radius: 20px; 923 - cursor: pointer; 924 - transition: all 0.2s; 925 - font-size: 0.9rem; 926 - } 927 - 928 - .pack-filter-btn:hover { 929 - background: #e9ecef; 930 - border-color: #adb5bd; 931 - } 932 - 933 - .pack-filter-btn.active { 934 - background: #007bff; 935 - color: white; 936 - border-color: #007bff; 937 - } 938 - 939 - .pack-filter-btn .count { 940 - margin-left: 0.3rem; 941 - font-size: 0.85rem; 942 - opacity: 0.8; 943 - } 944 - 945 - .tools-grid { 946 - display: grid; 947 - gap: 1.5rem; 948 - animation: fadeIn 0.3s ease; 949 - } 950 - 951 - .tool-card { 952 - background: white; 953 - border: 1px solid #e0e0e0; 954 - border-radius: 12px; 955 - padding: 1.5rem; 956 - transition: all 0.2s ease; 957 - position: relative; 958 - } 959 - 960 - .tool-card:hover { 961 - border-color: #007bff; 962 - box-shadow: 0 4px 12px rgba(0,123,255,0.1); 963 - transform: translateY(-2px); 964 - } 965 - 966 - .tool-card.hidden { 967 - display: none; 968 - } 969 - 970 - .tool-header { 971 - display: flex; 972 - align-items: start; 973 - justify-content: space-between; 974 - margin-bottom: 1rem; 975 - } 976 - 977 - .tool-name { 978 - font-size: 1.2rem; 979 - font-weight: 600; 980 - color: #333; 981 - margin: 0; 982 - } 983 - 984 - .tool-packs { 985 - display: flex; 986 - gap: 0.3rem; 987 - flex-wrap: wrap; 988 - } 989 - 990 - .tool-pack-badge { 991 - display: inline-block; 992 - padding: 0.2rem 0.6rem; 993 - background: #e3f2fd; 994 - color: #1976d2; 995 - border-radius: 12px; 996 - font-size: 0.75rem; 997 - font-weight: 500; 998 - } 999 - 1000 - .tool-description { 1001 - color: #666; 1002 - line-height: 1.6; 1003 - margin-bottom: 1rem; 1004 - } 1005 - 1006 - .tool-params { 1007 - border-top: 1px solid #e0e0e0; 1008 - padding-top: 1rem; 1009 - margin-top: auto; 1010 - } 1011 - 1012 - .tool-params-title { 1013 - font-size: 0.9rem; 1014 - font-weight: 600; 1015 - color: #555; 1016 - margin-bottom: 0.5rem; 1017 - } 1018 - 1019 - .param-list { 1020 - display: flex; 1021 - flex-direction: column; 1022 - gap: 0.5rem; 1023 - } 1024 - 1025 - .param-item { 1026 - display: flex; 1027 - align-items: start; 1028 - gap: 0.5rem; 1029 - font-size: 0.9rem; 1030 - } 1031 - 1032 - .param-name { 1033 - font-family: 'Monaco', 'Menlo', monospace; 1034 - background: #f8f9fa; 1035 - padding: 0.2rem 0.5rem; 1036 - border-radius: 4px; 1037 - color: #d63384; 1038 - white-space: nowrap; 1039 - } 1040 - 1041 - .param-required { 1042 - color: #dc3545; 1043 - font-weight: 600; 1044 - } 1045 - 1046 - .param-optional { 1047 - color: #6c757d; 1048 - font-style: italic; 1049 - } 1050 - 1051 - .param-type { 1052 - color: #0969da; 1053 - font-family: 'Monaco', 'Menlo', monospace; 1054 - font-size: 0.85rem; 1055 - } 1056 - 1057 - .param-description { 1058 - color: #666; 1059 - flex: 1; 1060 - } 1061 - 1062 - .loading-spinner { 1063 - text-align: center; 1064 - padding: 3rem; 1065 - color: #666; 1066 - font-style: italic; 1067 - } 1068 - 1069 - .tools-empty-state { 1070 - text-align: center; 1071 - padding: 3rem; 1072 - color: #666; 1073 - } 1074 - 1075 - .tools-empty-state h3 { 1076 - color: #333; 1077 - margin-bottom: 0.5rem; 1078 - } 1079 - 1080 - /* Collapsible parameters */ 1081 - .tool-params.collapsed .param-list { 1082 - display: none; 1083 - } 1084 - 1085 - .tool-params-toggle { 1086 - cursor: pointer; 1087 - user-select: none; 1088 - display: flex; 1089 - align-items: center; 1090 - gap: 0.3rem; 1091 - } 1092 - 1093 - .tool-params-toggle::before { 1094 - content: '▼'; 1095 - font-size: 0.7rem; 1096 - transition: transform 0.2s; 1097 - display: inline-block; 1098 - } 1099 - 1100 - .tool-params.collapsed .tool-params-toggle::before { 1101 - transform: rotate(-90deg); 1102 - } 6 + {% include 'agents/_styles.html' %} 1103 7 </style> 1104 8 {% endblock %} 1105 9 {% block body %} ··· 1423 327 1424 328 <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script> 1425 329 <script> 1426 - let availableAgents = []; 1427 - let availableTopics = []; 1428 - let availableTools = []; 1429 - let toolPacks = {}; 1430 - let currentMainTab = 'log'; 1431 - let currentEditingItem = null; 1432 - let currentEditingType = null; // 'agent' or 'topic' 1433 - let isEditMode = false; 1434 - let activePackFilter = 'all'; 1435 - let originalPromptContent = ''; // Store original content for cancel 1436 - let currentTopicContent = ''; 1437 - let currentTopicTitle = ''; 1438 - 1439 - // Switch between main tabs 1440 - function switchMainTab(tabName) { 1441 - // Update tab buttons 1442 - document.querySelectorAll('.tab-button').forEach(tab => { 1443 - tab.classList.remove('active'); 1444 - }); 1445 - event.target.classList.add('active'); 1446 - 1447 - // Update tab content 1448 - document.querySelectorAll('.main-tab-content').forEach(content => { 1449 - content.classList.remove('active'); 1450 - }); 1451 - document.getElementById(tabName + 'Tab').classList.add('active'); 1452 - 1453 - currentMainTab = tabName; 1454 - 1455 - // Load data for the selected tab if needed 1456 - if (tabName === 'log') { 1457 - loadAgentSessions(); 1458 - startAutoRefresh(); 1459 - } else if (tabName === 'agents') { 1460 - stopAutoRefresh(); 1461 - if (!document.getElementById('agentCards').children.length) { 1462 - loadAvailableAgents(); 1463 - } 1464 - } else if (tabName === 'topics') { 1465 - stopAutoRefresh(); 1466 - if (!document.getElementById('topicCards').children.length) { 1467 - loadAvailableTopics(); 1468 - } 1469 - } else if (tabName === 'tools') { 1470 - stopAutoRefresh(); 1471 - if (availableTools.length === 0) { 1472 - loadAvailableTools(); 1473 - } 1474 - } 1475 - } 1476 - 1477 - // Load available agents and render cards 1478 - function loadAvailableAgents() { 1479 - fetch('{{ url_for('agents.available_agents') }}') 1480 - .then(r => r.json()) 1481 - .then(agents => { 1482 - availableAgents = agents; 1483 - renderAgentCards(); 1484 - }); 1485 - } 1486 - 1487 - // Load available topics and render cards 1488 - function loadAvailableTopics() { 1489 - fetch('{{ url_for('agents.available_topics') }}') 1490 - .then(r => r.json()) 1491 - .then(topics => { 1492 - availableTopics = topics; 1493 - renderTopicCards(); 1494 - }); 1495 - } 1496 - 1497 - // Generic function to render cards for both agents and topics 1498 - function renderCards(items, containerId, itemType, createModalFunc) { 1499 - const container = document.getElementById(containerId); 1500 - container.innerHTML = ''; 1501 - 1502 - // Add Create card first 1503 - const createCard = document.createElement('div'); 1504 - createCard.className = 'agent-card create-agent-card'; 1505 - const itemLabel = itemType === 'agent' ? 'Agent' : 'Topic'; 1506 - createCard.innerHTML = ` 1507 - <h3>+ Create New ${itemLabel}</h3> 1508 - `; 1509 - createCard.addEventListener('click', createModalFunc); 1510 - container.appendChild(createCard); 1511 - 1512 - // Add existing items 1513 - items.forEach(item => { 1514 - const card = document.createElement('div'); 1515 - card.className = 'agent-card'; 1516 - 1517 - // Add disabled class for disabled topics 1518 - if (itemType === 'topic' && item.disabled) { 1519 - card.className += ' disabled'; 1520 - } 1521 - 1522 - card.dataset.itemId = item.id; 1523 - card.dataset.itemType = itemType; 1524 - card.title = item.description; // Set description as tooltip 1525 - 1526 - // Add color border and hover effects for topics 1527 - if (itemType === 'topic' && item.color) { 1528 - card.style.borderLeftWidth = '4px'; 1529 - card.style.borderLeftColor = item.color; 1530 - 1531 - // Set hover color to match topic color 1532 - card.addEventListener('mouseenter', () => { 1533 - card.style.borderColor = item.color; 1534 - card.style.boxShadow = `0 2px 8px ${item.color}33`; // 33 = 20% opacity in hex 1535 - }); 1536 - 1537 - card.addEventListener('mouseleave', () => { 1538 - card.style.borderColor = '#e0e0e0'; 1539 - card.style.borderLeftColor = item.color; // Keep left border colored 1540 - card.style.boxShadow = ''; 1541 - }); 1542 - } 1543 - 1544 - // Create title with color indicator for topics 1545 - let titleHtml = item.title; 1546 - if (itemType === 'topic') { 1547 - if (item.color) { 1548 - titleHtml = `<span class="topic-indicator" style="background-color: ${item.color}"></span>${item.title}`; 1549 - } 1550 - if (item.disabled) { 1551 - titleHtml += ' <span style="color: #6c757d; font-size: 0.8em;">(disabled)</span>'; 1552 - } 1553 - } 1554 - 1555 - // Build metadata badges HTML 1556 - let metadataHtml = ''; 1557 - if (itemType === 'agent') { 1558 - const badges = []; 1559 - 1560 - // Schedule badge 1561 - if (item.schedule === 'daily') { 1562 - badges.push('<span class="metadata-badge schedule"><span class="badge-icon">📅</span>Daily</span>'); 1563 - } 1564 - 1565 - // Priority badge 1566 - if (item.priority !== undefined && item.priority !== null) { 1567 - badges.push(`<span class="metadata-badge priority"><span class="badge-icon">⚡</span>P${item.priority}</span>`); 1568 - } 1569 - 1570 - // Multi-domain badge 1571 - if (item.multi_domain) { 1572 - badges.push('<span class="metadata-badge multi-domain"><span class="badge-icon">🌐</span>Multi-Domain</span>'); 1573 - } 1574 - 1575 - // Tools badge (simplified) 1576 - if (item.tools) { 1577 - const toolsList = item.tools.split(',').map(t => t.trim()); 1578 - if (toolsList.length === 1) { 1579 - badges.push(`<span class="metadata-badge tools"><span class="badge-icon">🔧</span>${toolsList[0]}</span>`); 1580 - } else { 1581 - badges.push(`<span class="metadata-badge tools"><span class="badge-icon">🔧</span>${toolsList.length} packs</span>`); 1582 - } 1583 - } 1584 - 1585 - // Backend badge 1586 - if (item.backend) { 1587 - badges.push(`<span class="metadata-badge backend"><span class="badge-icon">🤖</span>${item.backend}</span>`); 1588 - } 1589 - 1590 - if (badges.length > 0) { 1591 - metadataHtml = `<div class="agent-metadata">${badges.join('')}</div>`; 1592 - } 1593 - } 1594 - 1595 - card.innerHTML = ` 1596 - <h3>${titleHtml}</h3> 1597 - ${metadataHtml} 1598 - `; 1599 - 1600 - // Create chat link for agents only 1601 - if (itemType === 'agent') { 1602 - const chatLink = document.createElement('a'); 1603 - chatLink.className = 'chat-link'; 1604 - chatLink.href = `/chat?persona=${item.id}`; 1605 - chatLink.innerHTML = '💬'; 1606 - chatLink.title = `Chat with ${item.title}`; 1607 - chatLink.onclick = (e) => { 1608 - e.stopPropagation(); // Prevent card click when clicking chat link 1609 - }; 1610 - card.appendChild(chatLink); 1611 - } 1612 - 1613 - card.addEventListener('click', (e) => { 1614 - viewItemDetails(item.id, itemType, e); 1615 - }); 1616 - 1617 - container.appendChild(card); 1618 - }); 1619 - } 1620 - 1621 - // Render agent selection cards 1622 - function renderAgentCards() { 1623 - // Sort agents by priority (if scheduled) then by title 1624 - const sortedAgents = [...availableAgents].sort((a, b) => { 1625 - // Scheduled agents with priority come first 1626 - if (a.schedule === 'daily' && b.schedule === 'daily') { 1627 - // Both scheduled, sort by priority (lower number = higher priority) 1628 - const aPriority = a.priority ?? 50; 1629 - const bPriority = b.priority ?? 50; 1630 - if (aPriority !== bPriority) { 1631 - return aPriority - bPriority; 1632 - } 1633 - } else if (a.schedule === 'daily') { 1634 - return -1; // a comes first 1635 - } else if (b.schedule === 'daily') { 1636 - return 1; // b comes first 1637 - } 1638 - // Fall back to alphabetical by title 1639 - return a.title.localeCompare(b.title); 1640 - }); 1641 - 1642 - renderCards(sortedAgents, 'agentCards', 'agent', openCreatePersonaModal); 1643 - } 1644 - 1645 - // Render topic selection cards 1646 - function renderTopicCards() { 1647 - renderCards(availableTopics, 'topicCards', 'topic', openCreateTopicModal); 1648 - } 1649 - 1650 - // View item details in modal (generic for both agents and topics) 1651 - function viewItemDetails(itemId, itemType, event) { 1652 - event.stopPropagation(); 1653 - 1654 - const items = itemType === 'agent' ? availableAgents : availableTopics; 1655 - const item = items.find(i => i.id === itemId); 1656 - if (!item) return; 1657 - 1658 - currentEditingItem = itemId; 1659 - currentEditingType = itemType; 1660 - originalPromptContent = ''; // Store original content for cancel 1661 - 1662 - // Set title 1663 - const modalTitle = document.getElementById('modalTitle'); 1664 - modalTitle.textContent = item.title; 1665 - 1666 - // Show/hide metadata sections based on type 1667 - const agentMetadata = document.getElementById('agentMetadata'); 1668 - const topicMetadata = document.getElementById('topicMetadata'); 1669 - const promptLabel = document.getElementById('promptLabel'); 1670 - 1671 - if (itemType === 'agent') { 1672 - agentMetadata.style.display = 'grid'; 1673 - topicMetadata.style.display = 'none'; 1674 - promptLabel.textContent = 'System Prompt'; 1675 - } else { 1676 - agentMetadata.style.display = 'none'; 1677 - topicMetadata.style.display = 'grid'; 1678 - promptLabel.textContent = 'Topic Content'; 1679 - // Set topic metadata 1680 - document.getElementById('modalTopicEnabled').checked = !item.disabled; 1681 - document.getElementById('modalTopicColor').value = item.color || '#6c757d'; 1682 - } 1683 - 1684 - document.getElementById('promptDisplay').innerHTML = 'Loading...'; 1685 - document.getElementById('itemModal').style.display = 'block'; 1686 - 1687 - // Determine the correct endpoint based on item type 1688 - const contentUrl = itemType === 'agent' 1689 - ? `{{ url_for('agents.agent_content', agent_id='') }}${itemId}` 1690 - : `{{ url_for('agents.topic_content', topic_id='') }}${itemId}`; 1691 - 1692 - // Load agent's config if it's an agent 1693 - if (itemType === 'agent') { 1694 - // Set defaults first 1695 - document.getElementById('modalSchedule').checked = false; 1696 - document.getElementById('modalPriority').value = ''; 1697 - document.getElementById('modalMultiDomain').checked = false; 1698 - document.getElementById('modalBackend').value = ''; 1699 - document.getElementById('modalModel').value = ''; 1700 - document.getElementById('modalTools').value = ''; 1701 - document.getElementById('modalToolsCustom').style.display = 'none'; 1702 - 1703 - // Load from item metadata (already fetched) 1704 - if (item.schedule === 'daily') { 1705 - document.getElementById('modalSchedule').checked = true; 1706 - } 1707 - if (item.priority !== undefined && item.priority !== null) { 1708 - document.getElementById('modalPriority').value = item.priority; 1709 - } 1710 - if (item.multi_domain) { 1711 - document.getElementById('modalMultiDomain').checked = true; 1712 - } 1713 - if (item.backend) { 1714 - document.getElementById('modalBackend').value = item.backend; 1715 - } 1716 - if (item.model) { 1717 - document.getElementById('modalModel').value = item.model; 1718 - } 1719 - if (item.tools) { 1720 - const toolsSelect = document.getElementById('modalTools'); 1721 - const toolsCustom = document.getElementById('modalToolsCustom'); 1722 - 1723 - // Check if tools match a predefined option 1724 - let matched = false; 1725 - for (let option of toolsSelect.options) { 1726 - if (option.value && option.value === item.tools) { 1727 - toolsSelect.value = item.tools; 1728 - matched = true; 1729 - break; 1730 - } 1731 - } 1732 - 1733 - if (!matched && item.tools) { 1734 - // Custom tools 1735 - toolsSelect.value = 'custom'; 1736 - toolsCustom.value = item.tools; 1737 - toolsCustom.style.display = 'block'; 1738 - } 1739 - } 1740 - } 1741 - 1742 - // Load and render item content 1743 - fetch(contentUrl) 1744 - .then(r => r.json()) 1745 - .then(data => { 1746 - if (data.error) { 1747 - document.getElementById('promptDisplay').innerHTML = `<p>Error: ${data.error}</p>`; 1748 - originalPromptContent = ''; 1749 - } else { 1750 - // Store raw content for editing 1751 - const rawContent = data.content; 1752 - originalPromptContent = rawContent; 1753 - document.getElementById('itemContentEditor').value = rawContent; 1754 - 1755 - // Display content 1756 - document.getElementById('promptDisplay').textContent = rawContent; 1757 - } 1758 - }) 1759 - .catch(err => { 1760 - console.error('Failed to load content:', err); 1761 - document.getElementById('promptDisplay').innerHTML = `<p>Failed to load content</p>`; 1762 - }); 1763 - } 1764 - 1765 - // Title editing functions 1766 - function startTitleEdit() { 1767 - const modalTitle = document.getElementById('modalTitle'); 1768 - const modalTitleInput = document.getElementById('modalTitleInput'); 1769 - 1770 - modalTitleInput.value = modalTitle.textContent; 1771 - modalTitle.style.display = 'none'; 1772 - modalTitleInput.style.display = 'block'; 1773 - modalTitleInput.focus(); 1774 - modalTitleInput.select(); 1775 - } 1776 - 1777 - function saveTitleEdit() { 1778 - const modalTitle = document.getElementById('modalTitle'); 1779 - const modalTitleInput = document.getElementById('modalTitleInput'); 1780 - const newTitle = modalTitleInput.value.trim(); 1781 - 1782 - if (newTitle && newTitle !== modalTitle.textContent) { 1783 - // Save the title change 1784 - saveMetadata({ title: newTitle }); 1785 - modalTitle.textContent = newTitle; 1786 - } 1787 - 1788 - modalTitle.style.display = 'block'; 1789 - modalTitleInput.style.display = 'none'; 1790 - } 1791 - 1792 - function handleTitleKey(event) { 1793 - if (event.key === 'Enter') { 1794 - saveTitleEdit(); 1795 - } else if (event.key === 'Escape') { 1796 - const modalTitle = document.getElementById('modalTitle'); 1797 - const modalTitleInput = document.getElementById('modalTitleInput'); 1798 - modalTitle.style.display = 'block'; 1799 - modalTitleInput.style.display = 'none'; 1800 - } 1801 - } 1802 - 1803 - // Prompt editing functions 1804 - function startPromptEdit() { 1805 - const promptDisplay = document.getElementById('promptDisplay'); 1806 - const promptEditor = document.getElementById('itemContentEditor'); 1807 - const editBtn = document.getElementById('editPromptBtn'); 1808 - const saveBtn = document.getElementById('savePromptBtn'); 1809 - const cancelBtn = document.getElementById('cancelPromptBtn'); 1810 - 1811 - promptDisplay.style.display = 'none'; 1812 - promptEditor.style.display = 'block'; 1813 - editBtn.style.display = 'none'; 1814 - saveBtn.style.display = 'inline-block'; 1815 - cancelBtn.style.display = 'inline-block'; 1816 - promptEditor.focus(); 1817 - } 1818 - 1819 - function savePromptEdit() { 1820 - const promptDisplay = document.getElementById('promptDisplay'); 1821 - const promptEditor = document.getElementById('itemContentEditor'); 1822 - const newContent = promptEditor.value.trim(); 1823 - 1824 - if (!newContent) { 1825 - showError('Content cannot be empty'); 1826 - return; 1827 - } 1828 - 1829 - // Save the content 1830 - saveMetadata({ content: newContent }); 1831 - 1832 - promptDisplay.textContent = newContent; 1833 - originalPromptContent = newContent; 1834 - 1835 - // Reset UI 1836 - promptDisplay.style.display = 'block'; 1837 - promptEditor.style.display = 'none'; 1838 - document.getElementById('editPromptBtn').style.display = 'inline-block'; 1839 - document.getElementById('savePromptBtn').style.display = 'none'; 1840 - document.getElementById('cancelPromptBtn').style.display = 'none'; 1841 - } 1842 - 1843 - function cancelPromptEdit() { 1844 - const promptDisplay = document.getElementById('promptDisplay'); 1845 - const promptEditor = document.getElementById('itemContentEditor'); 1846 - 1847 - // Restore original content 1848 - promptEditor.value = originalPromptContent; 1849 - 1850 - // Reset UI 1851 - promptDisplay.style.display = 'block'; 1852 - promptEditor.style.display = 'none'; 1853 - document.getElementById('editPromptBtn').style.display = 'inline-block'; 1854 - document.getElementById('savePromptBtn').style.display = 'none'; 1855 - document.getElementById('cancelPromptBtn').style.display = 'none'; 1856 - } 1857 - 1858 - // Metadata saving function 1859 - async function saveMetadata(updates = {}) { 1860 - const requestBody = {}; 1861 - 1862 - if (currentEditingType === 'agent') { 1863 - // Gather all current metadata values 1864 - const currentTitle = document.getElementById('modalTitle').textContent; 1865 - const currentContent = document.getElementById('itemContentEditor').value.trim(); 1866 - 1867 - requestBody.title = updates.title || currentTitle; 1868 - requestBody.content = updates.content || currentContent; 1869 - 1870 - // Schedule 1871 - const isScheduled = document.getElementById('modalSchedule').checked; 1872 - if (isScheduled) { 1873 - requestBody.schedule = 'daily'; 1874 - } 1875 - 1876 - // Priority 1877 - const priority = document.getElementById('modalPriority').value; 1878 - if (priority !== '') { 1879 - requestBody.priority = parseInt(priority); 1880 - } 1881 - 1882 - // Multi-domain 1883 - const isMultiDomain = document.getElementById('modalMultiDomain').checked; 1884 - if (isMultiDomain) { 1885 - requestBody.multi_domain = true; 1886 - } 1887 - 1888 - // Backend 1889 - const backend = document.getElementById('modalBackend').value; 1890 - if (backend) { 1891 - requestBody.backend = backend; 1892 - } 1893 - 1894 - // Model 1895 - const model = document.getElementById('modalModel').value.trim(); 1896 - if (model) { 1897 - requestBody.model = model; 1898 - } 1899 - 1900 - // Tools 1901 - const toolsSelect = document.getElementById('modalTools').value; 1902 - const toolsCustom = document.getElementById('modalToolsCustom').value.trim(); 1903 - 1904 - if (toolsSelect === 'custom' && toolsCustom) { 1905 - requestBody.tools = toolsCustom; 1906 - } else if (toolsSelect && toolsSelect !== '') { 1907 - requestBody.tools = toolsSelect; 1908 - } 1909 - 1910 - const updateUrl = `{{ url_for('agents.update_agent', agent_id='') }}${currentEditingItem}`; 1911 - 1912 - try { 1913 - const response = await fetch(updateUrl, { 1914 - method: 'PUT', 1915 - headers: { 'Content-Type': 'application/json' }, 1916 - body: JSON.stringify(requestBody) 1917 - }); 1918 - 1919 - const data = await response.json(); 1920 - 1921 - if (response.ok) { 1922 - // Show saving indicator 1923 - showSavingIndicator(); 1924 - // Reload the list to reflect changes 1925 - loadAvailableAgents(); 1926 - } else { 1927 - showError(data.error || 'Failed to save changes'); 1928 - } 1929 - } catch (error) { 1930 - showError('Error saving changes: ' + error.message); 1931 - } 1932 - } 1933 - } 1934 - 1935 - // Topic metadata saving 1936 - async function saveTopicMetadata() { 1937 - const requestBody = { 1938 - title: document.getElementById('modalTitle').textContent, 1939 - content: document.getElementById('itemContentEditor').value.trim(), 1940 - disabled: !document.getElementById('modalTopicEnabled').checked, 1941 - color: document.getElementById('modalTopicColor').value 1942 - }; 1943 - 1944 - const updateUrl = `{{ url_for('agents.update_topic', topic_id='') }}${currentEditingItem}`; 1945 - 1946 - try { 1947 - const response = await fetch(updateUrl, { 1948 - method: 'PUT', 1949 - headers: { 'Content-Type': 'application/json' }, 1950 - body: JSON.stringify(requestBody) 1951 - }); 1952 - 1953 - const data = await response.json(); 1954 - 1955 - if (response.ok) { 1956 - showSavingIndicator(); 1957 - loadAvailableTopics(); 1958 - } else { 1959 - showError(data.error || 'Failed to save changes'); 1960 - } 1961 - } catch (error) { 1962 - showError('Error saving changes: ' + error.message); 1963 - } 1964 - } 1965 - 1966 - // Helper functions 1967 - function toggleSchedule() { 1968 - const checkbox = document.getElementById('modalSchedule'); 1969 - checkbox.checked = !checkbox.checked; 1970 - saveMetadata(); 1971 - } 1972 - 1973 - function toggleMultiDomain() { 1974 - const checkbox = document.getElementById('modalMultiDomain'); 1975 - checkbox.checked = !checkbox.checked; 1976 - saveMetadata(); 1977 - } 1978 - 1979 - function toggleTopicEnabled() { 1980 - const checkbox = document.getElementById('modalTopicEnabled'); 1981 - checkbox.checked = !checkbox.checked; 1982 - saveTopicMetadata(); 1983 - } 1984 - 1985 - function showSavingIndicator() { 1986 - const indicator = document.getElementById('savingIndicator'); 1987 - indicator.style.display = 'inline-block'; 1988 - setTimeout(() => { 1989 - indicator.style.display = 'none'; 1990 - }, 2000); 1991 - } 1992 - 1993 - // Show prompt modal with prompt text 1994 - function showPromptModal(promptText) { 1995 - const modal = document.getElementById('promptModal'); 1996 - const editor = document.getElementById('promptEditor'); 1997 - 1998 - // Set the prompt in the editor 1999 - editor.value = promptText; 2000 - 2001 - // Update preview 2002 - updatePromptPreview(); 2003 - 2004 - // Load available agents 2005 - loadPersonas(); 2006 - 2007 - // Show modal 2008 - modal.style.display = 'block'; 2009 - } 2010 - 2011 - // Close prompt modal 2012 - function closePromptModal() { 2013 - document.getElementById('promptModal').style.display = 'none'; 2014 - } 2015 - 2016 - // Switch between tabs 2017 - function switchTab(tabName) { 2018 - // Update tab buttons 2019 - document.querySelectorAll('.modal-tab').forEach(tab => { 2020 - tab.classList.remove('active'); 2021 - }); 2022 - event.target.classList.add('active'); 2023 - 2024 - // Update tab content 2025 - document.querySelectorAll('.tab-content').forEach(content => { 2026 - content.classList.remove('active'); 2027 - }); 2028 - document.getElementById(tabName + 'Tab').classList.add('active'); 2029 - 2030 - // Update preview if switching to preview tab 2031 - if (tabName === 'preview') { 2032 - updatePromptPreview(); 2033 - } 2034 - } 2035 - 2036 - // Update markdown preview 2037 - function updatePromptPreview() { 2038 - const editor = document.getElementById('promptEditor'); 2039 - const preview = document.getElementById('promptPreview'); 2040 - 2041 - if (editor.value.trim()) { 2042 - preview.innerHTML = marked.parse(editor.value); 2043 - } else { 2044 - preview.innerHTML = '<p><em>No prompt content to preview</em></p>'; 2045 - } 2046 - } 2047 - 2048 - // Load available agents 2049 - function loadPersonas() { 2050 - fetch('{{ url_for('agents.available_agents') }}') 2051 - .then(r => r.json()) 2052 - .then(agents => { 2053 - const select = document.getElementById('agentPersona'); 2054 - select.innerHTML = '<option value="default">Default</option>'; 2055 - 2056 - agents.forEach(agent => { 2057 - const option = document.createElement('option'); 2058 - option.value = agent.id; 2059 - option.textContent = agent.title; 2060 - select.appendChild(option); 2061 - }); 2062 - }) 2063 - .catch(err => { 2064 - console.error('Failed to load agents:', err); 2065 - }); 2066 - } 2067 - 2068 - // Start agent with current prompt and config 2069 - async function startAgent() { 2070 - const prompt = document.getElementById('promptEditor').value.trim(); 2071 - if (!prompt) { 2072 - showError('Please provide a prompt before starting the agent.'); 2073 - return; 2074 - } 2075 - 2076 - const backend = document.getElementById('agentBackend').value; 2077 - const model = document.getElementById('agentModel').value.trim(); 2078 - const maxTokens = parseInt(document.getElementById('maxTokens').value) || 0; 2079 - const persona = document.getElementById('agentPersona').value; 2080 - 2081 - // Build config object 2082 - const config = {}; 2083 - if (model) config.model = model; 2084 - if (maxTokens) config.max_tokens = maxTokens; 2085 - 2086 - const startBtn = document.getElementById('startAgentBtn'); 2087 - startBtn.disabled = true; 2088 - startBtn.textContent = '🚀 Starting...'; 2089 - 2090 - try { 2091 - const response = await fetch('{{ url_for('agents.start_agent') }}', { 2092 - method: 'POST', 2093 - headers: { 2094 - 'Content-Type': 'application/json', 2095 - }, 2096 - body: JSON.stringify({ 2097 - prompt: prompt, 2098 - backend: backend, 2099 - config: config, 2100 - persona: persona 2101 - }) 2102 - }); 2103 - 2104 - const data = await response.json(); 2105 - 2106 - if (!response.ok) { 2107 - throw new Error(data.error || 'Failed to start agent'); 2108 - } 2109 - 2110 - // Success - close modal and reload agents 2111 - closePromptModal(); 2112 - loadAgentSessions(); 2113 - 2114 - } catch (error) { 2115 - showError('Error starting agent: ' + error.message); 2116 - } finally { 2117 - startBtn.disabled = false; 2118 - startBtn.textContent = '🚀 Start Agent'; 2119 - } 2120 - } 2121 - 2122 - // Close item modal function 2123 - function closeItemModal() { 2124 - // Cancel any ongoing prompt edits if elements exist 2125 - const promptDisplay = document.getElementById('promptDisplay'); 2126 - const promptEditor = document.getElementById('itemContentEditor'); 2127 - 2128 - if (promptDisplay && promptEditor) { 2129 - // Reset prompt editor if it was being edited 2130 - if (promptEditor.style.display === 'block') { 2131 - promptEditor.value = originalPromptContent || ''; 2132 - promptDisplay.style.display = 'block'; 2133 - promptEditor.style.display = 'none'; 2134 - 2135 - const editBtn = document.getElementById('editPromptBtn'); 2136 - const saveBtn = document.getElementById('savePromptBtn'); 2137 - const cancelBtn = document.getElementById('cancelPromptBtn'); 2138 - 2139 - if (editBtn) editBtn.style.display = 'inline-block'; 2140 - if (saveBtn) saveBtn.style.display = 'none'; 2141 - if (cancelBtn) cancelBtn.style.display = 'none'; 2142 - } 2143 - } 2144 - 2145 - // Reset title if editing 2146 - const modalTitle = document.getElementById('modalTitle'); 2147 - const modalTitleInput = document.getElementById('modalTitleInput'); 2148 - if (modalTitle && modalTitleInput) { 2149 - modalTitle.style.display = 'block'; 2150 - modalTitleInput.style.display = 'none'; 2151 - } 2152 - 2153 - document.getElementById('itemModal').style.display = 'none'; 2154 - } 2155 - 2156 - // Moved to bottom of script with createPersonaModal handling 2157 - 2158 - // Pagination state 2159 - let currentPage = 0; 2160 - let pageSize = 20; 2161 - let totalAgents = 0; 2162 - 2163 - // Session history table functions 2164 - function buildRow(a) { 2165 - const tr = document.createElement('tr'); 2166 - const short = a.prompt.length > 80 ? a.prompt.slice(0, 80) + '…' : a.prompt; 2167 - const chatLink = `{{ url_for('chat.chat_page') }}?agent=${a.id}`; 2168 - 2169 - // Status badge 2170 - const statusClass = `status-${a.status || 'unknown'}`; 2171 - const statusBadge = `<span class="status-badge ${statusClass}">${a.status || 'unknown'}</span>`; 2172 - 2173 - // PID indicator for running agents 2174 - const pidIndicator = a.pid ? ` (PID: ${a.pid})` : ''; 2175 - 2176 - // Format runtime 2177 - let runtimeDisplay = ''; 2178 - if (a.runtime_seconds !== undefined && a.runtime_seconds !== null) { 2179 - const totalSeconds = Math.floor(a.runtime_seconds); 2180 - const minutes = Math.floor(totalSeconds / 60); 2181 - 2182 - if (totalSeconds > 59) { 2183 - // For 60 seconds or more, show just minutes 2184 - runtimeDisplay = `${minutes}m`; 2185 - } else { 2186 - // For less than 60 seconds, show as Xs 2187 - runtimeDisplay = `${totalSeconds}s`; 2188 - } 2189 - } 2190 - 2191 - tr.innerHTML = ` 2192 - <td><a href="${chatLink}">${statusBadge}</a></td> 2193 - <td><a href="${chatLink}">${a.since}</a></td> 2194 - <td>${runtimeDisplay}</td> 2195 - <td>${a.model}${pidIndicator}</td> 2196 - <td>${a.persona_title || a.persona}</td> 2197 - <td title="${a.prompt}"><a href="${chatLink}">${short}</a></td> 2198 - `; 2199 - tr.style.cursor = 'pointer'; 2200 - tr.onclick = () => window.location.href = chatLink; 2201 - return tr; 2202 - } 2203 - 2204 - function loadAgentSessions(page = 0) { 2205 - const offset = page * pageSize; 2206 - fetch(`{{ url_for('agents.agents_list') }}?type=all&limit=${pageSize}&offset=${offset}`) 2207 - .then(r => { 2208 - if (!r.ok) { 2209 - throw new Error(`HTTP ${r.status}: ${r.statusText}`); 2210 - } 2211 - return r.json(); 2212 - }) 2213 - .then(data => { 2214 - // Check for API error response 2215 - if (data.error) { 2216 - throw new Error(data.error); 2217 - } 2218 - 2219 - const agents = data.agents || []; 2220 - const pagination = data.pagination; 2221 - const liveCount = data.live_count || 0; 2222 - const historicalCount = data.historical_count || 0; 2223 - 2224 - // Separate live and historical agents 2225 - const liveAgents = agents.filter(a => a.status === 'running'); 2226 - const historicalAgents = agents.filter(a => a.status !== 'running'); 2227 - 2228 - // Update counts 2229 - document.getElementById('liveCount').textContent = liveCount; 2230 - document.getElementById('historicalCount').textContent = historicalCount; 2231 - 2232 - // Update live agents table 2233 - const liveTable = document.getElementById('liveAgentTable'); 2234 - const liveEmpty = document.getElementById('liveEmpty'); 2235 - 2236 - if (liveAgents.length > 0) { 2237 - liveTable.innerHTML = '<tr><th>Status</th><th>Started</th><th>Runtime</th><th>Model</th><th>Agent</th><th>Prompt</th></tr>'; 2238 - liveAgents.forEach(a => liveTable.appendChild(buildRow(a))); 2239 - liveTable.style.display = 'table'; 2240 - liveEmpty.style.display = 'none'; 2241 - } else { 2242 - liveTable.style.display = 'none'; 2243 - liveEmpty.style.display = 'block'; 2244 - } 2245 - 2246 - // Update historical agents table 2247 - const histTable = document.getElementById('historicalAgentTable'); 2248 - const histEmpty = document.getElementById('historicalEmpty'); 2249 - 2250 - if (historicalAgents.length > 0) { 2251 - histTable.innerHTML = '<tr><th>Status</th><th>Started</th><th>Runtime</th><th>Model</th><th>Agent</th><th>Prompt</th></tr>'; 2252 - historicalAgents.forEach(a => histTable.appendChild(buildRow(a))); 2253 - histTable.style.display = 'table'; 2254 - histEmpty.style.display = 'none'; 2255 - } else { 2256 - histTable.style.display = 'none'; 2257 - histEmpty.style.display = 'block'; 2258 - } 2259 - 2260 - // Update pagination if available 2261 - if (pagination && pagination.total > pagination.limit) { 2262 - currentPage = Math.floor(pagination.offset / pagination.limit); 2263 - totalAgents = pagination.total; 2264 - updatePagination(pagination); 2265 - } else { 2266 - document.getElementById('pagination').style.display = 'none'; 2267 - } 2268 - 2269 - // Hide any previous error warnings 2270 - hideConnectionWarning(); 2271 - }) 2272 - .catch(err => { 2273 - console.error('Failed to load agents:', err); 2274 - 2275 - // Still try to load historical agents even if cortex is down 2276 - fetch(`{{ url_for('agents.agents_list') }}?type=historical&limit=${pageSize}&offset=${offset}`) 2277 - .then(r => r.json()) 2278 - .then(data => { 2279 - const historicalAgents = data.agents || []; 2280 - const historicalCount = data.historical_count || 0; 2281 - 2282 - // Update historical count 2283 - document.getElementById('historicalCount').textContent = historicalCount; 2284 - 2285 - // Clear live agents section 2286 - document.getElementById('liveCount').textContent = '0'; 2287 - document.getElementById('liveAgentTable').style.display = 'none'; 2288 - document.getElementById('liveEmpty').style.display = 'block'; 2289 - 2290 - // Update historical agents table 2291 - const histTable = document.getElementById('historicalAgentTable'); 2292 - const histEmpty = document.getElementById('historicalEmpty'); 2293 - 2294 - if (historicalAgents.length > 0) { 2295 - histTable.innerHTML = '<tr><th>Status</th><th>Started</th><th>Runtime</th><th>Model</th><th>Agent</th><th>Prompt</th></tr>'; 2296 - historicalAgents.forEach(a => histTable.appendChild(buildRow(a))); 2297 - histTable.style.display = 'table'; 2298 - histEmpty.style.display = 'none'; 2299 - } else { 2300 - histTable.style.display = 'none'; 2301 - histEmpty.style.display = 'block'; 2302 - } 2303 - 2304 - // Update pagination 2305 - if (data.pagination && data.pagination.total > data.pagination.limit) { 2306 - updatePagination(data.pagination); 2307 - } else { 2308 - document.getElementById('pagination').style.display = 'none'; 2309 - } 2310 - }) 2311 - .catch(() => { 2312 - // Total failure - clear everything 2313 - document.getElementById('liveCount').textContent = '0'; 2314 - document.getElementById('historicalCount').textContent = '0'; 2315 - document.getElementById('liveAgentTable').style.display = 'none'; 2316 - document.getElementById('liveEmpty').style.display = 'block'; 2317 - document.getElementById('historicalAgentTable').style.display = 'none'; 2318 - document.getElementById('historicalEmpty').style.display = 'block'; 2319 - document.getElementById('pagination').style.display = 'none'; 2320 - }); 2321 - }); 2322 - } 2323 - 2324 - function updatePagination(pagination) { 2325 - const paginationDiv = document.getElementById('pagination'); 2326 - const prevBtn = document.getElementById('prevBtn'); 2327 - const nextBtn = document.getElementById('nextBtn'); 2328 - const pageInfo = document.getElementById('pageInfo'); 2329 - 2330 - if (pagination.total > pagination.limit) { 2331 - paginationDiv.style.display = 'flex'; 2332 - 2333 - const currentPageNum = Math.floor(pagination.offset / pagination.limit) + 1; 2334 - const totalPages = Math.ceil(pagination.total / pagination.limit); 2335 - 2336 - pageInfo.textContent = `Page ${currentPageNum} of ${totalPages} (${pagination.total} total)`; 2337 - 2338 - prevBtn.disabled = pagination.offset === 0; 2339 - nextBtn.disabled = !pagination.has_more; 2340 - } else { 2341 - paginationDiv.style.display = 'none'; 2342 - } 2343 - } 2344 - 2345 - function changePage(delta) { 2346 - const newPage = currentPage + delta; 2347 - if (newPage >= 0) { 2348 - loadAgentSessions(newPage); 2349 - } 2350 - } 2351 - 2352 - // Connection warning functions 2353 - function showConnectionWarning(message) { 2354 - let warning = document.getElementById('connectionWarning'); 2355 - if (!warning) { 2356 - warning = document.createElement('div'); 2357 - warning.id = 'connectionWarning'; 2358 - warning.style.cssText = 'background:#f8d7da;border:1px solid #f5c6cb;color:#721c24;padding:0.75rem;margin:1rem 0;border-radius:4px;text-align:center;'; 2359 - const container = document.querySelector('.container'); 2360 - const firstChild = container.children[1]; // Insert after h1 2361 - container.insertBefore(warning, firstChild); 2362 - } 2363 - warning.innerHTML = `⚠️ <strong>Cortex Service Unavailable:</strong> ${message}`; 2364 - warning.style.display = 'block'; 2365 - } 2366 - 2367 - function hideConnectionWarning() { 2368 - const warning = document.getElementById('connectionWarning'); 2369 - if (warning) { 2370 - warning.style.display = 'none'; 2371 - } 2372 - } 2373 - 2374 - // Show error message 2375 - function showError(message) { 2376 - alert(message); // Simple alert for now, can be improved with a toast notification 2377 - } 2378 - 2379 - // Create Agent Modal Functions 2380 - function openCreatePersonaModal() { 2381 - const modal = document.getElementById('createPersonaModal'); 2382 - modal.style.display = 'block'; 2383 - 2384 - // Reset form 2385 - document.getElementById('createPersonaForm').reset(); 2386 - document.getElementById('createPersonaForm').style.display = 'block'; 2387 - document.getElementById('createPersonaForm').classList.remove('busy'); 2388 - document.getElementById('createPersonaPromptStatus').classList.remove('show'); 2389 - document.getElementById('createPromptBtn').disabled = false; 2390 - } 2391 - 2392 - function closeCreatePersonaModal() { 2393 - document.getElementById('createPersonaModal').style.display = 'none'; 2394 - } 2395 - 2396 - // Create Topic Modal Functions 2397 - function openCreateTopicModal() { 2398 - const modal = document.getElementById('createTopicModal'); 2399 - modal.style.display = 'block'; 2400 - 2401 - // Reset form 2402 - document.getElementById('createTopicForm').reset(); 2403 - document.getElementById('topicResult').style.display = 'none'; 2404 - document.getElementById('createTopicBtn').disabled = false; 2405 - } 2406 - 2407 - function closeCreateTopicModal() { 2408 - document.getElementById('createTopicModal').style.display = 'none'; 2409 - } 2410 - 2411 - // Handle create agent form submission 2412 - document.getElementById('createPersonaForm').addEventListener('submit', async (e) => { 2413 - e.preventDefault(); 2414 - 2415 - const title = document.getElementById('personaTitle').value.trim(); 2416 - const input = document.getElementById('personaInput').value.trim(); 2417 - const form = e.target; 2418 - const status = document.getElementById('createPersonaPromptStatus'); 2419 - const createBtn = document.getElementById('createPromptBtn'); 2420 - 2421 - if (!title || !input) return; 2422 - 2423 - // Show prompt generation status 2424 - form.classList.add('busy'); 2425 - status.classList.add('show'); 2426 - createBtn.disabled = true; 2427 - 2428 - try { 2429 - const response = await fetch('{{ url_for('agents.create_plan') }}', { 2430 - method: 'POST', 2431 - headers: { 'Content-Type': 'application/json' }, 2432 - body: JSON.stringify({ 2433 - request: input, 2434 - model: '' // Uses Gemini Pro by default 2435 - }) 2436 - }); 2437 - 2438 - const data = await response.json(); 2439 - 2440 - if (!response.ok) { 2441 - throw new Error(data.error || 'Failed to create prompt'); 2442 - } 2443 - 2444 - const generatedPrompt = data.prompt || data.plan; 2445 - if (!generatedPrompt) { 2446 - throw new Error('No prompt returned by planner'); 2447 - } 2448 - 2449 - // Show the generated prompt 2450 - showPersonaPromptResult(generatedPrompt, title); 2451 - 2452 - } catch (error) { 2453 - showError('Error creating prompt: ' + error.message); 2454 - createBtn.disabled = false; 2455 - } finally { 2456 - // Remove busy state 2457 - form.classList.remove('busy'); 2458 - status.classList.remove('show'); 2459 - } 2460 - }); 2461 - 2462 - async function showPersonaPromptResult(prompt, title) { 2463 - // Auto-save the agent immediately 2464 - const isDaily = document.getElementById('personaDailySchedule').checked; 2465 - const priority = document.getElementById('personaPriority').value; 2466 - const toolsSelect = document.getElementById('personaToolPacks'); 2467 - const personaId = title.toLowerCase().replace(/[^a-z0-9]+/g, '_'); 2468 - 2469 - try { 2470 - // Create the agent 2471 - const requestBody = { 2472 - title: title, 2473 - content: prompt 2474 - }; 2475 - 2476 - // Add schedule if daily is checked 2477 - if (isDaily) { 2478 - requestBody.schedule = 'daily'; 2479 - } 2480 - 2481 - // Add priority if specified 2482 - if (priority !== '') { 2483 - requestBody.priority = parseInt(priority); 2484 - } 2485 - 2486 - // Add tools if specific packs are selected 2487 - const selectedTools = []; 2488 - for (let option of toolsSelect.options) { 2489 - if (option.selected && option.value !== '') { 2490 - selectedTools.push(option.value); 2491 - } 2492 - } 2493 - if (selectedTools.length > 0) { 2494 - requestBody.tools = selectedTools.join(','); 2495 - } 2496 - 2497 - const response = await fetch(`{{ url_for('agents.update_agent', agent_id='') }}${personaId}`, { 2498 - method: 'PUT', 2499 - headers: { 'Content-Type': 'application/json' }, 2500 - body: JSON.stringify(requestBody) 2501 - }); 2502 - 2503 - const data = await response.json(); 2504 - 2505 - if (response.ok) { 2506 - // Close create modal 2507 - closeCreatePersonaModal(); 2508 - 2509 - // Reload agents list 2510 - await loadAvailableAgents(); 2511 - 2512 - // Open the edit modal with the new agent 2513 - setTimeout(() => { 2514 - // Find the new agent in the list 2515 - const newAgent = availableAgents.find(a => a.id === personaId); 2516 - if (newAgent) { 2517 - viewItemDetails(personaId, 'agent', { stopPropagation: () => {} }); 2518 - } 2519 - }, 100); 2520 - } else { 2521 - throw new Error(data.error || 'Failed to save agent'); 2522 - } 2523 - } catch (error) { 2524 - showError('Error saving agent: ' + error.message); 2525 - // Re-enable the form 2526 - document.getElementById('createPersonaForm').classList.remove('busy'); 2527 - document.getElementById('createPersonaPromptStatus').classList.remove('show'); 2528 - document.getElementById('createPromptBtn').disabled = false; 2529 - } 2530 - } 2531 - 2532 - // Handle create topic form submission 2533 - document.getElementById('createTopicForm').addEventListener('submit', async (e) => { 2534 - e.preventDefault(); 2535 - 2536 - const title = document.getElementById('topicTitle').value.trim(); 2537 - const input = document.getElementById('topicInput').value.trim(); 2538 - const form = e.target; 2539 - const createBtn = document.getElementById('createTopicBtn'); 2540 - 2541 - if (!title || !input) return; 2542 - 2543 - // Show the topic content area 2544 - showTopicResult(input, title); 2545 - 2546 - // Hide form 2547 - form.style.display = 'none'; 2548 - }); 2549 - 2550 - function showTopicResult(content, title) { 2551 - const resultDiv = document.getElementById('topicResult'); 2552 - const resultContent = document.getElementById('topicResultContent'); 2553 - 2554 - // Store content and title for saving 2555 - currentTopicContent = content; 2556 - currentTopicTitle = title; 2557 - 2558 - // Display the content 2559 - resultContent.textContent = content; 2560 - resultDiv.style.display = 'block'; 2561 - } 2562 - 2563 - function editTopicContent() { 2564 - const resultContent = document.getElementById('topicResultContent'); 2565 - 2566 - // Convert to editable textarea 2567 - const textarea = document.createElement('textarea'); 2568 - textarea.value = resultContent.textContent; 2569 - textarea.style.width = '100%'; 2570 - textarea.style.minHeight = '400px'; 2571 - textarea.style.padding = '1rem'; 2572 - textarea.style.fontFamily = 'Monaco, Menlo, monospace'; 2573 - textarea.style.fontSize = '0.9rem'; 2574 - 2575 - resultContent.innerHTML = ''; 2576 - resultContent.appendChild(textarea); 2577 - 2578 - // Update the content when editing 2579 - textarea.addEventListener('input', () => { 2580 - currentTopicContent = textarea.value; 2581 - }); 2582 - } 2583 - 2584 - async function saveNewTopic() { 2585 - const title = currentTopicTitle; 2586 - const content = currentTopicContent; 2587 - 2588 - if (!title || !content) { 2589 - showError('Missing title or content'); 2590 - return; 2591 - } 2592 - 2593 - // Generate a unique ID for the new topic 2594 - const topicId = title.toLowerCase().replace(/[^a-z0-9]+/g, '_'); 2595 - 2596 - try { 2597 - // Create or update the topic 2598 - const response = await fetch(`{{ url_for('agents.update_topic', topic_id='') }}${topicId}`, { 2599 - method: 'PUT', 2600 - headers: { 'Content-Type': 'application/json' }, 2601 - body: JSON.stringify({ 2602 - title: title, 2603 - content: content 2604 - }) 2605 - }); 2606 - 2607 - const data = await response.json(); 2608 - 2609 - if (response.ok) { 2610 - alert('Topic saved successfully!'); 2611 - closeCreateTopicModal(); 2612 - // Reset form for next use 2613 - document.getElementById('createTopicForm').style.display = 'block'; 2614 - document.getElementById('topicResult').style.display = 'none'; 2615 - // Reload topics 2616 - loadAvailableTopics(); 2617 - } else { 2618 - throw new Error(data.error || 'Failed to save topic'); 2619 - } 2620 - } catch (error) { 2621 - showError('Error saving topic: ' + error.message); 2622 - } 2623 - } 2624 - 2625 - // Update modal close handlers 2626 - window.onclick = function(event) { 2627 - const itemModal = document.getElementById('itemModal'); 2628 - const promptModal = document.getElementById('promptModal'); 2629 - const createPersonaModal = document.getElementById('createPersonaModal'); 2630 - const createTopicModal = document.getElementById('createTopicModal'); 2631 - 2632 - if (event.target == itemModal) { 2633 - closeItemModal(); 2634 - } 2635 - if (event.target == promptModal) { 2636 - promptModal.style.display = 'none'; 2637 - } 2638 - if (event.target == createPersonaModal) { 2639 - createPersonaModal.style.display = 'none'; 2640 - } 2641 - if (event.target == createTopicModal) { 2642 - createTopicModal.style.display = 'none'; 2643 - } 2644 - }; 2645 - 2646 - // Auto-refresh live agents 2647 - let refreshInterval = null; 2648 - 2649 - function startAutoRefresh() { 2650 - // Refresh every 5 seconds if on the log tab 2651 - if (currentMainTab === 'log' && !refreshInterval) { 2652 - refreshInterval = setInterval(() => { 2653 - if (document.visibilityState === 'visible') { 2654 - // Only refresh the current page 2655 - loadAgentSessions(currentPage); 2656 - } 2657 - }, 5000); 2658 - } 2659 - } 2660 - 2661 - function stopAutoRefresh() { 2662 - if (refreshInterval) { 2663 - clearInterval(refreshInterval); 2664 - refreshInterval = null; 2665 - } 2666 - } 2667 - 2668 - // Start/stop refresh based on tab 2669 - document.addEventListener('visibilitychange', () => { 2670 - if (document.visibilityState === 'hidden') { 2671 - stopAutoRefresh(); 2672 - } else if (currentMainTab === 'log') { 2673 - startAutoRefresh(); 2674 - } 2675 - }); 2676 - 2677 - // Handle tools dropdown change for modal 2678 - document.addEventListener('DOMContentLoaded', () => { 2679 - const toolsSelect = document.getElementById('modalTools'); 2680 - if (toolsSelect) { 2681 - toolsSelect.addEventListener('change', () => { 2682 - const customInput = document.getElementById('modalToolsCustom'); 2683 - if (toolsSelect.value === 'custom') { 2684 - customInput.style.display = 'block'; 2685 - customInput.focus(); 2686 - } else { 2687 - customInput.style.display = 'none'; 2688 - } 2689 - // Auto-save when tools change 2690 - if (currentEditingType === 'agent') { 2691 - saveMetadata(); 2692 - } 2693 - }); 2694 - } 2695 - }); 2696 - 2697 - // Load available tools and render them 2698 - function loadAvailableTools() { 2699 - fetch('{{ url_for('agents.available_tools') }}') 2700 - .then(r => r.json()) 2701 - .then(data => { 2702 - if (data.error) { 2703 - document.getElementById('toolsGrid').innerHTML = ` 2704 - <div class="tools-empty-state"> 2705 - <h3>Error Loading Tools</h3> 2706 - <p>${data.error}</p> 2707 - </div> 2708 - `; 2709 - return; 2710 - } 2711 - 2712 - availableTools = data.tools || []; 2713 - toolPacks = data.packs || {}; 2714 - renderPackFilters(); 2715 - renderToolsGrid(); 2716 - setupToolsSearch(); 2717 - }) 2718 - .catch(err => { 2719 - console.error('Failed to load tools:', err); 2720 - document.getElementById('toolsGrid').innerHTML = ` 2721 - <div class="tools-empty-state"> 2722 - <h3>Failed to Load Tools</h3> 2723 - <p>Please ensure the MCP tools server is configured correctly.</p> 2724 - </div> 2725 - `; 2726 - }); 2727 - } 2728 - 2729 - // Render pack filter buttons 2730 - function renderPackFilters() { 2731 - const container = document.getElementById('packFilters'); 2732 - container.innerHTML = ''; 2733 - 2734 - // All tools button 2735 - const allBtn = document.createElement('button'); 2736 - allBtn.className = 'pack-filter-btn active'; 2737 - allBtn.innerHTML = `All<span class="count">${availableTools.length}</span>`; 2738 - allBtn.onclick = () => filterByPack('all'); 2739 - container.appendChild(allBtn); 2740 - 2741 - // Pack-specific buttons 2742 - Object.entries(toolPacks).forEach(([packName, packInfo]) => { 2743 - const btn = document.createElement('button'); 2744 - btn.className = 'pack-filter-btn'; 2745 - btn.innerHTML = `${packInfo.name}<span class="count">${packInfo.tools.length}</span>`; 2746 - btn.title = packInfo.description; 2747 - btn.onclick = () => filterByPack(packName); 2748 - container.appendChild(btn); 2749 - }); 2750 - 2751 - // Uncategorized button (tools not in any pack) 2752 - const uncategorized = availableTools.filter(t => t.packs.length === 0); 2753 - if (uncategorized.length > 0) { 2754 - const uncatBtn = document.createElement('button'); 2755 - uncatBtn.className = 'pack-filter-btn'; 2756 - uncatBtn.innerHTML = `Other<span class="count">${uncategorized.length}</span>`; 2757 - uncatBtn.onclick = () => filterByPack('uncategorized'); 2758 - container.appendChild(uncatBtn); 2759 - } 2760 - } 2761 - 2762 - // Filter tools by pack 2763 - function filterByPack(pack) { 2764 - activePackFilter = pack; 2765 - 2766 - // Update button states 2767 - document.querySelectorAll('.pack-filter-btn').forEach(btn => { 2768 - btn.classList.remove('active'); 2769 - }); 2770 - event.target.classList.add('active'); 2771 - 2772 - // Apply filter 2773 - applyToolsFilter(); 2774 - } 2775 - 2776 - // Apply search and pack filters 2777 - function applyToolsFilter() { 2778 - const searchTerm = document.getElementById('toolsSearch').value.toLowerCase(); 2779 - const cards = document.querySelectorAll('.tool-card'); 2780 - 2781 - cards.forEach(card => { 2782 - const toolName = card.dataset.toolName; 2783 - const tool = availableTools.find(t => t.name === toolName); 2784 - if (!tool) return; 2785 - 2786 - // Check pack filter 2787 - let packMatch = true; 2788 - if (activePackFilter !== 'all') { 2789 - if (activePackFilter === 'uncategorized') { 2790 - packMatch = tool.packs.length === 0; 2791 - } else { 2792 - packMatch = tool.packs.includes(activePackFilter); 2793 - } 2794 - } 2795 - 2796 - // Check search filter 2797 - let searchMatch = true; 2798 - if (searchTerm) { 2799 - searchMatch = tool.name.toLowerCase().includes(searchTerm) || 2800 - (tool.description && tool.description.toLowerCase().includes(searchTerm)); 2801 - } 2802 - 2803 - // Show/hide card 2804 - if (packMatch && searchMatch) { 2805 - card.classList.remove('hidden'); 2806 - } else { 2807 - card.classList.add('hidden'); 2808 - } 2809 - }); 2810 - } 2811 - 2812 - // Setup search functionality 2813 - function setupToolsSearch() { 2814 - const searchInput = document.getElementById('toolsSearch'); 2815 - searchInput.addEventListener('input', applyToolsFilter); 2816 - } 2817 - 2818 - // Render tools grid 2819 - function renderToolsGrid() { 2820 - const container = document.getElementById('toolsGrid'); 2821 - container.innerHTML = ''; 2822 - 2823 - if (availableTools.length === 0) { 2824 - container.innerHTML = ` 2825 - <div class="tools-empty-state"> 2826 - <h3>No Tools Available</h3> 2827 - <p>MCP tools will appear here once configured.</p> 2828 - </div> 2829 - `; 2830 - return; 2831 - } 2832 - 2833 - availableTools.forEach(tool => { 2834 - const card = createToolCard(tool); 2835 - container.appendChild(card); 2836 - }); 2837 - } 2838 - 2839 - // Create a tool card element 2840 - function createToolCard(tool) { 2841 - const card = document.createElement('div'); 2842 - card.className = 'tool-card'; 2843 - card.dataset.toolName = tool.name; 2844 - 2845 - // Header with name and pack badges 2846 - const header = document.createElement('div'); 2847 - header.className = 'tool-header'; 2848 - 2849 - const name = document.createElement('h3'); 2850 - name.className = 'tool-name'; 2851 - name.textContent = tool.name; 2852 - header.appendChild(name); 2853 - 2854 - if (tool.packs && tool.packs.length > 0) { 2855 - const packs = document.createElement('div'); 2856 - packs.className = 'tool-packs'; 2857 - tool.packs.forEach(packName => { 2858 - const badge = document.createElement('span'); 2859 - badge.className = 'tool-pack-badge'; 2860 - badge.textContent = packName; 2861 - packs.appendChild(badge); 2862 - }); 2863 - header.appendChild(packs); 2864 - } 2865 - 2866 - card.appendChild(header); 2867 - 2868 - // Description 2869 - const desc = document.createElement('div'); 2870 - desc.className = 'tool-description'; 2871 - desc.innerHTML = marked.parse(tool.description || 'No description available'); 2872 - card.appendChild(desc); 2873 - 2874 - // Parameters (if available) 2875 - if (tool.input_schema && tool.input_schema.properties) { 2876 - const params = createParametersSection(tool.input_schema); 2877 - if (params) { 2878 - card.appendChild(params); 2879 - } 2880 - } 2881 - 2882 - return card; 2883 - } 2884 - 2885 - // Create parameters section for a tool 2886 - function createParametersSection(schema) { 2887 - const properties = schema.properties || {}; 2888 - const required = schema.required || []; 2889 - 2890 - if (Object.keys(properties).length === 0) { 2891 - return null; 2892 - } 2893 - 2894 - const section = document.createElement('div'); 2895 - section.className = 'tool-params'; 2896 - 2897 - const title = document.createElement('div'); 2898 - title.className = 'tool-params-toggle tool-params-title'; 2899 - title.textContent = `Parameters (${Object.keys(properties).length})`; 2900 - title.onclick = () => section.classList.toggle('collapsed'); 2901 - section.appendChild(title); 2902 - 2903 - const list = document.createElement('div'); 2904 - list.className = 'param-list'; 2905 - 2906 - Object.entries(properties).forEach(([paramName, paramSchema]) => { 2907 - const item = document.createElement('div'); 2908 - item.className = 'param-item'; 2909 - 2910 - const name = document.createElement('span'); 2911 - name.className = 'param-name'; 2912 - name.textContent = paramName; 2913 - item.appendChild(name); 2914 - 2915 - // Required/optional indicator 2916 - const reqSpan = document.createElement('span'); 2917 - if (required.includes(paramName)) { 2918 - reqSpan.className = 'param-required'; 2919 - reqSpan.textContent = '*'; 2920 - } else { 2921 - reqSpan.className = 'param-optional'; 2922 - reqSpan.textContent = '(optional)'; 2923 - } 2924 - item.appendChild(reqSpan); 2925 - 2926 - // Type 2927 - if (paramSchema.type) { 2928 - const type = document.createElement('span'); 2929 - type.className = 'param-type'; 2930 - type.textContent = `[${paramSchema.type}]`; 2931 - item.appendChild(type); 2932 - } 2933 - 2934 - // Description 2935 - if (paramSchema.description) { 2936 - const desc = document.createElement('span'); 2937 - desc.className = 'param-description'; 2938 - desc.textContent = paramSchema.description; 2939 - item.appendChild(desc); 2940 - } 2941 - 2942 - list.appendChild(item); 2943 - }); 2944 - 2945 - section.appendChild(list); 2946 - 2947 - // Start collapsed if more than 3 parameters 2948 - if (Object.keys(properties).length > 3) { 2949 - section.classList.add('collapsed'); 2950 - } 2951 - 2952 - return section; 2953 - } 2954 - 2955 - // Initialize - The Log tab is shown by default now 2956 - setTimeout(() => { 2957 - loadAgentSessions(); 2958 - startAutoRefresh(); 2959 - }, 100); 330 + {% include 'agents/_scripts.html' %} 2960 331 </script> 2961 332 {% endblock %}
+1534
dream/templates/agents/_scripts.html
··· 1 + let availableAgents = []; 2 + let availableTopics = []; 3 + let availableTools = []; 4 + let toolPacks = {}; 5 + let currentMainTab = 'log'; 6 + let currentEditingItem = null; 7 + let currentEditingType = null; // 'agent' or 'topic' 8 + let isEditMode = false; 9 + let activePackFilter = 'all'; 10 + let originalPromptContent = ''; // Store original content for cancel 11 + let currentTopicContent = ''; 12 + let currentTopicTitle = ''; 13 + 14 + // Switch between main tabs 15 + function switchMainTab(tabName) { 16 + // Update tab buttons 17 + document.querySelectorAll('.tab-button').forEach(tab => { 18 + tab.classList.remove('active'); 19 + }); 20 + event.target.classList.add('active'); 21 + 22 + // Update tab content 23 + document.querySelectorAll('.main-tab-content').forEach(content => { 24 + content.classList.remove('active'); 25 + }); 26 + document.getElementById(tabName + 'Tab').classList.add('active'); 27 + 28 + currentMainTab = tabName; 29 + 30 + // Load data for the selected tab if needed 31 + if (tabName === 'log') { 32 + loadAgentSessions(); 33 + startAutoRefresh(); 34 + } else if (tabName === 'agents') { 35 + stopAutoRefresh(); 36 + if (!document.getElementById('agentCards').children.length) { 37 + loadAvailableAgents(); 38 + } 39 + } else if (tabName === 'topics') { 40 + stopAutoRefresh(); 41 + if (!document.getElementById('topicCards').children.length) { 42 + loadAvailableTopics(); 43 + } 44 + } else if (tabName === 'tools') { 45 + stopAutoRefresh(); 46 + if (availableTools.length === 0) { 47 + loadAvailableTools(); 48 + } 49 + } 50 + } 51 + 52 + // Load available agents and render cards 53 + function loadAvailableAgents() { 54 + fetch('{{ url_for('agents.available_agents') }}') 55 + .then(r => r.json()) 56 + .then(agents => { 57 + availableAgents = agents; 58 + renderAgentCards(); 59 + }); 60 + } 61 + 62 + // Load available topics and render cards 63 + function loadAvailableTopics() { 64 + fetch('{{ url_for('agents.available_topics') }}') 65 + .then(r => r.json()) 66 + .then(topics => { 67 + availableTopics = topics; 68 + renderTopicCards(); 69 + }); 70 + } 71 + 72 + // Generic function to render cards for both agents and topics 73 + function renderCards(items, containerId, itemType, createModalFunc) { 74 + const container = document.getElementById(containerId); 75 + container.innerHTML = ''; 76 + 77 + // Add Create card first 78 + const createCard = document.createElement('div'); 79 + createCard.className = 'agent-card create-agent-card'; 80 + const itemLabel = itemType === 'agent' ? 'Agent' : 'Topic'; 81 + createCard.innerHTML = ` 82 + <h3>+ Create New ${itemLabel}</h3> 83 + `; 84 + createCard.addEventListener('click', createModalFunc); 85 + container.appendChild(createCard); 86 + 87 + // Add existing items 88 + items.forEach(item => { 89 + const card = document.createElement('div'); 90 + card.className = 'agent-card'; 91 + 92 + // Add disabled class for disabled topics 93 + if (itemType === 'topic' && item.disabled) { 94 + card.className += ' disabled'; 95 + } 96 + 97 + card.dataset.itemId = item.id; 98 + card.dataset.itemType = itemType; 99 + card.title = item.description; // Set description as tooltip 100 + 101 + // Add color border and hover effects for topics 102 + if (itemType === 'topic' && item.color) { 103 + card.style.borderLeftWidth = '4px'; 104 + card.style.borderLeftColor = item.color; 105 + 106 + // Set hover color to match topic color 107 + card.addEventListener('mouseenter', () => { 108 + card.style.borderColor = item.color; 109 + card.style.boxShadow = `0 2px 8px ${item.color}33`; // 33 = 20% opacity in hex 110 + }); 111 + 112 + card.addEventListener('mouseleave', () => { 113 + card.style.borderColor = '#e0e0e0'; 114 + card.style.borderLeftColor = item.color; // Keep left border colored 115 + card.style.boxShadow = ''; 116 + }); 117 + } 118 + 119 + // Create title with color indicator for topics 120 + let titleHtml = item.title; 121 + if (itemType === 'topic') { 122 + if (item.color) { 123 + titleHtml = `<span class="topic-indicator" style="background-color: ${item.color}"></span>${item.title}`; 124 + } 125 + if (item.disabled) { 126 + titleHtml += ' <span style="color: #6c757d; font-size: 0.8em;">(disabled)</span>'; 127 + } 128 + } 129 + 130 + // Build metadata badges HTML 131 + let metadataHtml = ''; 132 + if (itemType === 'agent') { 133 + const badges = []; 134 + 135 + // Schedule badge 136 + if (item.schedule === 'daily') { 137 + badges.push('<span class="metadata-badge schedule"><span class="badge-icon">📅</span>Daily</span>'); 138 + } 139 + 140 + // Priority badge 141 + if (item.priority !== undefined && item.priority !== null) { 142 + badges.push(`<span class="metadata-badge priority"><span class="badge-icon">⚡</span>P${item.priority}</span>`); 143 + } 144 + 145 + // Multi-domain badge 146 + if (item.multi_domain) { 147 + badges.push('<span class="metadata-badge multi-domain"><span class="badge-icon">🌐</span>Multi-Domain</span>'); 148 + } 149 + 150 + // Tools badge (simplified) 151 + if (item.tools) { 152 + const toolsList = item.tools.split(',').map(t => t.trim()); 153 + if (toolsList.length === 1) { 154 + badges.push(`<span class="metadata-badge tools"><span class="badge-icon">🔧</span>${toolsList[0]}</span>`); 155 + } else { 156 + badges.push(`<span class="metadata-badge tools"><span class="badge-icon">🔧</span>${toolsList.length} packs</span>`); 157 + } 158 + } 159 + 160 + // Backend badge 161 + if (item.backend) { 162 + badges.push(`<span class="metadata-badge backend"><span class="badge-icon">🤖</span>${item.backend}</span>`); 163 + } 164 + 165 + if (badges.length > 0) { 166 + metadataHtml = `<div class="agent-metadata">${badges.join('')}</div>`; 167 + } 168 + } 169 + 170 + card.innerHTML = ` 171 + <h3>${titleHtml}</h3> 172 + ${metadataHtml} 173 + `; 174 + 175 + // Create chat link for agents only 176 + if (itemType === 'agent') { 177 + const chatLink = document.createElement('a'); 178 + chatLink.className = 'chat-link'; 179 + chatLink.href = `/chat?persona=${item.id}`; 180 + chatLink.innerHTML = '💬'; 181 + chatLink.title = `Chat with ${item.title}`; 182 + chatLink.onclick = (e) => { 183 + e.stopPropagation(); // Prevent card click when clicking chat link 184 + }; 185 + card.appendChild(chatLink); 186 + } 187 + 188 + card.addEventListener('click', (e) => { 189 + viewItemDetails(item.id, itemType, e); 190 + }); 191 + 192 + container.appendChild(card); 193 + }); 194 + } 195 + 196 + // Render agent selection cards 197 + function renderAgentCards() { 198 + // Sort agents by priority (if scheduled) then by title 199 + const sortedAgents = [...availableAgents].sort((a, b) => { 200 + // Scheduled agents with priority come first 201 + if (a.schedule === 'daily' && b.schedule === 'daily') { 202 + // Both scheduled, sort by priority (lower number = higher priority) 203 + const aPriority = a.priority ?? 50; 204 + const bPriority = b.priority ?? 50; 205 + if (aPriority !== bPriority) { 206 + return aPriority - bPriority; 207 + } 208 + } else if (a.schedule === 'daily') { 209 + return -1; // a comes first 210 + } else if (b.schedule === 'daily') { 211 + return 1; // b comes first 212 + } 213 + // Fall back to alphabetical by title 214 + return a.title.localeCompare(b.title); 215 + }); 216 + 217 + renderCards(sortedAgents, 'agentCards', 'agent', openCreatePersonaModal); 218 + } 219 + 220 + // Render topic selection cards 221 + function renderTopicCards() { 222 + renderCards(availableTopics, 'topicCards', 'topic', openCreateTopicModal); 223 + } 224 + 225 + // View item details in modal (generic for both agents and topics) 226 + function viewItemDetails(itemId, itemType, event) { 227 + event.stopPropagation(); 228 + 229 + const items = itemType === 'agent' ? availableAgents : availableTopics; 230 + const item = items.find(i => i.id === itemId); 231 + if (!item) return; 232 + 233 + currentEditingItem = itemId; 234 + currentEditingType = itemType; 235 + originalPromptContent = ''; // Store original content for cancel 236 + 237 + // Set title 238 + const modalTitle = document.getElementById('modalTitle'); 239 + modalTitle.textContent = item.title; 240 + 241 + // Show/hide metadata sections based on type 242 + const agentMetadata = document.getElementById('agentMetadata'); 243 + const topicMetadata = document.getElementById('topicMetadata'); 244 + const promptLabel = document.getElementById('promptLabel'); 245 + 246 + if (itemType === 'agent') { 247 + agentMetadata.style.display = 'grid'; 248 + topicMetadata.style.display = 'none'; 249 + promptLabel.textContent = 'System Prompt'; 250 + } else { 251 + agentMetadata.style.display = 'none'; 252 + topicMetadata.style.display = 'grid'; 253 + promptLabel.textContent = 'Topic Content'; 254 + // Set topic metadata 255 + document.getElementById('modalTopicEnabled').checked = !item.disabled; 256 + document.getElementById('modalTopicColor').value = item.color || '#6c757d'; 257 + } 258 + 259 + document.getElementById('promptDisplay').innerHTML = 'Loading...'; 260 + document.getElementById('itemModal').style.display = 'block'; 261 + 262 + // Determine the correct endpoint based on item type 263 + const contentUrl = itemType === 'agent' 264 + ? `{{ url_for('agents.agent_content', agent_id='') }}${itemId}` 265 + : `{{ url_for('agents.topic_content', topic_id='') }}${itemId}`; 266 + 267 + // Load agent's config if it's an agent 268 + if (itemType === 'agent') { 269 + // Set defaults first 270 + document.getElementById('modalSchedule').checked = false; 271 + document.getElementById('modalPriority').value = ''; 272 + document.getElementById('modalMultiDomain').checked = false; 273 + document.getElementById('modalBackend').value = ''; 274 + document.getElementById('modalModel').value = ''; 275 + document.getElementById('modalTools').value = ''; 276 + document.getElementById('modalToolsCustom').style.display = 'none'; 277 + 278 + // Load from item metadata (already fetched) 279 + if (item.schedule === 'daily') { 280 + document.getElementById('modalSchedule').checked = true; 281 + } 282 + if (item.priority !== undefined && item.priority !== null) { 283 + document.getElementById('modalPriority').value = item.priority; 284 + } 285 + if (item.multi_domain) { 286 + document.getElementById('modalMultiDomain').checked = true; 287 + } 288 + if (item.backend) { 289 + document.getElementById('modalBackend').value = item.backend; 290 + } 291 + if (item.model) { 292 + document.getElementById('modalModel').value = item.model; 293 + } 294 + if (item.tools) { 295 + const toolsSelect = document.getElementById('modalTools'); 296 + const toolsCustom = document.getElementById('modalToolsCustom'); 297 + 298 + // Check if tools match a predefined option 299 + let matched = false; 300 + for (let option of toolsSelect.options) { 301 + if (option.value && option.value === item.tools) { 302 + toolsSelect.value = item.tools; 303 + matched = true; 304 + break; 305 + } 306 + } 307 + 308 + if (!matched && item.tools) { 309 + // Custom tools 310 + toolsSelect.value = 'custom'; 311 + toolsCustom.value = item.tools; 312 + toolsCustom.style.display = 'block'; 313 + } 314 + } 315 + } 316 + 317 + // Load and render item content 318 + fetch(contentUrl) 319 + .then(r => r.json()) 320 + .then(data => { 321 + if (data.error) { 322 + document.getElementById('promptDisplay').innerHTML = `<p>Error: ${data.error}</p>`; 323 + originalPromptContent = ''; 324 + } else { 325 + // Store raw content for editing 326 + const rawContent = data.content; 327 + originalPromptContent = rawContent; 328 + document.getElementById('itemContentEditor').value = rawContent; 329 + 330 + // Display content 331 + document.getElementById('promptDisplay').textContent = rawContent; 332 + } 333 + }) 334 + .catch(err => { 335 + console.error('Failed to load content:', err); 336 + document.getElementById('promptDisplay').innerHTML = `<p>Failed to load content</p>`; 337 + }); 338 + } 339 + 340 + // Title editing functions 341 + function startTitleEdit() { 342 + const modalTitle = document.getElementById('modalTitle'); 343 + const modalTitleInput = document.getElementById('modalTitleInput'); 344 + 345 + modalTitleInput.value = modalTitle.textContent; 346 + modalTitle.style.display = 'none'; 347 + modalTitleInput.style.display = 'block'; 348 + modalTitleInput.focus(); 349 + modalTitleInput.select(); 350 + } 351 + 352 + function saveTitleEdit() { 353 + const modalTitle = document.getElementById('modalTitle'); 354 + const modalTitleInput = document.getElementById('modalTitleInput'); 355 + const newTitle = modalTitleInput.value.trim(); 356 + 357 + if (newTitle && newTitle !== modalTitle.textContent) { 358 + // Save the title change 359 + saveMetadata({ title: newTitle }); 360 + modalTitle.textContent = newTitle; 361 + } 362 + 363 + modalTitle.style.display = 'block'; 364 + modalTitleInput.style.display = 'none'; 365 + } 366 + 367 + function handleTitleKey(event) { 368 + if (event.key === 'Enter') { 369 + saveTitleEdit(); 370 + } else if (event.key === 'Escape') { 371 + const modalTitle = document.getElementById('modalTitle'); 372 + const modalTitleInput = document.getElementById('modalTitleInput'); 373 + modalTitle.style.display = 'block'; 374 + modalTitleInput.style.display = 'none'; 375 + } 376 + } 377 + 378 + // Prompt editing functions 379 + function startPromptEdit() { 380 + const promptDisplay = document.getElementById('promptDisplay'); 381 + const promptEditor = document.getElementById('itemContentEditor'); 382 + const editBtn = document.getElementById('editPromptBtn'); 383 + const saveBtn = document.getElementById('savePromptBtn'); 384 + const cancelBtn = document.getElementById('cancelPromptBtn'); 385 + 386 + promptDisplay.style.display = 'none'; 387 + promptEditor.style.display = 'block'; 388 + editBtn.style.display = 'none'; 389 + saveBtn.style.display = 'inline-block'; 390 + cancelBtn.style.display = 'inline-block'; 391 + promptEditor.focus(); 392 + } 393 + 394 + function savePromptEdit() { 395 + const promptDisplay = document.getElementById('promptDisplay'); 396 + const promptEditor = document.getElementById('itemContentEditor'); 397 + const newContent = promptEditor.value.trim(); 398 + 399 + if (!newContent) { 400 + showError('Content cannot be empty'); 401 + return; 402 + } 403 + 404 + // Save the content 405 + saveMetadata({ content: newContent }); 406 + 407 + promptDisplay.textContent = newContent; 408 + originalPromptContent = newContent; 409 + 410 + // Reset UI 411 + promptDisplay.style.display = 'block'; 412 + promptEditor.style.display = 'none'; 413 + document.getElementById('editPromptBtn').style.display = 'inline-block'; 414 + document.getElementById('savePromptBtn').style.display = 'none'; 415 + document.getElementById('cancelPromptBtn').style.display = 'none'; 416 + } 417 + 418 + function cancelPromptEdit() { 419 + const promptDisplay = document.getElementById('promptDisplay'); 420 + const promptEditor = document.getElementById('itemContentEditor'); 421 + 422 + // Restore original content 423 + promptEditor.value = originalPromptContent; 424 + 425 + // Reset UI 426 + promptDisplay.style.display = 'block'; 427 + promptEditor.style.display = 'none'; 428 + document.getElementById('editPromptBtn').style.display = 'inline-block'; 429 + document.getElementById('savePromptBtn').style.display = 'none'; 430 + document.getElementById('cancelPromptBtn').style.display = 'none'; 431 + } 432 + 433 + // Metadata saving function 434 + async function saveMetadata(updates = {}) { 435 + const requestBody = {}; 436 + 437 + if (currentEditingType === 'agent') { 438 + // Gather all current metadata values 439 + const currentTitle = document.getElementById('modalTitle').textContent; 440 + const currentContent = document.getElementById('itemContentEditor').value.trim(); 441 + 442 + requestBody.title = updates.title || currentTitle; 443 + requestBody.content = updates.content || currentContent; 444 + 445 + // Schedule 446 + const isScheduled = document.getElementById('modalSchedule').checked; 447 + if (isScheduled) { 448 + requestBody.schedule = 'daily'; 449 + } 450 + 451 + // Priority 452 + const priority = document.getElementById('modalPriority').value; 453 + if (priority !== '') { 454 + requestBody.priority = parseInt(priority); 455 + } 456 + 457 + // Multi-domain 458 + const isMultiDomain = document.getElementById('modalMultiDomain').checked; 459 + if (isMultiDomain) { 460 + requestBody.multi_domain = true; 461 + } 462 + 463 + // Backend 464 + const backend = document.getElementById('modalBackend').value; 465 + if (backend) { 466 + requestBody.backend = backend; 467 + } 468 + 469 + // Model 470 + const model = document.getElementById('modalModel').value.trim(); 471 + if (model) { 472 + requestBody.model = model; 473 + } 474 + 475 + // Tools 476 + const toolsSelect = document.getElementById('modalTools').value; 477 + const toolsCustom = document.getElementById('modalToolsCustom').value.trim(); 478 + 479 + if (toolsSelect === 'custom' && toolsCustom) { 480 + requestBody.tools = toolsCustom; 481 + } else if (toolsSelect && toolsSelect !== '') { 482 + requestBody.tools = toolsSelect; 483 + } 484 + 485 + const updateUrl = `{{ url_for('agents.update_agent', agent_id='') }}${currentEditingItem}`; 486 + 487 + try { 488 + const response = await fetch(updateUrl, { 489 + method: 'PUT', 490 + headers: { 'Content-Type': 'application/json' }, 491 + body: JSON.stringify(requestBody) 492 + }); 493 + 494 + const data = await response.json(); 495 + 496 + if (response.ok) { 497 + // Show saving indicator 498 + showSavingIndicator(); 499 + // Reload the list to reflect changes 500 + loadAvailableAgents(); 501 + } else { 502 + showError(data.error || 'Failed to save changes'); 503 + } 504 + } catch (error) { 505 + showError('Error saving changes: ' + error.message); 506 + } 507 + } 508 + } 509 + 510 + // Topic metadata saving 511 + async function saveTopicMetadata() { 512 + const requestBody = { 513 + title: document.getElementById('modalTitle').textContent, 514 + content: document.getElementById('itemContentEditor').value.trim(), 515 + disabled: !document.getElementById('modalTopicEnabled').checked, 516 + color: document.getElementById('modalTopicColor').value 517 + }; 518 + 519 + const updateUrl = `{{ url_for('agents.update_topic', topic_id='') }}${currentEditingItem}`; 520 + 521 + try { 522 + const response = await fetch(updateUrl, { 523 + method: 'PUT', 524 + headers: { 'Content-Type': 'application/json' }, 525 + body: JSON.stringify(requestBody) 526 + }); 527 + 528 + const data = await response.json(); 529 + 530 + if (response.ok) { 531 + showSavingIndicator(); 532 + loadAvailableTopics(); 533 + } else { 534 + showError(data.error || 'Failed to save changes'); 535 + } 536 + } catch (error) { 537 + showError('Error saving changes: ' + error.message); 538 + } 539 + } 540 + 541 + // Helper functions 542 + function toggleSchedule() { 543 + const checkbox = document.getElementById('modalSchedule'); 544 + checkbox.checked = !checkbox.checked; 545 + saveMetadata(); 546 + } 547 + 548 + function toggleMultiDomain() { 549 + const checkbox = document.getElementById('modalMultiDomain'); 550 + checkbox.checked = !checkbox.checked; 551 + saveMetadata(); 552 + } 553 + 554 + function toggleTopicEnabled() { 555 + const checkbox = document.getElementById('modalTopicEnabled'); 556 + checkbox.checked = !checkbox.checked; 557 + saveTopicMetadata(); 558 + } 559 + 560 + function showSavingIndicator() { 561 + const indicator = document.getElementById('savingIndicator'); 562 + indicator.style.display = 'inline-block'; 563 + setTimeout(() => { 564 + indicator.style.display = 'none'; 565 + }, 2000); 566 + } 567 + 568 + // Show prompt modal with prompt text 569 + function showPromptModal(promptText) { 570 + const modal = document.getElementById('promptModal'); 571 + const editor = document.getElementById('promptEditor'); 572 + 573 + // Set the prompt in the editor 574 + editor.value = promptText; 575 + 576 + // Update preview 577 + updatePromptPreview(); 578 + 579 + // Load available agents 580 + loadPersonas(); 581 + 582 + // Show modal 583 + modal.style.display = 'block'; 584 + } 585 + 586 + // Close prompt modal 587 + function closePromptModal() { 588 + document.getElementById('promptModal').style.display = 'none'; 589 + } 590 + 591 + // Switch between tabs 592 + function switchTab(tabName) { 593 + // Update tab buttons 594 + document.querySelectorAll('.modal-tab').forEach(tab => { 595 + tab.classList.remove('active'); 596 + }); 597 + event.target.classList.add('active'); 598 + 599 + // Update tab content 600 + document.querySelectorAll('.tab-content').forEach(content => { 601 + content.classList.remove('active'); 602 + }); 603 + document.getElementById(tabName + 'Tab').classList.add('active'); 604 + 605 + // Update preview if switching to preview tab 606 + if (tabName === 'preview') { 607 + updatePromptPreview(); 608 + } 609 + } 610 + 611 + // Update markdown preview 612 + function updatePromptPreview() { 613 + const editor = document.getElementById('promptEditor'); 614 + const preview = document.getElementById('promptPreview'); 615 + 616 + if (editor.value.trim()) { 617 + preview.innerHTML = marked.parse(editor.value); 618 + } else { 619 + preview.innerHTML = '<p><em>No prompt content to preview</em></p>'; 620 + } 621 + } 622 + 623 + // Load available agents 624 + function loadPersonas() { 625 + fetch('{{ url_for('agents.available_agents') }}') 626 + .then(r => r.json()) 627 + .then(agents => { 628 + const select = document.getElementById('agentPersona'); 629 + select.innerHTML = '<option value="default">Default</option>'; 630 + 631 + agents.forEach(agent => { 632 + const option = document.createElement('option'); 633 + option.value = agent.id; 634 + option.textContent = agent.title; 635 + select.appendChild(option); 636 + }); 637 + }) 638 + .catch(err => { 639 + console.error('Failed to load agents:', err); 640 + }); 641 + } 642 + 643 + // Start agent with current prompt and config 644 + async function startAgent() { 645 + const prompt = document.getElementById('promptEditor').value.trim(); 646 + if (!prompt) { 647 + showError('Please provide a prompt before starting the agent.'); 648 + return; 649 + } 650 + 651 + const backend = document.getElementById('agentBackend').value; 652 + const model = document.getElementById('agentModel').value.trim(); 653 + const maxTokens = parseInt(document.getElementById('maxTokens').value) || 0; 654 + const persona = document.getElementById('agentPersona').value; 655 + 656 + // Build config object 657 + const config = {}; 658 + if (model) config.model = model; 659 + if (maxTokens) config.max_tokens = maxTokens; 660 + 661 + const startBtn = document.getElementById('startAgentBtn'); 662 + startBtn.disabled = true; 663 + startBtn.textContent = '🚀 Starting...'; 664 + 665 + try { 666 + const response = await fetch('{{ url_for('agents.start_agent') }}', { 667 + method: 'POST', 668 + headers: { 669 + 'Content-Type': 'application/json', 670 + }, 671 + body: JSON.stringify({ 672 + prompt: prompt, 673 + backend: backend, 674 + config: config, 675 + persona: persona 676 + }) 677 + }); 678 + 679 + const data = await response.json(); 680 + 681 + if (!response.ok) { 682 + throw new Error(data.error || 'Failed to start agent'); 683 + } 684 + 685 + // Success - close modal and reload agents 686 + closePromptModal(); 687 + loadAgentSessions(); 688 + 689 + } catch (error) { 690 + showError('Error starting agent: ' + error.message); 691 + } finally { 692 + startBtn.disabled = false; 693 + startBtn.textContent = '🚀 Start Agent'; 694 + } 695 + } 696 + 697 + // Close item modal function 698 + function closeItemModal() { 699 + // Cancel any ongoing prompt edits if elements exist 700 + const promptDisplay = document.getElementById('promptDisplay'); 701 + const promptEditor = document.getElementById('itemContentEditor'); 702 + 703 + if (promptDisplay && promptEditor) { 704 + // Reset prompt editor if it was being edited 705 + if (promptEditor.style.display === 'block') { 706 + promptEditor.value = originalPromptContent || ''; 707 + promptDisplay.style.display = 'block'; 708 + promptEditor.style.display = 'none'; 709 + 710 + const editBtn = document.getElementById('editPromptBtn'); 711 + const saveBtn = document.getElementById('savePromptBtn'); 712 + const cancelBtn = document.getElementById('cancelPromptBtn'); 713 + 714 + if (editBtn) editBtn.style.display = 'inline-block'; 715 + if (saveBtn) saveBtn.style.display = 'none'; 716 + if (cancelBtn) cancelBtn.style.display = 'none'; 717 + } 718 + } 719 + 720 + // Reset title if editing 721 + const modalTitle = document.getElementById('modalTitle'); 722 + const modalTitleInput = document.getElementById('modalTitleInput'); 723 + if (modalTitle && modalTitleInput) { 724 + modalTitle.style.display = 'block'; 725 + modalTitleInput.style.display = 'none'; 726 + } 727 + 728 + document.getElementById('itemModal').style.display = 'none'; 729 + } 730 + 731 + // Moved to bottom of script with createPersonaModal handling 732 + 733 + // Pagination state 734 + let currentPage = 0; 735 + let pageSize = 20; 736 + let totalAgents = 0; 737 + 738 + // Session history table functions 739 + function buildRow(a) { 740 + const tr = document.createElement('tr'); 741 + const short = a.prompt.length > 80 ? a.prompt.slice(0, 80) + '…' : a.prompt; 742 + const chatLink = `{{ url_for('chat.chat_page') }}?agent=${a.id}`; 743 + 744 + // Status badge 745 + const statusClass = `status-${a.status || 'unknown'}`; 746 + const statusBadge = `<span class="status-badge ${statusClass}">${a.status || 'unknown'}</span>`; 747 + 748 + // PID indicator for running agents 749 + const pidIndicator = a.pid ? ` (PID: ${a.pid})` : ''; 750 + 751 + // Format runtime 752 + let runtimeDisplay = ''; 753 + if (a.runtime_seconds !== undefined && a.runtime_seconds !== null) { 754 + const totalSeconds = Math.floor(a.runtime_seconds); 755 + const minutes = Math.floor(totalSeconds / 60); 756 + 757 + if (totalSeconds > 59) { 758 + // For 60 seconds or more, show just minutes 759 + runtimeDisplay = `${minutes}m`; 760 + } else { 761 + // For less than 60 seconds, show as Xs 762 + runtimeDisplay = `${totalSeconds}s`; 763 + } 764 + } 765 + 766 + tr.innerHTML = ` 767 + <td><a href="${chatLink}">${statusBadge}</a></td> 768 + <td><a href="${chatLink}">${a.since}</a></td> 769 + <td>${runtimeDisplay}</td> 770 + <td>${a.model}${pidIndicator}</td> 771 + <td>${a.persona_title || a.persona}</td> 772 + <td title="${a.prompt}"><a href="${chatLink}">${short}</a></td> 773 + `; 774 + tr.style.cursor = 'pointer'; 775 + tr.onclick = () => window.location.href = chatLink; 776 + return tr; 777 + } 778 + 779 + function loadAgentSessions(page = 0) { 780 + const offset = page * pageSize; 781 + fetch(`{{ url_for('agents.agents_list') }}?type=all&limit=${pageSize}&offset=${offset}`) 782 + .then(r => { 783 + if (!r.ok) { 784 + throw new Error(`HTTP ${r.status}: ${r.statusText}`); 785 + } 786 + return r.json(); 787 + }) 788 + .then(data => { 789 + // Check for API error response 790 + if (data.error) { 791 + throw new Error(data.error); 792 + } 793 + 794 + const agents = data.agents || []; 795 + const pagination = data.pagination; 796 + const liveCount = data.live_count || 0; 797 + const historicalCount = data.historical_count || 0; 798 + 799 + // Separate live and historical agents 800 + const liveAgents = agents.filter(a => a.status === 'running'); 801 + const historicalAgents = agents.filter(a => a.status !== 'running'); 802 + 803 + // Update counts 804 + document.getElementById('liveCount').textContent = liveCount; 805 + document.getElementById('historicalCount').textContent = historicalCount; 806 + 807 + // Update live agents table 808 + const liveTable = document.getElementById('liveAgentTable'); 809 + const liveEmpty = document.getElementById('liveEmpty'); 810 + 811 + if (liveAgents.length > 0) { 812 + liveTable.innerHTML = '<tr><th>Status</th><th>Started</th><th>Runtime</th><th>Model</th><th>Agent</th><th>Prompt</th></tr>'; 813 + liveAgents.forEach(a => liveTable.appendChild(buildRow(a))); 814 + liveTable.style.display = 'table'; 815 + liveEmpty.style.display = 'none'; 816 + } else { 817 + liveTable.style.display = 'none'; 818 + liveEmpty.style.display = 'block'; 819 + } 820 + 821 + // Update historical agents table 822 + const histTable = document.getElementById('historicalAgentTable'); 823 + const histEmpty = document.getElementById('historicalEmpty'); 824 + 825 + if (historicalAgents.length > 0) { 826 + histTable.innerHTML = '<tr><th>Status</th><th>Started</th><th>Runtime</th><th>Model</th><th>Agent</th><th>Prompt</th></tr>'; 827 + historicalAgents.forEach(a => histTable.appendChild(buildRow(a))); 828 + histTable.style.display = 'table'; 829 + histEmpty.style.display = 'none'; 830 + } else { 831 + histTable.style.display = 'none'; 832 + histEmpty.style.display = 'block'; 833 + } 834 + 835 + // Update pagination if available 836 + if (pagination && pagination.total > pagination.limit) { 837 + currentPage = Math.floor(pagination.offset / pagination.limit); 838 + totalAgents = pagination.total; 839 + updatePagination(pagination); 840 + } else { 841 + document.getElementById('pagination').style.display = 'none'; 842 + } 843 + 844 + // Hide any previous error warnings 845 + hideConnectionWarning(); 846 + }) 847 + .catch(err => { 848 + console.error('Failed to load agents:', err); 849 + 850 + // Still try to load historical agents even if cortex is down 851 + fetch(`{{ url_for('agents.agents_list') }}?type=historical&limit=${pageSize}&offset=${offset}`) 852 + .then(r => r.json()) 853 + .then(data => { 854 + const historicalAgents = data.agents || []; 855 + const historicalCount = data.historical_count || 0; 856 + 857 + // Update historical count 858 + document.getElementById('historicalCount').textContent = historicalCount; 859 + 860 + // Clear live agents section 861 + document.getElementById('liveCount').textContent = '0'; 862 + document.getElementById('liveAgentTable').style.display = 'none'; 863 + document.getElementById('liveEmpty').style.display = 'block'; 864 + 865 + // Update historical agents table 866 + const histTable = document.getElementById('historicalAgentTable'); 867 + const histEmpty = document.getElementById('historicalEmpty'); 868 + 869 + if (historicalAgents.length > 0) { 870 + histTable.innerHTML = '<tr><th>Status</th><th>Started</th><th>Runtime</th><th>Model</th><th>Agent</th><th>Prompt</th></tr>'; 871 + historicalAgents.forEach(a => histTable.appendChild(buildRow(a))); 872 + histTable.style.display = 'table'; 873 + histEmpty.style.display = 'none'; 874 + } else { 875 + histTable.style.display = 'none'; 876 + histEmpty.style.display = 'block'; 877 + } 878 + 879 + // Update pagination 880 + if (data.pagination && data.pagination.total > data.pagination.limit) { 881 + updatePagination(data.pagination); 882 + } else { 883 + document.getElementById('pagination').style.display = 'none'; 884 + } 885 + }) 886 + .catch(() => { 887 + // Total failure - clear everything 888 + document.getElementById('liveCount').textContent = '0'; 889 + document.getElementById('historicalCount').textContent = '0'; 890 + document.getElementById('liveAgentTable').style.display = 'none'; 891 + document.getElementById('liveEmpty').style.display = 'block'; 892 + document.getElementById('historicalAgentTable').style.display = 'none'; 893 + document.getElementById('historicalEmpty').style.display = 'block'; 894 + document.getElementById('pagination').style.display = 'none'; 895 + }); 896 + }); 897 + } 898 + 899 + function updatePagination(pagination) { 900 + const paginationDiv = document.getElementById('pagination'); 901 + const prevBtn = document.getElementById('prevBtn'); 902 + const nextBtn = document.getElementById('nextBtn'); 903 + const pageInfo = document.getElementById('pageInfo'); 904 + 905 + if (pagination.total > pagination.limit) { 906 + paginationDiv.style.display = 'flex'; 907 + 908 + const currentPageNum = Math.floor(pagination.offset / pagination.limit) + 1; 909 + const totalPages = Math.ceil(pagination.total / pagination.limit); 910 + 911 + pageInfo.textContent = `Page ${currentPageNum} of ${totalPages} (${pagination.total} total)`; 912 + 913 + prevBtn.disabled = pagination.offset === 0; 914 + nextBtn.disabled = !pagination.has_more; 915 + } else { 916 + paginationDiv.style.display = 'none'; 917 + } 918 + } 919 + 920 + function changePage(delta) { 921 + const newPage = currentPage + delta; 922 + if (newPage >= 0) { 923 + loadAgentSessions(newPage); 924 + } 925 + } 926 + 927 + // Connection warning functions 928 + function showConnectionWarning(message) { 929 + let warning = document.getElementById('connectionWarning'); 930 + if (!warning) { 931 + warning = document.createElement('div'); 932 + warning.id = 'connectionWarning'; 933 + warning.style.cssText = 'background:#f8d7da;border:1px solid #f5c6cb;color:#721c24;padding:0.75rem;margin:1rem 0;border-radius:4px;text-align:center;'; 934 + const container = document.querySelector('.container'); 935 + const firstChild = container.children[1]; // Insert after h1 936 + container.insertBefore(warning, firstChild); 937 + } 938 + warning.innerHTML = `⚠️ <strong>Cortex Service Unavailable:</strong> ${message}`; 939 + warning.style.display = 'block'; 940 + } 941 + 942 + function hideConnectionWarning() { 943 + const warning = document.getElementById('connectionWarning'); 944 + if (warning) { 945 + warning.style.display = 'none'; 946 + } 947 + } 948 + 949 + // Show error message 950 + function showError(message) { 951 + alert(message); // Simple alert for now, can be improved with a toast notification 952 + } 953 + 954 + // Create Agent Modal Functions 955 + function openCreatePersonaModal() { 956 + const modal = document.getElementById('createPersonaModal'); 957 + modal.style.display = 'block'; 958 + 959 + // Reset form 960 + document.getElementById('createPersonaForm').reset(); 961 + document.getElementById('createPersonaForm').style.display = 'block'; 962 + document.getElementById('createPersonaForm').classList.remove('busy'); 963 + document.getElementById('createPersonaPromptStatus').classList.remove('show'); 964 + document.getElementById('createPromptBtn').disabled = false; 965 + } 966 + 967 + function closeCreatePersonaModal() { 968 + document.getElementById('createPersonaModal').style.display = 'none'; 969 + } 970 + 971 + // Create Topic Modal Functions 972 + function openCreateTopicModal() { 973 + const modal = document.getElementById('createTopicModal'); 974 + modal.style.display = 'block'; 975 + 976 + // Reset form 977 + document.getElementById('createTopicForm').reset(); 978 + document.getElementById('topicResult').style.display = 'none'; 979 + document.getElementById('createTopicBtn').disabled = false; 980 + } 981 + 982 + function closeCreateTopicModal() { 983 + document.getElementById('createTopicModal').style.display = 'none'; 984 + } 985 + 986 + // Handle create agent form submission 987 + document.getElementById('createPersonaForm').addEventListener('submit', async (e) => { 988 + e.preventDefault(); 989 + 990 + const title = document.getElementById('personaTitle').value.trim(); 991 + const input = document.getElementById('personaInput').value.trim(); 992 + const form = e.target; 993 + const status = document.getElementById('createPersonaPromptStatus'); 994 + const createBtn = document.getElementById('createPromptBtn'); 995 + 996 + if (!title || !input) return; 997 + 998 + // Show prompt generation status 999 + form.classList.add('busy'); 1000 + status.classList.add('show'); 1001 + createBtn.disabled = true; 1002 + 1003 + try { 1004 + const response = await fetch('{{ url_for('agents.create_plan') }}', { 1005 + method: 'POST', 1006 + headers: { 'Content-Type': 'application/json' }, 1007 + body: JSON.stringify({ 1008 + request: input, 1009 + model: '' // Uses Gemini Pro by default 1010 + }) 1011 + }); 1012 + 1013 + const data = await response.json(); 1014 + 1015 + if (!response.ok) { 1016 + throw new Error(data.error || 'Failed to create prompt'); 1017 + } 1018 + 1019 + const generatedPrompt = data.prompt || data.plan; 1020 + if (!generatedPrompt) { 1021 + throw new Error('No prompt returned by planner'); 1022 + } 1023 + 1024 + // Show the generated prompt 1025 + showPersonaPromptResult(generatedPrompt, title); 1026 + 1027 + } catch (error) { 1028 + showError('Error creating prompt: ' + error.message); 1029 + createBtn.disabled = false; 1030 + } finally { 1031 + // Remove busy state 1032 + form.classList.remove('busy'); 1033 + status.classList.remove('show'); 1034 + } 1035 + }); 1036 + 1037 + async function showPersonaPromptResult(prompt, title) { 1038 + // Auto-save the agent immediately 1039 + const isDaily = document.getElementById('personaDailySchedule').checked; 1040 + const priority = document.getElementById('personaPriority').value; 1041 + const toolsSelect = document.getElementById('personaToolPacks'); 1042 + const personaId = title.toLowerCase().replace(/[^a-z0-9]+/g, '_'); 1043 + 1044 + try { 1045 + // Create the agent 1046 + const requestBody = { 1047 + title: title, 1048 + content: prompt 1049 + }; 1050 + 1051 + // Add schedule if daily is checked 1052 + if (isDaily) { 1053 + requestBody.schedule = 'daily'; 1054 + } 1055 + 1056 + // Add priority if specified 1057 + if (priority !== '') { 1058 + requestBody.priority = parseInt(priority); 1059 + } 1060 + 1061 + // Add tools if specific packs are selected 1062 + const selectedTools = []; 1063 + for (let option of toolsSelect.options) { 1064 + if (option.selected && option.value !== '') { 1065 + selectedTools.push(option.value); 1066 + } 1067 + } 1068 + if (selectedTools.length > 0) { 1069 + requestBody.tools = selectedTools.join(','); 1070 + } 1071 + 1072 + const response = await fetch(`{{ url_for('agents.update_agent', agent_id='') }}${personaId}`, { 1073 + method: 'PUT', 1074 + headers: { 'Content-Type': 'application/json' }, 1075 + body: JSON.stringify(requestBody) 1076 + }); 1077 + 1078 + const data = await response.json(); 1079 + 1080 + if (response.ok) { 1081 + // Close create modal 1082 + closeCreatePersonaModal(); 1083 + 1084 + // Reload agents list 1085 + await loadAvailableAgents(); 1086 + 1087 + // Open the edit modal with the new agent 1088 + setTimeout(() => { 1089 + // Find the new agent in the list 1090 + const newAgent = availableAgents.find(a => a.id === personaId); 1091 + if (newAgent) { 1092 + viewItemDetails(personaId, 'agent', { stopPropagation: () => {} }); 1093 + } 1094 + }, 100); 1095 + } else { 1096 + throw new Error(data.error || 'Failed to save agent'); 1097 + } 1098 + } catch (error) { 1099 + showError('Error saving agent: ' + error.message); 1100 + // Re-enable the form 1101 + document.getElementById('createPersonaForm').classList.remove('busy'); 1102 + document.getElementById('createPersonaPromptStatus').classList.remove('show'); 1103 + document.getElementById('createPromptBtn').disabled = false; 1104 + } 1105 + } 1106 + 1107 + // Handle create topic form submission 1108 + document.getElementById('createTopicForm').addEventListener('submit', async (e) => { 1109 + e.preventDefault(); 1110 + 1111 + const title = document.getElementById('topicTitle').value.trim(); 1112 + const input = document.getElementById('topicInput').value.trim(); 1113 + const form = e.target; 1114 + const createBtn = document.getElementById('createTopicBtn'); 1115 + 1116 + if (!title || !input) return; 1117 + 1118 + // Show the topic content area 1119 + showTopicResult(input, title); 1120 + 1121 + // Hide form 1122 + form.style.display = 'none'; 1123 + }); 1124 + 1125 + function showTopicResult(content, title) { 1126 + const resultDiv = document.getElementById('topicResult'); 1127 + const resultContent = document.getElementById('topicResultContent'); 1128 + 1129 + // Store content and title for saving 1130 + currentTopicContent = content; 1131 + currentTopicTitle = title; 1132 + 1133 + // Display the content 1134 + resultContent.textContent = content; 1135 + resultDiv.style.display = 'block'; 1136 + } 1137 + 1138 + function editTopicContent() { 1139 + const resultContent = document.getElementById('topicResultContent'); 1140 + 1141 + // Convert to editable textarea 1142 + const textarea = document.createElement('textarea'); 1143 + textarea.value = resultContent.textContent; 1144 + textarea.style.width = '100%'; 1145 + textarea.style.minHeight = '400px'; 1146 + textarea.style.padding = '1rem'; 1147 + textarea.style.fontFamily = 'Monaco, Menlo, monospace'; 1148 + textarea.style.fontSize = '0.9rem'; 1149 + 1150 + resultContent.innerHTML = ''; 1151 + resultContent.appendChild(textarea); 1152 + 1153 + // Update the content when editing 1154 + textarea.addEventListener('input', () => { 1155 + currentTopicContent = textarea.value; 1156 + }); 1157 + } 1158 + 1159 + async function saveNewTopic() { 1160 + const title = currentTopicTitle; 1161 + const content = currentTopicContent; 1162 + 1163 + if (!title || !content) { 1164 + showError('Missing title or content'); 1165 + return; 1166 + } 1167 + 1168 + // Generate a unique ID for the new topic 1169 + const topicId = title.toLowerCase().replace(/[^a-z0-9]+/g, '_'); 1170 + 1171 + try { 1172 + // Create or update the topic 1173 + const response = await fetch(`{{ url_for('agents.update_topic', topic_id='') }}${topicId}`, { 1174 + method: 'PUT', 1175 + headers: { 'Content-Type': 'application/json' }, 1176 + body: JSON.stringify({ 1177 + title: title, 1178 + content: content 1179 + }) 1180 + }); 1181 + 1182 + const data = await response.json(); 1183 + 1184 + if (response.ok) { 1185 + alert('Topic saved successfully!'); 1186 + closeCreateTopicModal(); 1187 + // Reset form for next use 1188 + document.getElementById('createTopicForm').style.display = 'block'; 1189 + document.getElementById('topicResult').style.display = 'none'; 1190 + // Reload topics 1191 + loadAvailableTopics(); 1192 + } else { 1193 + throw new Error(data.error || 'Failed to save topic'); 1194 + } 1195 + } catch (error) { 1196 + showError('Error saving topic: ' + error.message); 1197 + } 1198 + } 1199 + 1200 + // Update modal close handlers 1201 + window.onclick = function(event) { 1202 + const itemModal = document.getElementById('itemModal'); 1203 + const promptModal = document.getElementById('promptModal'); 1204 + const createPersonaModal = document.getElementById('createPersonaModal'); 1205 + const createTopicModal = document.getElementById('createTopicModal'); 1206 + 1207 + if (event.target == itemModal) { 1208 + closeItemModal(); 1209 + } 1210 + if (event.target == promptModal) { 1211 + promptModal.style.display = 'none'; 1212 + } 1213 + if (event.target == createPersonaModal) { 1214 + createPersonaModal.style.display = 'none'; 1215 + } 1216 + if (event.target == createTopicModal) { 1217 + createTopicModal.style.display = 'none'; 1218 + } 1219 + }; 1220 + 1221 + // Auto-refresh live agents 1222 + let refreshInterval = null; 1223 + 1224 + function startAutoRefresh() { 1225 + // Refresh every 5 seconds if on the log tab 1226 + if (currentMainTab === 'log' && !refreshInterval) { 1227 + refreshInterval = setInterval(() => { 1228 + if (document.visibilityState === 'visible') { 1229 + // Only refresh the current page 1230 + loadAgentSessions(currentPage); 1231 + } 1232 + }, 5000); 1233 + } 1234 + } 1235 + 1236 + function stopAutoRefresh() { 1237 + if (refreshInterval) { 1238 + clearInterval(refreshInterval); 1239 + refreshInterval = null; 1240 + } 1241 + } 1242 + 1243 + // Start/stop refresh based on tab 1244 + document.addEventListener('visibilitychange', () => { 1245 + if (document.visibilityState === 'hidden') { 1246 + stopAutoRefresh(); 1247 + } else if (currentMainTab === 'log') { 1248 + startAutoRefresh(); 1249 + } 1250 + }); 1251 + 1252 + // Handle tools dropdown change for modal 1253 + document.addEventListener('DOMContentLoaded', () => { 1254 + const toolsSelect = document.getElementById('modalTools'); 1255 + if (toolsSelect) { 1256 + toolsSelect.addEventListener('change', () => { 1257 + const customInput = document.getElementById('modalToolsCustom'); 1258 + if (toolsSelect.value === 'custom') { 1259 + customInput.style.display = 'block'; 1260 + customInput.focus(); 1261 + } else { 1262 + customInput.style.display = 'none'; 1263 + } 1264 + // Auto-save when tools change 1265 + if (currentEditingType === 'agent') { 1266 + saveMetadata(); 1267 + } 1268 + }); 1269 + } 1270 + }); 1271 + 1272 + // Load available tools and render them 1273 + function loadAvailableTools() { 1274 + fetch('{{ url_for('agents.available_tools') }}') 1275 + .then(r => r.json()) 1276 + .then(data => { 1277 + if (data.error) { 1278 + document.getElementById('toolsGrid').innerHTML = ` 1279 + <div class="tools-empty-state"> 1280 + <h3>Error Loading Tools</h3> 1281 + <p>${data.error}</p> 1282 + </div> 1283 + `; 1284 + return; 1285 + } 1286 + 1287 + availableTools = data.tools || []; 1288 + toolPacks = data.packs || {}; 1289 + renderPackFilters(); 1290 + renderToolsGrid(); 1291 + setupToolsSearch(); 1292 + }) 1293 + .catch(err => { 1294 + console.error('Failed to load tools:', err); 1295 + document.getElementById('toolsGrid').innerHTML = ` 1296 + <div class="tools-empty-state"> 1297 + <h3>Failed to Load Tools</h3> 1298 + <p>Please ensure the MCP tools server is configured correctly.</p> 1299 + </div> 1300 + `; 1301 + }); 1302 + } 1303 + 1304 + // Render pack filter buttons 1305 + function renderPackFilters() { 1306 + const container = document.getElementById('packFilters'); 1307 + container.innerHTML = ''; 1308 + 1309 + // All tools button 1310 + const allBtn = document.createElement('button'); 1311 + allBtn.className = 'pack-filter-btn active'; 1312 + allBtn.innerHTML = `All<span class="count">${availableTools.length}</span>`; 1313 + allBtn.onclick = () => filterByPack('all'); 1314 + container.appendChild(allBtn); 1315 + 1316 + // Pack-specific buttons 1317 + Object.entries(toolPacks).forEach(([packName, packInfo]) => { 1318 + const btn = document.createElement('button'); 1319 + btn.className = 'pack-filter-btn'; 1320 + btn.innerHTML = `${packInfo.name}<span class="count">${packInfo.tools.length}</span>`; 1321 + btn.title = packInfo.description; 1322 + btn.onclick = () => filterByPack(packName); 1323 + container.appendChild(btn); 1324 + }); 1325 + 1326 + // Uncategorized button (tools not in any pack) 1327 + const uncategorized = availableTools.filter(t => t.packs.length === 0); 1328 + if (uncategorized.length > 0) { 1329 + const uncatBtn = document.createElement('button'); 1330 + uncatBtn.className = 'pack-filter-btn'; 1331 + uncatBtn.innerHTML = `Other<span class="count">${uncategorized.length}</span>`; 1332 + uncatBtn.onclick = () => filterByPack('uncategorized'); 1333 + container.appendChild(uncatBtn); 1334 + } 1335 + } 1336 + 1337 + // Filter tools by pack 1338 + function filterByPack(pack) { 1339 + activePackFilter = pack; 1340 + 1341 + // Update button states 1342 + document.querySelectorAll('.pack-filter-btn').forEach(btn => { 1343 + btn.classList.remove('active'); 1344 + }); 1345 + event.target.classList.add('active'); 1346 + 1347 + // Apply filter 1348 + applyToolsFilter(); 1349 + } 1350 + 1351 + // Apply search and pack filters 1352 + function applyToolsFilter() { 1353 + const searchTerm = document.getElementById('toolsSearch').value.toLowerCase(); 1354 + const cards = document.querySelectorAll('.tool-card'); 1355 + 1356 + cards.forEach(card => { 1357 + const toolName = card.dataset.toolName; 1358 + const tool = availableTools.find(t => t.name === toolName); 1359 + if (!tool) return; 1360 + 1361 + // Check pack filter 1362 + let packMatch = true; 1363 + if (activePackFilter !== 'all') { 1364 + if (activePackFilter === 'uncategorized') { 1365 + packMatch = tool.packs.length === 0; 1366 + } else { 1367 + packMatch = tool.packs.includes(activePackFilter); 1368 + } 1369 + } 1370 + 1371 + // Check search filter 1372 + let searchMatch = true; 1373 + if (searchTerm) { 1374 + searchMatch = tool.name.toLowerCase().includes(searchTerm) || 1375 + (tool.description && tool.description.toLowerCase().includes(searchTerm)); 1376 + } 1377 + 1378 + // Show/hide card 1379 + if (packMatch && searchMatch) { 1380 + card.classList.remove('hidden'); 1381 + } else { 1382 + card.classList.add('hidden'); 1383 + } 1384 + }); 1385 + } 1386 + 1387 + // Setup search functionality 1388 + function setupToolsSearch() { 1389 + const searchInput = document.getElementById('toolsSearch'); 1390 + searchInput.addEventListener('input', applyToolsFilter); 1391 + } 1392 + 1393 + // Render tools grid 1394 + function renderToolsGrid() { 1395 + const container = document.getElementById('toolsGrid'); 1396 + container.innerHTML = ''; 1397 + 1398 + if (availableTools.length === 0) { 1399 + container.innerHTML = ` 1400 + <div class="tools-empty-state"> 1401 + <h3>No Tools Available</h3> 1402 + <p>MCP tools will appear here once configured.</p> 1403 + </div> 1404 + `; 1405 + return; 1406 + } 1407 + 1408 + availableTools.forEach(tool => { 1409 + const card = createToolCard(tool); 1410 + container.appendChild(card); 1411 + }); 1412 + } 1413 + 1414 + // Create a tool card element 1415 + function createToolCard(tool) { 1416 + const card = document.createElement('div'); 1417 + card.className = 'tool-card'; 1418 + card.dataset.toolName = tool.name; 1419 + 1420 + // Header with name and pack badges 1421 + const header = document.createElement('div'); 1422 + header.className = 'tool-header'; 1423 + 1424 + const name = document.createElement('h3'); 1425 + name.className = 'tool-name'; 1426 + name.textContent = tool.name; 1427 + header.appendChild(name); 1428 + 1429 + if (tool.packs && tool.packs.length > 0) { 1430 + const packs = document.createElement('div'); 1431 + packs.className = 'tool-packs'; 1432 + tool.packs.forEach(packName => { 1433 + const badge = document.createElement('span'); 1434 + badge.className = 'tool-pack-badge'; 1435 + badge.textContent = packName; 1436 + packs.appendChild(badge); 1437 + }); 1438 + header.appendChild(packs); 1439 + } 1440 + 1441 + card.appendChild(header); 1442 + 1443 + // Description 1444 + const desc = document.createElement('div'); 1445 + desc.className = 'tool-description'; 1446 + desc.innerHTML = marked.parse(tool.description || 'No description available'); 1447 + card.appendChild(desc); 1448 + 1449 + // Parameters (if available) 1450 + if (tool.input_schema && tool.input_schema.properties) { 1451 + const params = createParametersSection(tool.input_schema); 1452 + if (params) { 1453 + card.appendChild(params); 1454 + } 1455 + } 1456 + 1457 + return card; 1458 + } 1459 + 1460 + // Create parameters section for a tool 1461 + function createParametersSection(schema) { 1462 + const properties = schema.properties || {}; 1463 + const required = schema.required || []; 1464 + 1465 + if (Object.keys(properties).length === 0) { 1466 + return null; 1467 + } 1468 + 1469 + const section = document.createElement('div'); 1470 + section.className = 'tool-params'; 1471 + 1472 + const title = document.createElement('div'); 1473 + title.className = 'tool-params-toggle tool-params-title'; 1474 + title.textContent = `Parameters (${Object.keys(properties).length})`; 1475 + title.onclick = () => section.classList.toggle('collapsed'); 1476 + section.appendChild(title); 1477 + 1478 + const list = document.createElement('div'); 1479 + list.className = 'param-list'; 1480 + 1481 + Object.entries(properties).forEach(([paramName, paramSchema]) => { 1482 + const item = document.createElement('div'); 1483 + item.className = 'param-item'; 1484 + 1485 + const name = document.createElement('span'); 1486 + name.className = 'param-name'; 1487 + name.textContent = paramName; 1488 + item.appendChild(name); 1489 + 1490 + // Required/optional indicator 1491 + const reqSpan = document.createElement('span'); 1492 + if (required.includes(paramName)) { 1493 + reqSpan.className = 'param-required'; 1494 + reqSpan.textContent = '*'; 1495 + } else { 1496 + reqSpan.className = 'param-optional'; 1497 + reqSpan.textContent = '(optional)'; 1498 + } 1499 + item.appendChild(reqSpan); 1500 + 1501 + // Type 1502 + if (paramSchema.type) { 1503 + const type = document.createElement('span'); 1504 + type.className = 'param-type'; 1505 + type.textContent = `[${paramSchema.type}]`; 1506 + item.appendChild(type); 1507 + } 1508 + 1509 + // Description 1510 + if (paramSchema.description) { 1511 + const desc = document.createElement('span'); 1512 + desc.className = 'param-description'; 1513 + desc.textContent = paramSchema.description; 1514 + item.appendChild(desc); 1515 + } 1516 + 1517 + list.appendChild(item); 1518 + }); 1519 + 1520 + section.appendChild(list); 1521 + 1522 + // Start collapsed if more than 3 parameters 1523 + if (Object.keys(properties).length > 3) { 1524 + section.classList.add('collapsed'); 1525 + } 1526 + 1527 + return section; 1528 + } 1529 + 1530 + // Initialize - The Log tab is shown by default now 1531 + setTimeout(() => { 1532 + loadAgentSessions(); 1533 + startAutoRefresh(); 1534 + }, 100);
+1097
dream/templates/agents/_styles.html
··· 1 + .container { padding: 0 1em; } 2 + 3 + /* Tab Navigation */ 4 + .tab-nav { 5 + display: flex; 6 + gap: 0.5rem; 7 + margin-bottom: 2rem; 8 + border-bottom: 2px solid #e0e0e0; 9 + padding-bottom: 0; 10 + } 11 + 12 + .tab-button { 13 + padding: 0.75rem 1.5rem; 14 + background: none; 15 + border: none; 16 + border-bottom: 3px solid transparent; 17 + color: #666; 18 + font-size: 1rem; 19 + cursor: pointer; 20 + transition: all 0.2s ease; 21 + margin-bottom: -2px; 22 + } 23 + 24 + .tab-button:hover { 25 + color: #007bff; 26 + background: #f8f9ff; 27 + } 28 + 29 + .tab-button.active { 30 + color: #007bff; 31 + border-bottom-color: #007bff; 32 + font-weight: 500; 33 + } 34 + 35 + /* Tab Content */ 36 + .tab-container { 37 + min-height: 400px; 38 + } 39 + 40 + .main-tab-content { 41 + display: none; 42 + animation: fadeIn 0.3s ease; 43 + } 44 + 45 + .main-tab-content.active { 46 + display: block; 47 + } 48 + 49 + @keyframes fadeIn { 50 + from { opacity: 0; } 51 + to { opacity: 1; } 52 + } 53 + 54 + /* Agent Cards */ 55 + .agent-cards { 56 + display: grid; 57 + grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); 58 + gap: 1rem; 59 + margin-bottom: 2rem; 60 + } 61 + 62 + .agent-card { 63 + background: white; 64 + border: 2px solid #e0e0e0; 65 + border-radius: 8px; 66 + padding: 1rem; 67 + cursor: pointer; 68 + transition: all 0.2s ease; 69 + position: relative; 70 + } 71 + 72 + .agent-card:hover { 73 + border-color: #007bff; 74 + box-shadow: 0 2px 8px rgba(0, 123, 255, 0.1); 75 + } 76 + 77 + .agent-card .chat-link { 78 + position: absolute; 79 + top: 0.5rem; 80 + right: 0.5rem; 81 + font-size: 1.5rem; 82 + text-decoration: none; 83 + opacity: 0; 84 + transition: opacity 0.2s ease; 85 + background: white; 86 + border-radius: 4px; 87 + padding: 0.2rem 0.4rem; 88 + box-shadow: 0 2px 4px rgba(0,0,0,0.1); 89 + } 90 + 91 + .agent-card:hover .chat-link { 92 + opacity: 1; 93 + } 94 + 95 + .agent-card .chat-link:hover { 96 + transform: scale(1.1); 97 + box-shadow: 0 2px 8px rgba(0,0,0,0.2); 98 + } 99 + 100 + .agent-card.selected { 101 + border-color: #007bff; 102 + background: #f8f9ff; 103 + box-shadow: 0 0 0 1px #007bff; 104 + } 105 + 106 + .agent-card.disabled { 107 + opacity: 0.6; 108 + background: #f8f8f8; 109 + } 110 + 111 + .agent-card h3 { 112 + margin: 0 0 0.5rem 0; 113 + font-size: 1.1rem; 114 + color: #333; 115 + } 116 + 117 + .agent-card p { 118 + margin: 0; 119 + color: #666; 120 + font-size: 0.9rem; 121 + line-height: 1.4; 122 + } 123 + 124 + /* Agent metadata badges */ 125 + .agent-metadata { 126 + display: flex; 127 + flex-wrap: wrap; 128 + gap: 0.3rem; 129 + margin-top: 0.5rem; 130 + } 131 + 132 + .metadata-badge { 133 + display: inline-flex; 134 + align-items: center; 135 + gap: 0.2rem; 136 + padding: 0.2rem 0.5rem; 137 + border-radius: 12px; 138 + font-size: 0.75rem; 139 + font-weight: 500; 140 + background: #f0f0f0; 141 + color: #555; 142 + } 143 + 144 + .metadata-badge.schedule { 145 + background: #e3f2fd; 146 + color: #1976d2; 147 + } 148 + 149 + .metadata-badge.priority { 150 + background: #fff3e0; 151 + color: #f57c00; 152 + } 153 + 154 + .metadata-badge.multi-domain { 155 + background: #f3e5f5; 156 + color: #7b1fa2; 157 + } 158 + 159 + .metadata-badge.tools { 160 + background: #e8f5e9; 161 + color: #388e3c; 162 + } 163 + 164 + .metadata-badge.backend { 165 + background: #fce4ec; 166 + color: #c2185b; 167 + } 168 + 169 + .metadata-badge .badge-icon { 170 + font-size: 0.9rem; 171 + } 172 + 173 + /* Create Agent Card */ 174 + .create-agent-card { 175 + background: #f8f9ff; 176 + border: 2px dashed #007bff; 177 + display: flex; 178 + flex-direction: column; 179 + align-items: center; 180 + justify-content: center; 181 + min-height: 80px; 182 + } 183 + 184 + .create-agent-card:hover { 185 + background: #e8ecff; 186 + border-style: solid; 187 + } 188 + 189 + .create-agent-card h3 { 190 + color: #007bff; 191 + margin: 0; 192 + font-size: 1.2rem; 193 + } 194 + 195 + /* Topic card with colored indicator */ 196 + .topic-indicator { 197 + display: inline-block; 198 + width: 12px; 199 + height: 12px; 200 + border-radius: 50%; 201 + margin-right: 0.5rem; 202 + vertical-align: middle; 203 + } 204 + 205 + /* Input Form */ 206 + .input-area { 207 + display: flex; 208 + flex-direction: column; 209 + gap: 1rem; 210 + margin-bottom: 2rem; 211 + } 212 + 213 + .input-area textarea { 214 + width: 100%; 215 + padding: 0.75rem; 216 + border: 1px solid #ddd; 217 + border-radius: 4px; 218 + font-family: inherit; 219 + resize: vertical; 220 + } 221 + 222 + .input-area button { 223 + padding: 0.75rem 1.5rem; 224 + background: #007bff; 225 + color: white; 226 + border: none; 227 + border-radius: 4px; 228 + cursor: pointer; 229 + font-size: 1.2rem; 230 + align-self: flex-start; 231 + } 232 + 233 + .input-area button:disabled { 234 + background: #ccc; 235 + cursor: not-allowed; 236 + } 237 + 238 + .input-area button:hover:not(:disabled) { 239 + background: #0056b3; 240 + } 241 + 242 + .input-area.busy { 243 + position: relative; 244 + } 245 + 246 + .input-area.busy::after { 247 + content: ''; 248 + position: absolute; 249 + top: 0; 250 + left: 0; 251 + right: 0; 252 + bottom: 0; 253 + background: rgba(255, 255, 255, 0.8); 254 + cursor: wait; 255 + z-index: 10; 256 + } 257 + 258 + .input-area.busy textarea, 259 + .input-area.busy button { 260 + pointer-events: none; 261 + } 262 + 263 + .planning-status { 264 + margin-top: 0.5rem; 265 + padding: 0.5rem; 266 + background: #e3f2fd; 267 + border: 1px solid #90caf9; 268 + border-radius: 4px; 269 + font-size: 0.9rem; 270 + display: none; 271 + } 272 + 273 + .planning-status.show { 274 + display: block; 275 + } 276 + 277 + /* Session History Table */ 278 + table { 279 + border-collapse: collapse; 280 + width: 100%; 281 + margin-top: 2rem; 282 + } 283 + th, td { 284 + padding: 4px 8px; 285 + border-bottom: 1px solid #ddd; 286 + white-space: nowrap; 287 + text-align: left; 288 + } 289 + th { 290 + font-weight: 600; 291 + background-color: #f8f9fa; 292 + } 293 + /* Adjust column widths */ 294 + td:nth-child(1), th:nth-child(1) { /* Status */ 295 + width: 100px; 296 + } 297 + td:nth-child(2), th:nth-child(2) { /* Started */ 298 + width: 120px; 299 + } 300 + td:nth-child(3), th:nth-child(3) { /* Runtime */ 301 + width: 80px; 302 + text-align: right; 303 + } 304 + td:nth-child(4), th:nth-child(4) { /* Model */ 305 + width: 150px; 306 + } 307 + td:nth-child(5), th:nth-child(5) { /* Agent */ 308 + width: 180px; 309 + } 310 + td:last-child { /* Prompt */ 311 + overflow: hidden; 312 + text-overflow: ellipsis; 313 + max-width: 0; 314 + } 315 + tr:hover { 316 + background-color: #f8f9fa; 317 + } 318 + tr a { 319 + color: #007bff; 320 + text-decoration: none; 321 + } 322 + tr a:hover { 323 + text-decoration: underline; 324 + } 325 + 326 + /* Status indicators */ 327 + .status-badge { 328 + display: inline-block; 329 + padding: 2px 6px; 330 + border-radius: 3px; 331 + font-size: 0.75rem; 332 + font-weight: 500; 333 + text-transform: uppercase; 334 + margin-right: 0.5rem; 335 + } 336 + 337 + .status-running { 338 + background-color: #d4edda; 339 + color: #155724; 340 + } 341 + 342 + .status-finished { 343 + background-color: #d1ecf1; 344 + color: #0c5460; 345 + } 346 + 347 + .status-error { 348 + background-color: #f8d7da; 349 + color: #721c24; 350 + } 351 + 352 + .status-interrupted { 353 + background-color: #fff3cd; 354 + color: #856404; 355 + } 356 + 357 + .status-unknown { 358 + background-color: #e2e3e5; 359 + color: #383d41; 360 + } 361 + 362 + /* Section headers for agent log */ 363 + .section-header { 364 + margin: 2rem 0 1rem 0; 365 + padding: 0.5rem 0; 366 + border-bottom: 2px solid #007bff; 367 + font-size: 1.1rem; 368 + font-weight: 500; 369 + color: #333; 370 + display: flex; 371 + align-items: center; 372 + justify-content: space-between; 373 + } 374 + 375 + .section-header .count { 376 + font-size: 0.9rem; 377 + font-weight: normal; 378 + color: #666; 379 + background: #f8f9fa; 380 + padding: 2px 8px; 381 + border-radius: 12px; 382 + } 383 + 384 + .empty-state { 385 + text-align: center; 386 + padding: 2rem; 387 + color: #666; 388 + font-style: italic; 389 + } 390 + 391 + /* Pagination */ 392 + .pagination { 393 + display: flex; 394 + justify-content: center; 395 + align-items: center; 396 + gap: 0.5rem; 397 + margin-top: 1rem; 398 + padding: 1rem; 399 + } 400 + 401 + .pagination button { 402 + padding: 0.5rem 1rem; 403 + border: 1px solid #ddd; 404 + background: white; 405 + cursor: pointer; 406 + border-radius: 4px; 407 + transition: all 0.2s ease; 408 + } 409 + 410 + .pagination button:hover:not(:disabled) { 411 + background: #007bff; 412 + color: white; 413 + border-color: #007bff; 414 + } 415 + 416 + .pagination button:disabled { 417 + opacity: 0.5; 418 + cursor: not-allowed; 419 + } 420 + 421 + .pagination .page-info { 422 + margin: 0 1rem; 423 + font-size: 0.9rem; 424 + color: #666; 425 + } 426 + 427 + /* Modal Styling (reused from entities.html) */ 428 + .modal { 429 + display: none; 430 + position: fixed; 431 + z-index: 1000; 432 + left: 0; 433 + top: 0; 434 + width: 100%; 435 + height: 100%; 436 + background-color: rgba(0,0,0,0.5); 437 + } 438 + 439 + .modal-content { 440 + background-color: white; 441 + margin: 5% auto; 442 + border-radius: 8px; 443 + width: 90%; 444 + max-width: 800px; 445 + max-height: 80vh; 446 + position: relative; 447 + box-shadow: 0 4px 6px rgba(0,0,0,0.1); 448 + overflow: hidden; 449 + } 450 + 451 + .close { 452 + color: #aaa; 453 + float: right; 454 + font-size: 28px; 455 + font-weight: bold; 456 + cursor: pointer; 457 + line-height: 1; 458 + position: absolute; 459 + top: 15px; 460 + right: 20px; 461 + background: white; 462 + z-index: 3; 463 + } 464 + 465 + .close:hover { 466 + color: #000; 467 + } 468 + 469 + .modal-header { 470 + padding: 20px 40px 15px 20px; 471 + border-bottom: 1px solid #e0e0e0; 472 + background: white; 473 + border-radius: 8px 8px 0 0; 474 + } 475 + 476 + .modal-header h3 { 477 + margin: 0; 478 + color: #333; 479 + } 480 + 481 + .modal-body { 482 + max-height: calc(80vh - 80px); 483 + overflow-y: auto; 484 + padding: 20px; 485 + } 486 + 487 + .modal-body .markdown-content h1, 488 + .modal-body .markdown-content h2, 489 + .modal-body .markdown-content h3, 490 + .modal-body .markdown-content h4, 491 + .modal-body .markdown-content h5, 492 + .modal-body .markdown-content h6 { 493 + margin-top: 1.5rem; 494 + margin-bottom: 0.75rem; 495 + color: #333; 496 + } 497 + 498 + .modal-body .markdown-content p { 499 + margin-bottom: 1rem; 500 + line-height: 1.6; 501 + } 502 + 503 + .modal-body .markdown-content ul, 504 + .modal-body .markdown-content ol { 505 + margin-bottom: 1rem; 506 + padding-left: 2rem; 507 + } 508 + 509 + .modal-body .markdown-content code { 510 + background: #f8f9fa; 511 + padding: 0.2rem 0.4rem; 512 + border-radius: 3px; 513 + font-family: 'Courier New', monospace; 514 + } 515 + 516 + .modal-body .markdown-content pre { 517 + background: #f8f9fa; 518 + padding: 1rem; 519 + border-radius: 4px; 520 + overflow-x: auto; 521 + margin-bottom: 1rem; 522 + } 523 + 524 + /* Planning Modal Tabs */ 525 + .modal-tabs { 526 + display: flex; 527 + border-bottom: 1px solid #e0e0e0; 528 + margin: -20px -20px 20px -20px; 529 + background: #f8f9fa; 530 + } 531 + 532 + .modal-tab { 533 + padding: 12px 20px; 534 + cursor: pointer; 535 + border: none; 536 + background: none; 537 + font-size: 0.9rem; 538 + color: #666; 539 + transition: all 0.2s ease; 540 + flex: 1; 541 + text-align: center; 542 + } 543 + 544 + .modal-tab:hover { 545 + background: #e9ecef; 546 + color: #333; 547 + } 548 + 549 + .modal-tab.active { 550 + color: #007bff; 551 + background: white; 552 + border-bottom: 2px solid #007bff; 553 + } 554 + 555 + .tab-content { 556 + display: none; 557 + } 558 + 559 + .tab-content.active { 560 + display: block; 561 + } 562 + 563 + .prompt-editor { 564 + width: 100%; 565 + min-height: 400px; 566 + border: 1px solid #ddd; 567 + border-radius: 4px; 568 + padding: 1rem; 569 + font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; 570 + font-size: 0.9rem; 571 + line-height: 1.4; 572 + resize: vertical; 573 + } 574 + 575 + .config-section { 576 + margin-bottom: 1.5rem; 577 + } 578 + 579 + .config-section label { 580 + display: block; 581 + margin-bottom: 0.5rem; 582 + font-weight: 500; 583 + color: #333; 584 + } 585 + 586 + .config-section select, 587 + .config-section input { 588 + width: 100%; 589 + padding: 0.5rem; 590 + border: 1px solid #ddd; 591 + border-radius: 4px; 592 + font-size: 0.9rem; 593 + } 594 + 595 + .start-agent-btn { 596 + background: #28a745; 597 + color: white; 598 + border: none; 599 + padding: 0.75rem 2rem; 600 + border-radius: 4px; 601 + cursor: pointer; 602 + font-size: 1rem; 603 + margin-top: 1rem; 604 + } 605 + 606 + .start-agent-btn:hover { 607 + background: #218838; 608 + } 609 + 610 + .start-agent-btn:disabled { 611 + background: #6c757d; 612 + cursor: not-allowed; 613 + } 614 + 615 + /* Item Modal Styles - 80% viewport */ 616 + #itemModal .modal-content { 617 + width: 80vw; 618 + height: 80vh; 619 + max-width: 1400px; 620 + margin: 2.5% auto; 621 + display: flex; 622 + flex-direction: column; 623 + } 624 + 625 + #itemModal .modal-body { 626 + flex: 1; 627 + overflow-y: auto; 628 + padding: 1.5rem; 629 + } 630 + 631 + /* Create Agent Modal Styles */ 632 + #createPersonaModal .modal-content { 633 + max-width: 700px; 634 + } 635 + 636 + #personaTitle { 637 + width: 100%; 638 + padding: 0.75rem; 639 + border: 1px solid #ddd; 640 + border-radius: 4px; 641 + font-size: 1rem; 642 + margin-bottom: 1rem; 643 + } 644 + 645 + /* Prompt content display box for create modals */ 646 + .prompt-result-box { 647 + background: #f8f9fa; 648 + border: 1px solid #dee2e6; 649 + border-radius: 4px; 650 + padding: 1rem; 651 + margin: 1rem 0; 652 + max-height: 400px; 653 + overflow-y: auto; 654 + white-space: pre-wrap; 655 + font-family: 'Monaco', 'Menlo', monospace; 656 + font-size: 0.9rem; 657 + } 658 + 659 + .modal-actions { 660 + display: flex; 661 + gap: 1rem; 662 + margin-top: 1rem; 663 + } 664 + 665 + .modal-actions button { 666 + padding: 0.75rem 1.5rem; 667 + border: none; 668 + border-radius: 4px; 669 + cursor: pointer; 670 + font-size: 1rem; 671 + } 672 + 673 + .modal-actions button:first-child { 674 + background: #28a745; 675 + color: white; 676 + } 677 + 678 + .modal-actions button:first-child:hover { 679 + background: #218838; 680 + } 681 + 682 + .modal-actions button:last-child { 683 + background: #6c757d; 684 + color: white; 685 + } 686 + 687 + .modal-actions button:last-child:hover { 688 + background: #545b62; 689 + } 690 + 691 + /* Enhanced Agent Modal Styles */ 692 + .modal-title-editable { 693 + display: inline-block; 694 + padding: 0.5rem; 695 + border-radius: 4px; 696 + cursor: pointer; 697 + transition: background 0.2s; 698 + font-size: 1.5rem; 699 + font-weight: 600; 700 + margin: 0; 701 + } 702 + 703 + .modal-title-editable:hover { 704 + background: #f0f0f0; 705 + } 706 + 707 + .modal-title-input { 708 + font-size: 1.5rem; 709 + font-weight: 600; 710 + padding: 0.5rem; 711 + border: 2px solid #007bff; 712 + border-radius: 4px; 713 + width: 100%; 714 + outline: none; 715 + } 716 + 717 + .metadata-section { 718 + background: #f8f9fa; 719 + border-radius: 8px; 720 + padding: 1rem; 721 + margin: 1rem 0; 722 + display: grid; 723 + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); 724 + gap: 1rem; 725 + } 726 + 727 + .metadata-item { 728 + display: flex; 729 + flex-direction: column; 730 + gap: 0.25rem; 731 + } 732 + 733 + .metadata-item label { 734 + font-size: 0.85rem; 735 + color: #666; 736 + font-weight: 500; 737 + } 738 + 739 + .metadata-item input, 740 + .metadata-item select { 741 + padding: 0.5rem; 742 + border: 1px solid #ddd; 743 + border-radius: 4px; 744 + background: white; 745 + font-size: 0.95rem; 746 + } 747 + 748 + .metadata-item input[type="checkbox"] { 749 + width: auto; 750 + cursor: pointer; 751 + } 752 + 753 + .checkbox-wrapper { 754 + display: flex; 755 + align-items: center; 756 + gap: 0.5rem; 757 + padding: 0.5rem; 758 + background: white; 759 + border: 1px solid #ddd; 760 + border-radius: 4px; 761 + cursor: pointer; 762 + } 763 + 764 + .checkbox-wrapper:hover { 765 + background: #f0f0f0; 766 + } 767 + 768 + .prompt-section { 769 + position: relative; 770 + border: 1px solid #ddd; 771 + border-radius: 8px; 772 + overflow: hidden; 773 + margin: 1rem 0; 774 + } 775 + 776 + .prompt-header { 777 + display: flex; 778 + justify-content: space-between; 779 + align-items: center; 780 + padding: 0.75rem 1rem; 781 + background: #f8f9fa; 782 + border-bottom: 1px solid #ddd; 783 + } 784 + 785 + .prompt-label { 786 + font-weight: 500; 787 + color: #333; 788 + } 789 + 790 + .prompt-actions { 791 + display: flex; 792 + gap: 0.5rem; 793 + } 794 + 795 + .prompt-actions button { 796 + padding: 0.4rem 0.8rem; 797 + border: none; 798 + border-radius: 4px; 799 + cursor: pointer; 800 + font-size: 0.9rem; 801 + transition: all 0.2s; 802 + } 803 + 804 + .edit-prompt-btn { 805 + background: #007bff; 806 + color: white; 807 + } 808 + 809 + .edit-prompt-btn:hover { 810 + background: #0056b3; 811 + } 812 + 813 + .save-prompt-btn { 814 + background: #28a745; 815 + color: white; 816 + } 817 + 818 + .save-prompt-btn:hover { 819 + background: #218838; 820 + } 821 + 822 + .cancel-prompt-btn { 823 + background: #6c757d; 824 + color: white; 825 + } 826 + 827 + .cancel-prompt-btn:hover { 828 + background: #545b62; 829 + } 830 + 831 + /* Prompt section container in item modal */ 832 + .prompt-content { 833 + padding: 1rem; 834 + max-height: 500px; 835 + overflow-y: auto; 836 + background: white; 837 + } 838 + 839 + .prompt-display { 840 + white-space: pre-wrap; 841 + font-family: 'Monaco', 'Menlo', monospace; 842 + font-size: 0.9rem; 843 + line-height: 1.6; 844 + color: #333; 845 + } 846 + 847 + .prompt-editor { 848 + width: 100%; 849 + min-height: 400px; 850 + padding: 1rem; 851 + border: none; 852 + font-family: 'Monaco', 'Menlo', monospace; 853 + font-size: 0.9rem; 854 + line-height: 1.6; 855 + resize: vertical; 856 + outline: none; 857 + } 858 + 859 + .saving-indicator { 860 + display: inline-block; 861 + padding: 0.25rem 0.75rem; 862 + background: #28a745; 863 + color: white; 864 + border-radius: 4px; 865 + font-size: 0.85rem; 866 + animation: fadeInOut 2s; 867 + position: absolute; 868 + top: 0.75rem; 869 + right: 1rem; 870 + } 871 + 872 + @keyframes fadeInOut { 873 + 0% { opacity: 0; } 874 + 20% { opacity: 1; } 875 + 80% { opacity: 1; } 876 + 100% { opacity: 0; } 877 + } 878 + 879 + /* Tools Tab Styles */ 880 + .tools-container { 881 + padding: 1rem 0; 882 + } 883 + 884 + .tools-header { 885 + margin-bottom: 2rem; 886 + display: flex; 887 + flex-direction: column; 888 + gap: 1rem; 889 + } 890 + 891 + .tools-search { 892 + width: 100%; 893 + max-width: 400px; 894 + padding: 0.75rem 1rem; 895 + border: 1px solid #ddd; 896 + border-radius: 8px; 897 + font-size: 1rem; 898 + transition: border-color 0.2s; 899 + } 900 + 901 + .tools-search:focus { 902 + outline: none; 903 + border-color: #007bff; 904 + box-shadow: 0 0 0 2px rgba(0,123,255,0.1); 905 + } 906 + 907 + .pack-filters { 908 + display: flex; 909 + gap: 0.5rem; 910 + flex-wrap: wrap; 911 + } 912 + 913 + .pack-filter-btn { 914 + padding: 0.5rem 1rem; 915 + background: #f8f9fa; 916 + border: 1px solid #dee2e6; 917 + border-radius: 20px; 918 + cursor: pointer; 919 + transition: all 0.2s; 920 + font-size: 0.9rem; 921 + } 922 + 923 + .pack-filter-btn:hover { 924 + background: #e9ecef; 925 + border-color: #adb5bd; 926 + } 927 + 928 + .pack-filter-btn.active { 929 + background: #007bff; 930 + color: white; 931 + border-color: #007bff; 932 + } 933 + 934 + .pack-filter-btn .count { 935 + margin-left: 0.3rem; 936 + font-size: 0.85rem; 937 + opacity: 0.8; 938 + } 939 + 940 + .tools-grid { 941 + display: grid; 942 + gap: 1.5rem; 943 + animation: fadeIn 0.3s ease; 944 + } 945 + 946 + .tool-card { 947 + background: white; 948 + border: 1px solid #e0e0e0; 949 + border-radius: 12px; 950 + padding: 1.5rem; 951 + transition: all 0.2s ease; 952 + position: relative; 953 + } 954 + 955 + .tool-card:hover { 956 + border-color: #007bff; 957 + box-shadow: 0 4px 12px rgba(0,123,255,0.1); 958 + transform: translateY(-2px); 959 + } 960 + 961 + .tool-card.hidden { 962 + display: none; 963 + } 964 + 965 + .tool-header { 966 + display: flex; 967 + align-items: start; 968 + justify-content: space-between; 969 + margin-bottom: 1rem; 970 + } 971 + 972 + .tool-name { 973 + font-size: 1.2rem; 974 + font-weight: 600; 975 + color: #333; 976 + margin: 0; 977 + } 978 + 979 + .tool-packs { 980 + display: flex; 981 + gap: 0.3rem; 982 + flex-wrap: wrap; 983 + } 984 + 985 + .tool-pack-badge { 986 + display: inline-block; 987 + padding: 0.2rem 0.6rem; 988 + background: #e3f2fd; 989 + color: #1976d2; 990 + border-radius: 12px; 991 + font-size: 0.75rem; 992 + font-weight: 500; 993 + } 994 + 995 + .tool-description { 996 + color: #666; 997 + line-height: 1.6; 998 + margin-bottom: 1rem; 999 + } 1000 + 1001 + .tool-params { 1002 + border-top: 1px solid #e0e0e0; 1003 + padding-top: 1rem; 1004 + margin-top: auto; 1005 + } 1006 + 1007 + .tool-params-title { 1008 + font-size: 0.9rem; 1009 + font-weight: 600; 1010 + color: #555; 1011 + margin-bottom: 0.5rem; 1012 + } 1013 + 1014 + .param-list { 1015 + display: flex; 1016 + flex-direction: column; 1017 + gap: 0.5rem; 1018 + } 1019 + 1020 + .param-item { 1021 + display: flex; 1022 + align-items: start; 1023 + gap: 0.5rem; 1024 + font-size: 0.9rem; 1025 + } 1026 + 1027 + .param-name { 1028 + font-family: 'Monaco', 'Menlo', monospace; 1029 + background: #f8f9fa; 1030 + padding: 0.2rem 0.5rem; 1031 + border-radius: 4px; 1032 + color: #d63384; 1033 + white-space: nowrap; 1034 + } 1035 + 1036 + .param-required { 1037 + color: #dc3545; 1038 + font-weight: 600; 1039 + } 1040 + 1041 + .param-optional { 1042 + color: #6c757d; 1043 + font-style: italic; 1044 + } 1045 + 1046 + .param-type { 1047 + color: #0969da; 1048 + font-family: 'Monaco', 'Menlo', monospace; 1049 + font-size: 0.85rem; 1050 + } 1051 + 1052 + .param-description { 1053 + color: #666; 1054 + flex: 1; 1055 + } 1056 + 1057 + .loading-spinner { 1058 + text-align: center; 1059 + padding: 3rem; 1060 + color: #666; 1061 + font-style: italic; 1062 + } 1063 + 1064 + .tools-empty-state { 1065 + text-align: center; 1066 + padding: 3rem; 1067 + color: #666; 1068 + } 1069 + 1070 + .tools-empty-state h3 { 1071 + color: #333; 1072 + margin-bottom: 0.5rem; 1073 + } 1074 + 1075 + /* Collapsible parameters */ 1076 + .tool-params.collapsed .param-list { 1077 + display: none; 1078 + } 1079 + 1080 + .tool-params-toggle { 1081 + cursor: pointer; 1082 + user-select: none; 1083 + display: flex; 1084 + align-items: center; 1085 + gap: 0.3rem; 1086 + } 1087 + 1088 + .tool-params-toggle::before { 1089 + content: '▼'; 1090 + font-size: 0.7rem; 1091 + transition: transform 0.2s; 1092 + display: inline-block; 1093 + } 1094 + 1095 + .tool-params.collapsed .tool-params-toggle::before { 1096 + transform: rotate(-90deg); 1097 + }