dsh-remote-plugin 0.6.7 → 0.6.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/public/index.html CHANGED
@@ -2,7 +2,7 @@
2
2
  <html lang="zh-CN">
3
3
  <head>
4
4
  <meta charset="utf-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover, user-scalable=no">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
6
6
  <meta name="theme-color" content="#05348B">
7
7
  <meta name="mobile-web-app-capable" content="yes">
8
8
  <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
@@ -20,9 +20,9 @@
20
20
  <span class="brand-name">DSH Remote</span>
21
21
  </div>
22
22
  <div class="topbar-right">
23
- <button id="btn-donate" class="topbar-btn icon-btn" data-i18n-title="settings.donate" data-i18n-aria="settings.donate">🎁</button>
24
- <button id="btn-feedback" class="topbar-btn icon-btn" data-i18n-title="feedback.title" data-i18n-aria="feedback.title" aria-haspopup="menu" aria-expanded="false">💬</button>
25
- <button id="btn-refresh" class="topbar-btn icon-btn" data-i18n-title="a11y.refresh" data-i18n-aria="a11y.refresh">⟳</button>
23
+ <button id="btn-donate" class="topbar-btn icon-btn" data-i18n-title="settings.donate" data-i18n-aria="settings.donate"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 8h11v6a4 4 0 0 1-4 4H10a4 4 0 0 1-4-4Z"/><path d="M17 10h1.5a2.5 2.5 0 0 1 0 5H17M9 4c0 1 1 1.2 1 2M13 4c0 1 1 1.2 1 2M5 20h13"/></svg></button>
24
+ <button id="btn-feedback" class="topbar-btn icon-btn" data-i18n-title="feedback.title" data-i18n-aria="feedback.title" aria-haspopup="menu" aria-expanded="false"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 11.5a7.5 7.5 0 0 1-7.5 7.5H7l-3 2 .8-3.1A7.5 7.5 0 1 1 20 11.5Z"/><path d="M8 11.5h.01M12 11.5h.01M16 11.5h.01"/></svg></button>
25
+ <button id="btn-refresh" class="topbar-btn icon-btn" data-i18n-title="a11y.refresh" data-i18n-aria="a11y.refresh"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 11a8 8 0 1 0 1 4"/><path d="M20 5v6h-6"/></svg></button>
26
26
  <span id="conn-badge" class="topbar-btn conn-badge off" data-i18n="conn.off">未连接</span>
27
27
  </div>
28
28
  </header>
@@ -60,7 +60,7 @@
60
60
  <div id="fs-path" class="fs-path" data-i18n="fs.loading">加载中…</div>
61
61
  <button id="fs-new-workspace" class="mini-btn" data-i18n="fs.newWorkspace">新建工作区</button>
62
62
  <button id="fs-upload-btn" class="mini-btn" data-i18n="fs.upload">上传</button>
63
- <button id="fs-refresh" class="icon-btn" data-i18n-title="a11y.refresh" data-i18n-aria="a11y.refresh">⟳</button>
63
+ <button id="fs-refresh" class="icon-btn" data-i18n-title="a11y.refresh" data-i18n-aria="a11y.refresh"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 11a8 8 0 1 0 1 4"/><path d="M20 5v6h-6"/></svg></button>
64
64
  </div>
65
65
  <div id="fs-progress" class="fs-progress hidden">
66
66
  <div class="fs-progress-track"><div id="fs-progress-bar" class="fs-progress-bar"></div></div>
@@ -78,12 +78,13 @@
78
78
  <!-- 会话详情 -->
79
79
  <section id="view-session" class="view hidden">
80
80
  <div id="session-head" class="session-head">
81
- <button id="btn-back" class="icon-btn">‹</button>
81
+ <button id="btn-back" class="icon-btn" data-i18n-title="settings.back" data-i18n-aria="settings.back">‹</button>
82
82
  <div class="session-head-meta">
83
83
  <div id="session-title" class="session-title" data-i18n="session.loading">加载中…</div>
84
84
  <div id="session-sub" class="session-sub"></div>
85
85
  </div>
86
- <button id="btn-stats" class="icon-btn" data-i18n-title="stats.title">📊</button>
86
+ <button id="btn-stats" class="icon-btn" data-i18n-title="stats.title" data-i18n-aria="stats.title"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 19V9M12 19V5M19 19v-7M3 19h18"/></svg></button>
87
+ <button id="btn-fs-send" class="session-send-btn hidden" type="button" data-i18n="composer.send" data-i18n-title="composer.send" data-i18n-aria="composer.send">发送</button>
87
88
  <button id="btn-cancel" class="danger-btn hidden" data-i18n="session.stop">停止</button>
88
89
  </div>
89
90
 
@@ -102,7 +103,7 @@
102
103
  </div>
103
104
  </div>
104
105
 
105
- <div class="composer-wrap">
106
+ <div id="composer-wrap" class="composer-wrap">
106
107
  <div id="composer-menu" class="composer-menu hidden">
107
108
  <div class="menu-group">
108
109
  <div class="menu-title" data-i18n="menu.commandsTitle">输入指令</div>
@@ -133,20 +134,73 @@
133
134
  <div id="menu-efforts" class="menu-chips"></div>
134
135
  </div>
135
136
  </div>
137
+ <div id="composer-fs-handle" class="composer-fs-handle" aria-hidden="true"><span></span></div>
138
+ <div id="composer-image-menu" class="composer-image-menu hidden" role="menu">
139
+ <button type="button" class="composer-image-option" data-image-source="CAMERA" role="menuitem"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 8.5h3l1.5-2h7L17 8.5h3v10H4z"/><circle cx="12" cy="13.5" r="3.2"/></svg><span data-i18n="composer.takePhoto">拍照</span></button>
140
+ <button type="button" class="composer-image-option" data-image-source="PHOTOS" role="menuitem"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3.5" y="4" width="17" height="16" rx="2"/><circle cx="8.5" cy="9" r="1.2"/><path d="m5 17 4.5-4.5 3 3 2-2L19 17"/></svg><span data-i18n="composer.choosePhoto">相册</span></button>
141
+ </div>
142
+ <div id="composer-attachments" class="composer-attachments hidden"></div>
136
143
  <div class="composer">
137
144
  <button id="btn-plus" class="plus-btn" data-i18n-aria="a11y.more" data-i18n-title="a11y.moreTitle">+</button>
