opc-agent 4.0.33 → 4.0.35

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.
@@ -297,6 +297,56 @@
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
+ .pattern-card.active { border-color: var(--accent); box-shadow: var(--glow-sm); }
305
+ .agent-list-container { overflow-y: auto; flex: 1; min-height: 0; }
306
+ .agent-list-item {
307
+ display: flex; align-items: center; gap: 10px; padding: 10px 16px; border-radius: 12px;
308
+ cursor: pointer; color: var(--text-muted); transition: all 0.2s ease; font-size: 14px; margin-bottom: 2px; position: relative;
309
+ }
310
+ .agent-list-item:hover { background: var(--bg-hover); color: var(--text); transform: translateX(4px); }
311
+ .agent-list-item.active { background: var(--accent-light); color: #fff; font-weight: 600; box-shadow: var(--glow-sm); border: 1px solid var(--border); }
312
+ .agent-list-item .agent-icon { width: 24px; text-align: center; font-size: 16px; }
313
+ .agent-list-item .agent-name { flex: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
314
+ .agent-list-item .status-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
315
+ .agent-list-item .status-dot.online { background: var(--green); box-shadow: 0 0 6px var(--green); }
316
+ .agent-list-item .status-dot.offline { background: var(--text-dim); }
317
+ .agent-list-item .status-dot.error { background: var(--red); box-shadow: 0 0 6px var(--red); }
318
+ .sidebar-bottom { margin-top: auto; flex-shrink: 0; }
319
+ .sidebar-nav { display: flex; flex-direction: column; flex: 1; min-height: 0; overflow: hidden; }
320
+
321
+ /* Agent Detail Page */
322
+ .agent-detail-header { display: flex; justify-content: space-between; align-items: center; padding: 20px 28px; border-bottom: 1px solid var(--border); }
323
+ .agent-detail-info { display: flex; align-items: center; gap: 12px; }
324
+ .agent-detail-icon { font-size: 28px; }
325
+ .agent-detail-name { font-size: 20px; font-weight: 700; margin: 0; }
326
+ .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; }
327
+ .agent-detail-toggle:hover { background: var(--bg-hover); color: var(--text); border-color: var(--accent); }
328
+ .agent-detail-toggle.active { background: var(--accent-light); color: var(--accent); border-color: var(--accent); }
329
+ #page-agent-detail { display: none; flex-direction: column; height: 100vh; }
330
+ #page-agent-detail.active { display: flex; }
331
+ .agent-chat-view { display: flex; flex-direction: column; flex: 1; min-height: 0; }
332
+ .agent-chat-messages { flex: 1; overflow-y: auto; padding: 24px 28px; display: flex; flex-direction: column; gap: 16px; }
333
+ .agent-chat-welcome { display: flex; flex-direction: column; align-items: center; justify-content: center; flex: 1; color: var(--text-dim); }
334
+ .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); }
335
+ .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; }
336
+ .agent-chat-input:focus { border-color: var(--accent); }
337
+ .agent-chat-send { padding: 12px 20px; border-radius: 12px; font-weight: 600; flex-shrink: 0; }
338
+ .agent-chat-msg { max-width: 75%; padding: 12px 16px; border-radius: 16px; font-size: 14px; line-height: 1.6; word-break: break-word; }
339
+ .agent-chat-msg.user { align-self: flex-end; background: var(--accent); color: #fff; border-bottom-right-radius: 4px; }
340
+ .agent-chat-msg.assistant { align-self: flex-start; background: var(--bg-card); border: 1px solid var(--border); border-bottom-left-radius: 4px; }
341
+ .agent-settings-view { flex: 1; display: flex; flex-direction: column; min-height: 0; }
342
+ .agent-settings-tabs { display: flex; gap: 4px; padding: 16px 28px 0; border-bottom: 1px solid var(--border); overflow-x: auto; flex-shrink: 0; }
343
+ .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; }
344
+ .agent-tab:hover { color: var(--text); background: var(--bg-hover); }
345
+ .agent-tab.active { color: var(--accent); border-bottom-color: var(--accent); font-weight: 600; }
346
+ .agent-settings-content { flex: 1; overflow-y: auto; padding: 24px 28px; }
347
+ .agent-tab-panel { display: none; }
348
+ .agent-tab-panel.active { display: block; }
349
+ .agent-tab-panel h3 { margin-bottom: 12px; font-size: 18px; }
300
350
  </style>
301
351
  </head>
302
352
  <body>
@@ -306,35 +356,44 @@
306
356
  <nav class="sidebar">
307
357
  <div class="sidebar-logo">⚡ <span>OPC Studio</span></div>
308
358
  <div class="sidebar-nav">
309
- <div class="nav-item active" data-page="dashboard" onclick="navigate('dashboard')">
310
- <span class="icon">🏠</span> Dashboard
311
- </div>
312
- <div class="nav-item" data-page="chat" onclick="openLastChat()">
313
- <span class="icon">💬</span> Chat
359
+ <!-- Section 1: My Agents -->
360
+ <div class="sidebar-section-title">🤖 我的 Agent</div>
361
+ <div class="agent-list-container" id="sidebar-agent-list">
362
+ <div style="padding: 12px 16px; color: var(--text-dim); font-size: 13px;">加载中...</div>
314
363
  </div>
315
- <div class="nav-item" data-page="templates" onclick="navigate('templates')">
316
- <span class="icon">👤</span> Templates
364
+
365
+ <!-- Section 2: Collaboration Groups -->
366
+ <div class="sidebar-divider"></div>
367
+ <div class="sidebar-section-title">👥 协作群组</div>
368
+ <div class="agent-list-container" id="groups-list" style="max-height:120px;">
369
+ <!-- dynamically loaded -->
317
370
  </div>
318
- <div class="nav-item" data-page="skills" onclick="navigate('skills')">
319
- <span class="icon">🧩</span> Skills Market
371
+ <div class="nav-item" data-page="create-group" onclick="navigate('create-group')">
372
+ <span class="icon">➕</span> 新建群组
320
373
  </div>
374
+
375
+ <!-- Section 3: Create Agent -->
376
+ <div class="sidebar-divider"></div>
321
377
  <div class="nav-item" data-page="create" onclick="navigate('create')">
322
- <span class="icon">✨</span> Create Agent
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
378
+ <span class="icon">➕</span> 新建 Agent
329
379
  </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
