opc-agent 4.0.32 → 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.
@@ -7,19 +7,20 @@
7
7
  <style>
8
8
  * { margin: 0; padding: 0; box-sizing: border-box; }
9
9
  :root {
10
- --bg: #0a0a1a; --bg-card: rgba(255,255,255,0.03); --bg-hover: rgba(255,255,255,0.06); --bg-input: rgba(255,255,255,0.05);
11
- --border: rgba(255,255,255,0.08); --text: #f0f0f8; --text-muted: #8888aa; --text-dim: #555577;
12
- --accent: #7c5cff; --accent-hover: #6a4aee; --accent-light: rgba(124,92,255,0.12);
13
- --green: #34d399; --red: #f87171; --yellow: #fbbf24; --purple: #a78bfa;
14
- --gradient: linear-gradient(135deg, #7c5cff 0%, #00d4ff 100%);
15
- --glow: 0 0 20px rgba(124,92,255,0.25);
16
- --glow-sm: 0 0 10px rgba(124,92,255,0.15);
17
- --card-glow: 0 4px 30px rgba(0,0,0,0.3);
10
+ --bg: #05051a; --bg-card: rgba(15,15,40,0.6); --bg-hover: rgba(124,92,255,0.08); --bg-input: rgba(15,15,40,0.8);
11
+ --border: rgba(124,92,255,0.15); --text: #f0f0ff; --text-muted: #9090cc; --text-dim: #5555aa;
12
+ --accent: #8b5cf6; --accent-hover: #7c3aed; --accent-light: rgba(139,92,246,0.15);
13
+ --green: #10b981; --red: #f87171; --yellow: #fbbf24; --purple: #c084fc;
14
+ --gradient: linear-gradient(135deg, #8b5cf6 0%, #06b6d4 50%, #8b5cf6 100%);
15
+ --gradient-btn: linear-gradient(135deg, #8b5cf6 0%, #3b82f6 50%, #06b6d4 100%);
16
+ --glow: 0 0 30px rgba(139,92,246,0.4), 0 0 60px rgba(6,182,212,0.2);
17
+ --glow-sm: 0 0 15px rgba(139,92,246,0.25);
18
+ --card-glow: 0 4px 40px rgba(0,0,0,0.5), inset 0 1px 0 rgba(255,255,255,0.05);
18
19
  --font: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
19
20
  --mono: 'JetBrains Mono', 'SF Mono', 'Fira Code', monospace;
20
- --radius: 12px; --radius-lg: 20px;
21
+ --radius: 14px; --radius-lg: 24px;
21
22
  }
22
- body { font-family: var(--font); background: var(--bg); color: var(--text); min-height: 100vh; font-size: 15px; line-height: 1.6; background-image: radial-gradient(ellipse at top, rgba(124,92,255,0.08) 0%, transparent 50%), radial-gradient(ellipse at bottom right, rgba(0,212,255,0.05) 0%, transparent 50%); }
23
+ body { font-family: var(--font); background: var(--bg); color: var(--text); min-height: 100vh; font-size: 15px; line-height: 1.6; background-image: radial-gradient(ellipse at 20% 0%, rgba(139,92,246,0.12) 0%, transparent 50%), radial-gradient(ellipse at 80% 100%, rgba(6,182,212,0.08) 0%, transparent 50%), url("data:image/svg+xml,%3Csvg width='60' height='60' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0 30h60M30 0v60' stroke='rgba(139,92,246,0.04)' stroke-width='0.5'/%3E%3C/svg%3E"); }
23
24
  a { color: var(--accent); text-decoration: none; }
24
25
  button { font-family: var(--font); cursor: pointer; border: none; }
25
26
  input, select, textarea { font-family: var(--font); }
@@ -27,19 +28,20 @@
27
28
  /* Layout */
28
29
  .app { display: flex; min-height: 100vh; }
29
30
  .sidebar {
30
- width: 260px; background: rgba(10,10,30,0.8); backdrop-filter: blur(20px); border-right: 1px solid var(--border);
31
- padding: 20px 14px; display: flex; flex-direction: column; position: fixed; height: 100vh; z-index: 100;
31
+ width: 270px; background: rgba(5,5,30,0.9); backdrop-filter: blur(30px); border-right: 1px solid var(--border);
32
+ padding: 24px 16px; display: flex; flex-direction: column; position: fixed; height: 100vh; z-index: 100;
33
+ background-image: linear-gradient(180deg, rgba(139,92,246,0.05) 0%, transparent 30%);
32
34
  }
33
- .sidebar-logo { font-size: 20px; font-weight: 700; padding: 12px 12px; margin-bottom: 24px; display: flex; align-items: center; gap: 10px; background: var(--gradient); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
35
+ .sidebar-logo { font-size: 22px; font-weight: 700; padding: 12px 12px; margin-bottom: 28px; display: flex; align-items: center; gap: 10px; background: var(--gradient); -webkit-background-clip: text; -webkit-text-fill-color: transparent; letter-spacing: -0.5px; }
34
36
  .sidebar-logo span { background: var(--gradient); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
35
37
  .nav-item {
36
- display: flex; align-items: center; gap: 12px; padding: 10px 14px; border-radius: 10px;
37
- cursor: pointer; color: var(--text-muted); transition: all 0.2s ease; font-size: 15px; margin-bottom: 3px;
38
+ display: flex; align-items: center; gap: 12px; padding: 12px 16px; border-radius: 12px;
39
+ cursor: pointer; color: var(--text-muted); transition: all 0.2s ease; font-size: 15px; margin-bottom: 4px; position: relative;
38
40
  }
39
41
  .nav-item:hover { background: var(--bg-hover); color: var(--text); transform: translateX(4px); }
40
- .nav-item.active { background: var(--accent-light); color: var(--accent); font-weight: 500; box-shadow: var(--glow-sm); }
41
- .nav-item .icon { width: 22px; text-align: center; font-size: 18px; }
42
- .main { flex: 1; margin-left: 260px; min-height: 100vh; }
42
+ .nav-item.active { background: var(--accent-light); color: #fff; font-weight: 600; box-shadow: var(--glow-sm); border: 1px solid var(--border); }
43
+ .nav-item .icon { width: 24px; text-align: center; font-size: 18px; }
44
+ .main { flex: 1; margin-left: 270px; min-height: 100vh; }
43
45
 
44
46
  /* Mobile */
45
47
  .mobile-header { display: none; background: var(--bg-card); border-bottom: 1px solid var(--border); padding: 12px 16px; position: sticky; top: 0; z-index: 50; }
@@ -56,20 +58,23 @@
56
58
  /* Page container */
57
59
  .page { display: none; padding: 32px; max-width: 1200px; margin: 0 auto; }
58
60
  .page.active { display: block; }
59
- .page-title { font-size: 28px; font-weight: 700; margin-bottom: 8px; background: var(--gradient); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
61
+ .page-title { font-size: 28px; font-weight: 700; margin-bottom: 8px; background: var(--gradient); background-size: 200% auto; -webkit-background-clip: text; -webkit-text-fill-color: transparent; animation: shimmer 3s linear infinite; letter-spacing: -0.5px; }
62
+ @keyframes shimmer { 0% { background-position: 0% center; } 100% { background-position: 200% center; } }
60
63
  .page-subtitle { color: var(--text-muted); font-size: 15px; margin-bottom: 24px; }
61
64
 
62
65
  /* Cards */
63
- .card { background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius); padding: 20px; transition: all 0.25s ease; backdrop-filter: blur(10px); box-shadow: var(--card-glow); }
64
- .card:hover { border-color: rgba(124,92,255,0.3); transform: translateY(-2px); box-shadow: 0 8px 40px rgba(0,0,0,0.4), var(--glow-sm); }
66
+ .card { background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius); padding: 20px; transition: all 0.3s ease; backdrop-filter: blur(10px); box-shadow: var(--card-glow); position: relative; overflow: hidden; }
67
+ .card::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 2px; background: var(--gradient); transition: left 0.4s ease; }
68
+ .card:hover { border-color: rgba(139,92,246,0.4); transform: translateY(-3px); box-shadow: 0 12px 50px rgba(0,0,0,0.5), var(--glow); }
69
+ .card:hover::before { left: 0; }
65
70
  .card-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 16px; }
66
71
 
67
72
  /* Buttons */
68
- .btn { padding: 8px 20px; border-radius: 10px; font-size: 14px; font-weight: 500; transition: all 0.2s ease; display: inline-flex; align-items: center; gap: 8px; height: 38px; line-height: 20px; }
69
- .btn-primary { background: var(--gradient); color: white; box-shadow: var(--glow-sm); }
70
- .btn-primary:hover { transform: translateY(-1px); box-shadow: var(--glow); }
71
- .btn-secondary { background: rgba(255,255,255,0.05); color: var(--text); border: 1px solid var(--border); backdrop-filter: blur(10px); }
72
- .btn-secondary:hover { border-color: var(--accent); box-shadow: var(--glow-sm); }
73
+ .btn { padding: 8px 20px; border-radius: 10px; font-size: 14px; font-weight: 500; transition: all 0.2s ease; display: inline-flex; align-items: center; gap: 8px; height: 38px; line-height: 20px; position: relative; overflow: hidden; }
74
+ .btn-primary { background: var(--gradient-btn); background-size: 200% auto; color: white; box-shadow: var(--glow-sm); }
75
+ .btn-primary:hover { background-position: right center; transform: translateY(-2px); box-shadow: var(--glow); }
76
+ .btn-secondary { background: var(--bg-card); color: var(--text); border: 1px solid var(--border); backdrop-filter: blur(10px); }
77
+ .btn-secondary:hover { border-color: var(--accent); box-shadow: var(--glow-sm); transform: translateY(-1px); }
73
78
  .btn-danger { background: rgba(239,68,68,0.1); color: var(--red); }
74
79
  .btn-danger:hover { background: rgba(239,68,68,0.2); }
75
80
  .btn-sm { padding: 6px 12px; font-size: 13px; height: 32px; }
@@ -168,9 +173,9 @@
168
173
  .empty-state .empty-desc { font-size: 14px; margin-bottom: 24px; }
169
174
 
170
175
  /* Confirm dialog */
171
- .dialog-overlay { display: none; position: fixed; inset: 0; background: rgba(0,0,0,0.7); backdrop-filter: blur(8px); z-index: 200; align-items: center; justify-content: center; }
176
+ .dialog-overlay { display: none; position: fixed; inset: 0; background: rgba(5,5,26,0.85); backdrop-filter: blur(12px); z-index: 200; align-items: center; justify-content: center; }
172
177
  .dialog-overlay.show { display: flex; }
173
- .dialog { background: rgba(20,20,40,0.95); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: 28px; max-width: 400px; width: 90%; backdrop-filter: blur(20px); box-shadow: 0 20px 60px rgba(0,0,0,0.5), var(--glow); }
178
+ .dialog { background: rgba(15,15,45,0.95); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: 32px; max-width: 420px; width: 90%; backdrop-filter: blur(30px); box-shadow: 0 25px 80px rgba(0,0,0,0.6), var(--glow); }
174
179
  .dialog-title { font-size: 16px; font-weight: 600; margin-bottom: 8px; }
175
180
  .dialog-desc { font-size: 14px; color: var(--text-muted); margin-bottom: 20px; }
176
181
  .dialog-actions { display: flex; justify-content: flex-end; gap: 8px; }
@@ -200,11 +205,11 @@
200
205
  .tab-panel.active { display: block; }
201
206
 
202
207
  /* Status dot */
203
- .status-dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; animation: pulse 2s ease-in-out infinite; }
204
- .status-dot.green { background: var(--green); box-shadow: 0 0 8px var(--green); }
205
- .status-dot.red { background: var(--red); box-shadow: 0 0 8px var(--red); }
206
- .status-dot.yellow { background: var(--yellow); box-shadow: 0 0 8px var(--yellow); }
207
- @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } }
208
+ .status-dot { width: 10px; height: 10px; border-radius: 50%; display: inline-block; animation: pulse 2s ease-in-out infinite; }
209
+ .status-dot.green { background: var(--green); box-shadow: 0 0 12px var(--green), 0 0 4px var(--green); }
210
+ .status-dot.red { background: var(--red); box-shadow: 0 0 12px var(--red), 0 0 4px var(--red); }
211
+ .status-dot.yellow { background: var(--yellow); box-shadow: 0 0 12px var(--yellow), 0 0 4px var(--yellow); }
212
+ @keyframes pulse { 0%, 100% { opacity: 1; transform: scale(1); } 50% { opacity: 0.6; transform: scale(0.85); } }
208
213
 