138
- <textarea id="composer-input" rows="1" data-i18n-placeholder="composer.placeholder" placeholder="给 DSH 发消息…"></textarea>
145
+ <div id="composer-input-wrap" class="composer-input-wrap">
146
+ <textarea id="composer-input" rows="1" data-i18n-placeholder="composer.placeholder" placeholder="给 DSH 发消息…"></textarea>
147
+ <button id="btn-fs-toggle" class="composer-fs-btn hidden" type="button" data-i18n-title="composer.fullscreen" data-i18n-aria="composer.fullscreen">
148
+ <svg id="fs-ico-expand" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" aria-hidden="true"><path d="M8 3H3v5M16 3h5v5M8 21H3v-5M21 16v5h-5"/></svg>
149
+ <svg id="fs-ico-collapse" class="hidden" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" aria-hidden="true"><path d="M9 3v6H3M15 3v6h6M9 21v-6H3M21 15h-6v6"/></svg>
150
+ </button>
151
+ </div>
152
+ <button id="btn-image" class="composer-image-btn" type="button" data-i18n-title="composer.addImage" data-i18n-aria="composer.addImage"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3.5" y="4" width="17" height="16" rx="2"/><circle cx="8.5" cy="9" r="1.2"/><path d="m5 17 4.5-4.5 3 3 2-2L19 17"/></svg></button>
139
153
  <button id="btn-send" class="send-btn" data-i18n="composer.send">发送</button>
140
154
  </div>
155
+ <input id="composer-camera-input" class="hidden" type="file" accept="image/*" capture="environment">
156
+ <input id="composer-gallery-input" class="hidden" type="file" accept="image/*" multiple>
141
157
  </div>
142
158
  </section>
143
159
 
144
- <!-- 待办 -->
160
+ <!-- 系统总览 -->
145
161
  <section id="view-activity" class="view hidden">
146
- <div class="section-head"><span data-i18n="pending.title">待处理</span><span id="pending-count" class="muted"></span></div>
147
- <div id="pending-list" class="pending-list"></div>
148
- <div class="section-head"><span data-i18n="jobs.title">后台任务</span></div>
149
- <div id="jobs-list" class="jobs-list"></div>
162
+ <div class="overview-head">
163
+ <div>
164
+ <div class="overview-eyebrow" data-i18n="overview.eyebrow">SYSTEM OVERVIEW</div>
165
+ <h1 class="overview-title" data-i18n="overview.title">主页</h1>
166
+ <p class="overview-subtitle" data-i18n="overview.subtitle">实时状态与需要你处理的事项</p>
167
+ </div>
168
+ <button id="overview-refresh" class="mini-btn overview-refresh" data-i18n-title="overview.refresh" data-i18n-aria="overview.refresh"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 11a8 8 0 1 0 1 4"/><path d="M20 5v6h-6"/></svg></button>
169
+ </div>
170
+
171
+ <div class="overview-pulse-card">
172
+ <div class="overview-pulse-copy">
173
+ <div class="overview-eyebrow" data-i18n="overview.systemPulse">系统链路</div>
174
+ <div id="overview-status" class="overview-status" data-i18n="overview.checking">检查中…</div>
175
+ <div id="overview-status-desc" class="overview-status-desc">—</div>
176
+ <button id="overview-primary-action" class="overview-primary-action" type="button" data-i18n="overview.action.openSession">打开最近会话</button>
177
+ </div>
178
+ <div id="overview-pulse-ring" class="system-pulse-ring" style="--pulse-pct:0%" aria-label="System health">
179
+ <div class="system-pulse-core"><strong id="overview-health">—</strong><span id="overview-health-caption">—</span></div>
180
+ </div>
181
+ <div id="overview-links" class="overview-links" aria-label="System components">
182
+ <div class="overview-link" data-overview-link="gateway"><i></i><span data-i18n="overview.gateway">网关</span><b>—</b></div>
183
+ <div class="overview-link" data-overview-link="dsh"><i></i><span data-i18n="overview.dsh">DSH 上游</span><b>—</b></div>
184
+ <div class="overview-link" data-overview-link="mux"><i></i><span data-i18n="overview.mux">实时 mux</span><b>—</b></div>
185
+ <div class="overview-link" data-overview-link="host"><i></i><span data-i18n="overview.host">实时 host</span><b>—</b></div>
186
+ </div>
187
+ </div>
188
+
189
+ <div class="overview-section-head"><span data-i18n="overview.attention">需要你处理</span><span id="overview-attention-count" class="overview-section-meta"></span></div>
190
+ <div id="overview-attention-list" class="overview-attention-list"></div>
191
+
192
+ <div class="overview-section-head"><span data-i18n="overview.metrics">运行指标</span></div>
193
+ <div class="overview-metric-grid">
194
+ <div class="overview-metric"><span data-i18n="overview.dshVersion">DSH 版本</span><strong id="overview-dsh-version">—</strong></div>
195
+ <div class="overview-metric"><span data-i18n="overview.gatewayVersion">网关状态</span><strong id="overview-gateway-version">—</strong></div>
196
+ <div class="overview-metric"><span data-i18n="overview.activeSessions">活跃会话</span><strong id="overview-active-sessions">0</strong></div>
197
+ <div class="overview-metric"><span data-i18n="overview.connectionMode">连接模式</span><strong id="overview-connection-mode">—</strong></div>
198
+ </div>
199
+
200
+ <div class="overview-section-head"><span data-i18n="overview.activity">近期活动</span><span id="overview-active-count" class="overview-section-meta"></span></div>
201
+ <div id="overview-session-list" class="overview-session-list"></div>
202
+
203
+ <div class="overview-legacy hidden" aria-hidden="true"><div id="pending-list"></div><div id="jobs-list"></div><span id="pending-count"></span></div>
150
204
  </section>
151
205
 
152
206
  <!-- Token 统计 -->
@@ -322,6 +376,10 @@
322
376
  <div><div class="setting-name" data-i18n="settings.taskDoneTitle">任务结束通知</div><div class="setting-desc" data-i18n="settings.taskDoneDesc">任务/目标完成时推送“✅ 任务已完成”</div></div>
323
377
  <label class="switch"><input type="checkbox" id="opt-task-done"><span class="slider"></span></label>
324
378
  </div>
379
+ <div class="setting-row">
380
+ <div><div class="setting-name" data-i18n="settings.announcementHistoryTitle">历史公告</div><div class="setting-desc" data-i18n="settings.announcementHistoryDesc">查看以前收到的公告</div></div>
381
+ <button id="btn-announcement-history" class="mini-btn" data-i18n="settings.announcementHistory">查看记录</button>
382
+ </div>
325
383
  </div>
326
384
  </div>
327
385
 
@@ -400,16 +458,37 @@
400
458
  </button>
401
459
  </div>
402
460
 