335
- </div>
336
- <div class="nav-item" data-page="schedules" onclick="navigate('schedules')">
337
- <span class="icon">⏰</span> Schedules
380
+
381
+ <!-- Section 3: Global Config -->
382
+ <div class="sidebar-bottom">
383
+ <div class="sidebar-divider"></div>
384
+ <div class="sidebar-section-title">⚙️ 全局配置</div>
385
+ <div class="nav-item" data-page="global-runtime" onclick="navigate('global-runtime')">
386
+ <span class="icon">🚀</span> Runtime
387
+ </div>
388
+ <div class="nav-item" data-page="global-models" onclick="navigate('global-models')">
389
+ <span class="icon">🧠</span> Models
390
+ </div>
391
+ <div class="nav-item" data-page="global-memory" onclick="navigate('global-memory')">
392
+ <span class="icon">💾</span> Memory
393
+ </div>
394
+ <div class="nav-item" data-page="global-templates" onclick="navigate('global-templates')">
395
+ <span class="icon">📋</span> Templates
396
+ </div>
338
397
  </div>
339
398
  </div>
340
399
  <div style="padding: 12px; border-top: 1px solid var(--border); font-size: 12px; color: var(--text-dim);">
@@ -371,7 +430,120 @@
371
430
  </div>
372
431
  </div>
373
432
 
433
+ <!-- Agent Detail Page -->
434
+ <div class="page" id="page-agent-detail">
435
+ <div class="agent-detail-header">
436
+ <div class="agent-detail-info">
437
+ <span class="agent-detail-icon" id="agent-detail-icon">🤖</span>
438
+ <h1 class="agent-detail-name" id="agent-detail-name">Agent</h1>
439
+ <span class="status-dot online" id="agent-detail-status"></span>
440
+ </div>
441
+ <button class="agent-detail-toggle" id="agent-detail-toggle" onclick="toggleAgentSettings()">⚙️</button>
442
+ </div>
443
+
444
+ <!-- Chat View (default) -->
445
+ <div class="agent-chat-view" id="agent-chat-view">
446
+ <div class="agent-chat-messages" id="agent-chat-messages">
447
+ <div class="agent-chat-welcome" id="agent-chat-welcome">
448
+ <div style="font-size: 48px; margin-bottom: 16px;">💬</div>
449
+ <div style="font-size: 18px; font-weight: 600; margin-bottom: 8px;">开始对话</div>
450
+ <div style="color: var(--text-muted); font-size: 14px;">向你的 Agent 发送第一条消息</div>
451
+ </div>
452
+ </div>
453
+ <div class="agent-chat-input-bar">
454
+ <textarea class="agent-chat-input" id="agent-chat-input" placeholder="输入消息..." rows="1" onkeydown="handleAgentChatKey(event)"></textarea>
455
+ <button class="btn btn-primary agent-chat-send" onclick="sendAgentChat()">发送</button>
456
+ </div>
457
+ </div>
458
+
459
+ <!-- Settings View (hidden) -->
460
+ <div class="agent-settings-view" id="agent-settings-view" style="display:none;">
461
+ <div class="agent-settings-tabs">
462
+ <div class="agent-tab active" data-atab="role" onclick="switchAgentTab('role')">👤 角色</div>
463
+ <div class="agent-tab" data-atab="models" onclick="switchAgentTab('models')">🤖 模型</div>
464
+ <div class="agent-tab" data-atab="channels" onclick="switchAgentTab('channels')">📡 渠道</div>
465
+ <div class="agent-tab" data-atab="memory" onclick="switchAgentTab('memory')">🧠 记忆</div>
466
+ <div class="agent-tab" data-atab="skills" onclick="switchAgentTab('skills')">🧩 技能</div>
467
+ <div class="agent-tab" data-atab="schedules" onclick="switchAgentTab('schedules')">⏰ 定时</div>
468
+ <div class="agent-tab" data-atab="usage" onclick="switchAgentTab('usage')">📊 统计</div>
469
+ </div>
470
+ <div class="agent-settings-content" id="agent-settings-content">
471
+ <div class="agent-tab-panel active" id="atab-role">
472
+ <h3>角色配置</h3>
473
+ <p style="color:var(--text-muted)">Agent 角色和人设配置(即将上线)</p>
474
+ </div>
475
+ <div class="agent-tab-panel" id="atab-models">
476
+ <h3>模型配置</h3>
477
+ <p style="color:var(--text-muted)">Agent 使用的模型配置(即将上线)</p>
478
+ </div>
479
+ <div class="agent-tab-panel" id="atab-channels">
480
+ <h3>渠道配置</h3>
481
+ <p style="color:var(--text-muted)">Agent 接入的渠道配置(即将上线)</p>
482
+ </div>
483
+ <div class="agent-tab-panel" id="atab-memory">
484
+ <h3>记忆管理</h3>
485
+ <p style="color:var(--text-muted)">Agent 记忆和知识库(即将上线)</p>
486
+ </div>
487
+ <div class="agent-tab-panel" id="atab-skills">
488
+ <h3>技能配置</h3>
489
+ <p style="color:var(--text-muted)">Agent 已安装的技能(即将上线)</p>
490
+ </div>
491
+ <div class="agent-tab-panel" id="atab-schedules">
492
+ <h3>定时任务</h3>
493
+ <p style="color:var(--text-muted)">Agent 定时任务配置(即将上线)</p>
494
+ </div>
495
+ <div class="agent-tab-panel" id="atab-usage">
496
+ <h3>用量统计</h3>
497
+ <p style="color:var(--text-muted)">Agent 使用量和成本(即将上线)</p>
498
+ </div>
499
+ </div>
500
+ </div>
501
+ </div>
502
+
374
503
  <!-- Templates Page -->
504
+ <!-- Create Group Page -->
505
+ <div class="page" id="page-create-group">
506
+ <h1 class="page-title">新建协作群组</h1>
507
+ <p class="page-subtitle">选择协作模式,拉入 Agent,开始多角色协作</p>
508
+ <div class="card" style="max-width:600px;">
509
+ <div class="label">群组名称</div>
510
+ <input class="input" id="group-name" placeholder="例如:产品讨论组">
511
+ <div class="label" style="margin-top:16px;">协作模式</div>
512
+ <div class="card-grid" style="grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap:12px; margin-bottom:16px;">
513
+ <div class="card pattern-card active" onclick="selectPattern('debate')" id="pat-debate" style="padding:14px;cursor:pointer;text-align:center;">
514
+ <div style="font-size:24px;margin-bottom:6px;">⚔️</div>
515
+ <div style="font-size:14px;font-weight:600;">Debate</div>
516
+ <div style="font-size:12px;color:var(--text-muted);">正反方辩论 + 裁判总结</div>
517
+ </div>
518
+ <div class="card pattern-card" onclick="selectPattern('voting')" id="pat-voting" style="padding:14px;cursor:pointer;text-align:center;">
519
+ <div style="font-size:24px;margin-bottom:6px;">🗳️</div>
520
+ <div style="font-size:14px;font-weight:600;">Voting</div>
521
+ <div style="font-size:12px;color:var(--text-muted);">多 Agent 投票表决</div>
522
+ </div>
523
+ <div class="card pattern-card" onclick="selectPattern('pipeline')" id="pat-pipeline" style="padding:14px;cursor:pointer;text-align:center;">
524
+ <div style="font-size:24px;margin-bottom:6px;">🔗</div>
525
+ <div style="font-size:14px;font-weight:600;">Pipeline</div>
526
+ <div style="font-size:12px;color:var(--text-muted);">链式处理,逐步传递</div>
527
+ </div>
528
+ <div class="card pattern-card" onclick="selectPattern('hierarchy')" id="pat-hierarchy" style="padding:14px;cursor:pointer;text-align:center;">
529
+ <div style="font-size:24px;margin-bottom:6px;">🏛️</div>
530
+ <div style="font-size:14px;font-weight:600;">Hierarchy</div>
531
+ <div style="font-size:12px;color:var(--text-muted);">上下级分配任务</div>
532
+ </div>
533
+ <div class="card pattern-card" onclick="selectPattern('shared-memory')" id="pat-shared-memory" style="padding:14px;cursor:pointer;text-align:center;">
534
+ <div style="font-size:24px;margin-bottom:6px;">🧠</div>
535
+ <div style="font-size:14px;font-weight:600;">Shared Memory</div>
536
+ <div style="font-size:12px;color:var(--text-muted);">共享记忆空间协作</div>
537
+ </div>
538
+ </div>
539
+ <div class="label">选择 Agent 成员</div>
540
+ <div id="group-agent-select" style="margin-bottom:16px;">
541
+ <p style="color:var(--text-muted);font-size:13px;">请先创建 Agent,再拉入群组</p>
542
+ </div>
543
+ <button class="btn btn-primary" onclick="createGroup()">✨ 创建群组</button>
544
+ </div>
545
+ </div>
546
+
375
547
  <div class="page" id="page-templates">