209
214
  /* Channel card */
210
215
  .channel-card { display: flex; align-items: center; gap: 16px; cursor: pointer; }
@@ -292,6 +297,55 @@
292
297
  .settings-subnav { width: 100%; display: flex; flex-wrap: wrap; gap: 4px; }
293
298
  .snav-item { padding: 8px 10px; font-size: 12px; flex-direction: column; gap: 4px; text-align: center; min-width: 70px; }
294
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; }
295
349
  </style>
296
350
  </head>
297
351
  <body>
@@ -301,35 +355,34 @@
301
355
  <nav class="sidebar">
302
356
  <div class="sidebar-logo">⚡ <span>OPC Studio</span></div>
303
357
  <div class="sidebar-nav">
304
- <div class="nav-item active" data-page="dashboard" onclick="navigate('dashboard')">
305
- <span class="icon">🏠</span> Dashboard
306
- </div>
307
- <div class="nav-item" data-page="chat" onclick="openLastChat()">
308
- <span class="icon">💬</span> Chat
309
- </div>
310
- <div class="nav-item" data-page="templates" onclick="navigate('templates')">
311
- <span class="icon">👤</span> Templates
312
- </div>
313
- <div class="nav-item" data-page="skills" onclick="navigate('skills')">
314
- <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>
315
362
  </div>
