opc-agent 4.0.33 → 4.0.34
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/studio-ui/index.html +284 -28
- package/fix-sidebar.mjs +188 -0
- package/package.json +1 -1
- package/src/studio-ui/index.html +284 -28
|
@@ -297,6 +297,55 @@
|
|
|
297
297
|
.settings-subnav { width: 100%; display: flex; flex-wrap: wrap; gap: 4px; }
|
|
298
298
|
.snav-item { padding: 8px 10px; font-size: 12px; flex-direction: column; gap: 4px; text-align: center; min-width: 70px; }
|
|
299
299
|
}
|
|
300
|
+
|
|
301
|
+
/* Sidebar restructure */
|
|
302
|
+
.sidebar-section-title { font-size: 11px; letter-spacing: 1px; color: var(--text-dim); margin: 20px 12px 8px; text-transform: uppercase; font-weight: 600; }
|
|
303
|
+
.sidebar-divider { height: 1px; background: var(--border); margin: 8px 12px; }
|
|
304
|
+
.agent-list-container { overflow-y: auto; flex: 1; min-height: 0; }
|
|
305
|
+
.agent-list-item {
|
|
306
|
+
display: flex; align-items: center; gap: 10px; padding: 10px 16px; border-radius: 12px;
|
|
307
|
+
cursor: pointer; color: var(--text-muted); transition: all 0.2s ease; font-size: 14px; margin-bottom: 2px; position: relative;
|
|
308
|
+
}
|
|
309
|
+
.agent-list-item:hover { background: var(--bg-hover); color: var(--text); transform: translateX(4px); }
|
|
310
|
+
.agent-list-item.active { background: var(--accent-light); color: #fff; font-weight: 600; box-shadow: var(--glow-sm); border: 1px solid var(--border); }
|
|
311
|
+
.agent-list-item .agent-icon { width: 24px; text-align: center; font-size: 16px; }
|
|
312
|
+
.agent-list-item .agent-name { flex: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
313
|
+
.agent-list-item .status-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
|
|
314
|
+
.agent-list-item .status-dot.online { background: var(--green); box-shadow: 0 0 6px var(--green); }
|
|
315
|
+
.agent-list-item .status-dot.offline { background: var(--text-dim); }
|
|
316
|
+
.agent-list-item .status-dot.error { background: var(--red); box-shadow: 0 0 6px var(--red); }
|
|
317
|
+
.sidebar-bottom { margin-top: auto; flex-shrink: 0; }
|
|
318
|
+
.sidebar-nav { display: flex; flex-direction: column; flex: 1; min-height: 0; overflow: hidden; }
|
|
319
|
+
|
|
320
|
+
/* Agent Detail Page */
|
|
321
|
+
.agent-detail-header { display: flex; justify-content: space-between; align-items: center; padding: 20px 28px; border-bottom: 1px solid var(--border); }
|
|
322
|
+
.agent-detail-info { display: flex; align-items: center; gap: 12px; }
|
|
323
|
+
.agent-detail-icon { font-size: 28px; }
|
|
324
|
+
.agent-detail-name { font-size: 20px; font-weight: 700; margin: 0; }
|
|
325
|
+
.agent-detail-toggle { background: var(--bg-card); border: 1px solid var(--border); border-radius: 10px; width: 40px; height: 40px; font-size: 18px; cursor: pointer; color: var(--text-muted); transition: all 0.2s; display: flex; align-items: center; justify-content: center; }
|
|
326
|
+
.agent-detail-toggle:hover { background: var(--bg-hover); color: var(--text); border-color: var(--accent); }
|
|
327
|
+
.agent-detail-toggle.active { background: var(--accent-light); color: var(--accent); border-color: var(--accent); }
|
|
328
|
+
#page-agent-detail { display: none; flex-direction: column; height: 100vh; }
|
|
329
|
+
#page-agent-detail.active { display: flex; }
|
|
330
|
+
.agent-chat-view { display: flex; flex-direction: column; flex: 1; min-height: 0; }
|
|
331
|
+
.agent-chat-messages { flex: 1; overflow-y: auto; padding: 24px 28px; display: flex; flex-direction: column; gap: 16px; }
|
|
332
|
+
.agent-chat-welcome { display: flex; flex-direction: column; align-items: center; justify-content: center; flex: 1; color: var(--text-dim); }
|
|
333
|
+
.agent-chat-input-bar { display: flex; gap: 12px; padding: 16px 28px; border-top: 1px solid var(--border); background: rgba(5,5,30,0.5); backdrop-filter: blur(10px); }
|
|
334
|
+
.agent-chat-input { flex: 1; background: var(--bg-input); border: 1px solid var(--border); border-radius: 12px; padding: 12px 16px; color: var(--text); font-size: 14px; resize: none; outline: none; font-family: var(--font); max-height: 120px; }
|
|
335
|
+
.agent-chat-input:focus { border-color: var(--accent); }
|
|
336
|
+
.agent-chat-send { padding: 12px 20px; border-radius: 12px; font-weight: 600; flex-shrink: 0; }
|
|
337
|
+
.agent-chat-msg { max-width: 75%; padding: 12px 16px; border-radius: 16px; font-size: 14px; line-height: 1.6; word-break: break-word; }
|
|
338
|
+
.agent-chat-msg.user { align-self: flex-end; background: var(--accent); color: #fff; border-bottom-right-radius: 4px; }
|
|
339
|
+
.agent-chat-msg.assistant { align-self: flex-start; background: var(--bg-card); border: 1px solid var(--border); border-bottom-left-radius: 4px; }
|
|
340
|
+
.agent-settings-view { flex: 1; display: flex; flex-direction: column; min-height: 0; }
|
|
341
|
+
.agent-settings-tabs { display: flex; gap: 4px; padding: 16px 28px 0; border-bottom: 1px solid var(--border); overflow-x: auto; flex-shrink: 0; }
|
|
342
|
+
.agent-tab { padding: 10px 16px; border-radius: 10px 10px 0 0; cursor: pointer; color: var(--text-muted); font-size: 13px; white-space: nowrap; transition: all 0.15s; border-bottom: 2px solid transparent; }
|
|
343
|
+
.agent-tab:hover { color: var(--text); background: var(--bg-hover); }
|
|
344
|
+
.agent-tab.active { color: var(--accent); border-bottom-color: var(--accent); font-weight: 600; }
|
|
345
|
+
.agent-settings-content { flex: 1; overflow-y: auto; padding: 24px 28px; }
|
|
346
|
+
.agent-tab-panel { display: none; }
|
|
347
|
+
.agent-tab-panel.active { display: block; }
|
|
348
|
+
.agent-tab-panel h3 { margin-bottom: 12px; font-size: 18px; }
|
|
300
349
|
</style>
|
|
301
350
|
</head>
|
|
302
351
|
<body>
|
|
@@ -306,35 +355,34 @@
|
|
|
306
355
|
<nav class="sidebar">
|
|
307
356
|
<div class="sidebar-logo">⚡ <span>OPC Studio</span></div>
|
|
308
357
|
<div class="sidebar-nav">
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
<span class="icon">💬</span> Chat
|
|
314
|
-
</div>
|
|
315
|
-
<div class="nav-item" data-page="templates" onclick="navigate('templates')">
|
|
316
|
-
<span class="icon">👤</span> Templates
|
|
317
|
-
</div>
|
|
318
|
-
<div class="nav-item" data-page="skills" onclick="navigate('skills')">
|
|
319
|
-
<span class="icon">🧩</span> Skills Market
|
|
358
|
+
<!-- Section 1: My Agents -->
|
|
359
|
+
<div class="sidebar-section-title">🤖 我的 Agent</div>
|
|
360
|
+
<div class="agent-list-container" id="sidebar-agent-list">
|
|
361
|
+
<div style="padding: 12px 16px; color: var(--text-dim); font-size: 13px;">加载中...</div>
|
|
320
362
|
</div>
|
|
363
|
+
|
|
364
|
+
<!-- Section 2: Create -->
|
|
365
|
+
<div class="sidebar-divider"></div>
|
|
321
366
|
<div class="nav-item" data-page="create" onclick="navigate('create')">
|
|
322
|
-
<span class="icon"
|
|
323
|
-
</div>
|
|
324
|
-
<div class="nav-item" data-page="settings" onclick="currentSettingsTab='models';navigate('settings')">
|
|
325
|
-
<span class="icon">🤖</span> Models
|
|
326
|
-
</div>
|
|
327
|
-
<div class="nav-item" data-page="settings" onclick="currentSettingsTab='channels';navigate('settings')">
|
|
328
|
-
<span class="icon">📡</span> Channels
|
|
329
|
-
</div>
|
|
330
|
-
<div class="nav-item" data-page="settings" onclick="currentSettingsTab='memory';navigate('settings')">
|
|
331
|
-
<span class="icon">🧠</span> Memory
|
|
332
|
-
</div>
|
|
333
|
-
<div class="nav-item" data-page="settings" onclick="navigate('settings')">
|
|
334
|
-
<span class="icon">⚙️</span> Settings
|
|
367
|
+
<span class="icon">➕</span> 新建 Agent
|
|
335
368
|
</div>
|
|
336
|
-
|
|
337
|
-
|
|
369
|
+
|
|
370
|
+
<!-- Section 3: Global Config -->
|
|
371
|
+
<div class="sidebar-bottom">
|
|
372
|
+
<div class="sidebar-divider"></div>
|
|
373
|
+
<div class="sidebar-section-title">⚙️ 全局配置</div>
|
|
374
|
+
<div class="nav-item" data-page="global-runtime" onclick="navigate('global-runtime')">
|
|
375
|
+
<span class="icon">🚀</span> Runtime
|
|
376
|
+
</div>
|
|
377
|
+
<div class="nav-item" data-page="global-models" onclick="navigate('global-models')">
|
|
378
|
+
<span class="icon">🧠</span> Models
|
|
379
|
+
</div>
|
|
380
|
+
<div class="nav-item" data-page="global-memory" onclick="navigate('global-memory')">
|
|
381
|
+
<span class="icon">💾</span> Memory
|
|
382
|
+
</div>
|
|
383
|
+
<div class="nav-item" data-page="global-templates" onclick="navigate('global-templates')">
|
|
384
|
+
<span class="icon">📋</span> Templates
|
|
385
|
+
</div>
|
|
338
386
|
</div>
|
|
339
387
|
</div>
|
|
340
388
|
<div style="padding: 12px; border-top: 1px solid var(--border); font-size: 12px; color: var(--text-dim);">
|
|
@@ -371,6 +419,76 @@
|
|
|
371
419
|
</div>
|
|
372
420
|
</div>
|
|
373
421
|
|
|
422
|
+
<!-- Agent Detail Page -->
|
|
423
|
+
<div class="page" id="page-agent-detail">
|
|
424
|
+
<div class="agent-detail-header">
|
|
425
|
+
<div class="agent-detail-info">
|
|
426
|
+
<span class="agent-detail-icon" id="agent-detail-icon">🤖</span>
|
|
427
|
+
<h1 class="agent-detail-name" id="agent-detail-name">Agent</h1>
|
|
428
|
+
<span class="status-dot online" id="agent-detail-status"></span>
|
|
429
|
+
</div>
|
|
430
|
+
<button class="agent-detail-toggle" id="agent-detail-toggle" onclick="toggleAgentSettings()">⚙️</button>
|
|
431
|
+
</div>
|
|
432
|
+
|
|
433
|
+
<!-- Chat View (default) -->
|
|
434
|
+
<div class="agent-chat-view" id="agent-chat-view">
|
|
435
|
+
<div class="agent-chat-messages" id="agent-chat-messages">
|
|
436
|
+
<div class="agent-chat-welcome" id="agent-chat-welcome">
|
|
437
|
+
<div style="font-size: 48px; margin-bottom: 16px;">💬</div>
|
|
438
|
+
<div style="font-size: 18px; font-weight: 600; margin-bottom: 8px;">开始对话</div>
|
|
439
|
+
<div style="color: var(--text-muted); font-size: 14px;">向你的 Agent 发送第一条消息</div>
|
|
440
|
+
</div>
|
|
441
|
+
</div>
|
|
442
|
+
<div class="agent-chat-input-bar">
|
|
443
|
+
<textarea class="agent-chat-input" id="agent-chat-input" placeholder="输入消息..." rows="1" onkeydown="handleAgentChatKey(event)"></textarea>
|
|
444
|
+
<button class="btn btn-primary agent-chat-send" onclick="sendAgentChat()">发送</button>
|
|
445
|
+
</div>
|
|
446
|
+
</div>
|
|
447
|
+
|
|
448
|
+
<!-- Settings View (hidden) -->
|
|
449
|
+
<div class="agent-settings-view" id="agent-settings-view" style="display:none;">
|
|
450
|
+
<div class="agent-settings-tabs">
|
|
451
|
+
<div class="agent-tab active" data-atab="role" onclick="switchAgentTab('role')">👤 角色</div>
|
|
452
|
+
<div class="agent-tab" data-atab="models" onclick="switchAgentTab('models')">🤖 模型</div>
|
|
453
|
+
<div class="agent-tab" data-atab="channels" onclick="switchAgentTab('channels')">📡 渠道</div>
|
|
454
|
+
<div class="agent-tab" data-atab="memory" onclick="switchAgentTab('memory')">🧠 记忆</div>
|
|
455
|
+
<div class="agent-tab" data-atab="skills" onclick="switchAgentTab('skills')">🧩 技能</div>
|
|
456
|
+
<div class="agent-tab" data-atab="schedules" onclick="switchAgentTab('schedules')">⏰ 定时</div>
|
|
457
|
+
<div class="agent-tab" data-atab="usage" onclick="switchAgentTab('usage')">📊 统计</div>
|
|
458
|
+
</div>
|
|
459
|
+
<div class="agent-settings-content" id="agent-settings-content">
|
|
460
|
+
<div class="agent-tab-panel active" id="atab-role">
|
|
461
|
+
<h3>角色配置</h3>
|
|
462
|
+
<p style="color:var(--text-muted)">Agent 角色和人设配置(即将上线)</p>
|
|
463
|
+
</div>
|
|
464
|
+
<div class="agent-tab-panel" id="atab-models">
|
|
465
|
+
<h3>模型配置</h3>
|
|
466
|
+
<p style="color:var(--text-muted)">Agent 使用的模型配置(即将上线)</p>
|
|
467
|
+
</div>
|
|
468
|
+
<div class="agent-tab-panel" id="atab-channels">
|
|
469
|
+
<h3>渠道配置</h3>
|
|
470
|
+
<p style="color:var(--text-muted)">Agent 接入的渠道配置(即将上线)</p>
|
|
471
|
+
</div>
|
|
472
|
+
<div class="agent-tab-panel" id="atab-memory">
|
|
473
|
+
<h3>记忆管理</h3>
|
|
474
|
+
<p style="color:var(--text-muted)">Agent 记忆和知识库(即将上线)</p>
|
|
475
|
+
</div>
|
|
476
|
+
<div class="agent-tab-panel" id="atab-skills">
|
|
477
|
+
<h3>技能配置</h3>
|
|
478
|
+
<p style="color:var(--text-muted)">Agent 已安装的技能(即将上线)</p>
|
|
479
|
+
</div>
|
|
480
|
+
<div class="agent-tab-panel" id="atab-schedules">
|
|
481
|
+
<h3>定时任务</h3>
|
|
482
|
+
<p style="color:var(--text-muted)">Agent 定时任务配置(即将上线)</p>
|
|
483
|
+
</div>
|
|
484
|
+
<div class="agent-tab-panel" id="atab-usage">
|
|
485
|
+
<h3>用量统计</h3>
|
|
486
|
+
<p style="color:var(--text-muted)">Agent 使用量和成本(即将上线)</p>
|
|
487
|
+
</div>
|
|
488
|
+
</div>
|
|
489
|
+
</div>
|
|
490
|
+
</div>
|
|
491
|
+
|
|
374
492
|
<!-- Templates Page -->
|
|
375
493
|
<div class="page" id="page-templates">
|
|
376
494
|
<h1 class="page-title">Template Market</h1>
|
|
@@ -954,6 +1072,8 @@
|
|
|
954
1072
|
const parts = path.split('/').filter(Boolean);
|
|
955
1073
|
if (parts[0] === 'chat' && parts[1]) {
|
|
956
1074
|
openChat(parts[1]);
|
|
1075
|
+
} else if (parts[0] === 'agent' && parts[1]) {
|
|
1076
|
+
loadSidebarAgents().then(() => navigateToAgent(parts[1]));
|
|
957
1077
|
} else if (parts[0] === 'settings') {
|
|
958
1078
|
if (parts[1]) currentSettingsTab = parts[1];
|
|
959
1079
|
navigate('settings');
|
|
@@ -995,16 +1115,152 @@
|
|
|
995
1115
|
} catch(e) { console.error('Failed to load agents:', e); }
|
|
996
1116
|
}
|
|
997
1117
|
|
|
998
|
-
// ===
|
|
1118
|
+
// === Sidebar Agents ===
|
|
1119
|
+
let selectedAgentId = null;
|
|
1120
|
+
|
|
1121
|
+
async function loadSidebarAgents() {
|
|
1122
|
+
try {
|
|
1123
|
+
const res = await fetch('/api/agents');
|
|
1124
|
+
const data = await res.json();
|
|
1125
|
+
const agents = data.agents || data || [];
|
|
1126
|
+
window._sidebarAgents = agents;
|
|
1127
|
+
const container = document.getElementById('sidebar-agent-list');
|
|
1128
|
+
if (!agents.length) {
|
|
1129
|
+
container.innerHTML = '<div style="padding: 12px 16px; color: var(--text-dim); font-size: 13px;">暂无 Agent</div>';
|
|
1130
|
+
return;
|
|
1131
|
+
}
|
|
1132
|
+
container.innerHTML = agents.map(a => {
|
|
1133
|
+
const status = (a.status || 'offline').toLowerCase();
|
|
1134
|
+
const icon = a.emoji || a.icon || '🤖';
|
|
1135
|
+
const name = a.name || a.id;
|
|
1136
|
+
return `<div class="agent-list-item${selectedAgentId === a.id ? ' active' : ''}" data-agent-id="${a.id}" onclick="navigateToAgent('${a.id}')">
|
|
1137
|
+
<span class="agent-icon">${icon}</span>
|
|
1138
|
+
<span class="agent-name">${name}</span>
|
|
1139
|
+
<span class="status-dot ${status}"></span>
|
|
1140
|
+
</div>`;
|
|
1141
|
+
}).join('');
|
|
1142
|
+
} catch(e) {
|
|
1143
|
+
console.error('Failed to load sidebar agents:', e);
|
|
1144
|
+
const container = document.getElementById('sidebar-agent-list');
|
|
1145
|
+
if (container) container.innerHTML = '<div style="padding: 12px 16px; color: var(--text-dim); font-size: 13px;">加载失败</div>';
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
function navigateToAgent(agentId) {
|
|
1150
|
+
selectedAgentId = agentId;
|
|
1151
|
+
// Update sidebar active state
|
|
1152
|
+
document.querySelectorAll('.agent-list-item').forEach(el => el.classList.remove('active'));
|
|
1153
|
+
document.querySelectorAll('.nav-item').forEach(n => n.classList.remove('active'));
|
|
1154
|
+
const item = document.querySelector(`.agent-list-item[data-agent-id="${agentId}"]`);
|
|
1155
|
+
if (item) item.classList.add('active');
|
|
1156
|
+
|
|
1157
|
+
// Find agent data
|
|
1158
|
+
const agent = (window._sidebarAgents || []).find(a => a.id === agentId) || { id: agentId, name: agentId };
|
|
1159
|
+
document.getElementById('agent-detail-icon').textContent = agent.emoji || agent.icon || '🤖';
|
|
1160
|
+
document.getElementById('agent-detail-name').textContent = agent.name || agentId;
|
|
1161
|
+
const statusDot = document.getElementById('agent-detail-status');
|
|
1162
|
+
const status = (agent.status || 'offline').toLowerCase();
|
|
1163
|
+
statusDot.className = 'status-dot ' + status;
|
|
1164
|
+
|
|
1165
|
+
// Reset to chat view
|
|
1166
|
+
document.getElementById('agent-chat-view').style.display = '';
|
|
1167
|
+
document.getElementById('agent-settings-view').style.display = 'none';
|
|
1168
|
+
document.getElementById('agent-detail-toggle').classList.remove('active');
|
|
1169
|
+
document.getElementById('agent-chat-messages').innerHTML = document.querySelector('.agent-chat-welcome').outerHTML || '<div class="agent-chat-welcome"><div style="font-size:48px;margin-bottom:16px">💬</div><div style="font-size:18px;font-weight:600;margin-bottom:8px">开始对话</div><div style="color:var(--text-muted);font-size:14px">向你的 Agent 发送第一条消息</div></div>';
|
|
1170
|
+
document.getElementById('agent-chat-input').value = '';
|
|
1171
|
+
|
|
1172
|
+
// Show agent detail page
|
|
1173
|
+
document.querySelectorAll('.page, .chat-container').forEach(p => { p.classList.remove('active'); p.style.display = ''; });
|
|
1174
|
+
document.getElementById('page-agent-detail').classList.add('active');
|
|
1175
|
+
location.hash = `/agent/${agentId}`;
|
|
1176
|
+
toggleSidebar(false);
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
function toggleAgentSettings() {
|
|
1180
|
+
const chatView = document.getElementById('agent-chat-view');
|
|
1181
|
+
const settingsView = document.getElementById('agent-settings-view');
|
|
1182
|
+
const toggleBtn = document.getElementById('agent-detail-toggle');
|
|
1183
|
+
const showSettings = chatView.style.display !== 'none';
|
|
1184
|
+
chatView.style.display = showSettings ? 'none' : '';
|
|
1185
|
+
settingsView.style.display = showSettings ? '' : 'none';
|
|
1186
|
+
toggleBtn.classList.toggle('active', showSettings);
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
function switchAgentTab(tab) {
|
|
1190
|
+
document.querySelectorAll('.agent-tab').forEach(t => t.classList.remove('active'));
|
|
1191
|
+
document.querySelector(`.agent-tab[data-atab="${tab}"]`)?.classList.add('active');
|
|
1192
|
+
document.querySelectorAll('.agent-tab-panel').forEach(p => p.classList.remove('active'));
|
|
1193
|
+
document.getElementById(`atab-${tab}`)?.classList.add('active');
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
let agentChatHistory = [];
|
|
1197
|
+
|
|
1198
|
+
async function sendAgentChat() {
|
|
1199
|
+
const input = document.getElementById('agent-chat-input');
|
|
1200
|
+
const msg = input.value.trim();
|
|
1201
|
+
if (!msg || !selectedAgentId) return;
|
|
1202
|
+
input.value = '';
|
|
1203
|
+
input.style.height = 'auto';
|
|
1204
|
+
|
|
1205
|
+
const messagesEl = document.getElementById('agent-chat-messages');
|
|
1206
|
+
// Remove welcome screen
|
|
1207
|
+
const welcome = messagesEl.querySelector('.agent-chat-welcome');
|
|
1208
|
+
if (welcome) welcome.remove();
|
|
1209
|
+
|
|
1210
|
+
// Add user message
|
|
1211
|
+
const userDiv = document.createElement('div');
|
|
1212
|
+
userDiv.className = 'agent-chat-msg user';
|
|
1213
|
+
userDiv.textContent = msg;
|
|
1214
|
+
messagesEl.appendChild(userDiv);
|
|
1215
|
+
messagesEl.scrollTop = messagesEl.scrollHeight;
|
|
1216
|
+
|
|
1217
|
+
agentChatHistory.push({ role: 'user', content: msg });
|
|
1218
|
+
|
|
1219
|
+
// Send to API
|
|
1220
|
+
try {
|
|
1221
|
+
const res = await fetch(`/api/agents/${selectedAgentId}/chat`, {
|
|
1222
|
+
method: 'POST',
|
|
1223
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1224
|
+
body: JSON.stringify({ message: msg, history: agentChatHistory })
|
|
1225
|
+
});
|
|
1226
|
+
const data = await res.json();
|
|
1227
|
+
const reply = data.reply || data.message || data.content || JSON.stringify(data);
|
|
1228
|
+
const assistantDiv = document.createElement('div');
|
|
1229
|
+
assistantDiv.className = 'agent-chat-msg assistant';
|
|
1230
|
+
assistantDiv.textContent = reply;
|
|
1231
|
+
messagesEl.appendChild(assistantDiv);
|
|
1232
|
+
agentChatHistory.push({ role: 'assistant', content: reply });
|
|
1233
|
+
} catch(e) {
|
|
1234
|
+
const errDiv = document.createElement('div');
|
|
1235
|
+
errDiv.className = 'agent-chat-msg assistant';
|
|
1236
|
+
errDiv.style.borderColor = 'var(--red)';
|
|
1237
|
+
errDiv.textContent = `⚠️ 发送失败: ${e.message}`;
|
|
1238
|
+
messagesEl.appendChild(errDiv);
|
|
1239
|
+
}
|
|
1240
|
+
messagesEl.scrollTop = messagesEl.scrollHeight;
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
function handleAgentChatKey(e) {
|
|
1244
|
+
if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendAgentChat(); }
|
|
1245
|
+
// Auto-resize textarea
|
|
1246
|
+
e.target.style.height = 'auto';
|
|
1247
|
+
e.target.style.height = Math.min(e.target.scrollHeight, 120) + 'px';
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
// === Navigation ===
|
|
999
1251
|
function navigate(page) {
|
|
1000
1252
|
document.querySelectorAll('.page, .chat-container').forEach(p => { p.classList.remove('active'); p.style.display = ''; });
|
|
1001
1253
|
document.querySelectorAll('.nav-item').forEach(n => n.classList.remove('active'));
|
|
1002
1254
|
const navItem = document.querySelector(`.nav-item[data-page="${page}"]`);
|
|
1003
1255
|
if (navItem) navItem.classList.add('active');
|
|
1004
1256
|
|
|
1005
|
-
if (page === 'dashboard') { loadAgents(); loadHealthDashboard(); }
|
|
1257
|
+
if (page === 'dashboard') { loadAgents(); loadHealthDashboard(); loadSidebarAgents(); }
|
|
1006
1258
|
if (page === 'create') { renderWizard(); renderWizardTemplates(); }
|
|
1007
1259
|
if (page === 'settings') { showSettings(currentSettingsTab || 'models'); }
|
|
1260
|
+
if (page === 'global-runtime') { currentSettingsTab='status'; showSettings('status'); showPage('settings'); return; }
|
|
1261
|
+
if (page === 'global-models') { currentSettingsTab='models'; showSettings('models'); showPage('settings'); return; }
|
|
1262
|
+
if (page === 'global-memory') { currentSettingsTab='memory'; showSettings('memory'); showPage('settings'); return; }
|
|
1263
|
+
if (page === 'global-templates') { navigate('templates'); return; }
|
|
1008
1264
|
if (page === 'schedules') { loadSchedules(); }
|
|
1009
1265
|
if (page === 'skills') { loadSkillsMarketplace(); }
|
|
1010
1266
|
|
package/fix-sidebar.mjs
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync } from 'fs';
|
|
2
|
+
|
|
3
|
+
const file = 'C:\\Users\\mingjwan\\tmp-opc-clone\\src\\studio-ui\\index.html';
|
|
4
|
+
let html = readFileSync(file, 'utf-8');
|
|
5
|
+
|
|
6
|
+
// 1. Add CSS classes before the closing </style>
|
|
7
|
+
const newCSS = `
|
|
8
|
+
/* Sidebar restructure */
|
|
9
|
+
.sidebar-section-title { font-size: 11px; letter-spacing: 1px; color: var(--text-dim); margin: 20px 12px 8px; text-transform: uppercase; font-weight: 600; }
|
|
10
|
+
.sidebar-divider { height: 1px; background: var(--border); margin: 8px 12px; }
|
|
11
|
+
.agent-list-container { overflow-y: auto; flex: 1; min-height: 0; }
|
|
12
|
+
.agent-list-item {
|
|
13
|
+
display: flex; align-items: center; gap: 10px; padding: 10px 16px; border-radius: 12px;
|
|
14
|
+
cursor: pointer; color: var(--text-muted); transition: all 0.2s ease; font-size: 14px; margin-bottom: 2px; position: relative;
|
|
15
|
+
}
|
|
16
|
+
.agent-list-item:hover { background: var(--bg-hover); color: var(--text); transform: translateX(4px); }
|
|
17
|
+
.agent-list-item.active { background: var(--accent-light); color: #fff; font-weight: 600; box-shadow: var(--glow-sm); border: 1px solid var(--border); }
|
|
18
|
+
.agent-list-item .agent-icon { width: 24px; text-align: center; font-size: 16px; }
|
|
19
|
+
.agent-list-item .agent-name { flex: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
20
|
+
.agent-list-item .status-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
|
|
21
|
+
.agent-list-item .status-dot.online { background: var(--green); box-shadow: 0 0 6px var(--green); }
|
|
22
|
+
.agent-list-item .status-dot.offline { background: var(--text-dim); }
|
|
23
|
+
.agent-list-item .status-dot.error { background: var(--red); box-shadow: 0 0 6px var(--red); }
|
|
24
|
+
.sidebar-bottom { margin-top: auto; flex-shrink: 0; }
|
|
25
|
+
.sidebar-nav { display: flex; flex-direction: column; flex: 1; min-height: 0; overflow: hidden; }
|
|
26
|
+
`;
|
|
27
|
+
|
|
28
|
+
html = html.replace(' </style>', newCSS + ' </style>');
|
|
29
|
+
|
|
30
|
+
// 2. Replace sidebar nav content
|
|
31
|
+
const oldSidebar = ` <div class="sidebar-nav">
|
|
32
|
+
<div class="nav-item active" data-page="dashboard" onclick="navigate('dashboard')">
|
|
33
|
+
<span class="icon">🏠</span> Dashboard
|
|
34
|
+
</div>
|
|
35
|
+
<div class="nav-item" data-page="chat" onclick="openLastChat()">
|
|
36
|
+
<span class="icon">💬</span> Chat
|
|
37
|
+
</div>
|
|
38
|
+
<div class="nav-item" data-page="templates" onclick="navigate('templates')">
|
|
39
|
+
<span class="icon">👤</span> Templates
|
|
40
|
+
</div>
|
|
41
|
+
<div class="nav-item" data-page="skills" onclick="navigate('skills')">
|
|
42
|
+
<span class="icon">🧩</span> Skills Market
|
|
43
|
+
</div>
|
|
44
|
+
<div class="nav-item" data-page="create" onclick="navigate('create')">
|
|
45
|
+
<span class="icon">✨</span> Create Agent
|
|
46
|
+
</div>
|
|
47
|
+
<div class="nav-item" data-page="settings" onclick="currentSettingsTab='models';navigate('settings')">
|
|
48
|
+
<span class="icon">🤖</span> Models
|
|
49
|
+
</div>
|
|
50
|
+
<div class="nav-item" data-page="settings" onclick="currentSettingsTab='channels';navigate('settings')">
|
|
51
|
+
<span class="icon">📡</span> Channels
|
|
52
|
+
</div>
|
|
53
|
+
<div class="nav-item" data-page="settings" onclick="currentSettingsTab='memory';navigate('settings')">
|
|
54
|
+
<span class="icon">🧠</span> Memory
|
|
55
|
+
</div>
|
|
56
|
+
<div class="nav-item" data-page="settings" onclick="navigate('settings')">
|
|
57
|
+
<span class="icon">⚙️</span> Settings
|
|
58
|
+
</div>
|
|
59
|
+
<div class="nav-item" data-page="schedules" onclick="navigate('schedules')">
|
|
60
|
+
<span class="icon">⏰</span> Schedules
|
|
61
|
+
</div>
|
|
62
|
+
</div>`;
|
|
63
|
+
|
|
64
|
+
const newSidebar = ` <div class="sidebar-nav">
|
|
65
|
+
<!-- Section 1: My Agents -->
|
|
66
|
+
<div class="sidebar-section-title">🤖 我的 Agent</div>
|
|
67
|
+
<div class="agent-list-container" id="sidebar-agent-list">
|
|
68
|
+
<div style="padding: 12px 16px; color: var(--text-dim); font-size: 13px;">加载中...</div>
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
<!-- Section 2: Create -->
|
|
72
|
+
<div class="sidebar-divider"></div>
|
|
73
|
+
<div class="nav-item" data-page="create" onclick="navigate('create')">
|
|
74
|
+
<span class="icon">➕</span> 新建 Agent
|
|
75
|
+
</div>
|
|
76
|
+
|
|
77
|
+
<!-- Section 3: Global Config -->
|
|
78
|
+
<div class="sidebar-bottom">
|
|
79
|
+
<div class="sidebar-divider"></div>
|
|
80
|
+
<div class="sidebar-section-title">⚙️ 全局配置</div>
|
|
81
|
+
<div class="nav-item" data-page="global-runtime" onclick="navigate('global-runtime')">
|
|
82
|
+
<span class="icon">🚀</span> Runtime
|
|
83
|
+
</div>
|
|
84
|
+
<div class="nav-item" data-page="global-models" onclick="navigate('global-models')">
|
|
85
|
+
<span class="icon">🧠</span> Models
|
|
86
|
+
</div>
|
|
87
|
+
<div class="nav-item" data-page="global-memory" onclick="navigate('global-memory')">
|
|
88
|
+
<span class="icon">💾</span> Memory
|
|
89
|
+
</div>
|
|
90
|
+
<div class="nav-item" data-page="global-templates" onclick="navigate('global-templates')">
|
|
91
|
+
<span class="icon">📋</span> Templates
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
</div>`;
|
|
95
|
+
|
|
96
|
+
html = html.replace(oldSidebar, newSidebar);
|
|
97
|
+
|
|
98
|
+
// 3. Add JavaScript - extend navigate() and add loadSidebarAgents()
|
|
99
|
+
// Insert global-* navigation handling into navigate function
|
|
100
|
+
const oldNavigate = ` if (page === 'settings') { showSettings(currentSettingsTab || 'models'); }`;
|
|
101
|
+
const newNavigate = ` if (page === 'settings') { showSettings(currentSettingsTab || 'models'); }
|
|
102
|
+
if (page === 'global-runtime') { currentSettingsTab='status'; showSettings('status'); showPage('settings'); return; }
|
|
103
|
+
if (page === 'global-models') { currentSettingsTab='models'; showSettings('models'); showPage('settings'); return; }
|
|
104
|
+
if (page === 'global-memory') { currentSettingsTab='memory'; showSettings('memory'); showPage('settings'); return; }
|
|
105
|
+
if (page === 'global-templates') { navigate('templates'); return; }`;
|
|
106
|
+
|
|
107
|
+
html = html.replace(oldNavigate, newNavigate);
|
|
108
|
+
|
|
109
|
+
// Add loadSidebarAgents function and navigateToAgent before the navigate function
|
|
110
|
+
const navFuncMarker = ` // === Navigation ===`;
|
|
111
|
+
const sidebarJS = ` // === Sidebar Agents ===
|
|
112
|
+
let selectedAgentId = null;
|
|
113
|
+
|
|
114
|
+
async function loadSidebarAgents() {
|
|
115
|
+
try {
|
|
116
|
+
const res = await fetch('/api/agents');
|
|
117
|
+
const data = await res.json();
|
|
118
|
+
const agents = data.agents || data || [];
|
|
119
|
+
const container = document.getElementById('sidebar-agent-list');
|
|
120
|
+
if (!agents.length) {
|
|
121
|
+
container.innerHTML = '<div style="padding: 12px 16px; color: var(--text-dim); font-size: 13px;">暂无 Agent</div>';
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
container.innerHTML = agents.map(a => {
|
|
125
|
+
const status = (a.status || 'offline').toLowerCase();
|
|
126
|
+
const icon = a.emoji || a.icon || '🤖';
|
|
127
|
+
const name = a.name || a.id;
|
|
128
|
+
return \`<div class="agent-list-item\${selectedAgentId === a.id ? ' active' : ''}" data-agent-id="\${a.id}" onclick="navigateToAgent('\${a.id}')">
|
|
129
|
+
<span class="agent-icon">\${icon}</span>
|
|
130
|
+
<span class="agent-name">\${name}</span>
|
|
131
|
+
<span class="status-dot \${status}"></span>
|
|
132
|
+
</div>\`;
|
|
133
|
+
}).join('');
|
|
134
|
+
} catch(e) {
|
|
135
|
+
console.error('Failed to load sidebar agents:', e);
|
|
136
|
+
const container = document.getElementById('sidebar-agent-list');
|
|
137
|
+
if (container) container.innerHTML = '<div style="padding: 12px 16px; color: var(--text-dim); font-size: 13px;">加载失败</div>';
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function navigateToAgent(agentId) {
|
|
142
|
+
selectedAgentId = agentId;
|
|
143
|
+
// Update sidebar active state
|
|
144
|
+
document.querySelectorAll('.agent-list-item').forEach(el => el.classList.remove('active'));
|
|
145
|
+
const item = document.querySelector(\`.agent-list-item[data-agent-id="\${agentId}"]\`);
|
|
146
|
+
if (item) item.classList.add('active');
|
|
147
|
+
// For now, navigate to dashboard with agent context
|
|
148
|
+
navigate('dashboard');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
${navFuncMarker}`;
|
|
152
|
+
|
|
153
|
+
html = html.replace(navFuncMarker, sidebarJS);
|
|
154
|
+
|
|
155
|
+
// Also update nav-item active highlighting to support global-* pages
|
|
156
|
+
const oldNavActive = ` document.querySelectorAll('.nav-item').forEach(n => n.classList.remove('active'));
|
|
157
|
+
const navItem = document.querySelector(\`.nav-item[data-page="\${page}"]\`);
|
|
158
|
+
if (navItem) navItem.classList.add('active');`;
|
|
159
|
+
|
|
160
|
+
const newNavActive = ` document.querySelectorAll('.nav-item').forEach(n => n.classList.remove('active'));
|
|
161
|
+
document.querySelectorAll('.agent-list-item').forEach(n => n.classList.remove('active'));
|
|
162
|
+
const navItem = document.querySelector(\`.nav-item[data-page="\${page}"]\`);
|
|
163
|
+
if (navItem) navItem.classList.add('active');`;
|
|
164
|
+
|
|
165
|
+
html = html.replace(oldNavActive, newNavActive);
|
|
166
|
+
|
|
167
|
+
// Add loadSidebarAgents() call on page load - find DOMContentLoaded or init
|
|
168
|
+
// Look for where loadAgents is first called
|
|
169
|
+
const initMarker = `if (page === 'dashboard') { loadAgents(); loadHealthDashboard(); }`;
|
|
170
|
+
// We'll add the sidebar load call at the end of the navigate function's dashboard case isn't ideal.
|
|
171
|
+
// Let's find where the app initializes
|
|
172
|
+
const hashNav = `location.hash = \`/\${page}\`;`;
|
|
173
|
+
|
|
174
|
+
// Better: add it to the initial page load. Find where hash routing happens.
|
|
175
|
+
// Search for DOMContentLoaded or window.onload
|
|
176
|
+
const loadMatch = html.match(/(?:DOMContentLoaded|window\.onload|loadAgents\(\);\s*loadHealthDashboard)/);
|
|
177
|
+
|
|
178
|
+
// Just add it right after the navigate function definition area, in the init block
|
|
179
|
+
// Find "navigate('dashboard')" at the bottom (initial route)
|
|
180
|
+
const initRoute = html.indexOf("navigate('dashboard')");
|
|
181
|
+
// Let's just add a call in the dashboard load
|
|
182
|
+
html = html.replace(
|
|
183
|
+
`if (page === 'dashboard') { loadAgents(); loadHealthDashboard(); }`,
|
|
184
|
+
`if (page === 'dashboard') { loadAgents(); loadHealthDashboard(); loadSidebarAgents(); }`
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
writeFileSync(file, html, 'utf-8');
|
|
188
|
+
console.log('Done! Sidebar restructured.');
|
package/package.json
CHANGED
package/src/studio-ui/index.html
CHANGED
|
@@ -297,6 +297,55 @@
|
|
|
297
297
|
.settings-subnav { width: 100%; display: flex; flex-wrap: wrap; gap: 4px; }
|
|
298
298
|
.snav-item { padding: 8px 10px; font-size: 12px; flex-direction: column; gap: 4px; text-align: center; min-width: 70px; }
|
|
299
299
|
}
|
|
300
|
+
|
|
301
|
+
/* Sidebar restructure */
|
|
302
|
+
.sidebar-section-title { font-size: 11px; letter-spacing: 1px; color: var(--text-dim); margin: 20px 12px 8px; text-transform: uppercase; font-weight: 600; }
|
|
303
|
+
.sidebar-divider { height: 1px; background: var(--border); margin: 8px 12px; }
|
|
304
|
+
.agent-list-container { overflow-y: auto; flex: 1; min-height: 0; }
|
|
305
|
+
.agent-list-item {
|
|
306
|
+
display: flex; align-items: center; gap: 10px; padding: 10px 16px; border-radius: 12px;
|
|
307
|
+
cursor: pointer; color: var(--text-muted); transition: all 0.2s ease; font-size: 14px; margin-bottom: 2px; position: relative;
|
|
308
|
+
}
|
|
309
|
+
.agent-list-item:hover { background: var(--bg-hover); color: var(--text); transform: translateX(4px); }
|
|
310
|
+
.agent-list-item.active { background: var(--accent-light); color: #fff; font-weight: 600; box-shadow: var(--glow-sm); border: 1px solid var(--border); }
|
|
311
|
+
.agent-list-item .agent-icon { width: 24px; text-align: center; font-size: 16px; }
|
|
312
|
+
.agent-list-item .agent-name { flex: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
313
|
+
.agent-list-item .status-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
|
|
314
|
+
.agent-list-item .status-dot.online { background: var(--green); box-shadow: 0 0 6px var(--green); }
|
|
315
|
+
.agent-list-item .status-dot.offline { background: var(--text-dim); }
|
|
316
|
+
.agent-list-item .status-dot.error { background: var(--red); box-shadow: 0 0 6px var(--red); }
|
|
317
|
+
.sidebar-bottom { margin-top: auto; flex-shrink: 0; }
|
|
318
|
+
.sidebar-nav { display: flex; flex-direction: column; flex: 1; min-height: 0; overflow: hidden; }
|
|
319
|
+
|
|
320
|
+
/* Agent Detail Page */
|
|
321
|
+
.agent-detail-header { display: flex; justify-content: space-between; align-items: center; padding: 20px 28px; border-bottom: 1px solid var(--border); }
|
|
322
|
+
.agent-detail-info { display: flex; align-items: center; gap: 12px; }
|
|
323
|
+
.agent-detail-icon { font-size: 28px; }
|
|
324
|
+
.agent-detail-name { font-size: 20px; font-weight: 700; margin: 0; }
|
|
325
|
+
.agent-detail-toggle { background: var(--bg-card); border: 1px solid var(--border); border-radius: 10px; width: 40px; height: 40px; font-size: 18px; cursor: pointer; color: var(--text-muted); transition: all 0.2s; display: flex; align-items: center; justify-content: center; }
|
|
326
|
+
.agent-detail-toggle:hover { background: var(--bg-hover); color: var(--text); border-color: var(--accent); }
|
|
327
|
+
.agent-detail-toggle.active { background: var(--accent-light); color: var(--accent); border-color: var(--accent); }
|
|
328
|
+
#page-agent-detail { display: none; flex-direction: column; height: 100vh; }
|
|
329
|
+
#page-agent-detail.active { display: flex; }
|
|
330
|
+
.agent-chat-view { display: flex; flex-direction: column; flex: 1; min-height: 0; }
|
|
331
|
+
.agent-chat-messages { flex: 1; overflow-y: auto; padding: 24px 28px; display: flex; flex-direction: column; gap: 16px; }
|
|
332
|
+
.agent-chat-welcome { display: flex; flex-direction: column; align-items: center; justify-content: center; flex: 1; color: var(--text-dim); }
|
|
333
|
+
.agent-chat-input-bar { display: flex; gap: 12px; padding: 16px 28px; border-top: 1px solid var(--border); background: rgba(5,5,30,0.5); backdrop-filter: blur(10px); }
|
|
334
|
+
.agent-chat-input { flex: 1; background: var(--bg-input); border: 1px solid var(--border); border-radius: 12px; padding: 12px 16px; color: var(--text); font-size: 14px; resize: none; outline: none; font-family: var(--font); max-height: 120px; }
|
|
335
|
+
.agent-chat-input:focus { border-color: var(--accent); }
|
|
336
|
+
.agent-chat-send { padding: 12px 20px; border-radius: 12px; font-weight: 600; flex-shrink: 0; }
|
|
337
|
+
.agent-chat-msg { max-width: 75%; padding: 12px 16px; border-radius: 16px; font-size: 14px; line-height: 1.6; word-break: break-word; }
|
|
338
|
+
.agent-chat-msg.user { align-self: flex-end; background: var(--accent); color: #fff; border-bottom-right-radius: 4px; }
|
|
339
|
+
.agent-chat-msg.assistant { align-self: flex-start; background: var(--bg-card); border: 1px solid var(--border); border-bottom-left-radius: 4px; }
|
|
340
|
+
.agent-settings-view { flex: 1; display: flex; flex-direction: column; min-height: 0; }
|
|
341
|
+
.agent-settings-tabs { display: flex; gap: 4px; padding: 16px 28px 0; border-bottom: 1px solid var(--border); overflow-x: auto; flex-shrink: 0; }
|
|
342
|
+
.agent-tab { padding: 10px 16px; border-radius: 10px 10px 0 0; cursor: pointer; color: var(--text-muted); font-size: 13px; white-space: nowrap; transition: all 0.15s; border-bottom: 2px solid transparent; }
|
|
343
|
+
.agent-tab:hover { color: var(--text); background: var(--bg-hover); }
|
|
344
|
+
.agent-tab.active { color: var(--accent); border-bottom-color: var(--accent); font-weight: 600; }
|
|
345
|
+
.agent-settings-content { flex: 1; overflow-y: auto; padding: 24px 28px; }
|
|
346
|
+
.agent-tab-panel { display: none; }
|
|
347
|
+
.agent-tab-panel.active { display: block; }
|
|
348
|
+
.agent-tab-panel h3 { margin-bottom: 12px; font-size: 18px; }
|
|
300
349
|
</style>
|
|
301
350
|
</head>
|
|
302
351
|
<body>
|
|
@@ -306,35 +355,34 @@
|
|
|
306
355
|
<nav class="sidebar">
|
|
307
356
|
<div class="sidebar-logo">⚡ <span>OPC Studio</span></div>
|
|
308
357
|
<div class="sidebar-nav">
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
<span class="icon">💬</span> Chat
|
|
314
|
-
</div>
|
|
315
|
-
<div class="nav-item" data-page="templates" onclick="navigate('templates')">
|
|
316
|
-
<span class="icon">👤</span> Templates
|
|
317
|
-
</div>
|
|
318
|
-
<div class="nav-item" data-page="skills" onclick="navigate('skills')">
|
|
319
|
-
<span class="icon">🧩</span> Skills Market
|
|
358
|
+
<!-- Section 1: My Agents -->
|
|
359
|
+
<div class="sidebar-section-title">🤖 我的 Agent</div>
|
|
360
|
+
<div class="agent-list-container" id="sidebar-agent-list">
|
|
361
|
+
<div style="padding: 12px 16px; color: var(--text-dim); font-size: 13px;">加载中...</div>
|
|
320
362
|
</div>
|
|
363
|
+
|
|
364
|
+
<!-- Section 2: Create -->
|
|
365
|
+
<div class="sidebar-divider"></div>
|
|
321
366
|
<div class="nav-item" data-page="create" onclick="navigate('create')">
|
|
322
|
-
<span class="icon"
|
|
323
|
-
</div>
|
|
324
|
-
<div class="nav-item" data-page="settings" onclick="currentSettingsTab='models';navigate('settings')">
|
|
325
|
-
<span class="icon">🤖</span> Models
|
|
326
|
-
</div>
|
|
327
|
-
<div class="nav-item" data-page="settings" onclick="currentSettingsTab='channels';navigate('settings')">
|
|
328
|
-
<span class="icon">📡</span> Channels
|
|
329
|
-
</div>
|
|
330
|
-
<div class="nav-item" data-page="settings" onclick="currentSettingsTab='memory';navigate('settings')">
|
|
331
|
-
<span class="icon">🧠</span> Memory
|
|
332
|
-
</div>
|
|
333
|
-
<div class="nav-item" data-page="settings" onclick="navigate('settings')">
|
|
334
|
-
<span class="icon">⚙️</span> Settings
|
|
367
|
+
<span class="icon">➕</span> 新建 Agent
|
|
335
368
|
</div>
|
|
336
|
-
|
|
337
|
-
|
|
369
|
+
|
|
370
|
+
<!-- Section 3: Global Config -->
|
|
371
|
+
<div class="sidebar-bottom">
|
|
372
|
+
<div class="sidebar-divider"></div>
|
|
373
|
+
<div class="sidebar-section-title">⚙️ 全局配置</div>
|
|
374
|
+
<div class="nav-item" data-page="global-runtime" onclick="navigate('global-runtime')">
|
|
375
|
+
<span class="icon">🚀</span> Runtime
|
|
376
|
+
</div>
|
|
377
|
+
<div class="nav-item" data-page="global-models" onclick="navigate('global-models')">
|
|
378
|
+
<span class="icon">🧠</span> Models
|
|
379
|
+
</div>
|
|
380
|
+
<div class="nav-item" data-page="global-memory" onclick="navigate('global-memory')">
|
|
381
|
+
<span class="icon">💾</span> Memory
|
|
382
|
+
</div>
|
|
383
|
+
<div class="nav-item" data-page="global-templates" onclick="navigate('global-templates')">
|
|
384
|
+
<span class="icon">📋</span> Templates
|
|
385
|
+
</div>
|
|
338
386
|
</div>
|
|
339
387
|
</div>
|
|
340
388
|
<div style="padding: 12px; border-top: 1px solid var(--border); font-size: 12px; color: var(--text-dim);">
|
|
@@ -371,6 +419,76 @@
|
|
|
371
419
|
</div>
|
|
372
420
|
</div>
|
|
373
421
|
|
|
422
|
+
<!-- Agent Detail Page -->
|
|
423
|
+
<div class="page" id="page-agent-detail">
|
|
424
|
+
<div class="agent-detail-header">
|
|
425
|
+
<div class="agent-detail-info">
|
|
426
|
+
<span class="agent-detail-icon" id="agent-detail-icon">🤖</span>
|
|
427
|
+
<h1 class="agent-detail-name" id="agent-detail-name">Agent</h1>
|
|
428
|
+
<span class="status-dot online" id="agent-detail-status"></span>
|
|
429
|
+
</div>
|
|
430
|
+
<button class="agent-detail-toggle" id="agent-detail-toggle" onclick="toggleAgentSettings()">⚙️</button>
|
|
431
|
+
</div>
|
|
432
|
+
|
|
433
|
+
<!-- Chat View (default) -->
|
|
434
|
+
<div class="agent-chat-view" id="agent-chat-view">
|
|
435
|
+
<div class="agent-chat-messages" id="agent-chat-messages">
|
|
436
|
+
<div class="agent-chat-welcome" id="agent-chat-welcome">
|
|
437
|
+
<div style="font-size: 48px; margin-bottom: 16px;">💬</div>
|
|
438
|
+
<div style="font-size: 18px; font-weight: 600; margin-bottom: 8px;">开始对话</div>
|
|
439
|
+
<div style="color: var(--text-muted); font-size: 14px;">向你的 Agent 发送第一条消息</div>
|
|
440
|
+
</div>
|
|
441
|
+
</div>
|
|
442
|
+
<div class="agent-chat-input-bar">
|
|
443
|
+
<textarea class="agent-chat-input" id="agent-chat-input" placeholder="输入消息..." rows="1" onkeydown="handleAgentChatKey(event)"></textarea>
|
|
444
|
+
<button class="btn btn-primary agent-chat-send" onclick="sendAgentChat()">发送</button>
|
|
445
|
+
</div>
|
|
446
|
+
</div>
|
|
447
|
+
|
|
448
|
+
<!-- Settings View (hidden) -->
|
|
449
|
+
<div class="agent-settings-view" id="agent-settings-view" style="display:none;">
|
|
450
|
+
<div class="agent-settings-tabs">
|
|
451
|
+
<div class="agent-tab active" data-atab="role" onclick="switchAgentTab('role')">👤 角色</div>
|
|
452
|
+
<div class="agent-tab" data-atab="models" onclick="switchAgentTab('models')">🤖 模型</div>
|
|
453
|
+
<div class="agent-tab" data-atab="channels" onclick="switchAgentTab('channels')">📡 渠道</div>
|
|
454
|
+
<div class="agent-tab" data-atab="memory" onclick="switchAgentTab('memory')">🧠 记忆</div>
|
|
455
|
+
<div class="agent-tab" data-atab="skills" onclick="switchAgentTab('skills')">🧩 技能</div>
|
|
456
|
+
<div class="agent-tab" data-atab="schedules" onclick="switchAgentTab('schedules')">⏰ 定时</div>
|
|
457
|
+
<div class="agent-tab" data-atab="usage" onclick="switchAgentTab('usage')">📊 统计</div>
|
|
458
|
+
</div>
|
|
459
|
+
<div class="agent-settings-content" id="agent-settings-content">
|
|
460
|
+
<div class="agent-tab-panel active" id="atab-role">
|
|
461
|
+
<h3>角色配置</h3>
|
|
462
|
+
<p style="color:var(--text-muted)">Agent 角色和人设配置(即将上线)</p>
|
|
463
|
+
</div>
|
|
464
|
+
<div class="agent-tab-panel" id="atab-models">
|
|
465
|
+
<h3>模型配置</h3>
|
|
466
|
+
<p style="color:var(--text-muted)">Agent 使用的模型配置(即将上线)</p>
|
|
467
|
+
</div>
|
|
468
|
+
<div class="agent-tab-panel" id="atab-channels">
|
|
469
|
+
<h3>渠道配置</h3>
|
|
470
|
+
<p style="color:var(--text-muted)">Agent 接入的渠道配置(即将上线)</p>
|
|
471
|
+
</div>
|
|
472
|
+
<div class="agent-tab-panel" id="atab-memory">
|
|
473
|
+
<h3>记忆管理</h3>
|
|
474
|
+
<p style="color:var(--text-muted)">Agent 记忆和知识库(即将上线)</p>
|
|
475
|
+
</div>
|
|
476
|
+
<div class="agent-tab-panel" id="atab-skills">
|
|
477
|
+
<h3>技能配置</h3>
|
|
478
|
+
<p style="color:var(--text-muted)">Agent 已安装的技能(即将上线)</p>
|
|
479
|
+
</div>
|
|
480
|
+
<div class="agent-tab-panel" id="atab-schedules">
|
|
481
|
+
<h3>定时任务</h3>
|
|
482
|
+
<p style="color:var(--text-muted)">Agent 定时任务配置(即将上线)</p>
|
|
483
|
+
</div>
|
|
484
|
+
<div class="agent-tab-panel" id="atab-usage">
|
|
485
|
+
<h3>用量统计</h3>
|
|
486
|
+
<p style="color:var(--text-muted)">Agent 使用量和成本(即将上线)</p>
|
|
487
|
+
</div>
|
|
488
|
+
</div>
|
|
489
|
+
</div>
|
|
490
|
+
</div>
|
|
491
|
+
|
|
374
492
|
<!-- Templates Page -->
|
|
375
493
|
<div class="page" id="page-templates">
|
|
376
494
|
<h1 class="page-title">Template Market</h1>
|
|
@@ -954,6 +1072,8 @@
|
|
|
954
1072
|
const parts = path.split('/').filter(Boolean);
|
|
955
1073
|
if (parts[0] === 'chat' && parts[1]) {
|
|
956
1074
|
openChat(parts[1]);
|
|
1075
|
+
} else if (parts[0] === 'agent' && parts[1]) {
|
|
1076
|
+
loadSidebarAgents().then(() => navigateToAgent(parts[1]));
|
|
957
1077
|
} else if (parts[0] === 'settings') {
|
|
958
1078
|
if (parts[1]) currentSettingsTab = parts[1];
|
|
959
1079
|
navigate('settings');
|
|
@@ -995,16 +1115,152 @@
|
|
|
995
1115
|
} catch(e) { console.error('Failed to load agents:', e); }
|
|
996
1116
|
}
|
|
997
1117
|
|
|
998
|
-
// ===
|
|
1118
|
+
// === Sidebar Agents ===
|
|
1119
|
+
let selectedAgentId = null;
|
|
1120
|
+
|
|
1121
|
+
async function loadSidebarAgents() {
|
|
1122
|
+
try {
|
|
1123
|
+
const res = await fetch('/api/agents');
|
|
1124
|
+
const data = await res.json();
|
|
1125
|
+
const agents = data.agents || data || [];
|
|
1126
|
+
window._sidebarAgents = agents;
|
|
1127
|
+
const container = document.getElementById('sidebar-agent-list');
|
|
1128
|
+
if (!agents.length) {
|
|
1129
|
+
container.innerHTML = '<div style="padding: 12px 16px; color: var(--text-dim); font-size: 13px;">暂无 Agent</div>';
|
|
1130
|
+
return;
|
|
1131
|
+
}
|
|
1132
|
+
container.innerHTML = agents.map(a => {
|
|
1133
|
+
const status = (a.status || 'offline').toLowerCase();
|
|
1134
|
+
const icon = a.emoji || a.icon || '🤖';
|
|
1135
|
+
const name = a.name || a.id;
|
|
1136
|
+
return `<div class="agent-list-item${selectedAgentId === a.id ? ' active' : ''}" data-agent-id="${a.id}" onclick="navigateToAgent('${a.id}')">
|
|
1137
|
+
<span class="agent-icon">${icon}</span>
|
|
1138
|
+
<span class="agent-name">${name}</span>
|
|
1139
|
+
<span class="status-dot ${status}"></span>
|
|
1140
|
+
</div>`;
|
|
1141
|
+
}).join('');
|
|
1142
|
+
} catch(e) {
|
|
1143
|
+
console.error('Failed to load sidebar agents:', e);
|
|
1144
|
+
const container = document.getElementById('sidebar-agent-list');
|
|
1145
|
+
if (container) container.innerHTML = '<div style="padding: 12px 16px; color: var(--text-dim); font-size: 13px;">加载失败</div>';
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
function navigateToAgent(agentId) {
|
|
1150
|
+
selectedAgentId = agentId;
|
|
1151
|
+
// Update sidebar active state
|
|
1152
|
+
document.querySelectorAll('.agent-list-item').forEach(el => el.classList.remove('active'));
|
|
1153
|
+
document.querySelectorAll('.nav-item').forEach(n => n.classList.remove('active'));
|
|
1154
|
+
const item = document.querySelector(`.agent-list-item[data-agent-id="${agentId}"]`);
|
|
1155
|
+
if (item) item.classList.add('active');
|
|
1156
|
+
|
|
1157
|
+
// Find agent data
|
|
1158
|
+
const agent = (window._sidebarAgents || []).find(a => a.id === agentId) || { id: agentId, name: agentId };
|
|
1159
|
+
document.getElementById('agent-detail-icon').textContent = agent.emoji || agent.icon || '🤖';
|
|
1160
|
+
document.getElementById('agent-detail-name').textContent = agent.name || agentId;
|
|
1161
|
+
const statusDot = document.getElementById('agent-detail-status');
|
|
1162
|
+
const status = (agent.status || 'offline').toLowerCase();
|
|
1163
|
+
statusDot.className = 'status-dot ' + status;
|
|
1164
|
+
|
|
1165
|
+
// Reset to chat view
|
|
1166
|
+
document.getElementById('agent-chat-view').style.display = '';
|
|
1167
|
+
document.getElementById('agent-settings-view').style.display = 'none';
|
|
1168
|
+
document.getElementById('agent-detail-toggle').classList.remove('active');
|
|
1169
|
+
document.getElementById('agent-chat-messages').innerHTML = document.querySelector('.agent-chat-welcome').outerHTML || '<div class="agent-chat-welcome"><div style="font-size:48px;margin-bottom:16px">💬</div><div style="font-size:18px;font-weight:600;margin-bottom:8px">开始对话</div><div style="color:var(--text-muted);font-size:14px">向你的 Agent 发送第一条消息</div></div>';
|
|
1170
|
+
document.getElementById('agent-chat-input').value = '';
|
|
1171
|
+
|
|
1172
|
+
// Show agent detail page
|
|
1173
|
+
document.querySelectorAll('.page, .chat-container').forEach(p => { p.classList.remove('active'); p.style.display = ''; });
|
|
1174
|
+
document.getElementById('page-agent-detail').classList.add('active');
|
|
1175
|
+
location.hash = `/agent/${agentId}`;
|
|
1176
|
+
toggleSidebar(false);
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
function toggleAgentSettings() {
|
|
1180
|
+
const chatView = document.getElementById('agent-chat-view');
|
|
1181
|
+
const settingsView = document.getElementById('agent-settings-view');
|
|
1182
|
+
const toggleBtn = document.getElementById('agent-detail-toggle');
|
|
1183
|
+
const showSettings = chatView.style.display !== 'none';
|
|
1184
|
+
chatView.style.display = showSettings ? 'none' : '';
|
|
1185
|
+
settingsView.style.display = showSettings ? '' : 'none';
|
|
1186
|
+
toggleBtn.classList.toggle('active', showSettings);
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
function switchAgentTab(tab) {
|
|
1190
|
+
document.querySelectorAll('.agent-tab').forEach(t => t.classList.remove('active'));
|
|
1191
|
+
document.querySelector(`.agent-tab[data-atab="${tab}"]`)?.classList.add('active');
|
|
1192
|
+
document.querySelectorAll('.agent-tab-panel').forEach(p => p.classList.remove('active'));
|
|
1193
|
+
document.getElementById(`atab-${tab}`)?.classList.add('active');
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
let agentChatHistory = [];
|
|
1197
|
+
|
|
1198
|
+
async function sendAgentChat() {
|
|
1199
|
+
const input = document.getElementById('agent-chat-input');
|
|
1200
|
+
const msg = input.value.trim();
|
|
1201
|
+
if (!msg || !selectedAgentId) return;
|
|
1202
|
+
input.value = '';
|
|
1203
|
+
input.style.height = 'auto';
|
|
1204
|
+
|
|
1205
|
+
const messagesEl = document.getElementById('agent-chat-messages');
|
|
1206
|
+
// Remove welcome screen
|
|
1207
|
+
const welcome = messagesEl.querySelector('.agent-chat-welcome');
|
|
1208
|
+
if (welcome) welcome.remove();
|
|
1209
|
+
|
|
1210
|
+
// Add user message
|
|
1211
|
+
const userDiv = document.createElement('div');
|
|
1212
|
+
userDiv.className = 'agent-chat-msg user';
|
|
1213
|
+
userDiv.textContent = msg;
|
|
1214
|
+
messagesEl.appendChild(userDiv);
|
|
1215
|
+
messagesEl.scrollTop = messagesEl.scrollHeight;
|
|
1216
|
+
|
|
1217
|
+
agentChatHistory.push({ role: 'user', content: msg });
|
|
1218
|
+
|
|
1219
|
+
// Send to API
|
|
1220
|
+
try {
|
|
1221
|
+
const res = await fetch(`/api/agents/${selectedAgentId}/chat`, {
|
|
1222
|
+
method: 'POST',
|
|
1223
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1224
|
+
body: JSON.stringify({ message: msg, history: agentChatHistory })
|
|
1225
|
+
});
|
|
1226
|
+
const data = await res.json();
|
|
1227
|
+
const reply = data.reply || data.message || data.content || JSON.stringify(data);
|
|
1228
|
+
const assistantDiv = document.createElement('div');
|
|
1229
|
+
assistantDiv.className = 'agent-chat-msg assistant';
|
|
1230
|
+
assistantDiv.textContent = reply;
|
|
1231
|
+
messagesEl.appendChild(assistantDiv);
|
|
1232
|
+
agentChatHistory.push({ role: 'assistant', content: reply });
|
|
1233
|
+
} catch(e) {
|
|
1234
|
+
const errDiv = document.createElement('div');
|
|
1235
|
+
errDiv.className = 'agent-chat-msg assistant';
|
|
1236
|
+
errDiv.style.borderColor = 'var(--red)';
|
|
1237
|
+
errDiv.textContent = `⚠️ 发送失败: ${e.message}`;
|
|
1238
|
+
messagesEl.appendChild(errDiv);
|
|
1239
|
+
}
|
|
1240
|
+
messagesEl.scrollTop = messagesEl.scrollHeight;
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
function handleAgentChatKey(e) {
|
|
1244
|
+
if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendAgentChat(); }
|
|
1245
|
+
// Auto-resize textarea
|
|
1246
|
+
e.target.style.height = 'auto';
|
|
1247
|
+
e.target.style.height = Math.min(e.target.scrollHeight, 120) + 'px';
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
// === Navigation ===
|
|
999
1251
|
function navigate(page) {
|
|
1000
1252
|
document.querySelectorAll('.page, .chat-container').forEach(p => { p.classList.remove('active'); p.style.display = ''; });
|
|
1001
1253
|
document.querySelectorAll('.nav-item').forEach(n => n.classList.remove('active'));
|
|
1002
1254
|
const navItem = document.querySelector(`.nav-item[data-page="${page}"]`);
|
|
1003
1255
|
if (navItem) navItem.classList.add('active');
|
|
1004
1256
|
|
|
1005
|
-
if (page === 'dashboard') { loadAgents(); loadHealthDashboard(); }
|
|
1257
|
+
if (page === 'dashboard') { loadAgents(); loadHealthDashboard(); loadSidebarAgents(); }
|
|
1006
1258
|
if (page === 'create') { renderWizard(); renderWizardTemplates(); }
|
|
1007
1259
|
if (page === 'settings') { showSettings(currentSettingsTab || 'models'); }
|
|
1260
|
+
if (page === 'global-runtime') { currentSettingsTab='status'; showSettings('status'); showPage('settings'); return; }
|
|
1261
|
+
if (page === 'global-models') { currentSettingsTab='models'; showSettings('models'); showPage('settings'); return; }
|
|
1262
|
+
if (page === 'global-memory') { currentSettingsTab='memory'; showSettings('memory'); showPage('settings'); return; }
|
|
1263
|
+
if (page === 'global-templates') { navigate('templates'); return; }
|
|
1008
1264
|
if (page === 'schedules') { loadSchedules(); }
|
|
1009
1265
|
if (page === 'skills') { loadSkillsMarketplace(); }
|
|
1010
1266
|
|