376
548
  <h1 class="page-title">Template Market</h1>
377
549
  <p class="page-subtitle">Browse 100+ ready-to-use agent templates across 19 industries</p>
@@ -944,6 +1116,7 @@
944
1116
  // === Init ===
945
1117
  async function init() {
946
1118
  await Promise.all([loadTemplates(), loadAgents()]);
1119
+ loadSidebarGroups();
947
1120
  handleRoute();
948
1121
  window.addEventListener('popstate', handleRoute);
949
1122
  checkFirstRun();
@@ -954,6 +1127,8 @@
954
1127
  const parts = path.split('/').filter(Boolean);
955
1128
  if (parts[0] === 'chat' && parts[1]) {
956
1129
  openChat(parts[1]);
1130
+ } else if (parts[0] === 'agent' && parts[1]) {
1131
+ loadSidebarAgents().then(() => navigateToAgent(parts[1]));
957
1132
  } else if (parts[0] === 'settings') {
958
1133
  if (parts[1]) currentSettingsTab = parts[1];
959
1134
  navigate('settings');
@@ -995,16 +1170,189 @@
995
1170
  } catch(e) { console.error('Failed to load agents:', e); }
996
1171
  }
997
1172
 
998
- // === Navigation ===
1173
+ // === Sidebar Agents ===
1174
+ let selectedAgentId = null;
1175
+
1176
+ async function loadSidebarAgents() {
1177
+ try {
1178
+ const res = await fetch('/api/agents');
1179
+ const data = await res.json();
1180
+ const agents = data.agents || data || [];
1181
+ window._sidebarAgents = agents;
1182
+ const container = document.getElementById('sidebar-agent-list');
1183
+ if (!agents.length) {
1184
+ container.innerHTML = '<div style="padding: 12px 16px; color: var(--text-dim); font-size: 13px;">暂无 Agent</div>';
1185
+ return;
1186
+ }
1187
+ container.innerHTML = agents.map(a => {
1188
+ const status = (a.status || 'offline').toLowerCase();
1189
+ const icon = a.emoji || a.icon || '🤖';
1190
+ const name = a.name || a.id;
1191
+ return `<div class="agent-list-item${selectedAgentId === a.id ? ' active' : ''}" data-agent-id="${a.id}" onclick="navigateToAgent('${a.id}')">
1192
+ <span class="agent-icon">${icon}</span>
1193
+ <span class="agent-name">${name}</span>
1194
+ <span class="status-dot ${status}"></span>
1195
+ </div>`;
1196
+ }).join('');
1197
+ } catch(e) {
1198
+ console.error('Failed to load sidebar agents:', e);
1199
+ const container = document.getElementById('sidebar-agent-list');
1200
+ if (container) container.innerHTML = '<div style="padding: 12px 16px; color: var(--text-dim); font-size: 13px;">加载失败</div>';
1201
+ }
1202
+ }
1203
+
1204
+ // --- Collaboration Groups ---
1205
+ let selectedPattern = 'debate';
1206
+ function selectPattern(pat) {
1207
+ selectedPattern = pat;
1208
+ document.querySelectorAll('.pattern-card').forEach(c => c.classList.remove('active'));
1209
+ const el = document.getElementById('pat-' + pat);
1210
+ if (el) el.classList.add('active');
1211
+ }
1212
+ async function loadGroupAgentSelect() {
1213
+ try {
1214
+ const res = await fetch('/api/agents');
1215
+ const data = await res.json();
1216
+ const agents = data.agents || data || [];
1217
+ const container = document.getElementById('group-agent-select');
1218
+ if (!agents.length) {
1219
+ container.innerHTML = '<p style="color:var(--text-muted);font-size:13px;">请先创建 Agent,再拉入群组</p>';
1220
+ return;
1221
+ }
1222
+ container.innerHTML = agents.map(a => `<label style="display:flex;align-items:center;gap:8px;padding:6px 0;cursor:pointer;"><input type="checkbox" class="group-agent-cb" value="${a.id}"> <span>${a.templateIcon || a.icon || '🤖'}</span> <span style="font-size:14px;">${a.name}</span></label>`).join('');
1223
+ } catch(e) { console.error('loadGroupAgentSelect error', e); }
1224
+ }
1225
+ async function createGroup() {
1226
+ const name = document.getElementById('group-name').value.trim();
1227
+ if (!name) { alert('请输入群组名称'); return; }
1228
+ const members = [...document.querySelectorAll('.group-agent-cb:checked')].map(cb => cb.value);
1229
+ if (members.length < 2) { alert('至少选择 2 个 Agent'); return; }
1230
+ // TODO: POST to /api/groups
1231
+ alert('群组 "' + name + '" 创建成功!模式: ' + selectedPattern + ', 成员: ' + members.length + ' 个 Agent');
1232
+ navigate('dashboard');
1233
+ }
1234
+ async function loadSidebarGroups() {
1235
+ // TODO: fetch /api/groups and render
1236
+ const container = document.getElementById('groups-list');
1237
+ if (container) container.innerHTML = '<div style="padding:6px 16px;color:var(--text-dim);font-size:12px;">暂无群组</div>';
1238
+ }
1239
+
1240
+ function navigateToAgent(agentId) {
1241
+ selectedAgentId = agentId;
1242
+ // Update sidebar active state
1243
+ document.querySelectorAll('.agent-list-item').forEach(el => el.classList.remove('active'));
1244
+ document.querySelectorAll('.nav-item').forEach(n => n.classList.remove('active'));
1245
+ const item = document.querySelector(`.agent-list-item[data-agent-id="${agentId}"]`);
1246
+ if (item) item.classList.add('active');
1247
+
1248
+ // Find agent data
1249
+ const agent = (window._sidebarAgents || []).find(a => a.id === agentId) || { id: agentId, name: agentId };
1250
+ document.getElementById('agent-detail-icon').textContent = agent.emoji || agent.icon || '🤖';
1251
+ document.getElementById('agent-detail-name').textContent = agent.name || agentId;
1252
+ const statusDot = document.getElementById('agent-detail-status');
1253
+ const status = (agent.status || 'offline').toLowerCase();
1254
+ statusDot.className = 'status-dot ' + status;
1255
+
1256
+ // Reset to chat view
1257
+ document.getElementById('agent-chat-view').style.display = '';
1258
+ document.getElementById('agent-settings-view').style.display = 'none';
1259
+ document.getElementById('agent-detail-toggle').classList.remove('active');
1260
+ 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>';
1261
+ document.getElementById('agent-chat-input').value = '';
1262
+
1263
+ // Show agent detail page
1264
+ document.querySelectorAll('.page, .chat-container').forEach(p => { p.classList.remove('active'); p.style.display = ''; });
1265
+ document.getElementById('page-agent-detail').classList.add('active');
1266
+ location.hash = `/agent/${agentId}`;
1267
+ toggleSidebar(false);
1268
+ }
1269
+
1270
+ function toggleAgentSettings() {
1271
+ const chatView = document.getElementById('agent-chat-view');
1272
+ const settingsView = document.getElementById('agent-settings-view');
1273
+ const toggleBtn = document.getElementById('agent-detail-toggle');
1274
+ const showSettings = chatView.style.display !== 'none';
1275
+ chatView.style.display = showSettings ? 'none' : '';
1276
+ settingsView.style.display = showSettings ? '' : 'none';
1277
+ toggleBtn.classList.toggle('active', showSettings);
1278
+ }
1279
+
1280
+ function switchAgentTab(tab) {
1281
+ document.querySelectorAll('.agent-tab').forEach(t => t.classList.remove('active'));
1282
+ document.querySelector(`.agent-tab[data-atab="${tab}"]`)?.classList.add('active');
1283
+ document.querySelectorAll('.agent-tab-panel').forEach(p => p.classList.remove('active'));
1284
+ document.getElementById(`atab-${tab}`)?.classList.add('active');
1285
+ }
1286
+
1287
+ let agentChatHistory = [];
1288
+
1289
+ async function sendAgentChat() {
1290
+ const input = document.getElementById('agent-chat-input');
1291
+ const msg = input.value.trim();
1292
+ if (!msg || !selectedAgentId) return;
1293
+ input.value = '';
1294
+ input.style.height = 'auto';
1295
+
1296
+ const messagesEl = document.getElementById('agent-chat-messages');
1297
+ // Remove welcome screen
1298
+ const welcome = messagesEl.querySelector('.agent-chat-welcome');
1299
+ if (welcome) welcome.remove();
1300
+
1301
+ // Add user message
1302
+ const userDiv = document.createElement('div');
1303
+ userDiv.className = 'agent-chat-msg user';
1304
+ userDiv.textContent = msg;
1305
+ messagesEl.appendChild(userDiv);
1306
+ messagesEl.scrollTop = messagesEl.scrollHeight;
1307
+
1308
+ agentChatHistory.push({ role: 'user', content: msg });
1309
+
1310
+ // Send to API
1311
+ try {
1312
+ const res = await fetch(`/api/agents/${selectedAgentId}/chat`, {
1313
+ method: 'POST',
1314
+ headers: { 'Content-Type': 'application/json' },
1315
+ body: JSON.stringify({ message: msg, history: agentChatHistory })
1316
+ });
1317
+ const data = await res.json();
1318
+ const reply = data.reply || data.message || data.content || JSON.stringify(data);
1319
+ const assistantDiv = document.createElement('div');
1320
+ assistantDiv.className = 'agent-chat-msg assistant';
1321
+ assistantDiv.textContent = reply;
1322
+ messagesEl.appendChild(assistantDiv);
1323
+ agentChatHistory.push({ role: 'assistant', content: reply });
1324
+ } catch(e) {
1325
+ const errDiv = document.createElement('div');
1326
+ errDiv.className = 'agent-chat-msg assistant';
1327
+ errDiv.style.borderColor = 'var(--red)';
1328
+ errDiv.textContent = `⚠️ 发送失败: ${e.message}`;
1329
+ messagesEl.appendChild(errDiv);
1330
+ }
1331
+ messagesEl.scrollTop = messagesEl.scrollHeight;
1332
+ }
1333
+
1334
+ function handleAgentChatKey(e) {
1335
+ if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendAgentChat(); }
1336
+ // Auto-resize textarea
1337
+ e.target.style.height = 'auto';
1338
+ e.target.style.height = Math.min(e.target.scrollHeight, 120) + 'px';
1339
+ }
1340
+
1341
+ // === Navigation ===
999
1342
  function navigate(page) {
1000
1343
  document.querySelectorAll('.page, .chat-container').forEach(p => { p.classList.remove('active'); p.style.display = ''; });
1001
1344
  document.querySelectorAll('.nav-item').forEach(n => n.classList.remove('active'));
1002
1345
  const navItem = document.querySelector(`.nav-item[data-page="${page}"]`);
1003
1346
  if (navItem) navItem.classList.add('active');
1004
1347
 
1005
- if (page === 'dashboard') { loadAgents(); loadHealthDashboard(); }
1348
+ if (page === 'dashboard') { loadAgents(); loadHealthDashboard(); loadSidebarAgents(); }
1006
1349
  if (page === 'create') { renderWizard(); renderWizardTemplates(); }
1007
1350
  if (page === 'settings') { showSettings(currentSettingsTab || 'models'); }
1351
+ if (page === 'global-runtime') { currentSettingsTab='status'; showSettings('status'); showPage('settings'); return; }
1352
+ if (page === 'global-models') { currentSettingsTab='models'; showSettings('models'); showPage('settings'); return; }
1353
+ if (page === 'global-memory') { currentSettingsTab='memory'; showSettings('memory'); showPage('settings'); return; }
1354
+ if (page === 'global-templates') { navigate('templates'); return; }
1355
+ if (page === 'create-group') { loadGroupAgentSelect(); }
1008
1356
  if (page === 'schedules') { loadSchedules(); }
1009
1357
  if (page === 'skills') { loadSkillsMarketplace(); }
1010
1358
 
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opc-agent",
3
- "version": "4.0.33",
3
+ "version": "4.0.35",
4
4
  "description": "Open Agent Framework — Build, test, and run AI Agents for business workstations",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -297,6 +297,56 @@
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
+ .pattern-card.active { border-color: var(--accent); box-shadow: var(--glow-sm); }
305
+ .agent-list-container { overflow-y: auto; flex: 1; min-height: 0; }
306
+ .agent-list-item {
307
+ display: flex; align-items: center; gap: 10px; padding: 10px 16px; border-radius: 12px;
308
+ cursor: pointer; color: var(--text-muted); transition: all 0.2s ease; font-size: 14px; margin-bottom: 2px; position: relative;
309
+ }
310
+ .agent-list-item:hover { background: var(--bg-hover); color: var(--text); transform: translateX(4px); }
311
+ .agent-list-item.active { background: var(--accent-light); color: #fff; font-weight: 600; box-shadow: var(--glow-sm); border: 1px solid var(--border); }
312
+ .agent-list-item .agent-icon { width: 24px; text-align: center; font-size: 16px; }
313
+ .agent-list-item .agent-name { flex: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
314
+ .agent-list-item .status-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
315
+ .agent-list-item .status-dot.online { background: var(--green); box-shadow: 0 0 6px var(--green); }
316
+ .agent-list-item .status-dot.offline { background: var(--text-dim); }
317
+ .agent-list-item .status-dot.error { background: var(--red); box-shadow: 0 0 6px var(--red); }
318
+ .sidebar-bottom { margin-top: auto; flex-shrink: 0; }
319
+ .sidebar-nav { display: flex; flex-direction: column; flex: 1; min-height: 0; overflow: hidden; }
320
+
321
+ /* Agent Detail Page */
322
+ .agent-detail-header { display: flex; justify-content: space-between; align-items: center; padding: 20px 28px; border-bottom: 1px solid var(--border); }
323
+ .agent-detail-info { display: flex; align-items: center; gap: 12px; }
324
+ .agent-detail-icon { font-size: 28px; }
325
+ .agent-detail-name { font-size: 20px; font-weight: 700; margin: 0; }
326
+ .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; }
327
+ .agent-detail-toggle:hover { background: var(--bg-hover); color: var(--text); border-color: var(--accent); }
328
+ .agent-detail-toggle.active { background: var(--accent-light); color: var(--accent); border-color: var(--accent); }
329
+ #page-agent-detail { display: none; flex-direction: column; height: 100vh; }
330
+ #page-agent-detail.active { display: flex; }
331
+ .agent-chat-view { display: flex; flex-direction: column; flex: 1; min-height: 0; }
332
+ .agent-chat-messages { flex: 1; overflow-y: auto; padding: 24px 28px; display: flex; flex-direction: column; gap: 16px; }
333
+ .agent-chat-welcome { display: flex; flex-direction: column; align-items: center; justify-content: center; flex: 1; color: var(--text-dim); }
334
+ .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); }
335
+ .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; }
336
+ .agent-chat-input:focus { border-color: var(--accent); }
337
+ .agent-chat-send { padding: 12px 20px; border-radius: 12px; font-weight: 600; flex-shrink: 0; }
338
+ .agent-chat-msg { max-width: 75%; padding: 12px 16px; border-radius: 16px; font-size: 14px; line-height: 1.6; word-break: break-word; }
339
+ .agent-chat-msg.user { align-self: flex-end; background: var(--accent); color: #fff; border-bottom-right-radius: 4px; }
340
+ .agent-chat-msg.assistant { align-self: flex-start; background: var(--bg-card); border: 1px solid var(--border); border-bottom-left-radius: 4px; }
341
+ .agent-settings-view { flex: 1; display: flex; flex-direction: column; min-height: 0; }
342
+ .agent-settings-tabs { display: flex; gap: 4px; padding: 16px 28px 0; border-bottom: 1px solid var(--border); overflow-x: auto; flex-shrink: 0; }
343
+ .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; }
344
+ .agent-tab:hover { color: var(--text); background: var(--bg-hover); }
345
+ .agent-tab.active { color: var(--accent); border-bottom-color: var(--accent); font-weight: 600; }
346
+ .agent-settings-content { flex: 1; overflow-y: auto; padding: 24px 28px; }
347
+ .agent-tab-panel { display: none; }
348
+ .agent-tab-panel.active { display: block; }
349
+ .agent-tab-panel h3 { margin-bottom: 12px; font-size: 18px; }
300
350
  </style>