363
+
364
+ <!-- Section 2: Create -->
365
+ <div class="sidebar-divider"></div>
316
366
  <div class="nav-item" data-page="create" onclick="navigate('create')">
317
- <span class="icon">✨</span> Create Agent
318
- </div>
319
- <div class="nav-item" data-page="settings" onclick="currentSettingsTab='models';navigate('settings')">
320
- <span class="icon">🤖</span> Models
321
- </div>
322
- <div class="nav-item" data-page="settings" onclick="currentSettingsTab='channels';navigate('settings')">
323
- <span class="icon">📡</span> Channels
324
- </div>
325
- <div class="nav-item" data-page="settings" onclick="currentSettingsTab='memory';navigate('settings')">
326
- <span class="icon">🧠</span> Memory
327
- </div>
328
- <div class="nav-item" data-page="settings" onclick="navigate('settings')">
329
- <span class="icon">⚙️</span> Settings
367
+ <span class="icon">➕</span> 新建 Agent
330
368
  </div>
331
- <div class="nav-item" data-page="schedules" onclick="navigate('schedules')">
332
- <span class="icon">⏰</span> Schedules
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>
333
386
  </div>
334
387
  </div>
335
388
  <div style="padding: 12px; border-top: 1px solid var(--border); font-size: 12px; color: var(--text-dim);">
