modernx-gui 1.1.5 → 1.2.0

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.
Files changed (2) hide show
  1. package/dist/index.html +511 -96
  2. package/package.json +1 -1
package/dist/index.html CHANGED
@@ -3,7 +3,8 @@
3
3
  <head>
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>ModernX GUI</title>
6
+ <title>ModernX GUI - 实时状态监控</title>
7
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
7
8
  <style>
8
9
  * {
9
10
  margin: 0;
@@ -12,99 +13,220 @@
12
13
  }
13
14
 
14
15
  body {
15
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
16
- background: #f5f5f5;
16
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', sans-serif;
17
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
17
18
  color: #333;
19
+ overflow: hidden;
18
20
  }
19
21
 
20
22
  .header {
21
- background: #fff;
22
- padding: 1rem;
23
- border-bottom: 1px solid #e0e0e0;
24
- box-shadow: 0 2px 4px rgba(0,0,0,0.1);
23
+ background: rgba(255, 255, 255, 0.95);
24
+ backdrop-filter: blur(10px);
25
+ padding: 1rem 2rem;
26
+ border-bottom: 1px solid rgba(255, 255, 255, 0.2);
27
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
28
+ display: flex;
29
+ justify-content: space-between;
30
+ align-items: center;
25
31
  }
26
32
 