301
351
  </head>
302
352
  <body>
@@ -306,35 +356,44 @@
306
356
  <nav class="sidebar">
307
357
  <div class="sidebar-logo">⚡ <span>OPC Studio</span></div>
308
358
  <div class="sidebar-nav">
309
- <div class="nav-item active" data-page="dashboard" onclick="navigate('dashboard')">
310
- <span class="icon">🏠</span> Dashboard
311
- </div>
312
- <div class="nav-item" data-page="chat" onclick="openLastChat()">
313
- <span class="icon">💬</span> Chat
359
+ <!-- Section 1: My Agents -->
360
+ <div class="sidebar-section-title">🤖 我的 Agent</div>
361
+ <div class="agent-list-container" id="sidebar-agent-list">
362
+ <div style="padding: 12px 16px; color: var(--text-dim); font-size: 13px;">加载中...</div>
314
363
  </div>
315
- <div class="nav-item" data-page="templates" onclick="navigate('templates')">
316
- <span class="icon">👤</span> Templates
364
+
365
+ <!-- Section 2: Collaboration Groups -->
366
+ <div class="sidebar-divider"></div>
367
+ <div class="sidebar-section-title">👥 协作群组</div>
368
+ <div class="agent-list-container" id="groups-list" style="max-height:120px;">
369
+ <!-- dynamically loaded -->
317
370
  </div>