@@ -366,6 +419,76 @@
366
419
  </div>
367
420
  </div>
368
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
+
369
492
  <!-- Templates Page -->
370
493
  <div class="page" id="page-templates">
371
494
  <h1 class="page-title">Template Market</h1>
@@ -949,6 +1072,8 @@
949
1072
  const parts = path.split('/').filter(Boolean);
950
1073
  if (parts[0] === 'chat' && parts[1]) {
951
1074
  openChat(parts[1]);
1075
+ } else if (parts[0] === 'agent' && parts[1]) {
1076
+ loadSidebarAgents().then(() => navigateToAgent(parts[1]));
952
1077
  } else if (parts[0] === 'settings') {
953
1078
  if (parts[1]) currentSettingsTab = parts[1];
954
1079
  navigate('settings');
@@ -990,16 +1115,152 @@
990
1115
  } catch(e) { console.error('Failed to load agents:', e); }
991
1116
  }
992
1117
 
993
- // === Navigation ===
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 ===
994
1251
  function navigate(page) {
995
1252
  document.querySelectorAll('.page, .chat-container').forEach(p => { p.classList.remove('active'); p.style.display = ''; });
996
1253
  document.querySelectorAll('.nav-item').forEach(n => n.classList.remove('active'));
997
1254
  const navItem = document.querySelector(`.nav-item[data-page="${page}"]`);
998
1255
  if (navItem) navItem.classList.add('active');
999
1256
 
1000
- if (page === 'dashboard') { loadAgents(); loadHealthDashboard(); }
1257
+ if (page === 'dashboard') { loadAgents(); loadHealthDashboard(); loadSidebarAgents(); }
1001
1258
  if (page === 'create') { renderWizard(); renderWizardTemplates(); }
1002
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; }
1003
1264
  if (page === 'schedules') { loadSchedules(); }
1004
1265
  if (page === 'skills') { loadSkillsMarketplace(); }
1005
1266
 
