Upload 2 files
Browse files- index.html +373 -7
index.html
CHANGED
|
@@ -542,6 +542,140 @@
|
|
| 542 |
50%, 100% { opacity: 0; }
|
| 543 |
}
|
| 544 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 545 |
/* --- EXECUTE_CODE REZULTAT (formatiran prikaz umjesto sirovog JSON-a) --- */
|
| 546 |
.exec-result {
|
| 547 |
align-self: stretch;
|
|
@@ -627,9 +761,14 @@
|
|
| 627 |
|
| 628 |
<!-- Header -->
|
| 629 |
<header>
|
| 630 |
-
<div
|
| 631 |
-
<
|
| 632 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 633 |
</div>
|
| 634 |
<div style="display:flex; align-items:center; gap:0.2rem;">
|
| 635 |
<button class="settings-btn" id="open-mcp" aria-label="MCP Serveri" title="MCP Serveri">
|
|
@@ -641,6 +780,25 @@
|
|
| 641 |
</div>
|
| 642 |
</header>
|
| 643 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 644 |
<!-- Main Chat Area -->
|
| 645 |
<main id="chat-container">
|
| 646 |
<div class="message system">Dobrodošao. Odaberi model i unesi API Ključ u postavkama za početak.</div>
|
|
@@ -883,7 +1041,15 @@
|
|
| 883 |
mcpNewUrl: document.getElementById('mcp-new-url'),
|
| 884 |
mcpNewHeaderName: document.getElementById('mcp-new-header-name'),
|
| 885 |
mcpNewHeaderValue: document.getElementById('mcp-new-header-value'),
|
| 886 |
-
mcpAddBtn: document.getElementById('mcp-add-btn')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 887 |
};
|
| 888 |
|
| 889 |
// --- MCP SERVERI (podrazumijevana lista) ---
|
|
@@ -913,8 +1079,13 @@
|
|
| 913 |
// Historija razgovora (bez system poruke) - koristi se kao kontekst za API pozive.
|
| 914 |
// NAPOMENA: nema više hardkodiranog limita na broj poruka - historija raste
|
| 915 |
// prirodno sa razgovorom (provider/model API sam prijavi grešku ako se pređe kontekst).
|
|
|
|
| 916 |
messages: [],
|
| 917 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 918 |
// MCP serveri (runtime lista, sa statusom konekcije i keširanim tools/list po serveru)
|
| 919 |
mcpServers: [],
|
| 920 |
|
|
@@ -1079,6 +1250,7 @@
|
|
| 1079 |
|
| 1080 |
init() {
|
| 1081 |
this.loadSettings();
|
|
|
|
| 1082 |
this.mcpInit();
|
| 1083 |
this.loadModelsIntoSelect(this.config.provider, this.config.apiKey);
|
| 1084 |
this.bindEvents();
|
|
@@ -1436,6 +1608,192 @@
|
|
| 1436 |
}
|
| 1437 |
},
|
| 1438 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1439 |
bindEvents() {
|
| 1440 |
// Textarea auto-resize
|
| 1441 |
UI.userInput.addEventListener('input', function() {
|
|
@@ -1454,6 +1812,12 @@
|
|
| 1454 |
|
| 1455 |
UI.sendBtn.addEventListener('click', () => this.sendMessage());
|
| 1456 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1457 |
// File upload (attach button otvara sistemski file picker)
|
| 1458 |
UI.attachBtn.addEventListener('click', () => UI.fileInput.click());
|
| 1459 |
UI.fileInput.addEventListener('change', (e) => this.handleFileSelect(e.target.files[0]));
|
|
@@ -1876,6 +2240,7 @@ ${code}
|
|
| 1876 |
UI.loadingSpinner.style.display = 'none';
|
| 1877 |
UI.sendIcon.style.display = 'block';
|
| 1878 |
UI.sendBtn.disabled = UI.userInput.value.trim() === '' && !this.pendingAttachment;
|
|
|
|
| 1879 |
}
|
| 1880 |
},
|
| 1881 |
|
|
@@ -2001,9 +2366,10 @@ ${code}
|
|
| 2001 |
return JSON.stringify(execResult);
|
| 2002 |
}
|
| 2003 |
if (name === 'create_artifact') {
|
| 2004 |
-
|
| 2005 |
-
|
| 2006 |
-
|
|
|
|
| 2007 |
}
|
| 2008 |
if (name.startsWith('mcp__')) {
|
| 2009 |
const [, serverId, ...rest] = name.split('__');
|
|
|
|
| 542 |
50%, 100% { opacity: 0; }
|
| 543 |
}
|
| 544 |
|
| 545 |
+
/* --- SIDEBAR (istorija razgovora) --- */
|
| 546 |
+
.sidebar-overlay {
|
| 547 |
+
position: fixed;
|
| 548 |
+
inset: 0;
|
| 549 |
+
background: rgba(0, 0, 0, 0.5);
|
| 550 |
+
z-index: 40;
|
| 551 |
+
opacity: 0;
|
| 552 |
+
pointer-events: none;
|
| 553 |
+
transition: opacity 0.25s ease;
|
| 554 |
+
}
|
| 555 |
+
|
| 556 |
+
.sidebar-overlay.open {
|
| 557 |
+
opacity: 1;
|
| 558 |
+
pointer-events: auto;
|
| 559 |
+
}
|
| 560 |
+
|
| 561 |
+
.sidebar {
|
| 562 |
+
position: fixed;
|
| 563 |
+
top: 0;
|
| 564 |
+
left: 0;
|
| 565 |
+
height: 100%;
|
| 566 |
+
height: 100dvh;
|
| 567 |
+
width: min(300px, 82vw);
|
| 568 |
+
background-color: var(--surface-color);
|
| 569 |
+
border-right: 1px solid var(--border-color);
|
| 570 |
+
z-index: 50;
|
| 571 |
+
display: flex;
|
| 572 |
+
flex-direction: column;
|
| 573 |
+
transform: translateX(-100%);
|
| 574 |
+
transition: transform 0.25s ease;
|
| 575 |
+
box-shadow: var(--shadow-md);
|
| 576 |
+
}
|
| 577 |
+
|
| 578 |
+
.sidebar.open {
|
| 579 |
+
transform: translateX(0);
|
| 580 |
+
}
|
| 581 |
+
|
| 582 |
+
.sidebar-header {
|
| 583 |
+
display: flex;
|
| 584 |
+
align-items: center;
|
| 585 |
+
justify-content: space-between;
|
| 586 |
+
padding: 1rem;
|
| 587 |
+
border-bottom: 1px solid var(--border-color);
|
| 588 |
+
flex-shrink: 0;
|
| 589 |
+
}
|
| 590 |
+
|
| 591 |
+
.sidebar-new-chat {
|
| 592 |
+
display: flex;
|
| 593 |
+
align-items: center;
|
| 594 |
+
gap: 0.6rem;
|
| 595 |
+
margin: 0.8rem;
|
| 596 |
+
padding: 0.7rem 0.9rem;
|
| 597 |
+
background-color: var(--input-bg);
|
| 598 |
+
border: 1px solid var(--border-color);
|
| 599 |
+
border-radius: 8px;
|
| 600 |
+
color: var(--text-main);
|
| 601 |
+
font-family: inherit;
|
| 602 |
+
font-size: 0.9rem;
|
| 603 |
+
cursor: pointer;
|
| 604 |
+
transition: border-color 0.2s, color 0.2s;
|
| 605 |
+
flex-shrink: 0;
|
| 606 |
+
}
|
| 607 |
+
|
| 608 |
+
.sidebar-new-chat:hover {
|
| 609 |
+
border-color: var(--primary-color);
|
| 610 |
+
color: var(--primary-color);
|
| 611 |
+
}
|
| 612 |
+
|
| 613 |
+
.sidebar-section-label {
|
| 614 |
+
padding: 0 1rem;
|
| 615 |
+
font-size: 0.72rem;
|
| 616 |
+
text-transform: uppercase;
|
| 617 |
+
letter-spacing: 0.05em;
|
| 618 |
+
color: var(--text-secondary);
|
| 619 |
+
margin-bottom: 0.3rem;
|
| 620 |
+
flex-shrink: 0;
|
| 621 |
+
}
|
| 622 |
+
|
| 623 |
+
.sidebar-chat-list {
|
| 624 |
+
flex: 1;
|
| 625 |
+
overflow-y: auto;
|
| 626 |
+
padding: 0 0.5rem 0.5rem;
|
| 627 |
+
display: flex;
|
| 628 |
+
flex-direction: column;
|
| 629 |
+
gap: 0.15rem;
|
| 630 |
+
}
|
| 631 |
+
|
| 632 |
+
.sidebar-chat-item {
|
| 633 |
+
display: flex;
|
| 634 |
+
align-items: center;
|
| 635 |
+
gap: 0.5rem;
|
| 636 |
+
padding: 0.6rem 0.7rem;
|
| 637 |
+
border-radius: 8px;
|
| 638 |
+
cursor: pointer;
|
| 639 |
+
color: var(--text-main);
|
| 640 |
+
font-size: 0.85rem;
|
| 641 |
+
}
|
| 642 |
+
|
| 643 |
+
.sidebar-chat-item span {
|
| 644 |
+
flex: 1;
|
| 645 |
+
overflow: hidden;
|
| 646 |
+
text-overflow: ellipsis;
|
| 647 |
+
white-space: nowrap;
|
| 648 |
+
}
|
| 649 |
+
|
| 650 |
+
.sidebar-chat-item:hover {
|
| 651 |
+
background-color: var(--surface-hover);
|
| 652 |
+
}
|
| 653 |
+
|
| 654 |
+
.sidebar-chat-item.active {
|
| 655 |
+
background-color: var(--surface-hover);
|
| 656 |
+
color: var(--primary-color);
|
| 657 |
+
}
|
| 658 |
+
|
| 659 |
+
.sidebar-chat-delete {
|
| 660 |
+
background: none;
|
| 661 |
+
border: none;
|
| 662 |
+
color: var(--text-secondary);
|
| 663 |
+
cursor: pointer;
|
| 664 |
+
font-size: 0.8rem;
|
| 665 |
+
padding: 0.25rem 0.4rem;
|
| 666 |
+
border-radius: 4px;
|
| 667 |
+
opacity: 0;
|
| 668 |
+
transition: opacity 0.15s, color 0.15s;
|
| 669 |
+
flex-shrink: 0;
|
| 670 |
+
}
|
| 671 |
+
|
| 672 |
+
.sidebar-chat-item:hover .sidebar-chat-delete {
|
| 673 |
+
opacity: 1;
|
| 674 |
+
}
|
| 675 |
+
|
| 676 |
+
.sidebar-chat-delete:hover {
|
| 677 |
+
color: #ef4444;
|
| 678 |
+
}
|
| 679 |
/* --- EXECUTE_CODE REZULTAT (formatiran prikaz umjesto sirovog JSON-a) --- */
|
| 680 |
.exec-result {
|
| 681 |
align-self: stretch;
|
|
|
|
| 761 |
|
| 762 |
<!-- Header -->
|
| 763 |
<header>
|
| 764 |
+
<div style="display:flex; align-items:center; gap:0.4rem;">
|
| 765 |
+
<button class="settings-btn" id="open-sidebar" aria-label="Meni" title="Meni">
|
| 766 |
+
<i class="fa-solid fa-bars"></i>
|
| 767 |
+
</button>
|
| 768 |
+
<div class="app-title">
|
| 769 |
+
<i class="fa-solid fa-robot" style="color: var(--primary-color);"></i>
|
| 770 |
+
AgentScope AI
|
| 771 |
+
</div>
|
| 772 |
</div>
|
| 773 |
<div style="display:flex; align-items:center; gap:0.2rem;">
|
| 774 |
<button class="settings-btn" id="open-mcp" aria-label="MCP Serveri" title="MCP Serveri">
|
|
|
|
| 780 |
</div>
|
| 781 |
</header>
|
| 782 |
|
| 783 |
+
<!-- Sidebar (istorija razgovora) -->
|
| 784 |
+
<div class="sidebar-overlay" id="sidebar-overlay"></div>
|
| 785 |
+
<aside class="sidebar" id="sidebar">
|
| 786 |
+
<div class="sidebar-header">
|
| 787 |
+
<div class="app-title">
|
| 788 |
+
<i class="fa-solid fa-robot" style="color: var(--primary-color);"></i>
|
| 789 |
+
AgentScope AI
|
| 790 |
+
</div>
|
| 791 |
+
<button class="settings-btn" id="close-sidebar" aria-label="Zatvori meni">
|
| 792 |
+
<i class="fa-solid fa-xmark"></i>
|
| 793 |
+
</button>
|
| 794 |
+
</div>
|
| 795 |
+
<button class="sidebar-new-chat" id="sidebar-new-chat" type="button">
|
| 796 |
+
<i class="fa-solid fa-plus"></i> Novi razgovor
|
| 797 |
+
</button>
|
| 798 |
+
<div class="sidebar-section-label">Razgovori</div>
|
| 799 |
+
<div class="sidebar-chat-list" id="sidebar-chat-list"></div>
|
| 800 |
+
</aside>
|
| 801 |
+
|
| 802 |
<!-- Main Chat Area -->
|
| 803 |
<main id="chat-container">
|
| 804 |
<div class="message system">Dobrodošao. Odaberi model i unesi API Ključ u postavkama za početak.</div>
|
|
|
|
| 1041 |
mcpNewUrl: document.getElementById('mcp-new-url'),
|
| 1042 |
mcpNewHeaderName: document.getElementById('mcp-new-header-name'),
|
| 1043 |
mcpNewHeaderValue: document.getElementById('mcp-new-header-value'),
|
| 1044 |
+
mcpAddBtn: document.getElementById('mcp-add-btn'),
|
| 1045 |
+
|
| 1046 |
+
// Sidebar (istorija razgovora)
|
| 1047 |
+
sidebar: document.getElementById('sidebar'),
|
| 1048 |
+
sidebarOverlay: document.getElementById('sidebar-overlay'),
|
| 1049 |
+
openSidebarBtn: document.getElementById('open-sidebar'),
|
| 1050 |
+
closeSidebarBtn: document.getElementById('close-sidebar'),
|
| 1051 |
+
sidebarNewChatBtn: document.getElementById('sidebar-new-chat'),
|
| 1052 |
+
sidebarChatList: document.getElementById('sidebar-chat-list')
|
| 1053 |
};
|
| 1054 |
|
| 1055 |
// --- MCP SERVERI (podrazumijevana lista) ---
|
|
|
|
| 1079 |
// Historija razgovora (bez system poruke) - koristi se kao kontekst za API pozive.
|
| 1080 |
// NAPOMENA: nema više hardkodiranog limita na broj poruka - historija raste
|
| 1081 |
// prirodno sa razgovorom (provider/model API sam prijavi grešku ako se pređe kontekst).
|
| 1082 |
+
// Popunjava se u loadChats() pri pokretanju (iz sačuvanog chata ili novog).
|
| 1083 |
messages: [],
|
| 1084 |
|
| 1085 |
+
// Svi sačuvani razgovori (sidebar istorija) - { id, title, messages, updatedAt }[]
|
| 1086 |
+
chats: [],
|
| 1087 |
+
currentChatId: null,
|
| 1088 |
+
|
| 1089 |
// MCP serveri (runtime lista, sa statusom konekcije i keširanim tools/list po serveru)
|
| 1090 |
mcpServers: [],
|
| 1091 |
|
|
|
|
| 1250 |
|
| 1251 |
init() {
|
| 1252 |
this.loadSettings();
|
| 1253 |
+
this.loadChats();
|
| 1254 |
this.mcpInit();
|
| 1255 |
this.loadModelsIntoSelect(this.config.provider, this.config.apiKey);
|
| 1256 |
this.bindEvents();
|
|
|
|
| 1608 |
}
|
| 1609 |
},
|
| 1610 |
|
| 1611 |
+
// --- SIDEBAR / ISTORIJA RAZGOVORA ---
|
| 1612 |
+
// Sve u localStorage: 'agentscope_chats' (niz razgovora) i
|
| 1613 |
+
// 'agentscope_current_chat_id' (koji je trenutno otvoren).
|
| 1614 |
+
|
| 1615 |
+
loadChats() {
|
| 1616 |
+
let saved = null;
|
| 1617 |
+
try { saved = JSON.parse(localStorage.getItem('agentscope_chats') || 'null'); } catch (e) { /* ignoriši neispravan JSON */ }
|
| 1618 |
+
this.chats = Array.isArray(saved) ? saved : [];
|
| 1619 |
+
|
| 1620 |
+
const savedCurrentId = localStorage.getItem('agentscope_current_chat_id');
|
| 1621 |
+
const existing = this.chats.find(c => c.id === savedCurrentId);
|
| 1622 |
+
|
| 1623 |
+
if (existing) {
|
| 1624 |
+
this.currentChatId = existing.id;
|
| 1625 |
+
this.messages = existing.messages || [];
|
| 1626 |
+
this.renderChatMessages();
|
| 1627 |
+
} else if (this.chats.length) {
|
| 1628 |
+
this.currentChatId = this.chats[0].id;
|
| 1629 |
+
this.messages = this.chats[0].messages || [];
|
| 1630 |
+
this.renderChatMessages();
|
| 1631 |
+
} else {
|
| 1632 |
+
this.startNewChat(true);
|
| 1633 |
+
}
|
| 1634 |
+
this.renderSidebarChatList();
|
| 1635 |
+
},
|
| 1636 |
+
|
| 1637 |
+
saveChats() {
|
| 1638 |
+
localStorage.setItem('agentscope_chats', JSON.stringify(this.chats));
|
| 1639 |
+
if (this.currentChatId) localStorage.setItem('agentscope_current_chat_id', this.currentChatId);
|
| 1640 |
+
},
|
| 1641 |
+
|
| 1642 |
+
// Snima trenutni this.messages u odgovarajući unos u this.chats. Poziva se
|
| 1643 |
+
// nakon svake razmjene poruka. Naslov razgovora se automatski izvodi iz
|
| 1644 |
+
// prve korisničke poruke (dok god je naslov još uvijek podrazumijevan).
|
| 1645 |
+
persistCurrentChat() {
|
| 1646 |
+
if (!this.currentChatId) return;
|
| 1647 |
+
let chat = this.chats.find(c => c.id === this.currentChatId);
|
| 1648 |
+
if (!chat) {
|
| 1649 |
+
chat = { id: this.currentChatId, title: 'Novi razgovor', messages: [], updatedAt: Date.now() };
|
| 1650 |
+
this.chats.unshift(chat);
|
| 1651 |
+
}
|
| 1652 |
+
chat.messages = this.messages;
|
| 1653 |
+
chat.updatedAt = Date.now();
|
| 1654 |
+
if (!chat.title || chat.title === 'Novi razgovor') {
|
| 1655 |
+
const firstUserMsg = this.messages.find(m => m.role === 'user');
|
| 1656 |
+
if (firstUserMsg) {
|
| 1657 |
+
const text = (this.extractDisplayText(firstUserMsg.content) || '').trim();
|
| 1658 |
+
if (text) chat.title = text.slice(0, 45) + (text.length > 45 ? '…' : '');
|
| 1659 |
+
}
|
| 1660 |
+
}
|
| 1661 |
+
this.chats.sort((a, b) => b.updatedAt - a.updatedAt);
|
| 1662 |
+
this.saveChats();
|
| 1663 |
+
this.renderSidebarChatList();
|
| 1664 |
+
},
|
| 1665 |
+
|
| 1666 |
+
// Kreira nov, prazan razgovor i prebacuje se na njega
|
| 1667 |
+
startNewChat(skipUiReset) {
|
| 1668 |
+
const id = 'chat_' + Date.now() + '_' + Math.random().toString(36).slice(2, 8);
|
| 1669 |
+
const chat = { id, title: 'Novi razgovor', messages: [], updatedAt: Date.now() };
|
| 1670 |
+
this.chats.unshift(chat);
|
| 1671 |
+
this.currentChatId = id;
|
| 1672 |
+
this.messages = [];
|
| 1673 |
+
this.saveChats();
|
| 1674 |
+
this.renderSidebarChatList();
|
| 1675 |
+
if (!skipUiReset) {
|
| 1676 |
+
UI.chatContainer.innerHTML = '<div class="message system">Dobrodošao. Odaberi model i unesi API Ključ u postavkama za početak.</div>';
|
| 1677 |
+
this.closeSidebar();
|
| 1678 |
+
}
|
| 1679 |
+
},
|
| 1680 |
+
|
| 1681 |
+
// Prebacuje se na sačuvani razgovor i ponovo iscrtava poruke iz njegove historije
|
| 1682 |
+
switchChat(id) {
|
| 1683 |
+
if (id === this.currentChatId) { this.closeSidebar(); return; }
|
| 1684 |
+
const chat = this.chats.find(c => c.id === id);
|
| 1685 |
+
if (!chat) return;
|
| 1686 |
+
this.currentChatId = id;
|
| 1687 |
+
this.messages = chat.messages || [];
|
| 1688 |
+
localStorage.setItem('agentscope_current_chat_id', id);
|
| 1689 |
+
this.renderChatMessages();
|
| 1690 |
+
this.renderSidebarChatList();
|
| 1691 |
+
this.closeSidebar();
|
| 1692 |
+
},
|
| 1693 |
+
|
| 1694 |
+
deleteChat(id, event) {
|
| 1695 |
+
if (event) event.stopPropagation();
|
| 1696 |
+
if (!confirm('Obrisati ovaj razgovor? Ovo se ne može poništiti.')) return;
|
| 1697 |
+
this.chats = this.chats.filter(c => c.id !== id);
|
| 1698 |
+
this.saveChats();
|
| 1699 |
+
if (this.currentChatId === id) {
|
| 1700 |
+
if (this.chats.length) {
|
| 1701 |
+
this.switchChat(this.chats[0].id);
|
| 1702 |
+
} else {
|
| 1703 |
+
this.startNewChat();
|
| 1704 |
+
}
|
| 1705 |
+
} else {
|
| 1706 |
+
this.renderSidebarChatList();
|
| 1707 |
+
}
|
| 1708 |
+
},
|
| 1709 |
+
|
| 1710 |
+
// Izvlači tekst za prikaz iz sadržaja poruke (može biti string ili
|
| 1711 |
+
// multimodal niz [{type:'text',...}, {type:'image_url',...}])
|
| 1712 |
+
extractDisplayText(content) {
|
| 1713 |
+
if (typeof content === 'string') return content;
|
| 1714 |
+
if (Array.isArray(content)) {
|
| 1715 |
+
const textPart = content.find(p => p.type === 'text');
|
| 1716 |
+
return textPart ? textPart.text : '[slika]';
|
| 1717 |
+
}
|
| 1718 |
+
return '';
|
| 1719 |
+
},
|
| 1720 |
+
|
| 1721 |
+
// AI poruka renderovana odjednom (bez typewriter efekta) - koristi se pri
|
| 1722 |
+
// ponovnom iscrtavanju sačuvane historije razgovora (nema potrebe za animacijom).
|
| 1723 |
+
addAIMessageInstant(text) {
|
| 1724 |
+
const div = document.createElement('div');
|
| 1725 |
+
div.className = 'message ai';
|
| 1726 |
+
const parts = this.parseMessageParts(text);
|
| 1727 |
+
parts.forEach(part => {
|
| 1728 |
+
if (part.type === 'code') {
|
| 1729 |
+
div.appendChild(this.buildCodeBlockElement(part.lang, part.content));
|
| 1730 |
+
} else if (part.content) {
|
| 1731 |
+
const span = document.createElement('span');
|
| 1732 |
+
span.className = 'msg-text';
|
| 1733 |
+
span.textContent = part.content;
|
| 1734 |
+
div.appendChild(span);
|
| 1735 |
+
}
|
| 1736 |
+
});
|
| 1737 |
+
UI.chatContainer.appendChild(div);
|
| 1738 |
+
},
|
| 1739 |
+
|
| 1740 |
+
// Ponovo iscrtava chat-container na osnovu this.messages. Interne
|
| 1741 |
+
// "vodovodne" poruke (role: 'tool', ili assistant poruke sa tool_calls a
|
| 1742 |
+
// bez sadržaja) se ne prikazuju ponovo - ostaju samo kao API kontekst.
|
| 1743 |
+
renderChatMessages() {
|
| 1744 |
+
UI.chatContainer.innerHTML = '';
|
| 1745 |
+
if (!this.messages.length) {
|
| 1746 |
+
UI.chatContainer.innerHTML = '<div class="message system">Dobrodošao. Odaberi model i unesi API Ključ u postavkama za početak.</div>';
|
| 1747 |
+
return;
|
| 1748 |
+
}
|
| 1749 |
+
this.messages.forEach(msg => {
|
| 1750 |
+
if (msg.role === 'user') {
|
| 1751 |
+
const text = this.extractDisplayText(msg.content);
|
| 1752 |
+
const imagePart = Array.isArray(msg.content) ? msg.content.find(p => p.type === 'image_url') : null;
|
| 1753 |
+
this.addUserMessage(text, imagePart ? { type: 'image', dataUrl: imagePart.image_url.url } : null);
|
| 1754 |
+
} else if (msg.role === 'assistant' && msg.content && msg.content.trim()) {
|
| 1755 |
+
this.addAIMessageInstant(msg.content);
|
| 1756 |
+
}
|
| 1757 |
+
});
|
| 1758 |
+
this.scrollToBottom();
|
| 1759 |
+
},
|
| 1760 |
+
|
| 1761 |
+
renderSidebarChatList() {
|
| 1762 |
+
UI.sidebarChatList.innerHTML = '';
|
| 1763 |
+
this.chats.forEach(chat => {
|
| 1764 |
+
const item = document.createElement('div');
|
| 1765 |
+
item.className = 'sidebar-chat-item' + (chat.id === this.currentChatId ? ' active' : '');
|
| 1766 |
+
|
| 1767 |
+
const icon = document.createElement('i');
|
| 1768 |
+
icon.className = 'fa-regular fa-message';
|
| 1769 |
+
item.appendChild(icon);
|
| 1770 |
+
|
| 1771 |
+
const label = document.createElement('span');
|
| 1772 |
+
label.textContent = chat.title || 'Novi razgovor';
|
| 1773 |
+
item.appendChild(label);
|
| 1774 |
+
|
| 1775 |
+
const delBtn = document.createElement('button');
|
| 1776 |
+
delBtn.className = 'sidebar-chat-delete';
|
| 1777 |
+
delBtn.title = 'Obriši razgovor';
|
| 1778 |
+
delBtn.innerHTML = '<i class="fa-regular fa-trash-can"></i>';
|
| 1779 |
+
delBtn.addEventListener('click', (e) => this.deleteChat(chat.id, e));
|
| 1780 |
+
item.appendChild(delBtn);
|
| 1781 |
+
|
| 1782 |
+
item.addEventListener('click', () => this.switchChat(chat.id));
|
| 1783 |
+
UI.sidebarChatList.appendChild(item);
|
| 1784 |
+
});
|
| 1785 |
+
},
|
| 1786 |
+
|
| 1787 |
+
openSidebar() {
|
| 1788 |
+
UI.sidebar.classList.add('open');
|
| 1789 |
+
UI.sidebarOverlay.classList.add('open');
|
| 1790 |
+
},
|
| 1791 |
+
|
| 1792 |
+
closeSidebar() {
|
| 1793 |
+
UI.sidebar.classList.remove('open');
|
| 1794 |
+
UI.sidebarOverlay.classList.remove('open');
|
| 1795 |
+
},
|
| 1796 |
+
|
| 1797 |
bindEvents() {
|
| 1798 |
// Textarea auto-resize
|
| 1799 |
UI.userInput.addEventListener('input', function() {
|
|
|
|
| 1812 |
|
| 1813 |
UI.sendBtn.addEventListener('click', () => this.sendMessage());
|
| 1814 |
|
| 1815 |
+
// Sidebar (istorija razgovora)
|
| 1816 |
+
UI.openSidebarBtn.addEventListener('click', () => this.openSidebar());
|
| 1817 |
+
UI.closeSidebarBtn.addEventListener('click', () => this.closeSidebar());
|
| 1818 |
+
UI.sidebarOverlay.addEventListener('click', () => this.closeSidebar());
|
| 1819 |
+
UI.sidebarNewChatBtn.addEventListener('click', () => this.startNewChat());
|
| 1820 |
+
|
| 1821 |
// File upload (attach button otvara sistemski file picker)
|
| 1822 |
UI.attachBtn.addEventListener('click', () => UI.fileInput.click());
|
| 1823 |
UI.fileInput.addEventListener('change', (e) => this.handleFileSelect(e.target.files[0]));
|
|
|
|
| 2240 |
UI.loadingSpinner.style.display = 'none';
|
| 2241 |
UI.sendIcon.style.display = 'block';
|
| 2242 |
UI.sendBtn.disabled = UI.userInput.value.trim() === '' && !this.pendingAttachment;
|
| 2243 |
+
this.persistCurrentChat();
|
| 2244 |
}
|
| 2245 |
},
|
| 2246 |
|
|
|
|
| 2366 |
return JSON.stringify(execResult);
|
| 2367 |
}
|
| 2368 |
if (name === 'create_artifact') {
|
| 2369 |
+
// Ne otvaramo preview automatski - samo dodajemo poruku sa code
|
| 2370 |
+
// blokom koji ima dugme "Prikaži" (korisnik sam bira kad da klikne).
|
| 2371 |
+
this.addAIMessage(`🎨 Artifact "${args.title || 'bez naziva'}" je kreiran.\n\n\`\`\`${args.language || 'html'}\n${args.code || ''}\n\`\`\``);
|
| 2372 |
+
return 'Artifact je uspješno kreiran. Korisnik može kliknuti dugme "Prikaži" da ga vidi u live pregledu.';
|
| 2373 |
}
|
| 2374 |
if (name.startsWith('mcp__')) {
|
| 2375 |
const [, serverId, ...rest] = name.split('__');
|