318
- <div class="nav-item" data-page="skills" onclick="navigate('skills')">
319
- <span class="icon">🧩</span> Skills Market
371
+ <div class="nav-item" data-page="create-group" onclick="navigate('create-group')">
372
+ <span class="icon">➕</span> 新建群组
320
373
  </div>
374
+
375
+ <!-- Section 3: Create Agent -->
376
+ <div class="sidebar-divider"></div>
321
377
  <div class="nav-item" data-page="create" onclick="navigate('create')">
322
- <span class="icon">✨</span> Create Agent
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
378
+ <span class="icon">➕</span> 新建 Agent
329
379
  </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
335
- </div>
336
- <div class="nav-item" data-page="schedules" onclick="navigate('schedules')">
337
- <span class="icon">⏰</span> Schedules
380
+
381
+ <!-- Section 3: Global Config -->
382
+ <div class="sidebar-bottom">
383
+ <div class="sidebar-divider"></div>
384
+ <div class="sidebar-section-title">⚙️ 全局配置</div>
385
+ <div class="nav-item" data-page="global-runtime" onclick="navigate('global-runtime')">
386
+ <span class="icon">🚀</span> Runtime
387
+ </div>
388
+ <div class="nav-item" data-page="global-models" onclick="navigate('global-models')">
389
+ <span class="icon">🧠</span> Models
390
+ </div>
391
+ <div class="nav-item" data-page="global-memory" onclick="navigate('global-memory')">
392
+ <span class="icon">💾</span> Memory
393
+ </div>
394
+ <div class="nav-item" data-page="global-templates" onclick="navigate('global-templates')">
395
+ <span class="icon">📋</span> Templates
396
+ </div>
338
397
  </div>