@@ -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.32",
3
+ "version": "4.0.34",
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",
@@ -7,19 +7,20 @@
7
7
  <style>
8
8
  * { margin: 0; padding: 0; box-sizing: border-box; }
9
9
  :root {
10
- --bg: #0a0a1a; --bg-card: rgba(255,255,255,0.03); --bg-hover: rgba(255,255,255,0.06); --bg-input: rgba(255,255,255,0.05);
11
- --border: rgba(255,255,255,0.08); --text: #f0f0f8; --text-muted: #8888aa; --text-dim: #555577;
12
- --accent: #7c5cff; --accent-hover: #6a4aee; --accent-light: rgba(124,92,255,0.12);
13
- --green: #34d399; --red: #f87171; --yellow: #fbbf24; --purple: #a78bfa;
14
- --gradient: linear-gradient(135deg, #7c5cff 0%, #00d4ff 100%);
15
- --glow: 0 0 20px rgba(124,92,255,0.25);
16
- --glow-sm: 0 0 10px rgba(124,92,255,0.15);
17
- --card-glow: 0 4px 30px rgba(0,0,0,0.3);
10
+ --bg: #05051a; --bg-card: rgba(15,15,40,0.6); --bg-hover: rgba(124,92,255,0.08); --bg-input: rgba(15,15,40,0.8);
11
+ --border: rgba(124,92,255,0.15); --text: #f0f0ff; --text-muted: #9090cc; --text-dim: #5555aa;
12
+ --accent: #8b5cf6; --accent-hover: #7c3aed; --accent-light: rgba(139,92,246,0.15);
13
+ --green: #10b981; --red: #f87171; --yellow: #fbbf24; --purple: #c084fc;
14
+ --gradient: linear-gradient(135deg, #8b5cf6 0%, #06b6d4 50%, #8b5cf6 100%);
15
+ --gradient-btn: linear-gradient(135deg, #8b5cf6 0%, #3b82f6 50%, #06b6d4 100%);
16
+ --glow: 0 0 30px rgba(139,92,246,0.4), 0 0 60px rgba(6,182,212,0.2);
17
+ --glow-sm: 0 0 15px rgba(139,92,246,0.25);
18
+ --card-glow: 0 4px 40px rgba(0,0,0,0.5), inset 0 1px 0 rgba(255,255,255,0.05);
18
19
  --font: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
19
20
  --mono: 'JetBrains Mono', 'SF Mono', 'Fira Code', monospace;
20
- --radius: 12px; --radius-lg: 20px;
21
+ --radius: 14px; --radius-lg: 24px;
21
22
  }
22
- body { font-family: var(--font); background: var(--bg); color: var(--text); min-height: 100vh; font-size: 15px; line-height: 1.6; background-image: radial-gradient(ellipse at top, rgba(124,92,255,0.08) 0%, transparent 50%), radial-gradient(ellipse at bottom right, rgba(0,212,255,0.05) 0%, transparent 50%); }
23
+ body { font-family: var(--font); background: var(--bg); color: var(--text); min-height: 100vh; font-size: 15px; line-height: 1.6; background-image: radial-gradient(ellipse at 20% 0%, rgba(139,92,246,0.12) 0%, transparent 50%), radial-gradient(ellipse at 80% 100%, rgba(6,182,212,0.08) 0%, transparent 50%), url("data:image/svg+xml,%3Csvg width='60' height='60' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0 30h60M30 0v60' stroke='rgba(139,92,246,0.04)' stroke-width='0.5'/%3E%3C/svg%3E"); }
23
24
  a { color: var(--accent); text-decoration: none; }
24
25
  button { font-family: var(--font); cursor: pointer; border: none; }
25
26
  input, select, textarea { font-family: var(--font); }
@@ -27,19 +28,20 @@
27
28
  /* Layout */
28
29
  .app { display: flex; min-height: 100vh; }
29
30
  .sidebar {
30
- width: 260px; background: rgba(10,10,30,0.8); backdrop-filter: blur(20px); border-right: 1px solid var(--border);
31
- padding: 20px 14px; display: flex; flex-direction: column; position: fixed; height: 100vh; z-index: 100;
31
+ width: 270px; background: rgba(5,5,30,0.9); backdrop-filter: blur(30px); border-right: 1px solid var(--border);
32
+ padding: 24px 16px; display: flex; flex-direction: column; position: fixed; height: 100vh; z-index: 100;
33
+ background-image: linear-gradient(180deg, rgba(139,92,246,0.05) 0%, transparent 30%);
32
34
  }
33
- .sidebar-logo { font-size: 20px; font-weight: 700; padding: 12px 12px; margin-bottom: 24px; display: flex; align-items: center; gap: 10px; background: var(--gradient); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
35
+ .sidebar-logo { font-size: 22px; font-weight: 700; padding: 12px 12px; margin-bottom: 28px; display: flex; align-items: center; gap: 10px; background: var(--gradient); -webkit-background-clip: text; -webkit-text-fill-color: transparent; letter-spacing: -0.5px; }
34
36
  .sidebar-logo span { background: var(--gradient); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
35
37
  .nav-item {
36
- display: flex; align-items: center; gap: 12px; padding: 10px 14px; border-radius: 10px;
37
- cursor: pointer; color: var(--text-muted); transition: all 0.2s ease; font-size: 15px; margin-bottom: 3px;
38
+ display: flex; align-items: center; gap: 12px; padding: 12px 16px; border-radius: 12px;
39
+ cursor: pointer; color: var(--text-muted); transition: all 0.2s ease; font-size: 15px; margin-bottom: 4px; position: relative;
38
40
  }
39
41
  .nav-item:hover { background: var(--bg-hover); color: var(--text); transform: translateX(4px); }
40
- .nav-item.active { background: var(--accent-light); color: var(--accent); font-weight: 500; box-shadow: var(--glow-sm); }
41
- .nav-item .icon { width: 22px; text-align: center; font-size: 18px; }
42
- .main { flex: 1; margin-left: 260px; min-height: 100vh; }
42
+ .nav-item.active { background: var(--accent-light); color: #fff; font-weight: 600; box-shadow: var(--glow-sm); border: 1px solid var(--border); }
43
+ .nav-item .icon { width: 24px; text-align: center; font-size: 18px; }
44
+ .main { flex: 1; margin-left: 270px; min-height: 100vh; }
43
45
 
44
46
  /* Mobile */
45
47
  .mobile-header { display: none; background: var(--bg-card); border-bottom: 1px solid var(--border); padding: 12px 16px; position: sticky; top: 0; z-index: 50; }
@@ -56,20 +58,23 @@
56
58
  /* Page container */
57
59
  .page { display: none; padding: 32px; max-width: 1200px; margin: 0 auto; }
58
60
  .page.active { display: block; }
59
- .page-title { font-size: 28px; font-weight: 700; margin-bottom: 8px; background: var(--gradient); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
61
+ .page-title { font-size: 28px; font-weight: 700; margin-bottom: 8px; background: var(--gradient); background-size: 200% auto; -webkit-background-clip: text; -webkit-text-fill-color: transparent; animation: shimmer 3s linear infinite; letter-spacing: -0.5px; }
62
+ @keyframes shimmer { 0% { background-position: 0% center; } 100% { background-position: 200% center; } }
60
63
  .page-subtitle { color: var(--text-muted); font-size: 15px; margin-bottom: 24px; }
61
64
 
62
65
  /* Cards */
63
- .card { background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius); padding: 20px; transition: all 0.25s ease; backdrop-filter: blur(10px); box-shadow: var(--card-glow); }
64
- .card:hover { border-color: rgba(124,92,255,0.3); transform: translateY(-2px); box-shadow: 0 8px 40px rgba(0,0,0,0.4), var(--glow-sm); }
66
+ .card { background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius); padding: 20px; transition: all 0.3s ease; backdrop-filter: blur(10px); box-shadow: var(--card-glow); position: relative; overflow: hidden; }
67
+ .card::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 2px; background: var(--gradient); transition: left 0.4s ease; }
68
+ .card:hover { border-color: rgba(139,92,246,0.4); transform: translateY(-3px); box-shadow: 0 12px 50px rgba(0,0,0,0.5), var(--glow); }
69
+ .card:hover::before { left: 0; }
65
70
  .card-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 16px; }
66
71
 
67
72
  /* Buttons */
68
- .btn { padding: 8px 20px; border-radius: 10px; font-size: 14px; font-weight: 500; transition: all 0.2s ease; display: inline-flex; align-items: center; gap: 8px; height: 38px; line-height: 20px; }
69
- .btn-primary { background: var(--gradient); color: white; box-shadow: var(--glow-sm); }
70
- .btn-primary:hover { transform: translateY(-1px); box-shadow: var(--glow); }
71
- .btn-secondary { background: rgba(255,255,255,0.05); color: var(--text); border: 1px solid var(--border); backdrop-filter: blur(10px); }
72
- .btn-secondary:hover { border-color: var(--accent); box-shadow: var(--glow-sm); }
73
+ .btn { padding: 8px 20px; border-radius: 10px; font-size: 14px; font-weight: 500; transition: all 0.2s ease; display: inline-flex; align-items: center; gap: 8px; height: 38px; line-height: 20px; position: relative; overflow: hidden; }
74
+ .btn-primary { background: var(--gradient-btn); background-size: 200% auto; color: white; box-shadow: var(--glow-sm); }
75
+ .btn-primary:hover { background-position: right center; transform: translateY(-2px); box-shadow: var(--glow); }
76
+ .btn-secondary { background: var(--bg-card); color: var(--text); border: 1px solid var(--border); backdrop-filter: blur(10px); }
77
+ .btn-secondary:hover { border-color: var(--accent); box-shadow: var(--glow-sm); transform: translateY(-1px); }
73
78
  .btn-danger { background: rgba(239,68,68,0.1); color: var(--red); }
74
79
  .btn-danger:hover { background: rgba(239,68,68,0.2); }
75
80
  .btn-sm { padding: 6px 12px; font-size: 13px; height: 32px; }
@@ -168,9 +173,9 @@
168
173
  .empty-state .empty-desc { font-size: 14px; margin-bottom: 24px; }
169
174
 
170
175
  /* Confirm dialog */
171
- .dialog-overlay { display: none; position: fixed; inset: 0; background: rgba(0,0,0,0.7); backdrop-filter: blur(8px); z-index: 200; align-items: center; justify-content: center; }
176
+ .dialog-overlay { display: none; position: fixed; inset: 0; background: rgba(5,5,26,0.85); backdrop-filter: blur(12px); z-index: 200; align-items: center; justify-content: center; }
172
177
  .dialog-overlay.show { display: flex; }
173
- .dialog { background: rgba(20,20,40,0.95); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: 28px; max-width: 400px; width: 90%; backdrop-filter: blur(20px); box-shadow: 0 20px 60px rgba(0,0,0,0.5), var(--glow); }
178
+ .dialog { background: rgba(15,15,45,0.95); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: 32px; max-width: 420px; width: 90%; backdrop-filter: blur(30px); box-shadow: 0 25px 80px rgba(0,0,0,0.6), var(--glow); }
174
179
  .dialog-title { font-size: 16px; font-weight: 600; margin-bottom: 8px; }
175
180
  .dialog-desc { font-size: 14px; color: var(--text-muted); margin-bottom: 20px; }
176
181
  .dialog-actions { display: flex; justify-content: flex-end; gap: 8px; }
@@ -200,11 +205,11 @@
200
205
  .tab-panel.active { display: block; }
201
206
 
202
207
  /* Status dot */
203
- .status-dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; animation: pulse 2s ease-in-out infinite; }
204
- .status-dot.green { background: var(--green); box-shadow: 0 0 8px var(--green); }
205
- .status-dot.red { background: var(--red); box-shadow: 0 0 8px var(--red); }
206
- .status-dot.yellow { background: var(--yellow); box-shadow: 0 0 8px var(--yellow); }
207
- @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } }
208
+ .status-dot { width: 10px; height: 10px; border-radius: 50%; display: inline-block; animation: pulse 2s ease-in-out infinite; }
209
+ .status-dot.green { background: var(--green); box-shadow: 0 0 12px var(--green), 0 0 4px var(--green); }
210
+ .status-dot.red { background: var(--red); box-shadow: 0 0 12px var(--red), 0 0 4px var(--red); }
211
+ .status-dot.yellow { background: var(--yellow); box-shadow: 0 0 12px var(--yellow), 0 0 4px var(--yellow); }
212
+ @keyframes pulse { 0%, 100% { opacity: 1; transform: scale(1); } 50% { opacity: 0.6; transform: scale(0.85); } }
208
213
 