403
- <nav class="bottom-nav">
404
- <button data-view="view-home" class="nav-btn active"><span class="nav-ico">▤</span><span data-i18n="nav.sessions">会话</span></button>
405
- <button data-view="view-files" class="nav-btn"><span class="nav-ico">⇅</span><span data-i18n="nav.files">文件</span></button>
406
- <button data-view="view-activity" class="nav-btn"><span class="nav-ico">◷</span><span data-i18n="nav.pending">待办</span><b id="nav-pending" class="nav-badge hidden"></b></button>
407
- <button data-view="view-stats" class="nav-btn"><span class="nav-ico">▦</span><span data-i18n="nav.stats">统计</span></button>
408
- <button data-view="view-settings" class="nav-btn"><span class="nav-ico">⚙</span><span data-i18n="nav.settings">设置</span></button>
461
+ <nav class="bottom-nav" aria-label="主导航">
462
+ <div class="nav-wave" aria-hidden="true">
463
+ <svg viewBox="0 0 100 100" preserveAspectRatio="none">
464
+ <path class="nav-wave-fill" d="M0 39 C16 39 28 39 37 31 C44 25 46 10 50 10 C54 10 56 25 63 31 C72 39 84 39 100 39 V100 H0Z"/>
465
+ <path class="nav-wave-line" d="M0 39 C16 39 28 39 37 31 C44 25 46 10 50 10 C54 10 56 25 63 31 C72 39 84 39 100 39"/>
466
+ </svg>
467
+ </div>
468
+ <button data-view="view-home" class="nav-btn">
469
+ <span class="nav-ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="4" y="4" width="6" height="6" rx="1"/><rect x="14" y="4" width="6" height="6" rx="1"/><rect x="4" y="14" width="6" height="6" rx="1"/><rect x="14" y="14" width="6" height="6" rx="1"/></svg></span>
470
+ <span data-i18n="nav.sessions">会话</span>
471
+ </button>
472
+ <button data-view="view-files" class="nav-btn">
473
+ <span class="nav-ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M8 4v12M5 7l3-3 3 3M16 20V8m-3 9 3 3 3-3"/><path d="M4 20h7M13 4h7" opacity=".55"/></svg></span>
474
+ <span data-i18n="nav.files">文件</span>
475
+ </button>
476
+ <button data-view="view-activity" class="nav-btn nav-home">
477
+ <span class="nav-home-orb"><span class="nav-ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" aria-hidden="true"><circle cx="12" cy="12" r="7.5"/><circle cx="12" cy="12" r="2.2" fill="currentColor" stroke="none"/><path d="M12 2.5v3M12 18.5v3M2.5 12h3M18.5 12h3" opacity=".75"/></svg></span></span>
478
+ <span data-i18n="nav.home">主页</span><b id="nav-pending" class="nav-badge hidden"></b>
479
+ </button>
480
+ <button data-view="view-stats" class="nav-btn">
481
+ <span class="nav-ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 19V9M12 19V5M19 19v-7"/><path d="M3 19h18" opacity=".55"/></svg></span>
482
+ <span data-i18n="nav.stats">统计</span>
483
+ </button>
484
+ <button data-view="view-settings" class="nav-btn">
485
+ <span class="nav-ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.7 1.7 0 0 0 .3 1.9l.1.1-1.8 1.8-.1-.1a1.7 1.7 0 0 0-1.9-.3 1.7 1.7 0 0 0-1 1.5v.2h-2.5v-.2a1.7 1.7 0 0 0-1-1.5 1.7 1.7 0 0 0-1.9.3l-.1.1-1.8-1.8.1-.1A1.7 1.7 0 0 0 8.1 15a1.7 1.7 0 0 0-1.5-1H6v-2.5h.2a1.7 1.7 0 0 0 1.5-1 1.7 1.7 0 0 0-.3-1.9l-.1-.1 1.8-1.8.1.1a1.7 1.7 0 0 0 1.9.3 1.7 1.7 0 0 0 1-1.5v-.2h2.5v.2a1.7 1.7 0 0 0 1 1.5 1.7 1.7 0 0 0 1.9-.3l.1-.1 1.8 1.8-.1.1a1.7 1.7 0 0 0-.3 1.9 1.7 1.7 0 0 0 1.5 1h.2V14h-.2a1.7 1.7 0 0 0-1.5 1Z"/></svg></span>
486
+ <span data-i18n="nav.settings">设置</span>
487
+ </button>
409
488
  </nav>
410
489
 
411
490
  <!-- 审批模态 -->
412
- <div id="modal-approval" class="modal hidden">
491
+ <div id="modal-approval" class="modal hidden" role="dialog" aria-modal="true">
413
492
  <div class="modal-card">
414
493
  <div class="modal-title" data-i18n="modal.approvalTitle">工具审批</div>
415
494
  <div id="approval-body" class="modal-body"></div>
@@ -421,7 +500,7 @@
421
500
  </div>
422
501
 
423
502
  <!-- 提问模态 -->
424
- <div id="modal-question" class="modal hidden">
503
+ <div id="modal-question" class="modal hidden" role="dialog" aria-modal="true">
425
504
  <div class="modal-card">
426
505
  <div class="modal-title" data-i18n="modal.questionTitle">DSH 需要你回答</div>
427
506
  <div id="question-body" class="modal-body"></div>
@@ -433,7 +512,7 @@
433
512
  </div>
434
513
 
435
514
  <!-- 写反馈模态 -->
436
- <div id="modal-feedback" class="modal hidden">
515
+ <div id="modal-feedback" class="modal hidden" role="dialog" aria-modal="true">
437
516
  <div class="modal-card">
438
517
  <div class="modal-title" data-i18n="feedback.modalTitle">写反馈</div>
439
518
  <div class="modal-body">
@@ -453,7 +532,7 @@
453
532
  </div>
454
533
 
455
534
  <!-- 新建工作区模态 -->
456
- <div id="modal-workspace" class="modal hidden">
535
+ <div id="modal-workspace" class="modal hidden" role="dialog" aria-modal="true">
457
536
  <div class="modal-card">
458
537
  <div class="modal-title" data-i18n="workspace.createTitle">新建工作区</div>
459
538
  <div class="modal-body">
@@ -468,7 +547,7 @@
468
547
  </div>
469
548
  </div>
470
549
 
471
- <div id="modal-archive" class="modal hidden">
550
+ <div id="modal-archive" class="modal hidden" role="dialog" aria-modal="true">
472
551
  <div class="modal-card archive-modal-card">
473
552
  <div class="modal-title" data-i18n="session.archiveTitle">确认归档会话</div>
474
553
  <p class="archive-modal-desc" data-i18n="session.archiveDesc">归档后会话将从普通列表隐藏,但不会删除对话记录。</p>
@@ -484,7 +563,7 @@
484
563
  </div>
485
564
 
486
565
  <!-- goal 模态 -->
487
- <div id="modal-goal" class="modal hidden">
566
+ <div id="modal-goal" class="modal hidden" role="dialog" aria-modal="true">
488
567
  <div class="modal-card">
489
568
  <div class="modal-title" data-i18n="modal.goalTitle">目标控制</div>
490
569
  <div id="goal-body" class="modal-body"></div>
@@ -496,7 +575,7 @@
496
575
  </div>
497
576
 
498
577
  <!-- 统计模态 -->
499
- <div id="modal-stats" class="modal hidden">
578
+ <div id="modal-stats" class="modal hidden" role="dialog" aria-modal="true">
500
579
  <div class="modal-card">
501
580
  <div class="modal-title" data-i18n="modal.statsTitle">本轮统计</div>