339
398
  </div>
340
399
  <div style="padding: 12px; border-top: 1px solid var(--border); font-size: 12px; color: var(--text-dim);">
@@ -371,7 +430,120 @@
371
430
  </div>
372
431
  </div>
373
432
 
433
+ <!-- Agent Detail Page -->
434
+ <div class="page" id="page-agent-detail">
435
+ <div class="agent-detail-header">
436
+ <div class="agent-detail-info">
437
+ <span class="agent-detail-icon" id="agent-detail-icon">🤖</span>
438
+ <h1 class="agent-detail-name" id="agent-detail-name">Agent</h1>
439
+ <span class="status-dot online" id="agent-detail-status"></span>
440
+ </div>
441
+ <button class="agent-detail-toggle" id="agent-detail-toggle" onclick="toggleAgentSettings()">⚙️</button>
442
+ </div>
443
+
444
+ <!-- Chat View (default) -->
445
+ <div class="agent-chat-view" id="agent-chat-view">
446
+ <div class="agent-chat-messages" id="agent-chat-messages">
447
+ <div class="agent-chat-welcome" id="agent-chat-welcome">
448
+ <div style="font-size: 48px; margin-bottom: 16px;">💬</div>
449
+ <div style="font-size: 18px; font-weight: 600; margin-bottom: 8px;">开始对话</div>
450
+ <div style="color: var(--text-muted); font-size: 14px;">向你的 Agent 发送第一条消息</div>
451
+ </div>
452
+ </div>
453
+ <div class="agent-chat-input-bar">
454
+ <textarea class="agent-chat-input" id="agent-chat-input" placeholder="输入消息..." rows="1" onkeydown="handleAgentChatKey(event)"></textarea>
455
+ <button class="btn btn-primary agent-chat-send" onclick="sendAgentChat()">发送</button>
456
+ </div>
457
+ </div>
458
+
459
+ <!-- Settings View (hidden) -->
460
+ <div class="agent-settings-view" id="agent-settings-view" style="display:none;">
461
+ <div class="agent-settings-tabs">
462
+ <div class="agent-tab active" data-atab="role" onclick="switchAgentTab('role')">👤 角色</div>
463
+ <div class="agent-tab" data-atab="models" onclick="switchAgentTab('models')">🤖 模型</div>
464
+ <div class="agent-tab" data-atab="channels" onclick="switchAgentTab('channels')">📡 渠道</div>
465
+ <div class="agent-tab" data-atab="memory" onclick="switchAgentTab('memory')">🧠 记忆</div>
466
+ <div class="agent-tab" data-atab="skills" onclick="switchAgentTab('skills')">🧩 技能</div>
467
+ <div class="agent-tab" data-atab="schedules" onclick="switchAgentTab('schedules')">⏰ 定时</div>
468
+ <div class="agent-tab" data-atab="usage" onclick="switchAgentTab('usage')">📊 统计</div>
469
+ </div>
470
+ <div class="agent-settings-content" id="agent-settings-content">
471
+ <div class="agent-tab-panel active" id="atab-role">
472
+ <h3>角色配置</h3>
473
+ <p style="color:var(--text-muted)">Agent 角色和人设配置(即将上线)</p>
474
+ </div>
475
+ <div class="agent-tab-panel" id="atab-models">
476
+ <h3>模型配置</h3>
477
+ <p style="color:var(--text-muted)">Agent 使用的模型配置(即将上线)</p>
478
+ </div>
479
+ <div class="agent-tab-panel" id="atab-channels">
480
+ <h3>渠道配置</h3>
481
+ <p style="color:var(--text-muted)">Agent 接入的渠道配置(即将上线)</p>
482
+ </div>
483
+ <div class="agent-tab-panel" id="atab-memory">
484
+ <h3>记忆管理</h3>
485
+ <p style="color:var(--text-muted)">Agent 记忆和知识库(即将上线)</p>
486
+ </div>
487
+ <div class="agent-tab-panel" id="atab-skills">
488
+ <h3>技能配置</h3>
489
+ <p style="color:var(--text-muted)">Agent 已安装的技能(即将上线)</p>
490
+ </div>
491
+ <div class="agent-tab-panel" id="atab-schedules">
492
+ <h3>定时任务</h3>
493
+ <p style="color:var(--text-muted)">Agent 定时任务配置(即将上线)</p>
494
+ </div>
495
+ <div class="agent-tab-panel" id="atab-usage">
496
+ <h3>用量统计</h3>
497
+ <p style="color:var(--text-muted)">Agent 使用量和成本(即将上线)</p>
498
+ </div>
499
+ </div>
500
+ </div>
501
+ </div>
502
+
374
503
  <!-- Templates Page -->