209
214
  /* Channel card */
210
215
  .channel-card { display: flex; align-items: center; gap: 16px; cursor: pointer; }
@@ -292,6 +297,55 @@
292
297
  .settings-subnav { width: 100%; display: flex; flex-wrap: wrap; gap: 4px; }
293
298
  .snav-item { padding: 8px 10px; font-size: 12px; flex-direction: column; gap: 4px; text-align: center; min-width: 70px; }
294
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; }
295
349
  </style>
296
350
  </head>
297
351
  <body>
@@ -301,35 +355,34 @@
301
355
  <nav class="sidebar">
302
356
  <div class="sidebar-logo">⚡ <span>OPC Studio</span></div>
303
357
  <div class="sidebar-nav">
304
- <div class="nav-item active" data-page="dashboard" onclick="navigate('dashboard')">
305
- <span class="icon">🏠</span> Dashboard
306
- </div>
307
- <div class="nav-item" data-page="chat" onclick="openLastChat()">
308
- <span class="icon">💬</span> Chat
309
- </div>
310
- <div class="nav-item" data-page="templates" onclick="navigate('templates')">
311
- <span class="icon">👤</span> Templates
312
- </div>
313
- <div class="nav-item" data-page="skills" onclick="navigate('skills')">
314
- <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>
315
362
  </div>