502
581
  <div id="stats-body" class="modal-body"></div>
@@ -507,7 +586,7 @@
507
586
  </div>
508
587
 
509
588
  <!-- 皮肤选择模态 -->
510
- <div id="modal-theme" class="modal hidden">
589
+ <div id="modal-theme" class="modal hidden" role="dialog" aria-modal="true">
511
590
  <div class="modal-card">
512
591
  <div class="modal-title" data-i18n="theme.panelTitle">选择配色</div>
513
592
  <div id="theme-options" class="theme-panel-list"></div>
@@ -518,7 +597,7 @@
518
597
  </div>
519
598
 
520
599
  <!-- 赞赏支持模态 -->
521
- <div id="modal-donate" class="modal hidden">
600
+ <div id="modal-donate" class="modal hidden" role="dialog" aria-modal="true">
522
601
  <div class="modal-card">
523
602
  <div class="modal-title" data-i18n="settings.donate">赞赏支持</div>
524
603
  <div class="modal-body donate-body">
@@ -532,7 +611,7 @@
532
611
  </div>
533
612
 
534
613
  <!-- 更新内容模态 -->
535
- <div id="modal-notes" class="modal hidden">
614
+ <div id="modal-notes" class="modal hidden" role="dialog" aria-modal="true">
536
615
  <div class="modal-card">
537
616
  <div class="modal-title"><span id="notes-version" class="muted"></span> <span data-i18n="notes.title">更新内容</span></div>
538
617
  <div class="modal-body notes-body">
@@ -563,7 +642,18 @@
563
642
  </div>
564
643
  </div>
565
644
 
566
- <div id="toast" class="toast hidden"></div>
645
+ <div id="modal-announcement-history" class="modal hidden" role="dialog" aria-modal="true">
646
+ <div class="modal-card announcement-history-card">
647
+ <div class="announcement-kicker" data-i18n="announcement.historyLabel">公告记录</div>
648
+ <div class="modal-title" data-i18n="announcement.historyTitle">历史公告</div>
649
+ <div id="announcement-history-list" class="announcement-history-list"></div>
650
+ <div class="modal-actions">
651
+ <button id="announcement-history-close" class="btn subtle" data-i18n="announcement.close">关闭</button>
652
+ </div>
653
+ </div>
654
+ </div>
655
+
656
+ <div id="toast" class="toast hidden" role="status" aria-live="polite"></div>
567
657
 
568
658
  <script>
569
659
  window.APP_STR = {
@@ -574,7 +664,14 @@
574
664
  'conn.poll': '轮询', 'conn.pollTitle': '当前网络不支持实时推送,已降级为轮询(延迟数秒)',
575
665
  'conn.titleGroup': '{group} · {url}({ms})',
576
666
  'common.refreshing': '刷新中…',
577
- 'nav.sessions': '会话', 'nav.files': '文件', 'nav.pending': '待办', 'nav.stats': '统计', 'nav.settings': '设置',
667
+ 'nav.sessions': '会话', 'nav.files': '文件', 'nav.pending': '待办', 'nav.overview': '总览', 'nav.home': '主页', 'nav.stats': '统计', 'nav.settings': '设置',
668
+ 'overview.eyebrow': 'SYSTEM OVERVIEW', 'overview.title': '主页', 'overview.subtitle': '实时状态与需要你处理的事项',
669
+ 'overview.systemPulse': '系统链路', 'overview.nominal': '系统链路正常', 'overview.degraded': '部分链路需要关注', 'overview.offline': '连接已断开', 'overview.checking': '检查中…', 'overview.components': '{n}/4 个模块在线', 'overview.live': 'LIVE', 'overview.allLinked': '全部链路', 'overview.offlineCore': 'OFFLINE',
670
+ 'overview.gateway': '网关', 'overview.dsh': 'DSH 上游', 'overview.mux': '实时 mux', 'overview.host': '实时 host', 'overview.online': '在线', 'overview.offlineShort': '离线',
671
+ 'overview.attention': '需要你处理', 'overview.metrics': '运行指标', 'overview.activity': '近期活动', 'overview.dshVersion': 'DSH 版本', 'overview.gatewayVersion': '网关状态',
672
+ 'overview.activeSessions': '活跃会话', 'overview.connectionMode': '连接模式', 'overview.ws': '实时 WS', 'overview.poll': '降级轮询', 'overview.noActivity': '暂无后台活动', 'overview.noSession': '暂无会话',
673
+ 'overview.refresh': '刷新总览', 'overview.none': '暂无事项', 'overview.pendingCount': '{n} 项待处理', 'overview.activeCount': '{n} 个活跃会话',
674
+ 'overview.action.openSession': '打开最近会话', 'overview.action.newSession': '新建会话', 'overview.action.attention': '处理待办', 'overview.action.connect': '去设置连接', 'overview.action.refresh': '重新检查连接',
578
675
  'statsPage.title': 'Token 统计',
579
676
  'statsPage.todayTokens': '今日 Token', 'statsPage.todayCost': '今日费用', 'statsPage.peakShare': '高峰占比',
580
677
  'statsPage.input': '未缓存输入', 'statsPage.cacheRead': '缓存命中', 'statsPage.cacheWrite': '缓存写入', 'statsPage.output': '输出',
@@ -640,7 +737,7 @@
640
737
  'todos.title': '任务清单',
641
738
  'subagent.title': '子代理', 'subagent.diagnostic': '诊断项', 'subagent.running': '· 运行中', 'subagent.interrupt': '中断',
642
739
  'subagent.confirmInterrupt': '中断这个子代理当前回合?', 'subagent.interruptFailed': '中断失败', 'subagent.interruptSubmitted': '中断请求已提交',
643
- 'send.failed': '发送失败', 'send.commandSent': '指令已发送', 'send.sent': '已发送', 'send.commandExecuted': '命令已执行',
740
+ 'send.failed': '发送失败', 'send.commandSent': '指令已发送', 'send.sent': '已发送', 'send.imageSent': '图片已发送', 'send.commandExecuted': '命令已执行',
644
741
  'models.loading': '模型加载中…', 'models.loadFailed': '模型列表加载失败:{msg}', 'models.unavailable': '不可用', 'models.none': '没有可用模型',
645
742
  'models.switchFailed': '切换模型失败', 'models.switched': '已切换模型:{model}',
646
743
  'models.effortFailed': '切换思考深度失败', 'models.effortSwitched': '思考深度:{effort}',
@@ -650,7 +747,7 @@
650
747
  'menu.permission': '/permission 切换权限预设', 'menu.plan': '/plan 进入/退出计划模式',
651
748
  'perm.readOnly': 'read-only', 'perm.workspaceWrite': 'workspace-write', 'perm.dangerFullAccess': 'danger-full-access',
652
749
  'menu.modelTitle': '模型切换', 'menu.refresh': '刷新', 'menu.modelAuto': '点开时自动加载', 'menu.effortTitle': '思考深度',
653
- 'composer.placeholder': '给 DSH 发消息…', 'composer.send': '发送',
750
+ 'composer.placeholder': '给 DSH 发消息…', 'composer.send': '发送', 'composer.fullscreen': '全屏编辑', 'composer.exitFullscreen': '退出全屏编辑', 'composer.addImage': '添加图片', 'composer.takePhoto': '拍照', 'composer.choosePhoto': '相册', 'composer.imageAdded': '已添加图片', 'composer.imageRemoved': '已移除图片', 'composer.removeImage': '移除图片', 'composer.imageUnsupported': '仅支持 PNG、JPEG、WebP 或 GIF 图片', 'composer.imageTooLarge': '单张图片不能超过 {size}', 'composer.imageLimit': '最多添加 {count} 张图片', 'composer.imageSlashUnsupported': '带图片时不能发送斜杠命令', 'composer.imageReadFailed': '图片读取失败:{msg}',
654
751
  'pending.title': '待处理', 'pending.count': '{n} 项', 'pending.approvalTitle': '🔧 {tool} 请求批准', 'pending.noReason': '无说明',
655
752
  'pending.allow': '允许', 'pending.reject': '拒绝', 'pending.question': '❓ {question}',
656
753
  'pending.questionCount': '共 {n} 个问题', 'pending.answer': '去回答', 'pending.empty': '暂无待处理事项',
@@ -701,7 +798,7 @@
701
798
  'settings.back': '‹ 返回',
702
799
  'settings.servers': '服务器地址(可多个)', 'settings.speedTest': '测速', 'settings.add': '添加',
703
800
  'settings.scanTitle': '扫码连接', 'settings.scanDesc': '拍照/相册本地解码(无谷歌服务依赖);也可系统相机扫码或手动粘贴', 'settings.scan': '扫码',
704
- 'settings.notifyTitle': '通知', 'settings.notifyDesc': '收到审批/提问时推送',
801
+ 'settings.notifyTitle': '通知', 'settings.notifyDesc': '收到审批/提问时推送', 'settings.announcementHistoryTitle': '历史公告', 'settings.announcementHistoryDesc': '查看以前收到的公告', 'settings.announcementHistory': '查看记录',
705
802
  'settings.testNotifyTitle': '测试通知', 'settings.testNotifyDesc': '立即排一条 3 秒后的本地通知,用于排查系统通知限制', 'settings.testNotify': '发送测试通知',
706
803
  'settings.testNotifySent': '测试通知已排定,3 秒后到达', 'settings.testNotifyFailed': '发送测试通知失败:{msg}', 'settings.testNotifyUnavailable': '测试通知仅在 App 内可用',
707
804
  'settings.peakRemindTitle': '峰谷计费提醒', 'settings.peakRemindDesc': '每天 9:00 / 12:00 / 14:00 / 18:00 推送峰谷切换(App 内有效)',
@@ -753,7 +850,7 @@
753
850
  'modal.approvalTitle': '工具审批', 'modal.reject': '拒绝', 'modal.allowOnce': '允许一次',
754
851
  'modal.questionTitle': 'DSH 需要你回答', 'modal.later': '稍后', 'modal.submit': '提交',
755
852
  'modal.goalTitle': '目标控制', 'modal.close': '关闭', 'modal.updateGoal': '更新目标',
756
- 'announcement.label': '公告', 'announcement.later': '稍后再看', 'announcement.gotIt': '知道了', 'announcement.open': '查看详情',
853
+ 'announcement.label': '公告', 'announcement.later': '稍后再看', 'announcement.gotIt': '知道了', 'announcement.open': '查看详情', 'announcement.historyLabel': '公告记录', 'announcement.historyTitle': '历史公告', 'announcement.historyEmpty': '暂无历史公告', 'announcement.close': '关闭', 'announcement.noDate': '未标注日期',
757
854
  'modal.statsTitle': '本轮统计',
758
855
  'time.justNow': '刚刚', 'time.minAgo': ' 分钟前', 'time.hourAgo': ' 小时前'
759
856
  },