504
+ <!-- Create Group Page -->
505
+ <div class="page" id="page-create-group">
506
+ <h1 class="page-title">新建协作群组</h1>
507
+ <p class="page-subtitle">选择协作模式,拉入 Agent,开始多角色协作</p>
508
+ <div class="card" style="max-width:600px;">
509
+ <div class="label">群组名称</div>
510
+ <input class="input" id="group-name" placeholder="例如:产品讨论组">
511
+ <div class="label" style="margin-top:16px;">协作模式</div>
512
+ <div class="card-grid" style="grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap:12px; margin-bottom:16px;">
513
+ <div class="card pattern-card active" onclick="selectPattern('debate')" id="pat-debate" style="padding:14px;cursor:pointer;text-align:center;">
514
+ <div style="font-size:24px;margin-bottom:6px;">⚔️</div>
515
+ <div style="font-size:14px;font-weight:600;">Debate</div>
516
+ <div style="font-size:12px;color:var(--text-muted);">正反方辩论 + 裁判总结</div>
517
+ </div>
518
+ <div class="card pattern-card" onclick="selectPattern('voting')" id="pat-voting" style="padding:14px;cursor:pointer;text-align:center;">
519
+ <div style="font-size:24px;margin-bottom:6px;">🗳️</div>
520
+ <div style="font-size:14px;font-weight:600;">Voting</div>
521
+ <div style="font-size:12px;color:var(--text-muted);">多 Agent 投票表决</div>
522
+ </div>
523
+ <div class="card pattern-card" onclick="selectPattern('pipeline')" id="pat-pipeline" style="padding:14px;cursor:pointer;text-align:center;">
524
+ <div style="font-size:24px;margin-bottom:6px;">🔗</div>
525
+ <div style="font-size:14px;font-weight:600;">Pipeline</div>
526
+ <div style="font-size:12px;color:var(--text-muted);">链式处理,逐步传递</div>
527
+ </div>
528
+ <div class="card pattern-card" onclick="selectPattern('hierarchy')" id="pat-hierarchy" style="padding:14px;cursor:pointer;text-align:center;">
529
+ <div style="font-size:24px;margin-bottom:6px;">🏛️</div>
530
+ <div style="font-size:14px;font-weight:600;">Hierarchy</div>
531
+ <div style="font-size:12px;color:var(--text-muted);">上下级分配任务</div>
532
+ </div>
533
+ <div class="card pattern-card" onclick="selectPattern('shared-memory')" id="pat-shared-memory" style="padding:14px;cursor:pointer;text-align:center;">
534
+ <div style="font-size:24px;margin-bottom:6px;">🧠</div>
535
+ <div style="font-size:14px;font-weight:600;">Shared Memory</div>
536
+ <div style="font-size:12px;color:var(--text-muted);">共享记忆空间协作</div>
537
+ </div>
538
+ </div>
539
+ <div class="label">选择 Agent 成员</div>
540
+ <div id="group-agent-select" style="margin-bottom:16px;">
541
+ <p style="color:var(--text-muted);font-size:13px;">请先创建 Agent,再拉入群组</p>
542
+ </div>
543
+ <button class="btn btn-primary" onclick="createGroup()">✨ 创建群组</button>
544
+ </div>
545
+ </div>
546
+
375
547
  <div class="page" id="page-templates">
376
548
  <h1 class="page-title">Template Market</h1>
377
549
  <p class="page-subtitle">Browse 100+ ready-to-use agent templates across 19 industries</p>
@@ -944,6 +1116,7 @@
944
1116
  // === Init ===
945
1117
  async function init() {
946
1118
  await Promise.all([loadTemplates(), loadAgents()]);
1119
+ loadSidebarGroups();
947
1120
  handleRoute();
948
1121
  window.addEventListener('popstate', handleRoute);
949
1122
  checkFirstRun();
@@ -954,6 +1127,8 @@
954
1127
  const parts = path.split('/').filter(Boolean);
955
1128
  if (parts[0] === 'chat' && parts[1]) {
956
1129
  openChat(parts[1]);
1130
+ } else if (parts[0] === 'agent' && parts[1]) {
1131
+ loadSidebarAgents().then(() => navigateToAgent(parts[1]));
957
1132
  } else if (parts[0] === 'settings') {
958
1133
  if (parts[1]) currentSettingsTab = parts[1];
959
1134
  navigate('settings');
@@ -995,16 +1170,189 @@
995
1170
  } catch(e) { console.error('Failed to load agents:', e); }
996
1171
  }
997
1172
 