363
+
364
+ <!-- Section 2: Create -->
365
+ <div class="sidebar-divider"></div>
316
366
  <div class="nav-item" data-page="create" onclick="navigate('create')">
317
- <span class="icon">✨</span> Create Agent
318
- </div>
319
- <div class="nav-item" data-page="settings" onclick="currentSettingsTab='models';navigate('settings')">
320
- <span class="icon">🤖</span> Models
321
- </div>
322
- <div class="nav-item" data-page="settings" onclick="currentSettingsTab='channels';navigate('settings')">
323
- <span class="icon">📡</span> Channels
324
- </div>
325
- <div class="nav-item" data-page="settings" onclick="currentSettingsTab='memory';navigate('settings')">
326
- <span class="icon">🧠</span> Memory
327
- </div>
328
- <div class="nav-item" data-page="settings" onclick="navigate('settings')">
329
- <span class="icon">⚙️</span> Settings
367
+ <span class="icon">➕</span> 新建 Agent
330
368
  </div>
331
- <div class="nav-item" data-page="schedules" onclick="navigate('schedules')">
332
- <span class="icon">⏰</span> Schedules
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>
333
386
  </div>
334
387
  </div>
335
388
  <div style="padding: 12px; border-top: 1px solid var(--border); font-size: 12px; color: var(--text-dim);">