@@ -764,7 +861,14 @@
764
861
  'conn.poll': 'Polling', 'conn.pollTitle': 'Realtime push is unavailable on this network; degraded to polling (a few seconds delay)',
765
862
  'conn.titleGroup': '{group} · {url} ({ms})',
766
863
  'common.refreshing': 'Refreshing…',
767
- 'nav.sessions': 'Sessions', 'nav.files': 'Files', 'nav.pending': 'Inbox', 'nav.stats': 'Stats', 'nav.settings': 'Settings',
864
+ 'nav.sessions': 'Sessions', 'nav.files': 'Files', 'nav.pending': 'Inbox', 'nav.overview': 'Overview', 'nav.home': 'Home', 'nav.stats': 'Stats', 'nav.settings': 'Settings',
865
+ 'overview.eyebrow': 'SYSTEM OVERVIEW', 'overview.title': 'Home', 'overview.subtitle': 'Live status and items needing your attention',
866
+ 'overview.systemPulse': 'System pulse', 'overview.nominal': 'System nominal', 'overview.degraded': 'Some links need attention', 'overview.offline': 'Connection offline', 'overview.checking': 'Checking…', 'overview.components': '{n}/4 components online', 'overview.live': 'LIVE', 'overview.allLinked': 'ALL LINKED', 'overview.offlineCore': 'OFFLINE',
867
+ 'overview.gateway': 'Gateway', 'overview.dsh': 'DSH upstream', 'overview.mux': 'Live mux', 'overview.host': 'Live host', 'overview.online': 'Online', 'overview.offlineShort': 'Offline',
868
+ 'overview.attention': 'Needs attention', 'overview.metrics': 'Runtime metrics', 'overview.activity': 'Recent activity', 'overview.dshVersion': 'DSH version', 'overview.gatewayVersion': 'Gateway status',
869
+ 'overview.activeSessions': 'Active sessions', 'overview.connectionMode': 'Connection mode', 'overview.ws': 'Live WS', 'overview.poll': 'Degraded polling', 'overview.noActivity': 'No background activity', 'overview.noSession': 'No sessions yet',
870
+ 'overview.refresh': 'Refresh overview', 'overview.none': 'Nothing pending', 'overview.pendingCount': '{n} pending', 'overview.activeCount': '{n} active sessions',
871
+ 'overview.action.openSession': 'Open recent session', 'overview.action.newSession': 'New session', 'overview.action.attention': 'Review pending', 'overview.action.connect': 'Set up connection', 'overview.action.refresh': 'Check connection',
768
872
  'statsPage.title': 'Token stats',
769
873
  'statsPage.todayTokens': 'Tokens today', 'statsPage.todayCost': 'Cost today', 'statsPage.peakShare': 'Peak share',
770
874
  'statsPage.input': 'Uncached input', 'statsPage.cacheRead': 'Cache read', 'statsPage.cacheWrite': 'Cache write', 'statsPage.output': 'Output',