998
- // === Navigation ===
1173
+ // === Sidebar Agents ===
1174
+ let selectedAgentId = null;
1175
+
1176
+ async function loadSidebarAgents() {
1177
+ try {
1178
+ const res = await fetch('/api/agents');
1179
+ const data = await res.json();
1180
+ const agents = data.agents || data || [];
1181
+ window._sidebarAgents = agents;
1182
+ const container = document.getElementById('sidebar-agent-list');
1183
+ if (!agents.length) {
1184
+ container.innerHTML = '<div style="padding: 12px 16px; color: var(--text-dim); font-size: 13px;">暂无 Agent</div>';
1185
+ return;
1186
+ }
1187
+ container.innerHTML = agents.map(a => {
1188
+ const status = (a.status || 'offline').toLowerCase();
1189
+ const icon = a.emoji || a.icon || '🤖';
1190
+ const name = a.name || a.id;
1191
+ return `<div class="agent-list-item${selectedAgentId === a.id ? ' active' : ''}" data-agent-id="${a.id}" onclick="navigateToAgent('${a.id}')">
1192
+ <span class="agent-icon">${icon}</span>
1193
+ <span class="agent-name">${name}</span>
1194
+ <span class="status-dot ${status}"></span>
1195
+ </div>`;
1196
+ }).join('');
1197
+ } catch(e) {
1198
+ console.error('Failed to load sidebar agents:', e);
1199
+ const container = document.getElementById('sidebar-agent-list');
1200
+ if (container) container.innerHTML = '<div style="padding: 12px 16px; color: var(--text-dim); font-size: 13px;">加载失败</div>';
1201
+ }
1202
+ }
1203
+
1204
+ // --- Collaboration Groups ---
1205
+ let selectedPattern = 'debate';
1206
+ function selectPattern(pat) {
1207
+ selectedPattern = pat;
1208
+ document.querySelectorAll('.pattern-card').forEach(c => c.classList.remove('active'));
1209
+ const el = document.getElementById('pat-' + pat);
1210
+ if (el) el.classList.add('active');
1211
+ }
1212
+ async function loadGroupAgentSelect() {
1213
+ try {
1214
+ const res = await fetch('/api/agents');
1215
+ const data = await res.json();
1216
+ const agents = data.agents || data || [];
1217
+ const container = document.getElementById('group-agent-select');
1218
+ if (!agents.length) {
1219
+ container.innerHTML = '<p style="color:var(--text-muted);font-size:13px;">请先创建 Agent,再拉入群组</p>';
1220
+ return;
1221
+ }
1222
+ container.innerHTML = agents.map(a => `<label style="display:flex;align-items:center;gap:8px;padding:6px 0;cursor:pointer;"><input type="checkbox" class="group-agent-cb" value="${a.id}"> <span>${a.templateIcon || a.icon || '🤖'}</span> <span style="font-size:14px;">${a.name}</span></label>`).join('');
1223
+ } catch(e) { console.error('loadGroupAgentSelect error', e); }
1224
+ }
1225
+ async function createGroup() {
1226
+ const name = document.getElementById('group-name').value.trim();
1227
+ if (!name) { alert('请输入群组名称'); return; }
1228
+ const members = [...document.querySelectorAll('.group-agent-cb:checked')].map(cb => cb.value);
1229
+ if (members.length < 2) { alert('至少选择 2 个 Agent'); return; }
1230
+ // TODO: POST to /api/groups
1231
+ alert('群组 "' + name + '" 创建成功!模式: ' + selectedPattern + ', 成员: ' + members.length + ' 个 Agent');
1232
+ navigate('dashboard');
1233
+ }
1234
+ async function loadSidebarGroups() {
1235
+ // TODO: fetch /api/groups and render
1236
+ const container = document.getElementById('groups-list');
1237
+ if (container) container.innerHTML = '<div style="padding:6px 16px;color:var(--text-dim);font-size:12px;">暂无群组</div>';
1238
+ }
1239
+
1240
+ function navigateToAgent(agentId) {
1241
+ selectedAgentId = agentId;
1242
+ // Update sidebar active state
1243
+ document.querySelectorAll('.agent-list-item').forEach(el => el.classList.remove('active'));
1244
+ document.querySelectorAll('.nav-item').forEach(n => n.classList.remove('active'));
1245
+ const item = document.querySelector(`.agent-list-item[data-agent-id="${agentId}"]`);
1246
+ if (item) item.classList.add('active');
1247
+
1248
+ // Find agent data
1249
+ const agent = (window._sidebarAgents || []).find(a => a.id === agentId) || { id: agentId, name: agentId };
1250
+ document.getElementById('agent-detail-icon').textContent = agent.emoji || agent.icon || '🤖';
1251
+ document.getElementById('agent-detail-name').textContent = agent.name || agentId;
1252
+ const statusDot = document.getElementById('agent-detail-status');
1253
+ const status = (agent.status || 'offline').toLowerCase();
1254
+ statusDot.className = 'status-dot ' + status;
1255
+
1256
+ // Reset to chat view
1257
+ document.getElementById('agent-chat-view').style.display = '';
1258
+ document.getElementById('agent-settings-view').style.display = 'none';
1259
+ document.getElementById('agent-detail-toggle').classList.remove('active');
1260
+ 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>';
1261
+ document.getElementById('agent-chat-input').value = '';
1262
+
1263
+ // Show agent detail page
1264
+ document.querySelectorAll('.page, .chat-container').forEach(p => { p.classList.remove('active'); p.style.display = ''; });
1265
+ document.getElementById('page-agent-detail').classList.add('active');
1266
+ location.hash = `/agent/${agentId}`;
1267
+ toggleSidebar(false);
1268
+ }
1269
+
1270
+ function toggleAgentSettings() {
1271
+ const chatView = document.getElementById('agent-chat-view');
1272
+ const settingsView = document.getElementById('agent-settings-view');
1273
+ const toggleBtn = document.getElementById('agent-detail-toggle');
1274
+ const showSettings = chatView.style.display !== 'none';
1275
+ chatView.style.display = showSettings ? 'none' : '';
1276
+ settingsView.style.display = showSettings ? '' : 'none';
1277
+ toggleBtn.classList.toggle('active', showSettings);
1278
+ }
1279
+
1280
+ function switchAgentTab(tab) {
1281
+ document.querySelectorAll('.agent-tab').forEach(t => t.classList.remove('active'));
1282
+ document.querySelector(`.agent-tab[data-atab="${tab}"]`)?.classList.add('active');
1283
+ document.querySelectorAll('.agent-tab-panel').forEach(p => p.classList.remove('active'));
1284
+ document.getElementById(`atab-${tab}`)?.classList.add('active');
1285
+ }
1286
+
1287
+ let agentChatHistory = [];
1288
+
1289
+ async function sendAgentChat() {
1290
+ const input = document.getElementById('agent-chat-input');
1291
+ const msg = input.value.trim();
1292
+ if (!msg || !selectedAgentId) return;
1293
+ input.value = '';
1294
+ input.style.height = 'auto';
1295
+
1296
+ const messagesEl = document.getElementById('agent-chat-messages');
1297
+ // Remove welcome screen
1298
+ const welcome = messagesEl.querySelector('.agent-chat-welcome');
1299
+ if (welcome) welcome.remove();
1300
+
1301
+ // Add user message
1302
+ const userDiv = document.createElement('div');
1303
+ userDiv.className = 'agent-chat-msg user';
1304
+ userDiv.textContent = msg;
1305
+ messagesEl.appendChild(userDiv);
1306
+ messagesEl.scrollTop = messagesEl.scrollHeight;
1307
+
1308
+ agentChatHistory.push({ role: 'user', content: msg });
1309
+
1310
+ // Send to API
1311
+ try {
1312
+ const res = await fetch(`/api/agents/${selectedAgentId}/chat`, {
1313
+ method: 'POST',
1314
+ headers: { 'Content-Type': 'application/json' },
1315
+ body: JSON.stringify({ message: msg, history: agentChatHistory })
1316
+ });
1317
+ const data = await res.json();
1318
+ const reply = data.reply || data.message || data.content || JSON.stringify(data);
1319
+ const assistantDiv = document.createElement('div');
1320
+ assistantDiv.className = 'agent-chat-msg assistant';
1321
+ assistantDiv.textContent = reply;
1322
+ messagesEl.appendChild(assistantDiv);
1323
+ agentChatHistory.push({ role: 'assistant', content: reply });
1324
+ } catch(e) {
1325
+ const errDiv = document.createElement('div');
1326
+ errDiv.className = 'agent-chat-msg assistant';
1327
+ errDiv.style.borderColor = 'var(--red)';
1328
+ errDiv.textContent = `⚠️ 发送失败: ${e.message}`;
1329
+ messagesEl.appendChild(errDiv);
1330
+ }
1331
+ messagesEl.scrollTop = messagesEl.scrollHeight;
1332
+ }
1333
+
1334
+ function handleAgentChatKey(e) {
1335
+ if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendAgentChat(); }
1336
+ // Auto-resize textarea
1337
+ e.target.style.height = 'auto';
1338
+ e.target.style.height = Math.min(e.target.scrollHeight, 120) + 'px';
1339
+ }
1340
+
1341
+ // === Navigation ===
999
1342
  function navigate(page) {
1000
1343
  document.querySelectorAll('.page, .chat-container').forEach(p => { p.classList.remove('active'); p.style.display = ''; });
1001
1344
  document.querySelectorAll('.nav-item').forEach(n => n.classList.remove('active'));
1002
1345
  const navItem = document.querySelector(`.nav-item[data-page="${page}"]`);
1003
1346
  if (navItem) navItem.classList.add('active');
1004
1347
 
1005
- if (page === 'dashboard') { loadAgents(); loadHealthDashboard(); }
1348
+ if (page === 'dashboard') { loadAgents(); loadHealthDashboard(); loadSidebarAgents(); }
1006
1349
  if (page === 'create') { renderWizard(); renderWizardTemplates(); }
1007
1350
  if (page === 'settings') { showSettings(currentSettingsTab || 'models'); }
1351
+ if (page === 'global-runtime') { currentSettingsTab='status'; showSettings('status'); showPage('settings'); return; }
1352
+ if (page === 'global-models') { currentSettingsTab='models'; showSettings('models'); showPage('settings'); return; }
1353
+ if (page === 'global-memory') { currentSettingsTab='memory'; showSettings('memory'); showPage('settings'); return; }
1354
+ if (page === 'global-templates') { navigate('templates'); return; }
1355
+ if (page === 'create-group') { loadGroupAgentSelect(); }
1008
1356
  if (page === 'schedules') { loadSchedules(); }
1009
1357
  if (page === 'skills') { loadSkillsMarketplace(); }
1010
1358