27
33
  .header h1 {
28
34
  font-size: 1.5rem;
29
- color: #2196F3;
35
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
36
+ -webkit-background-clip: text;
37
+ -webkit-text-fill-color: transparent;
38
+ font-weight: 700;
39
+ }
40
+
41
+ .status-badge {
42
+ padding: 0.5rem 1rem;
43
+ border-radius: 20px;
44
+ font-size: 0.8rem;
45
+ font-weight: 600;
46
+ display: flex;
47
+ align-items: center;
48
+ gap: 0.5rem;
49
+ }
50
+
51
+ .status-badge.connected {
52
+ background: linear-gradient(135deg, #4CAF50, #45a049);
53
+ color: white;
54
+ }
55
+
56
+ .status-badge.disconnected {
57
+ background: linear-gradient(135deg, #f44336, #d32f2f);
58
+ color: white;
30
59
  }
31
60
 
32
- .container {
61
+ .main-container {
33
62
  display: flex;
34
- height: calc(100vh - 60px);
63
+ height: calc(100vh - 70px);
35
64
  }
36
65
 
37
66
  .sidebar {
38
- width: 250px;
39
- background: #fff;
40
- border-right: 1px solid #e0e0e0;
67
+ width: 280px;
68
+ background: rgba(255, 255, 255, 0.95);
69
+ backdrop-filter: blur(10px);
70
+ border-right: 1px solid rgba(255, 255, 255, 0.2);
71
+ overflow-y: auto;
72
+ }
73
+
74
+ .sidebar-section {
75
+ padding: 1.5rem;
76
+ border-bottom: 1px solid rgba(0, 0, 0, 0.1);
77
+ }
78
+
79
+ .sidebar-section h3 {
80
+ font-size: 0.9rem;
81
+ text-transform: uppercase;
82
+ color: #666;
83
+ margin-bottom: 1rem;
84
+ font-weight: 600;
85
+ }
86
+
87
+ .project-info {
88
+ background: rgba(102, 126, 234, 0.1);
89
+ border-radius: 12px;
41
90
  padding: 1rem;
91
+ margin-bottom: 1rem;
92
+ }
93
+
94
+ .project-info-item {
95
+ display: flex;
96
+ justify-content: space-between;
97
+ margin-bottom: 0.5rem;
98
+ font-size: 0.9rem;
99
+ }
100
+
101
+ .project-info-item:last-child {
102
+ margin-bottom: 0;
103
+ }
104
+
105
+ .model-item {
106
+ background: white;
107
+ border-radius: 8px;
108
+ padding: 1rem;
109
+ margin-bottom: 0.8rem;
110
+ cursor: pointer;
111
+ transition: all 0.3s ease;
112
+ border: 2px solid transparent;
113
+ }
114
+
115
+ .model-item:hover {
116
+ transform: translateY(-2px);
117
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
118
+ border-color: #667eea;
119
+ }
120
+
121
+ .model-item.active {
122
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
123
+ color: white;
124
+ }
125
+
126
+ .model-name {
127
+ font-weight: 600;
128
+ margin-bottom: 0.5rem;
129
+ }
130
+
131
+ .model-state-count {
132
+ font-size: 0.8rem;
133
+ opacity: 0.8;
42
134
  }
43
135
 
44
- .main {
136
+ .content-area {
45
137
  flex: 1;
46
138
  display: flex;
47
139
  flex-direction: column;
140
+ background: rgba(255, 255, 255, 0.95);
141
+ backdrop-filter: blur(10px);
48
142
  }
49
143
 
50
144
  .tabs {
51
- background: #fff;
52
- border-bottom: 1px solid #e0e0e0;
53
145
  display: flex;
146
+ background: rgba(255, 255, 255, 0.8);
147
+ border-bottom: 1px solid rgba(0, 0, 0, 0.1);
54
148
  }
55
149
 
56
150
  .tab {
57
151
  padding: 1rem 2rem;
58
152
  cursor: pointer;
59
- border-bottom: 2px solid transparent;
153
+ border-bottom: 3px solid transparent;
154
+ transition: all 0.3s ease;
155
+ font-weight: 500;
156
+ position: relative;
157
+ }
158
+
159
+ .tab:hover {
160
+ background: rgba(102, 126, 234, 0.1);
60
161
  }
61
162
 
62
163
  .tab.active {
63
- border-bottom-color: #2196F3;
64
- color: #2196F3;
164
+ border-bottom-color: #667eea;
165
+ color: #667eea;
65
166
  }
66
167
 
67
- .content {
168
+ .tab-content {
68
169
  flex: 1;
69
- padding: 1rem;
170
+ padding: 2rem;
70
171
  overflow-y: auto;
71
172
  }
72
173
 
73
174
  .state-view {
74
- background: #fff;
75
- border-radius: 8px;
76
- padding: 1rem;
77
- margin-bottom: 1rem;
78
- box-shadow: 0 2px 4px rgba(0,0,0,0.1);
175
+ background: white;
176
+ border-radius: 12px;
177
+ padding: 1.5rem;
178
+ margin-bottom: 1.5rem;
179
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
79
180
  }
80
181
 
81
182
  .state-view h3 {
82
- margin-bottom: 0.5rem;
183
+ margin-bottom: 1rem;
83
184
  color: #333;
185
+ font-size: 1.1rem;
186
+ display: flex;
187
+ align-items: center;
188
+ gap: 0.5rem;
84
189
  }
85
190
 
86
191
  .state-json {
87
- background: #f8f8f8;
192
+ background: #f8f9fa;
193
+ border: 1px solid #e9ecef;
194
+ border-radius: 8px;
88
195
  padding: 1rem;
89
- border-radius: 4px;
90
- font-family: 'Monaco', 'Menlo', monospace;
196
+ font-family: 'Monaco', 'Menlo', 'Consolas', monospace;
91
197
  font-size: 0.9rem;
92
198
  white-space: pre-wrap;
93
199
  word-break: break-all;
200
+ max-height: 400px;
201
+ overflow-y: auto;
94
202
  }
95
203
 
96
204
  .action-item {
97
- background: #fff;
98
- border-radius: 8px;
99
- padding: 1rem;
100
- margin-bottom: 0.5rem;
205
+ background: white;
206
+ border-radius: 12px;
207
+ padding: 1rem 1.5rem;
208
+ margin-bottom: 1rem;
101
209
  border-left: 4px solid #4CAF50;
102
- box-shadow: 0 2px 4px rgba(0,0,0,0.1);
210
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
211
+ transition: all 0.3s ease;
212
+ }
213
+
214
+ .action-item:hover {
215
+ transform: translateX(4px);
216
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
217
+ }
218
+
219
+ .action-header {
220
+ display: flex;
221
+ justify-content: space-between;
222
+ align-items: center;
223
+ margin-bottom: 0.5rem;
103
224
  }
104
225
 
105
226
  .action-type {
106
- font-weight: bold;
107
- color: #2196F3;
227
+ font-weight: 600;
228
+ color: #667eea;
229
+ font-size: 1rem;
108
230
  }
109
231
 
110
232
  .action-time {
@@ -112,63 +234,231 @@
112
234
  font-size: 0.8rem;
113
235
  }
114
236
 
115
- .status {
237
+ .action-payload {
238
+ background: #f8f9fa;
239
+ border-radius: 6px;
240
+ padding: 0.5rem;
241
+ font-family: monospace;
242
+ font-size: 0.9rem;
243
+ color: #495057;
244
+ }
245
+
246
+ .model-detail {
247
+ background: white;
248
+ border-radius: 12px;
249
+ padding: 1.5rem;
250
+ margin-bottom: 1.5rem;
251
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
252
+ }
253
+
254
+ .model-detail-header {
255
+ display: flex;
256
+ justify-content: space-between;
257
+ align-items: center;
258
+ margin-bottom: 1rem;
259
+ padding-bottom: 1rem;
260
+ border-bottom: 2px solid #f0f0f0;
261
+ }
262
+
263
+ .model-detail-title {
264
+ font-size: 1.2rem;
265
+ font-weight: 600;
266
+ color: #333;
267
+ }
268
+
269
+ .model-detail-actions {
270
+ display: flex;
271
+ gap: 0.5rem;
272
+ }
273
+
274
+ .btn {
116
275
  padding: 0.5rem 1rem;
117
- border-radius: 4px;
276
+ border: none;
277
+ border-radius: 6px;
278
+ cursor: pointer;
118
279
  font-size: 0.8rem;
119
- font-weight: bold;
280
+ transition: all 0.3s ease;
281
+ font-weight: 500;
282
+ }
283
+
284
+ .btn-primary {
285
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
286
+ color: white;
287
+ }
288
+
289
+ .btn-secondary {
290
+ background: #f8f9fa;
291
+ color: #495057;
292
+ border: 1px solid #dee2e6;
293
+ }
294
+
295
+ .btn:hover {
296
+ transform: translateY(-2px);
297
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
120
298
  }
121
299
 
122
- .status.connected {
123
- background: #E8F5E8;
124
- color: #2E7D32;
300
+ .stats-grid {
301
+ display: grid;
302
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
303
+ gap: 1rem;
304
+ margin-bottom: 2rem;
125
305
  }
126
306
 
127
- .status.disconnected {
128
- background: #FFEBEE;
129
- color: #C62828;
307
+ .stat-card {
308
+ background: white;
309
+ border-radius: 12px;
310
+ padding: 1.5rem;
311
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
312
+ text-align: center;
313
+ }
314
+
315
+ .stat-value {
316
+ font-size: 2rem;
317
+ font-weight: 700;
318
+ color: #667eea;
319
+ margin-bottom: 0.5rem;
320
+ }
321
+
322
+ .stat-label {
323
+ color: #666;
324
+ font-size: 0.9rem;
325
+ }
326
+
327
+ .loading {
328
+ display: flex;
329
+ justify-content: center;
330
+ align-items: center;
331
+ height: 200px;
332
+ color: #666;
333
+ }
334
+
335
+ .spinner {
336
+ border: 3px solid #f3f3f3;
337
+ border-top: 3px solid #667eea;
338
+ border-radius: 50%;
339
+ width: 40px;
340
+ height: 40px;
341
+ animation: spin 1s linear infinite;
342
+ margin-right: 1rem;
343
+ }
344
+
345
+ @keyframes spin {
346
+ 0% { transform: rotate(0deg); }
347
+ 100% { transform: rotate(360deg); }
348
+ }
349
+
350
+ .empty-state {
351
+ text-align: center;
352
+ padding: 3rem;
353
+ color: #666;
354
+ }
355
+
356
+ .empty-state i {
357
+ font-size: 3rem;
358
+ margin-bottom: 1rem;
359
+ opacity: 0.5;
130
360
  }
131
361
  </style>
132
362
  </head>
133
363
  <body>
134
- <div class="header">
135
- <h1>🚀 ModernX GUI</h1>
136
- <div class="status" id="connection-status">连接中...</div>
137
- </div>
364
+ <header class="header">
365
+ <h1><i class="fas fa-rocket"></i> ModernX GUI</h1>
366
+ <div class="status-badge" id="connection-status">
367
+ <i class="fas fa-circle"></i>
368
+ <span>连接中...</span>
369
+ </div>
370
+ </header>
138
371
 
139
- <div class="container">
140
- <div class="sidebar">
141
- <h3>项目信息</h3>
142
- <div id="project-info">加载中...</div>
372
+ <div class="main-container">
373
+ <aside class="sidebar">
374
+ <div class="sidebar-section">
375
+ <h3><i class="fas fa-info-circle"></i> 项目信息</h3>
376
+ <div class="project-info" id="project-info">
377
+ <div class="loading">
378
+ <div class="spinner"></div>
379
+ 加载中...
380
+ </div>
381
+ </div>
382
+ </div>
143
383
 
144
- <h3>模型列表</h3>
145
- <div id="models-list">加载中...</div>
146
- </div>
384
+ <div class="sidebar-section">
385
+ <h3><i class="fas fa-cubes"></i> 模型列表</h3>
386
+ <div id="models-list">
387
+ <div class="loading">
388
+ <div class="spinner"></div>
389
+ 加载中...
390
+ </div>
391
+ </div>
392
+ </div>
393
+ </aside>
147
394
 
148
- <div class="main">
395
+ <main class="content-area">
149
396
  <div class="tabs">
150
- <div class="tab active" data-tab="state">状态</div>
151
- <div class="tab" data-tab="actions">动作历史</div>
152
- <div class="tab" data-tab="models">模型</div>
397
+ <div class="tab active" data-tab="dashboard">
398
+ <i class="fas fa-tachometer-alt"></i> 仪表板
399
+ </div>
400
+ <div class="tab" data-tab="state">
401
+ <i class="fas fa-database"></i> 状态
402
+ </div>
403
+ <div class="tab" data-tab="actions">
404
+ <i class="fas fa-history"></i> 动作历史
405
+ </div>
406
+ <div class="tab" data-tab="models">
407
+ <i class="fas fa-cube"></i> 模型详情
408
+ </div>
153
409
  </div>
154
410
 
155
- <div class="content">
156
- <div id="state-content" class="tab-content">
157
- <div class="state-view">
158
- <h3>当前状态</h3>
159
- <div class="state-json" id="current-state">等待数据...</div>
411
+ <div class="tab-content" id="dashboard-content">
412
+ <div class="stats-grid">
413
+ <div class="stat-card">
414
+ <div class="stat-value" id="total-actions">0</div>
415
+ <div class="stat-label">总动作数</div>
416
+ </div>
417
+ <div class="stat-card">
418
+ <div class="stat-value" id="total-models">0</div>
419
+ <div class="stat-label">模型数量</div>
420
+ </div>
421
+ <div class="stat-card">
422
+ <div class="stat-value" id="connection-uptime">0s</div>
423
+ <div class="stat-label">连接时长</div>
424
+ </div>
425
+ <div class="stat-card">
426
+ <div class="stat-value" id="state-changes">0</div>
427
+ <div class="stat-label">状态变化</div>
160
428
  </div>
161
429
  </div>
162
430
 
163
- <div id="actions-content" class="tab-content" style="display: none;">
164
- <div id="actions-list">等待动作数据...</div>
431
+ <div class="state-view">
432
+ <h3><i class="fas fa-database"></i> 当前状态概览</h3>
433
+ <div class="state-json" id="dashboard-state">等待数据...</div>
165
434
  </div>
166
-
167
- <div id="models-content" class="tab-content" style="display: none;">
168
- <div id="models-detail">等待模型数据...</div>
435
+ </div>
436
+
437
+ <div class="tab-content" id="state-content" style="display: none;">
438
+ <div class="state-view">
439
+ <h3><i class="fas fa-database"></i> 完整状态树</h3>
440
+ <div class="state-json" id="current-state">等待数据...</div>
169
441
  </div>
170
442
  </div>
171
- </div>
443
+
444
+ <div class="tab-content" id="actions-content" style="display: none;">
445
+ <div id="actions-list">
446
+ <div class="empty-state">
447
+ <i class="fas fa-history"></i>
448
+ <div>暂无动作记录</div>
449
+ </div>
450
+ </div>
451
+ </div>
452
+
453
+ <div class="tab-content" id="models-content" style="display: none;">
454
+ <div id="models-detail">
455
+ <div class="empty-state">
456
+ <i class="fas fa-cube"></i>
457
+ <div>暂无模型数据</div>
458
+ </div>
459
+ </div>
460
+ </div>
461
+ </main>
172
462
  </div>
173
463
 
174
464
  <script>
@@ -179,6 +469,11 @@
179
469
  this.currentState = {};
180
470
  this.actions = [];
181
471
  this.models = [];
472
+ this.stats = {
473
+ totalActions: 0,
474
+ stateChanges: 0,
475
+ connectionStartTime: Date.now()
476
+ };
182
477
 
183
478
  this.init();
184
479
  }
@@ -187,6 +482,7 @@
187
482
  this.fetchProjectInfo();
188
483
  this.setupWebSocket();
189
484
  this.setupTabs();
485
+ this.startStatsUpdater();
190
486
  }
191
487
 
192
488
  async fetchProjectInfo() {
@@ -203,12 +499,26 @@
203
499
  const projectInfoEl = document.getElementById('project-info');
204
500
  if (this.projectInfo && this.projectInfo.name) {
205
501
  projectInfoEl.innerHTML = `
206
- <div><strong>项目名:</strong> ${this.projectInfo.name}</div>
207
- <div><strong>路径:</strong> ${this.projectInfo.path}</div>
208
- <div><strong>ModernX:</strong> ${this.projectInfo.hasModernX ? '✅' : '❌'}</div>
502
+ <div class="project-info-item">
503
+ <span><i class="fas fa-folder"></i> 项目名</span>
504
+ <span><strong>${this.projectInfo.name}</strong></span>
505
+ </div>
506
+ <div class="project-info-item">
507
+ <span><i class="fas fa-map-marker-alt"></i> 路径</span>
508
+ <span>${this.projectInfo.path}</span>
509
+ </div>
510
+ <div class="project-info-item">
511
+ <span><i class="fas fa-check-circle"></i> ModernX</span>
512
+ <span>${this.projectInfo.hasModernX ? '✅ 已启用' : '❌ 未检测到'}</span>
513
+ </div>
209
514
  `;
210
515
  } else {
211
- projectInfoEl.innerHTML = '<div>未检测到 ModernX 项目</div>';
516
+ projectInfoEl.innerHTML = `
517
+ <div class="project-info-item">
518
+ <span><i class="fas fa-exclamation-triangle"></i> 状态</span>
519
+ <span>未检测到 ModernX 项目</span>
520
+ </div>
521
+ `;
212
522
  }
213
523
  }
214
524
 
@@ -245,21 +555,33 @@
245
555
 
246
556
  updateConnectionStatus(status) {
247
557
  const statusEl = document.getElementById('connection-status');
248
- statusEl.className = `status ${status}`;
249
- statusEl.textContent = status === 'connected' ? '已连接' : '连接断开';
558
+ statusEl.className = `status-badge ${status}`;
559
+ statusEl.innerHTML = status === 'connected'
560
+ ? '<i class="fas fa-circle"></i><span>已连接</span>'
561
+ : '<i class="fas fa-circle"></i><span>连接断开</span>';
250
562
  }
251
563
 
252
564
  handleMessage(data) {
253
565
  if (data.type === 'state') {
566
+ const prevState = JSON.stringify(this.currentState);
254
567
  this.currentState = data.payload;
568
+ const newState = JSON.stringify(this.currentState);
569
+
570
+ if (prevState !== newState) {
571
+ this.stats.stateChanges++;
572
+ }
573
+
255
574
  this.renderState();
575
+ this.renderDashboardState();
256
576
  } else if (data.type === 'action') {
257
577
  this.actions.unshift(data.payload);
258
- this.actions = this.actions.slice(0, 50); // 只保留最近50个动作
578
+ this.actions = this.actions.slice(0, 100); // 只保留最近100个动作
579
+ this.stats.totalActions++;
259
580
  this.renderActions();
260
581
  } else if (data.type === 'models') {
261
582
  this.models = data.payload;
262
583
  this.renderModels();
584
+ this.renderModelsList();
263
585
  }
264
586
  }
265
587
 
@@ -268,46 +590,124 @@
268
590
  stateEl.textContent = JSON.stringify(this.currentState, null, 2);
269
591
  }
270
592
 
593
+ renderDashboardState() {
594
+ const stateEl = document.getElementById('dashboard-state');
595
+ const truncatedState = this.truncateState(this.currentState, 200);
596
+ stateEl.textContent = JSON.stringify(truncatedState, null, 2);
597
+ }
598
+
599
+ truncateState(state, maxChars) {
600
+ const str = JSON.stringify(state, null, 2);
601
+ if (str.length <= maxChars) return state;
602
+
603
+ const truncated = str.substring(0, maxChars);
604
+ try {
605
+ return JSON.parse(truncated);
606
+ } catch {
607
+ return { message: '状态数据过大,请查看完整状态页面' };
608
+ }
609
+ }
610
+
271
611
  renderActions() {
272
612
  const actionsEl = document.getElementById('actions-list');
273
613
  if (this.actions.length === 0) {
274
- actionsEl.innerHTML = '<div>暂无动作记录</div>';
614
+ actionsEl.innerHTML = `
615
+ <div class="empty-state">
616
+ <i class="fas fa-history"></i>
617
+ <div>暂无动作记录</div>
618
+ </div>
619
+ `;
275
620
  return;
276
621
  }
277
622
 
278
623
  actionsEl.innerHTML = this.actions.map(action => `
279
624
  <div class="action-item">
280
- <div class="action-type">${action.type}</div>
281
- <div class="action-time">${new Date(action.timestamp).toLocaleString()}</div>
282
- ${action.payload ? `<div>Payload: ${JSON.stringify(action.payload)}</div>` : ''}
625
+ <div class="action-header">
626
+ <div class="action-type">${action.type}</div>
627
+ <div class="action-time">${new Date(action.timestamp).toLocaleString()}</div>
628
+ </div>
629
+ ${action.payload ? `<div class="action-payload">${JSON.stringify(action.payload)}</div>` : ''}
283
630
  </div>
284
631
  `).join('');
285
632
  }
286
633
 
287
634
  renderModels() {
288
635
  const modelsEl = document.getElementById('models-detail');
289
- const modelsListEl = document.getElementById('models-list');
290
-
291
636
  if (this.models.length === 0) {
292
- modelsEl.innerHTML = '<div>暂无模型数据</div>';
293
- modelsListEl.innerHTML = '<div>无模型</div>';
637
+ modelsEl.innerHTML = `
638
+ <div class="empty-state">
639
+ <i class="fas fa-cube"></i>
640
+ <div>暂无模型数据</div>
641
+ </div>
642
+ `;
294
643
  return;
295
644
  }
296
645
 
297
- // 渲染侧边栏模型列表
298
- modelsListEl.innerHTML = this.models.map(model =>
299
- `<div style="padding: 0.5rem 0; border-bottom: 1px solid #eee;">${model.namespace}</div>`
300
- ).join('');
301
-
302
- // 渲染详细模型信息
303
646
  modelsEl.innerHTML = this.models.map(model => `
304
- <div class="state-view">
305
- <h3>${model.namespace}</h3>
647
+ <div class="model-detail">
648
+ <div class="model-detail-header">
649
+ <div class="model-detail-title">
650
+ <i class="fas fa-cube"></i> ${model.namespace}
651
+ </div>
652
+ <div class="model-detail-actions">
653
+ <button class="btn btn-secondary" onclick="gui.copyModelState('${model.namespace}')">
654
+ <i class="fas fa-copy"></i> 复制
655
+ </button>
656
+ </div>
657
+ </div>
306
658
  <div class="state-json">${JSON.stringify(model.state, null, 2)}</div>
307
659
  </div>
308
660
  `).join('');
309
661
  }
310
662
 
663
+ renderModelsList() {
664
+ const modelsListEl = document.getElementById('models-list');
665
+ if (this.models.length === 0) {
666
+ modelsListEl.innerHTML = `
667
+ <div class="empty-state">
668
+ <i class="fas fa-cube"></i>
669
+ <div>无模型</div>
670
+ </div>
671
+ `;
672
+ return;
673
+ }
674
+
675
+ modelsListEl.innerHTML = this.models.map(model => `
676
+ <div class="model-item" onclick="gui.selectModel('${model.namespace}')">
677
+ <div class="model-name">${model.namespace}</div>
678
+ <div class="model-state-count">${Object.keys(model.state || {}).length} 个状态</div>
679
+ </div>
680
+ `).join('');
681
+ }
682
+
683
+ selectModel(namespace) {
684
+ // 切换到模型详情标签
685
+ document.querySelectorAll('.tab').forEach(tab => tab.classList.remove('active'));
686
+ document.querySelector('[data-tab="models"]').classList.add('active');
687
+
688
+ // 显示对应内容
689
+ document.querySelectorAll('.tab-content').forEach(content => content.style.display = 'none');
690
+ document.getElementById('models-content').style.display = 'block';
691
+
692
+ // 高亮选中的模型
693
+ document.querySelectorAll('.model-item').forEach(item => item.classList.remove('active'));
694
+ event.target.closest('.model-item').classList.add('active');
695
+ }
696
+
697
+ copyModelState(namespace) {
698
+ const model = this.models.find(m => m.namespace === namespace);
699
+ if (model) {
700
+ navigator.clipboard.writeText(JSON.stringify(model.state, null, 2));
701
+ // 显示复制成功提示
702
+ const btn = event.target.closest('.btn');
703
+ const originalText = btn.innerHTML;
704
+ btn.innerHTML = '<i class="fas fa-check"></i> 已复制';
705
+ setTimeout(() => {
706
+ btn.innerHTML = originalText;
707
+ }, 2000);
708
+ }
709
+ }
710
+
311
711
  setupTabs() {
312
712
  const tabs = document.querySelectorAll('.tab');
313
713
  tabs.forEach(tab => {
@@ -326,11 +726,26 @@
326
726
  });
327
727
  });
328
728
  }
729
+
730
+ startStatsUpdater() {
731
+ setInterval(() => {
732
+ this.updateStats();
733
+ }, 1000);
734
+ }
735
+
736
+ updateStats() {
737
+ document.getElementById('total-actions').textContent = this.stats.totalActions;
738
+ document.getElementById('total-models').textContent = this.models.length;
739
+ document.getElementById('state-changes').textContent = this.stats.stateChanges;
740
+
741
+ const uptime = Math.floor((Date.now() - this.stats.connectionStartTime) / 1000);
742
+ document.getElementById('connection-uptime').textContent = `${uptime}s`;
743
+ }
329
744
  }
330
745
 
331
746
  // 启动 GUI
332
747
  document.addEventListener('DOMContentLoaded', () => {
333
- new ModernXGUI();
748
+ window.gui = new ModernXGUI();
334
749
  });
335
750
  </script>
336
751
  </body>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "modernx-gui",
3
- "version": "1.1.5",
3
+ "version": "1.2.0",
4
4
  "description": "ModernX development GUI with real-time visualization",
5
5
  "main": "dist/index.js",
6
6
  "bin": {