@@ -830,7 +934,7 @@
830
934
  'todos.title': 'Todos',
831
935
  'subagent.title': 'Subagents', 'subagent.diagnostic': 'Diagnostic', 'subagent.running': '· running', 'subagent.interrupt': 'Interrupt',
832
936
  'subagent.confirmInterrupt': 'Interrupt this subagent\'s current turn?', 'subagent.interruptFailed': 'Interrupt failed', 'subagent.interruptSubmitted': 'Interrupt requested',
833
- 'send.failed': 'Send failed', 'send.commandSent': 'Command sent', 'send.sent': 'Sent', 'send.commandExecuted': 'Command executed',
937
+ 'send.failed': 'Send failed', 'send.commandSent': 'Command sent', 'send.sent': 'Sent', 'send.imageSent': 'Image sent', 'send.commandExecuted': 'Command executed',
834
938
  'models.loading': 'Loading models…', 'models.loadFailed': 'Failed to load models: {msg}', 'models.unavailable': 'unavailable', 'models.none': 'No models available',
835
939
  'models.switchFailed': 'Model switch failed', 'models.switched': 'Switched model: {model}',
836
940
  'models.effortFailed': 'Failed to switch reasoning effort', 'models.effortSwitched': 'Reasoning effort: {effort}',
@@ -840,7 +944,7 @@
840
944
  'menu.permission': '/permission Switch permission preset', 'menu.plan': '/plan Enter/exit plan mode',
841
945
  'perm.readOnly': 'read-only', 'perm.workspaceWrite': 'workspace-write', 'perm.dangerFullAccess': 'danger-full-access',
842
946
  'menu.modelTitle': 'Model', 'menu.refresh': 'Refresh', 'menu.modelAuto': 'Auto-loads when opened', 'menu.effortTitle': 'Reasoning effort',
843
- 'composer.placeholder': 'Message DSH…', 'composer.send': 'Send',
947
+ 'composer.placeholder': 'Message DSH…', 'composer.send': 'Send', 'composer.fullscreen': 'Fullscreen editor', 'composer.exitFullscreen': 'Exit fullscreen editor', 'composer.addImage': 'Add image', 'composer.takePhoto': 'Camera', 'composer.choosePhoto': 'Gallery', 'composer.imageAdded': 'Image added', 'composer.imageRemoved': 'Image removed', 'composer.removeImage': 'Remove image', 'composer.imageUnsupported': 'Only PNG, JPEG, WebP or GIF images are supported', 'composer.imageTooLarge': 'Each image must be under {size}', 'composer.imageLimit': 'You can add up to {count} images', 'composer.imageSlashUnsupported': 'Slash commands cannot include images', 'composer.imageReadFailed': 'Could not read image: {msg}',
844
948
  'pending.title': 'Inbox', 'pending.count': '{n} items', 'pending.approvalTitle': '🔧 {tool} requests approval', 'pending.noReason': 'No reason given',
845
949
  'pending.allow': 'Allow', 'pending.reject': 'Reject', 'pending.question': '❓ {question}',
846
950
  'pending.questionCount': '{n} questions', 'pending.answer': 'Answer', 'pending.empty': 'Nothing pending',
@@ -891,7 +995,7 @@
891
995
  'settings.back': '‹ Back',
892
996
  'settings.servers': 'Servers (one or more)', 'settings.speedTest': 'Test', 'settings.add': 'Add',
893
997
  'settings.scanTitle': 'Scan to pair', 'settings.scanDesc': 'Photo/gallery QR decoding on device (no Google services); system camera or manual paste also work', 'settings.scan': 'Scan',
894
- 'settings.notifyTitle': 'Notifications', 'settings.notifyDesc': 'Push on approvals / questions',
998
+ 'settings.notifyTitle': 'Notifications', 'settings.notifyDesc': 'Push on approvals / questions', 'settings.announcementHistoryTitle': 'Announcement history', 'settings.announcementHistoryDesc': 'Review announcements you received before', 'settings.announcementHistory': 'View history',
895
999
  'settings.testNotifyTitle': 'Test notification', 'settings.testNotifyDesc': 'Schedule a local notification in 3 seconds to verify the notification pipeline', 'settings.testNotify': 'Send test notification',
896
1000
  'settings.testNotifySent': 'Test notification scheduled, arriving in 3s', 'settings.testNotifyFailed': 'Failed to send test notification: {msg}', 'settings.testNotifyUnavailable': 'Test notification is only available in the app',
897
1001
  'settings.peakRemindTitle': 'Peak/off-peak reminders', 'settings.peakRemindDesc': 'Push at 9:00 / 12:00 / 14:00 / 18:00 daily (in-app only)',
@@ -943,7 +1047,7 @@
943
1047
  'modal.approvalTitle': 'Tool approval', 'modal.reject': 'Reject', 'modal.allowOnce': 'Allow once',
944
1048
  'modal.questionTitle': 'DSH needs your answer', 'modal.later': 'Later', 'modal.submit': 'Submit',
945
1049
  'modal.goalTitle': 'Goal control', 'modal.close': 'Close', 'modal.updateGoal': 'Update goal',
946
- 'announcement.label': 'Announcement', 'announcement.later': 'Later', 'announcement.gotIt': 'Got it', 'announcement.open': 'View details',
1050
+ 'announcement.label': 'Announcement', 'announcement.later': 'Later', 'announcement.gotIt': 'Got it', 'announcement.open': 'View details', 'announcement.historyLabel': 'Announcement archive', 'announcement.historyTitle': 'Announcement history', 'announcement.historyEmpty': 'No announcement history', 'announcement.close': 'Close', 'announcement.noDate': 'No date',
947
1051
  'modal.statsTitle': 'This round',
948
1052
  'time.justNow': 'just now', 'time.minAgo': ' min ago', 'time.hourAgo': ' hr ago'
949
1053
  }