@@ -366,6 +419,76 @@
366
419
  </div>
367
420
  </div>
368
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
+
369
492
  <!-- Templates Page -->
370
493
  <div class="page" id="page-templates">
371
494
  <h1 class="page-title">Template Market</h1>
@@ -949,6 +1072,8 @@
949
1072
  const parts = path.split('/').filter(Boolean);
950
1073
  if (parts[0] === 'chat' && parts[1]) {
951
1074
  openChat(parts[1]);
1075
+ } else if (parts[0] === 'agent' && parts[1]) {
1076
+ loadSidebarAgents().then(() => navigateToAgent(parts[1]));
952
1077
  } else if (parts[0] === 'settings') {
953
1078
  if (parts[1]) currentSettingsTab = parts[1];
954
1079
  navigate('settings');
@@ -990,16 +1115,152 @@
990
1115
  } catch(e) { console.error('Failed to load agents:', e); }
991
1116
  }
992
1117
 
993
- // === Navigation ===
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 ===
994
1251
  function navigate(page) {
995
1252
  document.querySelectorAll('.page, .chat-container').forEach(p => { p.classList.remove('active'); p.style.display = ''; });
996
1253
  document.querySelectorAll('.nav-item').forEach(n => n.classList.remove('active'));
997
1254
  const navItem = document.querySelector(`.nav-item[data-page="${page}"]`);
998
1255
  if (navItem) navItem.classList.add('active');
999
1256
 
1000
- if (page === 'dashboard') { loadAgents(); loadHealthDashboard(); }
1257
+ if (page === 'dashboard') { loadAgents(); loadHealthDashboard(); loadSidebarAgents(); }
1001
1258
  if (page === 'create') { renderWizard(); renderWizardTemplates(); }
1002
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; }
1003
1264
  if (page === 'schedules') { loadSchedules(); }
1004
1265
  if (page === 'skills') { loadSkillsMarketplace(); }
1005
1266