@@ -0,0 +1,151 @@
1
+ <!doctype html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
6
+ <title>DSH Remote</title>
7
+ <script>/* 插件抽屉首帧沿用当前主题 */ (function(){try{var t=localStorage.getItem('dshTheme');if(t!=='default'&&t!=='dark'&&t!=='light'&&t!=='neutral'){t=window.matchMedia('(prefers-color-scheme: light)').matches?'light':'default'}document.documentElement.setAttribute('data-theme',t)}catch(e){}})()</script>
8
+ <link rel="stylesheet" href="theme-vars.css">
9
+ <style>
10
+ :root { color-scheme: dark; }
11
+ html[data-theme="light"], html[data-theme="neutral"] { color-scheme: light; }
12
+ * { box-sizing: border-box; }
13
+ body {
14
+ margin: 0; min-width: 280px; color: var(--dsr-text); background:
15
+ radial-gradient(420px 240px at 100% 0, var(--dsr-accent-soft), transparent 72%),
16
+ var(--dsr-bg); font: 13px/1.45 system-ui, -apple-system, "PingFang SC", "Noto Sans CJK SC", sans-serif;
17
+ }
18
+ button, a { font: inherit; }
19
+ button { cursor: pointer; }
20
+ .plugin-shell { min-height: 100vh; padding: 18px 16px 16px; display: flex; flex-direction: column; gap: 14px; }
21
+ .plugin-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
22
+ .plugin-brand { display: flex; align-items: center; gap: 9px; min-width: 0; }
23
+ .plugin-brand-mark { width: 11px; height: 11px; flex: none; border-radius: 50%; background: var(--dsr-accent); box-shadow: 0 0 0 5px var(--dsr-accent-soft), 0 0 18px var(--dsr-glow); }
24
+ .plugin-brand-copy { min-width: 0; display: flex; flex-direction: column; }
25
+ .plugin-brand-name { font-size: 15px; font-weight: 750; letter-spacing: -.1px; }
26
+ .plugin-brand-sub { color: var(--dsr-muted); font-size: 10px; }
27
+ .plugin-close { width: 32px; height: 32px; display: grid; place-items: center; flex: none; border: 1px solid var(--dsr-line); border-radius: 10px; background: var(--dsr-panel); color: var(--dsr-muted); }
28
+ .plugin-close:hover, .plugin-close:focus-visible { color: var(--dsr-text); border-color: var(--dsr-accent-line); background: var(--dsr-accent-soft); }
29
+ .plugin-close svg { width: 16px; height: 16px; }
30
+ .plugin-hero { position: relative; overflow: hidden; padding: 18px; border: 1px solid var(--dsr-line); border-radius: 20px; background: linear-gradient(145deg, var(--dsr-panel), var(--dsr-bg-2)); box-shadow: 0 16px 32px var(--dsr-shadow); }
31
+ .plugin-hero::after { content: ""; position: absolute; width: 150px; height: 150px; right: -76px; top: -76px; border-radius: 50%; background: var(--dsr-accent-soft); pointer-events: none; }
32
+ .plugin-eyebrow { color: var(--dsr-accent-strong); font-size: 10px; font-weight: 800; letter-spacing: 1.4px; }
33
+ .plugin-status { position: relative; z-index: 1; margin: 7px 0 4px; font-size: 24px; line-height: 1.15; font-weight: 780; letter-spacing: -.5px; }
34
+ .plugin-status-desc { position: relative; z-index: 1; min-height: 20px; color: var(--dsr-muted); font-size: 12px; }
35
+ .plugin-status-line { position: relative; z-index: 1; display: flex; align-items: center; gap: 7px; margin-top: 13px; color: var(--dsr-muted); font-size: 11px; }
36
+ .plugin-status-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--dsr-muted); box-shadow: 0 0 0 4px var(--dsr-line); }
37
+ .plugin-hero.ok .plugin-status-dot { background: var(--dsr-success); box-shadow: 0 0 0 4px var(--dsr-success-soft); }
38
+ .plugin-hero.warn .plugin-status-dot { background: var(--dsr-warning); box-shadow: 0 0 0 4px var(--dsr-warning-soft); }
39
+ .plugin-hero.off .plugin-status-dot { background: var(--dsr-error); box-shadow: 0 0 0 4px var(--dsr-error-soft); }
40
+ .plugin-primary { position: relative; z-index: 1; width: 100%; min-height: 42px; margin-top: 16px; border: 1px solid var(--dsr-accent-strong); border-radius: 12px; background: var(--dsr-accent-strong); color: var(--dsr-on-accent); font-weight: 750; transition: transform .18s ease, filter .18s ease; }
41
+ .plugin-primary:hover, .plugin-primary:focus-visible { filter: brightness(1.08); transform: translateY(-1px); }
42
+ .plugin-primary:disabled { cursor: wait; opacity: .65; transform: none; }
43
+ .plugin-actions { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; }
44
+ .plugin-action { min-height: 42px; padding: 7px 5px; border: 1px solid var(--dsr-line); border-radius: 12px; background: var(--dsr-panel); color: var(--dsr-text); font-size: 11px; }
45
+ .plugin-action:hover, .plugin-action:focus-visible { border-color: var(--dsr-accent-line); background: var(--dsr-accent-soft); }
46
+ .plugin-action svg { display: block; width: 17px; height: 17px; margin: 0 auto 3px; color: var(--dsr-accent-strong); }
47
+ .plugin-usage { padding: 14px; border: 1px solid var(--dsr-line); border-radius: 16px; background: linear-gradient(145deg, var(--dsr-panel), var(--dsr-bg-2)); }
48
+ .plugin-section-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; color: var(--dsr-text); font-size: 12px; font-weight: 750; }
49
+ .plugin-section-meta { color: var(--dsr-muted); font-size: 10px; font-weight: 500; }
50
+ .plugin-usage-grid { display: grid; grid-template-columns: minmax(0, 1fr) 82px; gap: 12px; align-items: center; margin-top: 11px; }
51
+ .plugin-usage-total { display: flex; align-items: baseline; gap: 6px; }
52
+ .plugin-usage-total strong { font-size: 25px; line-height: 1; font-variant-numeric: tabular-nums; letter-spacing: -.6px; }
53
+ .plugin-usage-total span { color: var(--dsr-muted); font-size: 11px; }
54
+ .plugin-usage-track { display: flex; height: 8px; gap: 2px; margin-top: 13px; overflow: hidden; border-radius: 999px; background: var(--dsr-line); }
55
+ .plugin-usage-track i { display: block; min-width: 2px; height: 100%; }
56
+ .plugin-usage-track .input { background: var(--dsr-accent); }
57
+ .plugin-usage-track .cache { background: var(--dsr-info); }
58
+ .plugin-usage-track .output { background: var(--dsr-accent-2); }
59
+ .plugin-usage-legend { display: flex; flex-wrap: wrap; gap: 4px 9px; margin-top: 7px; color: var(--dsr-muted); font-size: 9px; }
60
+ .plugin-usage-legend span { display: inline-flex; align-items: center; gap: 4px; }
61
+ .plugin-usage-legend i { width: 6px; height: 6px; border-radius: 50%; background: var(--dsr-line); }
62
+ .plugin-usage-legend .input i { background: var(--dsr-accent); }
63
+ .plugin-usage-legend .cache i { background: var(--dsr-info); }
64
+ .plugin-usage-legend .output i { background: var(--dsr-accent-2); }
65
+ .plugin-usage-ring { --plugin-peak: 0%; width: 76px; height: 76px; padding: 6px; display: grid; place-items: center; border-radius: 50%; background: conic-gradient(var(--dsr-accent-strong) 0 var(--plugin-peak), var(--dsr-line) var(--plugin-peak) 100%); box-shadow: 0 0 18px var(--dsr-glow); }
66
+ .plugin-usage-ring > div { width: 100%; height: 100%; display: flex; flex-direction: column; align-items: center; justify-content: center; border-radius: 50%; background: var(--dsr-bg-2); }
67
+ .plugin-usage-ring strong { font-size: 15px; line-height: 1; font-variant-numeric: tabular-nums; }
68
+ .plugin-usage-ring span { margin-top: 4px; color: var(--dsr-muted); font-size: 9px; }
69
+ .plugin-usage-cost { display: flex; align-items: baseline; gap: 8px; margin-top: 12px; padding-top: 10px; border-top: 1px solid var(--dsr-divider); }
70
+ .plugin-usage-cost span { color: var(--dsr-muted); font-size: 10px; }
71
+ .plugin-usage-cost strong { color: var(--dsr-accent-strong); font-size: 17px; font-variant-numeric: tabular-nums; }
72
+ .plugin-usage-cost small { margin-left: auto; color: var(--dsr-muted); font-size: 9px; }
73
+ .plugin-usage.empty { color: var(--dsr-muted); }
74
+ .plugin-usage-empty { padding: 12px 0 2px; font-size: 11px; }
75
+ .plugin-details { border-top: 1px solid var(--dsr-divider); padding-top: 12px; }
76
+ .plugin-detail-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-top: 10px; }
77
+ .plugin-detail { min-width: 0; padding: 9px 10px; border: 1px solid var(--dsr-line); border-radius: 11px; background: var(--dsr-panel); }
78
+ .plugin-detail-label { color: var(--dsr-muted); font-size: 10px; }
79
+ .plugin-detail-value { margin-top: 3px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12px; font-weight: 650; }
80
+ .plugin-foot { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-top: auto; color: var(--dsr-muted); font-size: 10px; }
81
+ .plugin-foot a { color: var(--dsr-accent-strong); text-decoration: none; }
82
+ .plugin-foot a:hover, .plugin-foot a:focus-visible { text-decoration: underline; }
83
+ .plugin-error { padding: 10px 12px; border: 1px solid var(--dsr-error-line); border-radius: 11px; background: var(--dsr-error-soft); color: var(--dsr-error); font-size: 11px; }
84
+ .hidden { display: none !important; }
85
+ :focus-visible { outline: 2px solid var(--dsr-accent); outline-offset: 2px; }
86
+ @media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: .01ms !important; transition-duration: .01ms !important; } }
87
+ </style>
88
+ </head>
89
+ <body>
90
+ <main class="plugin-shell">
91
+ <header class="plugin-head">
92
+ <div class="plugin-brand">
93
+ <span class="plugin-brand-mark" aria-hidden="true"></span>
94
+ <span class="plugin-brand-copy"><span class="plugin-brand-name">DSH Remote</span><span class="plugin-brand-sub">本地控制器</span></span>
95
+ </div>
96
+ <button id="plugin-close" class="plugin-close" type="button" aria-label="关闭面板" title="关闭面板">
97
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" aria-hidden="true"><path d="m6 6 12 12M18 6 6 18"/></svg>
98
+ </button>
99
+ </header>
100
+
101
+ <section id="plugin-hero" class="plugin-hero" aria-live="polite">
102
+ <div class="plugin-eyebrow">REMOTE CONTROL</div>
103
+ <div id="plugin-status" class="plugin-status">检查中…</div>
104
+ <div id="plugin-status-desc" class="plugin-status-desc">正在读取本地服务状态</div>
105
+ <div class="plugin-status-line"><span class="plugin-status-dot" aria-hidden="true"></span><span id="plugin-status-meta">—</span></div>
106
+ <button id="plugin-primary" class="plugin-primary" type="button">打开控制台</button>
107
+ </section>
108
+
109
+ <div id="plugin-error" class="plugin-error hidden" role="alert"></div>
110
+
111
+ <section class="plugin-actions" aria-label="快速操作">
112
+ <button id="plugin-console" class="plugin-action" type="button">
113
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="4" y="5" width="16" height="14" rx="2"/><path d="m8 10 2 2-2 2M12 15h4"/></svg>控制台
114
+ </button>
115
+ <button id="plugin-copy" class="plugin-action" type="button">
116
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="8" y="8" width="11" height="11" rx="2"/><path d="M16 8V6a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h2"/></svg>复制令牌
117
+ </button>
118
+ <button id="plugin-toggle" class="plugin-action" type="button">
119
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 3v9M7.8 5.4a7 7 0 1 0 8.4 0"/></svg><span id="plugin-toggle-label">启动网关</span>
120
+ </button>
121
+ </section>
122
+
123
+ <section id="plugin-usage" class="plugin-usage" aria-labelledby="plugin-usage-title">
124
+ <div class="plugin-section-head"><span id="plugin-usage-title">用量概览</span><span id="plugin-usage-date" class="plugin-section-meta">—</span></div>
125
+ <div class="plugin-usage-grid">
126
+ <div>
127
+ <div class="plugin-usage-total"><strong id="plugin-token-total">—</strong><span>今日 Token</span></div>
128
+ <div id="plugin-token-track" class="plugin-usage-track" aria-label="Token 分布"></div>
129
+ <div class="plugin-usage-legend"><span class="input"><i></i>输入</span><span class="cache"><i></i>缓存</span><span class="output"><i></i>输出</span></div>
130
+ </div>
131
+ <div id="plugin-usage-ring" class="plugin-usage-ring" style="--plugin-peak:0%"><div><strong id="plugin-peak-share">0%</strong><span>高峰占比</span></div></div>
132
+ </div>
133
+ <div class="plugin-usage-cost"><span>今日费用</span><strong id="plugin-cost">¥0.00</strong><small id="plugin-cost-split">等待网关统计</small></div>
134
+ <div id="plugin-usage-empty" class="plugin-usage-empty hidden">网关运行后显示 Token 与费用统计</div>
135
+ </section>
136
+
137
+ <section class="plugin-details" aria-labelledby="plugin-details-title">
138
+ <div class="plugin-section-head"><span id="plugin-details-title">运行详情</span><span class="plugin-section-meta">实时</span></div>
139
+ <div class="plugin-detail-grid">
140
+ <div class="plugin-detail"><div class="plugin-detail-label">插件版本</div><div id="plugin-version" class="plugin-detail-value">—</div></div>
141
+ <div class="plugin-detail"><div class="plugin-detail-label">网关版本</div><div id="plugin-gateway-version" class="plugin-detail-value">—</div></div>
142
+ <div class="plugin-detail"><div class="plugin-detail-label">设备在线</div><div id="plugin-devices" class="plugin-detail-value">—</div></div>
143
+ <div class="plugin-detail"><div class="plugin-detail-label">本地地址</div><div id="plugin-host" class="plugin-detail-value">—</div></div>
144
+ </div>
145
+ </section>
146
+
147
+ <footer class="plugin-foot"><span>状态面板 · 需要深入管理时进入控制台</span><a id="plugin-about" href="/remote/admin/" target="_blank" rel="noopener">关于与支持</a></footer>
148
+ </main>
149
+ <script src="plugin.js"></script>
150
+ </body>
151
+ </html>