dsh-remote-plugin 0.5.7 → 0.5.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.
@@ -0,0 +1,270 @@
1
+ /* DSH Remote 桌面端专用样式 · 零依赖 · 只引用 --dsr-* 皮肤变量 */
2
+ html, body { height: 100%; margin: 0; overflow: hidden; overscroll-behavior: none; background: var(--dsr-bg); color: var(--dsr-text); }
3
+ .ds-app { position: fixed; inset: 0; display: flex; overflow: hidden; }
4
+ .ds-sidebar { width: 280px; flex: none; display: flex; flex-direction: column; border-right: 1px solid var(--dsr-line); background: var(--dsr-panel); }
5
+ .ds-brand { display: flex; align-items: center; gap: 8px; padding: 14px 14px 10px; font-weight: 700; font-size: 15px; }
6
+ .ds-logo { color: var(--dsr-accent-strong); }
7
+ .ds-brand-name { flex: 1; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
8
+ .ds-gh { color: var(--dsr-muted); display: inline-flex; }
9
+ .ds-gh svg { width: 17px; height: 17px; fill: currentColor; }
10
+ .ds-new-session { padding: 0 12px 8px; }
11
+ .ds-btn { display: inline-flex; align-items: center; gap: 6px; background: var(--dsr-bg-2); color: var(--dsr-text); border: 1px solid var(--dsr-line); border-radius: 9px; padding: 6px 11px; font: inherit; font-size: 12.5px; cursor: pointer; white-space: nowrap; }
12
+ .ds-btn.primary { background: var(--dsr-accent-soft); border-color: var(--dsr-accent-line); color: var(--dsr-accent-strong); }
13
+ .ds-btn:hover { filter: brightness(1.06); }
14
+ .ds-btn:active { filter: brightness(.96); }
15
+ a.ds-btn { text-decoration: none; }
16
+ .ds-new-session .ds-btn { width: 100%; justify-content: center; }
17
+ .ds-section-label { font-size: 11px; color: var(--dsr-muted); letter-spacing: .8px; padding: 10px 14px 6px; font-weight: 700; }
18
+ .ds-session-list { flex: 1; min-height: 0; overflow-y: auto; overscroll-behavior: contain; padding: 0 8px; display: flex; flex-direction: column; gap: 3px; }
19
+ .ds-session-item { width: 100%; text-align: left; border: none; background: transparent; color: var(--dsr-text); font: inherit; font-size: 13px; padding: 8px 9px; border-radius: 9px; cursor: pointer; display: flex; flex-direction: column; gap: 3px; }
20
+ .ds-session-item:hover { background: var(--dsr-bg-2); }
21
+ .ds-session-item.current { background: var(--dsr-accent-soft); color: var(--dsr-accent-strong); }
22
+ .ds-session-title { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
23
+ .ds-session-meta { display: flex; gap: 7px; font-size: 11px; color: var(--dsr-muted); align-items: center; }
24
+ .ds-session-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--dsr-line); }
25
+ .ds-session-dot.running { background: var(--dsr-warning); }
26
+ .ds-sidebar-foot { border-top: 1px solid var(--dsr-line); padding: 8px; display: flex; flex-direction: column; gap: 3px; }
27
+ .ds-nav-item { display: flex; align-items: center; gap: 8px; width: 100%; text-align: left; border: none; background: transparent; color: var(--dsr-text); font: inherit; font-size: 13px; padding: 9px 10px; border-radius: 9px; cursor: pointer; }
28
+ .ds-nav-item:hover { background: var(--dsr-bg-2); }
29
+ .ds-nav-item.active { background: var(--dsr-accent-soft); color: var(--dsr-accent-strong); }
30
+ .ds-nav-ico { width: 18px; text-align: center; }
31
+
32
+ /* 侧边栏底部常驻反馈入口 */
33
+ .ds-feedback { position: relative; margin-top: 4px; }
34
+ .ds-feedback-btn {
35
+ display: flex; align-items: center; gap: 8px; width: 100%;
36
+ text-align: left; border: none; background: transparent; color: var(--dsr-text);
37
+ font: inherit; font-size: 13px; padding: 9px 10px; border-radius: 9px; cursor: pointer;
38
+ }
39
+ .ds-feedback-btn:hover { background: var(--dsr-bg-2); }
40
+ .ds-feedback-btn svg { width: 16px; height: 16px; flex-shrink: 0; }
41
+ .ds-feedback-menu {
42
+ position: absolute; bottom: calc(100% + 8px); left: 0; right: 0; z-index: 45;
43
+ background: var(--dsr-bg-2); border: 1px solid var(--dsr-line); border-radius: 14px;
44
+ box-shadow: 0 12px 34px var(--dsr-shadow); padding: 6px;
45
+ display: flex; flex-direction: column; gap: 2px;
46
+ animation: ds-feedback-in .18s ease;
47
+ }
48
+ .ds-feedback-menu.hidden { display: none; }
49
+ .ds-feedback-item {
50
+ width: 100%; min-height: 44px; display: flex; align-items: center; gap: 10px;
51
+ padding: 7px 8px; border-radius: 10px; border: none; background: transparent; color: var(--dsr-text);
52
+ font: inherit; text-align: left; cursor: pointer; text-decoration: none;
53
+ }
54
+ .ds-feedback-item:hover, .ds-feedback-item:focus-visible { background: var(--dsr-bg); outline: none; }
55
+ .ds-feedback-item.primary { background: var(--dsr-accent-soft); border: 1px solid var(--dsr-accent-line); }
56
+ .ds-feedback-ico {
57
+ width: 32px; height: 32px; border-radius: 9px; flex-shrink: 0;
58
+ display: grid; place-items: center; background: var(--dsr-panel); border: 1px solid var(--dsr-line); color: var(--dsr-accent-strong);
59
+ }
60
+ .ds-feedback-item.primary .ds-feedback-ico { background: var(--dsr-accent-strong); border-color: var(--dsr-accent-strong); color: var(--dsr-on-accent); }
61
+ .ds-feedback-ico svg { width: 16px; height: 16px; fill: currentColor; }
62
+ .ds-feedback-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
63
+ .ds-feedback-name { font-size: 13px; font-weight: 600; }
64
+ .ds-feedback-desc { font-size: 11px; color: var(--dsr-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
65
+ @keyframes ds-feedback-in { from { opacity: 0; transform: translateY(6px) } to { opacity: 1; transform: translateY(0) } }
66
+
67
+ /* 写反馈弹层 */
68
+ .ds-fb-chips { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 10px; }
69
+ .ds-fb-chip {
70
+ min-height: 36px; padding: 7px 14px; border-radius: 999px; font-size: 13px;
71
+ background: var(--dsr-panel); border: 1px solid var(--dsr-line); color: var(--dsr-text); cursor: pointer;
72
+ }
73
+ .ds-fb-chip.current { background: var(--dsr-accent-soft); border-color: var(--dsr-accent-line); color: var(--dsr-accent-strong); font-weight: 600; }
74
+ .ds-fb-textarea, .ds-fb-input {
75
+ width: 100%; box-sizing: border-box; margin-bottom: 10px;
76
+ background: var(--dsr-panel); color: var(--dsr-text); border: 1px solid var(--dsr-line); border-radius: 12px;
77
+ padding: 10px 12px; font: inherit; font-size: 14px; outline: none; resize: vertical;
78
+ }
79
+ .ds-fb-textarea:focus, .ds-fb-input:focus { border-color: var(--dsr-accent-line); }
80
+ .ds-fb-input { min-height: 42px; }
81
+
82
+ .ds-main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
83
+ .ds-topbar { height: 52px; flex: none; display: flex; align-items: center; gap: 10px; padding: 0 14px; border-bottom: 1px solid var(--dsr-line); background: var(--dsr-panel); }
84
+ .ds-icon-btn { width: 32px; height: 32px; border-radius: 9px; border: 1px solid var(--dsr-line); background: var(--dsr-bg-2); color: var(--dsr-text); cursor: pointer; font-size: 15px; display: inline-flex; align-items: center; justify-content: center; }
85
+ .ds-icon-btn:hover { filter: brightness(1.06); }
86
+ #btn-mobile-nav { display: none; }
87
+ .ds-title { flex: 1; min-width: 0; font-weight: 700; font-size: 15px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
88
+ .ds-top-right { display: flex; align-items: center; gap: 10px; }
89
+ .ds-conn { font-size: 11px; color: var(--dsr-muted); }
90
+ .ds-conn.on { color: var(--dsr-success); }
91
+ .ds-conn.ing { color: var(--dsr-warning); }
92
+ .ds-server { font-size: 12px; color: var(--dsr-muted); max-width: 320px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
93
+
94
+ .ds-content { flex: 1; min-height: 0; display: flex; }
95
+ .ds-view { flex: 1; min-width: 0; display: flex; flex-direction: column; padding: 14px 16px; overflow: hidden; }
96
+ .ds-view.hidden { display: none; }
97
+ .ds-view { animation: dsFade .15s ease; }
98
+ @keyframes dsFade { from { opacity: .5; } to { opacity: 1; } }
99
+
100
+ /* 会话视图 */
101
+ #view-chat { padding: 0; }
102
+ .ds-history { flex: 1; min-height: 0; overflow-y: auto; overscroll-behavior: contain; padding: 18px 22px; display: flex; flex-direction: column; gap: 10px; }
103
+ .ds-msg { max-width: 78%; padding: 9px 12px; border-radius: 12px; font-size: 13.5px; line-height: 1.55; white-space: pre-wrap; word-break: break-word; }
104
+ .ds-msg.user { align-self: flex-end; background: var(--dsr-accent-soft); border: 1px solid var(--dsr-accent-line); color: var(--dsr-accent-strong); }
105
+ .ds-msg.assistant { align-self: flex-start; background: var(--dsr-panel); border: 1px solid var(--dsr-line); color: var(--dsr-text); }
106
+ .ds-msg .role { font-size: 10px; color: var(--dsr-muted); margin-bottom: 3px; }
107
+ .ds-tool { align-self: flex-start; max-width: 78%; background: var(--dsr-bg-2); border: 1px solid var(--dsr-line); border-radius: 10px; padding: 8px 11px; font-size: 12px; }
108
+ .ds-tool summary { cursor: pointer; color: var(--dsr-muted); }
109
+ .ds-tool pre { margin: 6px 0 0; white-space: pre-wrap; word-break: break-all; font-size: 11px; color: var(--dsr-text); }
110
+ .ds-empty { color: var(--dsr-muted); text-align: center; padding: 30px 0; font-size: 13px; }
111
+ .ds-composer { flex: none; display: flex; gap: 10px; padding: 12px 16px; border-top: 1px solid var(--dsr-line); background: var(--dsr-panel); }
112
+ .ds-composer textarea { flex: 1; min-width: 0; resize: none; background: var(--dsr-bg-2); color: var(--dsr-text); border: 1px solid var(--dsr-line); border-radius: 11px; padding: 10px 12px; font: inherit; font-size: 13.5px; outline: none; max-height: 160px; }
113
+
114
+ /* 文件传输 */
115
+ .ds-fs-bar { display: flex; align-items: center; gap: 8px; margin: 8px 0; }
116
+ .ds-fs-path { flex: 1; min-width: 0; font-size: 12.5px; color: var(--dsr-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
117
+ .ds-fs-list { flex: 1; min-height: 0; overflow-y: auto; overscroll-behavior: contain; border: 1px solid var(--dsr-line); border-radius: 12px; background: var(--dsr-panel); }
118
+ .ds-fs-row { display: flex; align-items: center; gap: 9px; padding: 9px 12px; border-bottom: 1px solid var(--dsr-divider); font-size: 13px; }
119
+ .ds-fs-row:last-child { border-bottom: none; }
120
+ .ds-fs-row:hover { background: var(--dsr-bg-2); }
121
+ .ds-fs-name { flex: 1; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
122
+ .ds-fs-size { color: var(--dsr-muted); font-size: 12px; white-space: nowrap; }
123
+
124
+ /* 设置 */
125
+ .ds-settings { max-width: 720px; display: flex; flex-direction: column; gap: 10px; }
126
+ .ds-setting-row { display: flex; align-items: center; gap: 12px; background: var(--dsr-panel); border: 1px solid var(--dsr-line); border-radius: 12px; padding: 11px 13px; }
127
+ .ds-setting-row > div:first-child { flex: 1; min-width: 0; }
128
+ .ds-setting-name { font-size: 13.5px; font-weight: 600; }
129
+ .ds-setting-desc { font-size: 12px; color: var(--dsr-muted); margin-top: 2px; word-break: break-all; }
130
+ .ds-feedback-links { display: flex; gap: 8px; flex-wrap: wrap; padding: 0 13px; }
131
+ .ds-group-bar { display: flex; align-items: center; gap: 10px; padding: 2px 0; }
132
+ .ds-group-bar > label { font-size: 12px; color: var(--dsr-muted); white-space: nowrap; }
133
+ .ds-group-select { flex: 1; min-width: 0; position: relative; }
134
+ .ds-select-btn { width: 100%; display: flex; align-items: center; justify-content: space-between; gap: 6px; background: var(--dsr-panel); color: var(--dsr-text); border: 1px solid var(--dsr-line); border-radius: 10px; padding: 8px 10px; font: inherit; font-size: 13px; cursor: pointer; }
135
+ .ds-select-btn .caret { color: var(--dsr-muted); font-size: 10px; }
136
+ .ds-group-menu { position: absolute; left: 0; right: 0; top: calc(100% + 4px); z-index: 40; max-height: 240px; overflow-y: auto; background: var(--dsr-panel); border: 1px solid var(--dsr-line); border-radius: 12px; box-shadow: 0 8px 24px rgba(0,0,0,.28); padding: 4px; display: flex; flex-direction: column; gap: 2px; }
137
+ .ds-group-menu.hidden { display: none; }
138
+ .ds-group-option { width: 100%; text-align: left; background: none; border: none; color: var(--dsr-text); font: inherit; font-size: 13px; padding: 8px 10px; border-radius: 8px; cursor: pointer; }
139
+ .ds-group-option.current { background: var(--dsr-accent-soft); color: var(--dsr-accent-strong); font-weight: 600; }
140
+ .ds-server-list { display: flex; flex-direction: column; gap: 7px; }
141
+ .ds-srv-group { border: 1px solid var(--dsr-line); border-radius: 12px; background: var(--dsr-panel); overflow: hidden; }
142
+ .ds-srv-head { display: flex; align-items: center; gap: 6px; padding: 7px 9px; }
143
+ .ds-srv-name { flex: 1; min-width: 0; text-align: left; background: none; border: none; color: var(--dsr-text); font: inherit; font-size: 13px; font-weight: 600; cursor: pointer; padding: 2px 0; display: flex; align-items: center; gap: 6px; }
144
+ .ds-srv-count { font-size: 10.5px; color: var(--dsr-muted); background: var(--dsr-bg-2); border-radius: 999px; padding: 0 7px; line-height: 16px; }
145
+ .ds-srv-body { display: flex; flex-direction: column; gap: 6px; padding: 0 9px 9px; }
146
+ .ds-srv-body.hidden { display: none; }
147
+ .ds-server-row { display: flex; align-items: center; gap: 8px; background: var(--dsr-bg-2); border: 1px solid var(--dsr-line); border-radius: 10px; padding: 8px 10px; cursor: pointer; }
148
+ .ds-server-row.active { border-color: var(--dsr-info-line); background: var(--dsr-info-soft); }
149
+ .ds-server-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
150
+ .ds-server-note { font-size: 13px; word-break: break-all; }
151
+ .ds-server-url { font-size: 11.5px; color: var(--dsr-muted); word-break: break-all; }
152
+ .ds-server-badge { flex-shrink: 0; font-size: 11px; padding: 1px 7px; border-radius: 999px; background: var(--dsr-accent-soft); color: var(--dsr-accent-strong); border: 1px solid var(--dsr-accent-line); white-space: nowrap; }
153
+ .ds-server-badge.good { background: var(--dsr-success-soft); color: var(--dsr-success); border-color: var(--dsr-success-line); }
154
+ .ds-server-badge.bad { background: var(--dsr-warning-soft); color: var(--dsr-warning); border-color: var(--dsr-warning-line); }
155
+ .ds-server-add { display: flex; gap: 8px; }
156
+ .ds-server-add input { flex: 1; min-width: 0; background: var(--dsr-panel); color: var(--dsr-text); border: 1px solid var(--dsr-line); border-radius: 10px; padding: 8px 10px; font: inherit; font-size: 13px; outline: none; }
157
+ .ds-mini { width: 24px; height: 24px; border-radius: 7px; border: 1px solid var(--dsr-line); background: var(--dsr-panel); color: var(--dsr-muted); font-size: 12px; cursor: pointer; flex-shrink: 0; }
158
+ .ds-mini:hover { color: var(--dsr-text); }
159
+ .ds-switch { position: relative; display: inline-block; width: 36px; height: 21px; flex-shrink: 0; }
160
+ .ds-switch input { opacity: 0; width: 0; height: 0; }
161
+ .ds-switch .ds-slider { position: absolute; inset: 0; border-radius: 999px; background: var(--dsr-panel-2); border: 1px solid var(--dsr-line); transition: .2s; }
162
+ .ds-switch .ds-slider::before { content: ''; position: absolute; width: 15px; height: 15px; border-radius: 50%; left: 2px; top: 2px; background: var(--dsr-muted); transition: .2s; }
163
+ .ds-switch input:checked + .ds-slider { background: var(--dsr-accent-soft); border-color: var(--dsr-accent); }
164
+ .ds-switch input:checked + .ds-slider::before { transform: translateX(15px); background: var(--dsr-accent); }
165
+
166
+ /* 统计 */
167
+ .ds-stat-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 10px; }
168
+ .ds-stat-card { background: var(--dsr-panel); border: 1px solid var(--dsr-line); border-radius: 12px; padding: 13px 15px; }
169
+ .ds-stat-card .v { font-size: 20px; font-weight: 700; }
170
+ .ds-stat-card .k { font-size: 11.5px; color: var(--dsr-muted); margin-top: 3px; line-height: 1.5; }
171
+ .ds-bucket-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 3px 12px; margin-top: 6px; }
172
+ .ds-bucket-grid .b { display: flex; justify-content: space-between; gap: 8px; font-size: 12px; }
173
+ .ds-bucket-grid .n { color: var(--dsr-muted); }
174
+ .ds-bucket-grid .t { font-weight: 600; }
175
+ .ds-stats-legend { display: flex; gap: 12px; font-size: 11px; color: var(--dsr-muted); margin: 8px 0; }
176
+ .ds-stats-legend .sw { width: 9px; height: 9px; border-radius: 2px; display: inline-block; margin-right: 4px; }
177
+ .ds-stats-legend .sw.peak { background: var(--dsr-accent-strong); }
178
+ .ds-stats-legend .sw.off { background: var(--dsr-accent-2); }
179
+ .ds-stats-chart { display: flex; align-items: flex-end; gap: 8px; height: 200px; padding: 12px 14px 10px; background: var(--dsr-panel); border: 1px solid var(--dsr-line); border-radius: 12px; overflow-x: auto; }
180
+ .ds-stats-bar { flex: 1; min-width: 40px; height: 100%; display: flex; flex-direction: column; align-items: center; justify-content: flex-end; gap: 5px; }
181
+ .ds-stats-bar .bars { width: 100%; max-width: 46px; height: 100%; display: flex; flex-direction: column; justify-content: flex-end; border-radius: 6px 6px 0 0; overflow: hidden; background: var(--dsr-accent-soft); }
182
+ .ds-stats-bar .seg { width: 100%; }
183
+ .ds-stats-bar .seg.peak { background: var(--dsr-accent-strong); }
184
+ .ds-stats-bar .seg.off { background: var(--dsr-accent-2); }
185
+ .ds-stats-bar .lbl { font-size: 10px; color: var(--dsr-muted); white-space: nowrap; }
186
+ .ds-stats-bar .val { font-size: 10px; white-space: nowrap; }
187
+ .ds-stats-note { font-size: 11px; color: var(--dsr-muted); line-height: 1.6; margin-top: 8px; }
188
+
189
+ /* 统计抽屉(右侧滑出) */
190
+ .ds-drawer { position: fixed; top: 0; right: 0; bottom: 0; z-index: 110; width: min(540px, 100vw); background: var(--dsr-panel); border-left: 1px solid var(--dsr-line); box-shadow: -12px 0 32px rgba(0,0,0,.35); display: flex; flex-direction: column; animation: dsDrawerIn .2s ease; }
191
+ .ds-drawer.hidden { display: none; }
192
+ @keyframes dsDrawerIn { from { transform: translateX(40px); opacity: 0; } to { transform: none; opacity: 1; } }
193
+ .ds-drawer-head { flex: none; display: flex; align-items: center; justify-content: space-between; padding: 13px 15px; border-bottom: 1px solid var(--dsr-line); font-weight: 700; font-size: 15px; }
194
+ .ds-drawer-body { flex: 1; min-height: 0; overflow-y: auto; padding: 14px 15px; scrollbar-width: thin; scrollbar-color: var(--dsr-line) transparent; }
195
+ .ds-drawer-body::-webkit-scrollbar { width: 8px; }
196
+ .ds-drawer-body::-webkit-scrollbar-thumb { background: var(--dsr-line); border-radius: 999px; }
197
+ .ds-drawer-body::-webkit-scrollbar-thumb:hover { background: var(--dsr-muted); }
198
+
199
+ /* 通知卡片栈(右下角) */
200
+ .ds-toast-stack { position: fixed; right: 16px; bottom: 16px; z-index: 100; display: flex; flex-direction: column; gap: 10px; width: min(380px, calc(100vw - 32px)); }
201
+ .ds-notif-card { background: var(--dsr-panel); border: 1px solid var(--dsr-line); border-left: 3px solid var(--dsr-accent-strong); border-radius: 12px; box-shadow: 0 10px 28px rgba(0,0,0,.3); padding: 11px 13px; animation: dsSlideIn .22s ease; }
202
+ .ds-notif-card.question { border-left-color: var(--dsr-accent-2); }
203
+ @keyframes dsSlideIn { from { transform: translateX(30px); opacity: 0; } to { transform: none; opacity: 1; } }
204
+ .ds-notif-head { display: flex; align-items: center; gap: 7px; font-size: 12px; color: var(--dsr-muted); }
205
+ .ds-notif-title { font-size: 14px; font-weight: 700; margin: 4px 0 3px; }
206
+ .ds-notif-body { font-size: 12.5px; color: var(--dsr-muted); line-height: 1.5; white-space: pre-wrap; word-break: break-word; max-height: 120px; overflow-y: auto; }
207
+ .ds-notif-actions { display: flex; gap: 8px; margin-top: 9px; }
208
+ .ds-notif-actions .ds-btn { flex: 1; justify-content: center; }
209
+ .ds-notif-actions .allow { background: var(--dsr-success-soft); border-color: var(--dsr-success-line); color: var(--dsr-success); }
210
+ .ds-notif-actions .reject { background: var(--dsr-warning-soft); border-color: var(--dsr-warning-line); color: var(--dsr-warning); }
211
+
212
+ /* 模态 */
213
+ .ds-modal { position: fixed; inset: 0; z-index: 120; background: rgba(0,0,0,.5); display: flex; align-items: center; justify-content: center; padding: 16px; }
214
+ .ds-modal.hidden { display: none; }
215
+ .ds-modal-card { width: min(520px, 100%); background: var(--dsr-panel); border: 1px solid var(--dsr-line); border-radius: 14px; padding: 16px; }
216
+ .ds-modal-title { font-weight: 700; margin-bottom: 10px; }
217
+ .ds-modal-body { display: flex; flex-direction: column; gap: 10px; max-height: 60vh; overflow-y: auto; }
218
+ .ds-modal-actions { display: flex; gap: 8px; justify-content: flex-end; margin-top: 12px; }
219
+ .ds-q-item { border: 1px solid var(--dsr-line); border-radius: 10px; padding: 9px 11px; }
220
+ .ds-q-text { font-size: 13.5px; margin-bottom: 6px; }
221
+ .ds-q-option { display: flex; gap: 7px; align-items: flex-start; font-size: 13px; padding: 3px 0; }
222
+ .ds-q-option .muted { font-size: 11px; color: var(--dsr-muted); }
223
+ .ds-modal-body textarea { width: 100%; box-sizing: border-box; margin-top: 6px; background: var(--dsr-bg-2); color: var(--dsr-text); border: 1px solid var(--dsr-line); border-radius: 9px; padding: 7px 9px; font: inherit; font-size: 13px; outline: none; }
224
+
225
+ .ds-toast { position: fixed; left: 50%; bottom: 22px; transform: translateX(-50%); z-index: 150; background: var(--dsr-panel); border: 1px solid var(--dsr-line); border-radius: 10px; padding: 9px 15px; font-size: 13px; box-shadow: 0 8px 24px rgba(0,0,0,.3); }
226
+ .ds-toast.hidden { display: none; }
227
+
228
+ /* 自定义悬停提示: 定位在元素附近, 限制宽度并夹在视口内 */
229
+ .ds-tip {
230
+ position: fixed; z-index: 160; pointer-events: none;
231
+ max-width: min(260px, calc(100vw - 16px));
232
+ background: var(--dsr-bg-2); border: 1px solid var(--dsr-line); border-radius: 9px;
233
+ padding: 7px 10px; font-size: 12px; line-height: 1.6; color: var(--dsr-text);
234
+ box-shadow: 0 8px 24px var(--dsr-shadow);
235
+ white-space: pre-line; word-break: break-word;
236
+ }
237
+ .ds-tip.hidden { display: none; }
238
+
239
+ .mobile-only { display: none; }
240
+
241
+ /* 内置滚动条(去原生, 沿用皮肤变量) */
242
+ .ds-session-list, .ds-history, .ds-fs-list, .ds-group-menu, .ds-modal-body, .ds-notif-body { scrollbar-width: thin; scrollbar-color: var(--dsr-line) transparent; }
243
+ .ds-session-list::-webkit-scrollbar, .ds-history::-webkit-scrollbar, .ds-fs-list::-webkit-scrollbar,
244
+ .ds-group-menu::-webkit-scrollbar, .ds-modal-body::-webkit-scrollbar, .ds-notif-body::-webkit-scrollbar { width: 8px; height: 8px; }
245
+ .ds-session-list::-webkit-scrollbar-track, .ds-history::-webkit-scrollbar-track, .ds-fs-list::-webkit-scrollbar-track,
246
+ .ds-group-menu::-webkit-scrollbar-track, .ds-modal-body::-webkit-scrollbar-track, .ds-notif-body::-webkit-scrollbar-track { background: transparent; }
247
+ .ds-session-list::-webkit-scrollbar-thumb, .ds-history::-webkit-scrollbar-thumb, .ds-fs-list::-webkit-scrollbar-thumb,
248
+ .ds-group-menu::-webkit-scrollbar-thumb, .ds-modal-body::-webkit-scrollbar-thumb, .ds-notif-body::-webkit-scrollbar-thumb {
249
+ background: var(--dsr-line); border-radius: 999px;
250
+ }
251
+ .ds-session-list::-webkit-scrollbar-thumb:hover, .ds-history::-webkit-scrollbar-thumb:hover, .ds-fs-list::-webkit-scrollbar-thumb:hover,
252
+ .ds-group-menu::-webkit-scrollbar-thumb:hover, .ds-modal-body::-webkit-scrollbar-thumb:hover, .ds-notif-body::-webkit-scrollbar-thumb:hover { background: var(--dsr-muted); }
253
+
254
+ /* 桌面 hover 态 */
255
+ @media (hover: hover) and (pointer: fine) {
256
+ .ds-session-item:hover, .ds-nav-item:hover, .ds-server-row:hover, .ds-fs-row:hover { background: var(--dsr-bg-2); }
257
+ .ds-btn:hover { filter: brightness(1.08); }
258
+ }
259
+
260
+ /* 窄屏回退: 隐藏侧栏, 顶部导航, 通知卡片全宽 */
261
+ @media (max-width: 1023px) {
262
+ .ds-sidebar { display: none; }
263
+ #btn-mobile-nav { display: inline-flex; }
264
+ .ds-content { flex-direction: column; }
265
+ .ds-view { padding: 10px; overflow-y: auto; }
266
+ .ds-server { display: none; }
267
+ .ds-msg { max-width: 90%; }
268
+ .ds-toast-stack { left: 10px; right: 10px; width: auto; }
269
+ .mobile-only { display: block; }
270
+ }
@@ -0,0 +1,328 @@
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">
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
+ <link rel="stylesheet" href="desktop.css">
10
+ </head>
11
+ <body>
12
+ <div class="ds-app" id="ds-app">
13
+ <aside class="ds-sidebar" id="ds-sidebar">
14
+ <div class="ds-brand">
15
+ <span class="ds-logo">◆</span><span class="ds-brand-name">DSH Remote</span>
16
+ <a class="ds-gh" href="https://github.com/Blank-not-black/dsh-Remote" target="_blank" rel="noopener" data-i18n-title="ds.repo" data-i18n-aria="ds.repo">
17
+ <svg viewBox="0 0 16 16" aria-hidden="true"><path d="M8 0C3.58 0 0 3.58 0 8a8.01 8.01 0 0 0 5.47 7.59c.4.08.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27s1.36.09 2 .27c1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0 0 16 8c0-4.42-3.58-8-8-8Z"/></svg>
18
+ </a>
19
+ </div>
20
+ <div class="ds-new-session">
21
+ <button id="btn-new-session" class="ds-btn primary" data-i18n="ds.newSession">+ 新会话</button>
22
+ </div>
23
+ <div class="ds-section-label" data-i18n="ds.sessions">会话</div>
24
+ <div class="ds-session-list" id="session-list"></div>
25
+ <div class="ds-sidebar-foot">
26
+ <button class="ds-nav-item" data-view="view-files"><span class="ds-nav-ico">⇅</span><span data-i18n="ds.files">文件传输</span></button>
27
+ <button class="ds-nav-item" data-view="view-settings"><span class="ds-nav-ico">⚙</span><span data-i18n="ds.settings">设置</span></button>
28
+ <div class="ds-feedback">
29
+ <button id="btn-feedback" class="ds-feedback-btn" aria-haspopup="menu" aria-expanded="false" data-i18n-title="ds.feedback" data-i18n-aria="ds.feedback">
30
+ <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="M21 12a8 8 0 0 1-8 8H5l-1.5 2L4 20a8 8 0 0 1-1-4 8 8 0 0 1 18-4Z"/></svg>
31
+ <span data-i18n="ds.feedback">反馈</span>
32
+ </button>
33
+ <div id="feedback-menu" class="ds-feedback-menu hidden" role="menu" data-i18n-aria="ds.feedbackTitle">
34
+ <button class="ds-feedback-item" id="btn-write-feedback" role="menuitem">
35
+ <span class="ds-feedback-ico"><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 20h9"/><path d="M16.5 3.5a2.1 2.1 0 0 1 3 3L7 19l-4 1 1-4Z"/></svg></span>
36
+ <span class="ds-feedback-body"><span class="ds-feedback-name" data-i18n="ds.feedbackWrite">写反馈</span><span class="ds-feedback-desc" data-i18n="ds.feedbackWriteDesc">App 内直接提交</span></span>
37
+ </button>
38
+ <a class="ds-feedback-item primary" href="https://github.com/Blank-not-black/dsh-Remote/issues" target="_blank" rel="noopener" role="menuitem">
39
+ <span class="ds-feedback-ico"><svg viewBox="0 0 16 16" aria-hidden="true"><path d="M8 0C3.58 0 0 3.58 0 8a8.01 8.01 0 0 0 5.47 7.59c.4.08.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27s1.36.09 2 .27c1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0 0 16 8c0-4.42-3.58-8-8-8Z"/></svg></span>
40
+ <span class="ds-feedback-body"><span class="ds-feedback-name">GitHub Issues</span><span class="ds-feedback-desc" data-i18n="ds.feedbackGithubDesc">反馈 bug / 提建议</span></span>
41
+ </a>
42
+ <a class="ds-feedback-item" href="https://gitee.com/Blankneverfails/dsh-Remote/issues" target="_blank" rel="noopener" role="menuitem">
43
+ <span class="ds-feedback-ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="9" r="6"/><path d="M7.5 13.5 7 19a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2l-.5-5.5"/><circle cx="10" cy="8.6" r=".5" fill="currentColor" stroke="none"/><circle cx="14" cy="8.6" r=".5" fill="currentColor" stroke="none"/><path d="M11 10.8c.6.5 1.4.5 2 0"/></svg></span>
44
+ <span class="ds-feedback-body"><span class="ds-feedback-name">Gitee 反馈</span><span class="ds-feedback-desc" data-i18n="ds.feedbackGiteeDesc">国内镜像,无需代理</span></span>
45
+ </a>
46
+ <a class="ds-feedback-item" href="https://space.bilibili.com/419009275/dynamic" target="_blank" rel="noopener" role="menuitem">
47
+ <span class="ds-feedback-ico"><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="6" width="17" height="11" rx="2"/><path d="M8.5 3.5v3M15.5 3.5v3"/><circle cx="9.2" cy="10.6" r=".5" fill="currentColor" stroke="none"/><circle cx="14.8" cy="10.6" r=".5" fill="currentColor" stroke="none"/><path d="M9.5 14c1.4 1 3.6 1 5 0"/></svg></span>
48
+ <span class="ds-feedback-body"><span class="ds-feedback-name">B站交流</span><span class="ds-feedback-desc" data-i18n="ds.feedbackBiliDesc">UP 动态页交流</span></span>
49
+ </a>
50
+ <button class="ds-feedback-item" id="btn-copy-link" role="menuitem">
51
+ <span class="ds-feedback-ico"><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="3" width="12" height="12" rx="2"/><path d="M16 8h3a1 1 0 0 1 1 1v11a1 1 0 0 1-1 1H8a1 1 0 0 1-1-1v-3"/></svg></span>
52
+ <span class="ds-feedback-body"><span class="ds-feedback-name" data-i18n="ds.feedbackCopyLink">复制项目链接</span><span class="ds-feedback-desc" data-i18n="ds.feedbackCopyDesc">手动分享给朋友</span></span>
53
+ </button>
54
+ </div>
55
+ </div>
56
+ </div>
57
+ </aside>
58
+
59
+ <div class="ds-main">
60
+ <header class="ds-topbar">
61
+ <button id="btn-mobile-nav" class="ds-icon-btn" data-i18n-aria="ds.menu" data-i18n-title="ds.menu">☰</button>
62
+ <div class="ds-title" id="ds-title" data-i18n="ds.sessions">会话</div>
63
+ <div class="ds-top-right">
64
+ <span class="ds-conn" id="conn-badge" title="">●</span>
65
+ <span class="ds-server" id="server-badge"></span>
66
+ <button id="btn-stats-top" class="ds-icon-btn" data-i18n-aria="ds.stats" data-i18n-title="ds.stats">📊</button>
67
+ </div>
68
+ </header>
69
+
70
+ <main class="ds-content">
71
+ <!-- 会话列表视图(窄屏回退用) -->
72
+ <section id="view-sessions" class="ds-view">
73
+ <div class="ds-section-label mobile-only" data-i18n="ds.sessions">会话</div>
74
+ <div id="mobile-session-list" class="ds-session-list"></div>
75
+ </section>
76
+
77
+ <!-- 会话详情 -->
78
+ <section id="view-chat" class="ds-view">
79
+ <div id="history" class="ds-history" aria-live="polite"></div>
80
+ <div class="ds-composer">
81
+ <textarea id="composer" rows="1" data-i18n-placeholder="ds.composerPlaceholder" placeholder="输入消息…"></textarea>
82
+ <button id="btn-send" class="ds-btn primary" data-i18n="ds.send">发送</button>
83
+ </div>
84
+ </section>
85
+
86
+ <!-- 文件传输 -->
87
+ <section id="view-files" class="ds-view hidden">
88
+ <div class="ds-section-label" data-i18n="ds.files">文件传输</div>
89
+ <div class="ds-fs-bar">
90
+ <button id="fs-up" class="ds-btn" data-i18n="ds.fsUp">上级</button>
91
+ <span id="fs-path" class="ds-fs-path">/</span>
92
+ <button id="fs-refresh" class="ds-btn" data-i18n="ds.fsRefresh">刷新</button>
93
+ </div>
94
+ <div id="fs-list" class="ds-fs-list"></div>
95
+ </section>
96
+
97
+ <!-- 设置 -->
98
+ <section id="view-settings" class="ds-view hidden">
99
+ <div class="ds-section-label" data-i18n="ds.settings">设置</div>
100
+ <div class="ds-settings">
101
+ <div class="ds-setting-row">
102
+ <div><div class="ds-setting-name" data-i18n="ds.serversTitle">服务器(分组)</div><div class="ds-setting-desc" id="server-desc"></div></div>
103
+ <button id="btn-server-speed" class="ds-btn" data-i18n="ds.speedTest">测速</button>
104
+ </div>
105
+ <div class="ds-group-bar">
106
+ <label data-i18n="ds.currentGroup">当前组</label>
107
+ <div class="ds-group-select" id="group-select">
108
+ <button id="group-select-btn" class="ds-select-btn" type="button"><span id="group-select-label">默认</span><span class="caret">▾</span></button>
109
+ <div id="group-select-menu" class="ds-group-menu hidden"></div>
110
+ </div>
111
+ <button id="btn-group-add" class="ds-btn" data-i18n="ds.addGroup">+ 组</button>
112
+ </div>
113
+ <div id="server-list" class="ds-server-list"></div>
114
+ <div class="ds-server-add">
115
+ <input id="server-input" type="url" placeholder="http://IP:8787">
116
+ <button id="btn-server-add" class="ds-btn" data-i18n="ds.addServer">添加</button>
117
+ </div>
118
+ <div class="ds-setting-row">
119
+ <div><div class="ds-setting-name" data-i18n="ds.tokenTitle">访问令牌</div><div class="ds-setting-desc" id="token-desc"></div></div>
120
+ <button id="btn-copy-token" class="ds-btn" data-i18n="ds.copyToken">复制</button>
121
+ </div>
122
+ <div class="ds-setting-row">
123
+ <div><div class="ds-setting-name" data-i18n="ds.themeTitle">皮肤</div><div class="ds-setting-desc" data-i18n="ds.themeDesc">四套皮肤</div></div>
124
+ <button id="btn-theme" class="ds-btn">默认深空</button>
125
+ </div>
126
+ <div class="ds-setting-row">
127
+ <div><div class="ds-setting-name" data-i18n="ds.langTitle">语言 / Language</div></div>
128
+ <button id="btn-lang" class="ds-btn">EN</button>
129
+ </div>
130
+ <div class="ds-setting-row">
131
+ <div><div class="ds-setting-name" data-i18n="ds.feedbackTitle">反馈渠道</div><div class="ds-setting-desc" data-i18n="ds.feedbackDesc">GitHub / Gitee / B站:反馈 bug、提建议、唠嗑</div></div>
132
+ </div>
133
+ <div class="ds-feedback-links">
134
+ <a class="ds-btn" href="https://github.com/Blank-not-black/dsh-Remote" target="_blank" rel="noopener">GitHub</a>
135
+ <a class="ds-btn" href="https://gitee.com/Blankneverfails/dsh-Remote" target="_blank" rel="noopener">Gitee</a>
136
+ <a class="ds-btn" href="https://space.bilibili.com/419009275/dynamic" target="_blank" rel="noopener">B站</a>
137
+ </div>
138
+ </div>
139
+ </section>
140
+
141
+ </main>
142
+ </div>
143
+
144
+ <!-- 右下角审批/提问通知卡片栈 -->
145
+ <div class="ds-toast-stack" id="notif-stack" aria-live="polite"></div>
146
+
147
+ <!-- 统计抽屉 -->
148
+ <div class="ds-drawer hidden" id="stats-drawer" aria-hidden="true">
149
+ <div class="ds-drawer-head">
150
+ <span data-i18n="ds.stats">统计</span>
151
+ <button class="ds-mini" id="stats-drawer-close" data-i18n-aria="ds.ignore" data-i18n-title="ds.ignore">✕</button>
152
+ </div>
153
+ <div class="ds-drawer-body">
154
+ <div class="ds-stat-grid" id="stats-cards"></div>
155
+ <div id="stats-legend" class="ds-stats-legend"></div>
156
+ <div id="stats-chart" class="ds-stats-chart"></div>
157
+ <div id="stats-note" class="ds-stats-note"></div>
158
+ </div>
159
+ </div>
160
+ </div>
161
+
162
+ <!-- 提问回答模态(多问题仍用模态) -->
163
+ <div id="modal-question" class="ds-modal hidden">
164
+ <div class="ds-modal-card">
165
+ <div class="ds-modal-title" data-i18n="ds.questionTitle">回答问题</div>
166
+ <div id="question-body" class="ds-modal-body"></div>
167
+ <div class="ds-modal-actions">
168
+ <button id="btn-question-cancel" class="ds-btn" data-i18n="ds.ignore">忽略</button>
169
+ <button id="btn-question-submit" class="ds-btn primary" data-i18n="ds.submit">提交</button>
170
+ </div>
171
+ </div>
172
+ </div>
173
+
174
+ <!-- 写反馈模态 -->
175
+ <div id="modal-feedback" class="ds-modal hidden">
176
+ <div class="ds-modal-card">
177
+ <div class="ds-modal-title" data-i18n="ds.feedbackModalTitle">写反馈</div>
178
+ <div class="ds-modal-body">
179
+ <div class="ds-fb-chips" id="fb-chips">
180
+ <button class="ds-fb-chip current" data-fb-type="bug" data-i18n="ds.feedbackTypeBug">Bug</button>
181
+ <button class="ds-fb-chip" data-fb-type="suggestion" data-i18n="ds.feedbackTypeSuggestion">建议</button>
182
+ <button class="ds-fb-chip" data-fb-type="other" data-i18n="ds.feedbackTypeOther">其他</button>
183
+ </div>
184
+ <textarea id="fb-msg" class="ds-fb-textarea" rows="5" maxlength="2000" data-i18n-placeholder="ds.feedbackMessagePlaceholder" placeholder="请描述遇到的问题或建议(必填,≤2000 字)"></textarea>
185
+ <input id="fb-contact" class="ds-fb-input" maxlength="200" data-i18n-placeholder="ds.feedbackContactPlaceholder" placeholder="联系方式(可选):邮箱 / 微信 / B站 ID">
186
+ </div>
187
+ <div class="ds-modal-actions">
188
+ <button id="fb-cancel" class="ds-btn" data-i18n="ds.feedbackCancel">取消</button>
189
+ <button id="fb-submit" class="ds-btn primary" data-i18n="ds.feedbackSubmit">提交</button>
190
+ </div>
191
+ </div>
192
+ </div>
193
+
194
+ <div id="toast" class="ds-toast hidden"></div>
195
+ <div id="ds-tip" class="ds-tip hidden" role="tooltip" aria-hidden="true"></div>
196
+
197
+ <script>
198
+ window.DESKTOP_STR = {
199
+ zh: {
200
+ 'ds.repo': 'GitHub 仓库', 'ds.newSession': '+ 新会话', 'ds.sessions': '会话',
201
+ 'ds.files': '文件传输', 'ds.settings': '设置', 'ds.stats': '统计', 'ds.menu': '导航',
202
+ 'ds.send': '发送', 'ds.composerPlaceholder': '输入消息,Enter 发送…',
203
+ 'ds.fsUp': '上级', 'ds.fsRefresh': '刷新', 'ds.fsEmpty': '目录为空',
204
+ 'ds.serversTitle': '服务器(分组)', 'ds.speedTest': '测速', 'ds.currentGroup': '当前组',
205
+ 'ds.addGroup': '+ 组', 'ds.addServer': '添加', 'ds.tokenTitle': '访问令牌', 'ds.copyToken': '复制',
206
+ 'ds.themeTitle': '皮肤', 'ds.themeDesc': '默认深空 / 落日 / 易北爱乐厅 / 草原孤塔', 'ds.langTitle': '语言 / Language',
207
+ 'ds.feedbackTitle': '反馈渠道', 'ds.feedbackDesc': 'GitHub / Gitee / B站:反馈 bug、提建议、唠嗑',
208
+ 'ds.feedback': '反馈', 'ds.feedbackGithubDesc': '反馈 bug / 提建议', 'ds.feedbackGiteeDesc': '国内镜像,无需代理',
209
+ 'ds.feedbackBiliDesc': 'UP 动态页交流', 'ds.feedbackCopyLink': '复制项目链接', 'ds.feedbackCopyDesc': '手动分享给朋友',
210
+ 'ds.feedbackCopied': '项目链接已复制', 'ds.feedbackCopyFailed': '复制失败,请手动复制',
211
+ 'ds.feedbackWrite': '写反馈', 'ds.feedbackWriteDesc': 'App 内直接提交', 'ds.feedbackModalTitle': '写反馈',
212
+ 'ds.feedbackTypeBug': 'Bug', 'ds.feedbackTypeSuggestion': '建议', 'ds.feedbackTypeOther': '其他',
213
+ 'ds.feedbackMessagePlaceholder': '请描述遇到的问题或建议(必填,≤2000 字)',
214
+ 'ds.feedbackContactPlaceholder': '联系方式(可选):邮箱 / 微信 / B站 ID',
215
+ 'ds.feedbackCancel': '取消', 'ds.feedbackSubmit': '提交',
216
+ 'ds.feedbackEmpty': '请填写描述内容', 'ds.feedbackTooLong': '描述不能超过 2000 字',
217
+ 'ds.feedbackSubmitted': '已提交,感谢反馈',
218
+ 'ds.feedbackRateLimited': '提交太频繁,请 5 分钟后再试', 'ds.feedbackSubmitFailed': '提交失败:{msg}', 'ds.feedbackNetworkError': '网络错误',
219
+ 'ds.questionTitle': '回答问题', 'ds.ignore': '忽略', 'ds.submit': '提交',
220
+ 'ds.connOn': '已连接', 'ds.connOff': '未连接', 'ds.connIng': '连接中',
221
+ 'ds.currentServer': '{group} · {url}', 'ds.origin': '当前页面',
222
+ 'ds.toastSent': '已发送', 'ds.toastCopied': '令牌已复制', 'ds.toastAuth': '令牌无效',
223
+ 'ds.toastConnFailed': '连接失败', 'ds.toastOpFailed': '操作失败',
224
+ 'ds.sessionsEmpty': '暂无会话', 'ds.loading': '加载中…',
225
+ 'ds.historyLoading': '加载历史…', 'ds.historyEmpty': '暂无消息',
226
+ 'ds.allow': '允许', 'ds.reject': '拒绝',
227
+ 'ds.approvalTitle': '工具审批', 'ds.questionNotify': '需要回答',
228
+ 'ds.approvalBody': '{tool} · {server}', 'ds.approvalReason': '原因:{reason}',
229
+ 'ds.allowed': '已允许', 'ds.rejected': '已拒绝', 'ds.ignored': '已忽略',
230
+ 'ds.stale': '请求已失效',
231
+ 'ds.statsTodayTokens': '今日 Token', 'ds.statsTodayCost': '今日费用', 'ds.statsPeakShare': '高峰占比',
232
+ 'ds.statsInput': '未缓存输入', 'ds.statsCacheRead': '缓存命中', 'ds.statsCacheWrite': '缓存写入', 'ds.statsOutput': '输出',
233
+ 'ds.statsPeak': '高峰', 'ds.statsOff': '空闲', 'ds.statsDays': '近 {n} 天',
234
+ 'ds.statsGatewayDown': '统计需要网关运行', 'ds.statsEmpty': '暂无统计',
235
+ 'ds.statsNote': '注:本数据仅在使用 DeepSeek 官方 API 时估算;基于 token 计算,与官网账单可能有出入,一切以官网为准。统计自 2026-08-17 定价生效日起。',
236
+ 'ds.serversEmpty': '未添加备用服务器', 'ds.serversUntested': '未测速', 'ds.serversCurrent': ' · 当前',
237
+ 'ds.serversUnreachable': '不可达', 'ds.serversDelete': '删除', 'ds.serversEdit': '编辑',
238
+ 'ds.serversNeedAddress': '请输入服务器地址', 'ds.serversBadProtocol': '地址需以 http:// 或 https:// 开头',
239
+ 'ds.serversDuplicate': '该地址已存在', 'ds.serversAdded': '已添加', 'ds.serversEdited': '已更新',
240
+ 'ds.serversRemovedActive': '已删除当前服务器,重新测速…',
241
+ 'ds.serversCurrentDesc': '当前:{group} · {url}({ms})',
242
+ 'ds.serversPromptNote': '服务器备注(可留空):', 'ds.serversPromptEditUrl': '编辑服务器地址:',
243
+ 'ds.serversPromptEditNote': '编辑备注({url}):', 'ds.serversPromptEditGroup': '所属组:',
244
+ 'ds.serversManualSelected': '已切换至:{url}',
245
+ 'ds.speedTesting': '正在测速…', 'ds.speedSwitched': '已切换到:{url}({ms}ms)',
246
+ 'ds.speedAlreadyBest': '当前已是最快:{url}({ms}ms)', 'ds.speedAllDown': '全部服务器不可达',
247
+ 'ds.speedManualUsing': '手动模式:{url}({ms})',
248
+ 'ds.groupsNoServer': '组内暂无服务器', 'ds.groupsAutoSelect': '自动选择最快', 'ds.groupsDelete': '删除组',
249
+ 'ds.groupsAutoOn': '{group} 已切换为自动选择', 'ds.groupsAutoOff': '{group} 已切换为手动选择',
250
+ 'ds.groupsSwitched': '已切换到组:{group}', 'ds.groupsSwitchHint': '点击切换当前组,双击重命名',
251
+ 'ds.groupsPromptAdd': '新组名:', 'ds.groupsDuplicate': '组名已存在', 'ds.groupsAdded': '已创建组:{group}',
252
+ 'ds.groupsPromptRename': '重命名组「{group}」:', 'ds.groupsRenamed': '组已重命名',
253
+ 'ds.groupsCannotDeleteDefault': '默认组不可删除',
254
+ 'ds.groupsConfirmDelete': '删除组「{group}」?组内服务器将归入默认组。', 'ds.groupsDeleted': '组已删除',
255
+ 'ds.theme.default': '默认深空', 'ds.theme.dark': '落日', 'ds.theme.light': '易北爱乐厅', 'ds.theme.neutral': '草原孤塔',
256
+ 'ds.role.me': '我', 'ds.role.dsh': 'DSH', 'ds.toolDefault': '工具',
257
+ 'ds.questionOptions': '选项', 'ds.questionCustom': '自定义回答',
258
+ 'ds.questionNeedAnswer': '请选择一个选项或填写自定义回答', 'ds.questionSubmitted': '已提交回答',
259
+ 'ds.questionFrom': '来自 {server}',
260
+ },
261
+ en: {
262
+ 'ds.repo': 'GitHub repo', 'ds.newSession': '+ New session', 'ds.sessions': 'Sessions',
263
+ 'ds.files': 'Files', 'ds.settings': 'Settings', 'ds.stats': 'Stats', 'ds.menu': 'Menu',
264
+ 'ds.send': 'Send', 'ds.composerPlaceholder': 'Type a message, Enter to send…',
265
+ 'ds.fsUp': 'Up', 'ds.fsRefresh': 'Refresh', 'ds.fsEmpty': 'Empty directory',
266
+ 'ds.serversTitle': 'Servers (groups)', 'ds.speedTest': 'Test', 'ds.currentGroup': 'Current group',
267
+ 'ds.addGroup': '+ Group', 'ds.addServer': 'Add', 'ds.tokenTitle': 'Access token', 'ds.copyToken': 'Copy',
268
+ 'ds.themeTitle': 'Theme', 'ds.themeDesc': 'Deep Space / Sunset / Elbphilharmonie / Prairie Tower', 'ds.langTitle': 'Language',
269
+ 'ds.feedbackTitle': 'Feedback', 'ds.feedbackDesc': 'GitHub / Gitee / Bilibili: report bugs, suggest features, or just chat',
270
+ 'ds.feedback': 'Feedback', 'ds.feedbackGithubDesc': 'Report bugs · suggest features', 'ds.feedbackGiteeDesc': 'Mirror in China, no proxy needed',
271
+ 'ds.feedbackBiliDesc': 'Chat on the UP\'s Bilibili page', 'ds.feedbackCopyLink': 'Copy project link', 'ds.feedbackCopyDesc': 'Share it manually',
272
+ 'ds.feedbackCopied': 'Project link copied', 'ds.feedbackCopyFailed': 'Copy failed, copy manually',
273
+ 'ds.feedbackWrite': 'Write feedback', 'ds.feedbackWriteDesc': 'Submit from the app', 'ds.feedbackModalTitle': 'Write feedback',
274
+ 'ds.feedbackTypeBug': 'Bug', 'ds.feedbackTypeSuggestion': 'Suggestion', 'ds.feedbackTypeOther': 'Other',
275
+ 'ds.feedbackMessagePlaceholder': 'Describe the bug or suggestion (required, ≤2000 chars)',
276
+ 'ds.feedbackContactPlaceholder': 'Contact (optional): email / WeChat / Bilibili ID',
277
+ 'ds.feedbackCancel': 'Cancel', 'ds.feedbackSubmit': 'Submit',
278
+ 'ds.feedbackEmpty': 'Please fill in the description', 'ds.feedbackTooLong': 'Description must be ≤2000 characters',
279
+ 'ds.feedbackSubmitted': 'Submitted — thanks!',
280
+ 'ds.feedbackRateLimited': 'Too frequent, try again in 5 minutes', 'ds.feedbackSubmitFailed': 'Submit failed: {msg}', 'ds.feedbackNetworkError': 'Network error',
281
+ 'ds.questionTitle': 'Answer question', 'ds.ignore': 'Ignore', 'ds.submit': 'Submit',
282
+ 'ds.connOn': 'Connected', 'ds.connOff': 'Offline', 'ds.connIng': 'Connecting',
283
+ 'ds.currentServer': '{group} · {url}', 'ds.origin': 'this page',
284
+ 'ds.toastSent': 'Sent', 'ds.toastCopied': 'Token copied', 'ds.toastAuth': 'Invalid token',
285
+ 'ds.toastConnFailed': 'Connection failed', 'ds.toastOpFailed': 'Operation failed',
286
+ 'ds.sessionsEmpty': 'No sessions', 'ds.loading': 'Loading…',
287
+ 'ds.historyLoading': 'Loading history…', 'ds.historyEmpty': 'No messages yet',
288
+ 'ds.allow': 'Allow', 'ds.reject': 'Reject',
289
+ 'ds.approvalTitle': 'Tool approval', 'ds.questionNotify': 'Question',
290
+ 'ds.approvalBody': '{tool} · {server}', 'ds.approvalReason': 'Reason: {reason}',
291
+ 'ds.allowed': 'Allowed', 'ds.rejected': 'Rejected', 'ds.ignored': 'Ignored',
292
+ 'ds.stale': 'Request is stale',
293
+ 'ds.statsTodayTokens': 'Tokens today', 'ds.statsTodayCost': 'Cost today', 'ds.statsPeakShare': 'Peak share',
294
+ 'ds.statsInput': 'Uncached input', 'ds.statsCacheRead': 'Cache read', 'ds.statsCacheWrite': 'Cache write', 'ds.statsOutput': 'Output',
295
+ 'ds.statsPeak': 'Peak', 'ds.statsOff': 'Off-peak', 'ds.statsDays': 'Last {n} days',
296
+ 'ds.statsGatewayDown': 'Stats require the gateway', 'ds.statsEmpty': 'No stats yet',
297
+ 'ds.statsNote': 'Note: estimates assume the official DeepSeek API. Token-based calculation may differ from the official bill; always defer to deepseek.com. Stats start from the 2026-08-17 pricing date.',
298
+ 'ds.serversEmpty': 'No backup servers', 'ds.serversUntested': 'Untested', 'ds.serversCurrent': ' · current',
299
+ 'ds.serversUnreachable': 'Unreachable', 'ds.serversDelete': 'Delete', 'ds.serversEdit': 'Edit',
300
+ 'ds.serversNeedAddress': 'Enter a server address', 'ds.serversBadProtocol': 'Address must start with http:// or https://',
301
+ 'ds.serversDuplicate': 'Address already exists', 'ds.serversAdded': 'Added', 'ds.serversEdited': 'Updated',
302
+ 'ds.serversRemovedActive': 'Removed active server, re-testing…',
303
+ 'ds.serversCurrentDesc': 'Current: {group} · {url} ({ms})',
304
+ 'ds.serversPromptNote': 'Server note (optional):', 'ds.serversPromptEditUrl': 'Edit server URL:',
305
+ 'ds.serversPromptEditNote': 'Edit note ({url}):', 'ds.serversPromptEditGroup': 'Group:',
306
+ 'ds.serversManualSelected': 'Switched to: {url}',
307
+ 'ds.speedTesting': 'Testing servers…', 'ds.speedSwitched': 'Switched to: {url} ({ms}ms)',
308
+ 'ds.speedAlreadyBest': 'Already on fastest: {url} ({ms}ms)', 'ds.speedAllDown': 'All servers unreachable',
309
+ 'ds.speedManualUsing': 'Manual mode: {url} ({ms})',
310
+ 'ds.groupsNoServer': 'No servers in this group', 'ds.groupsAutoSelect': 'Auto-select fastest', 'ds.groupsDelete': 'Delete group',
311
+ 'ds.groupsAutoOn': '{group} is now auto-select', 'ds.groupsAutoOff': '{group} is now manual',
312
+ 'ds.groupsSwitched': 'Switched to group: {group}', 'ds.groupsSwitchHint': 'Click to switch, double-click to rename',
313
+ 'ds.groupsPromptAdd': 'New group name:', 'ds.groupsDuplicate': 'Group already exists', 'ds.groupsAdded': 'Created group: {group}',
314
+ 'ds.groupsPromptRename': 'Rename group "{group}":', 'ds.groupsRenamed': 'Group renamed',
315
+ 'ds.groupsCannotDeleteDefault': 'The default group cannot be deleted',
316
+ 'ds.groupsConfirmDelete': 'Delete group "{group}"? Its servers move to the default group.', 'ds.groupsDeleted': 'Group deleted',
317
+ 'ds.theme.default': 'Deep Space', 'ds.theme.dark': 'Sunset', 'ds.theme.light': 'Elbphilharmonie', 'ds.theme.neutral': 'Prairie Tower',
318
+ 'ds.role.me': 'Me', 'ds.role.dsh': 'DSH', 'ds.toolDefault': 'tool',
319
+ 'ds.questionOptions': 'Options', 'ds.questionCustom': 'Custom answer',
320
+ 'ds.questionNeedAnswer': 'Choose an option or write a custom answer', 'ds.questionSubmitted': 'Answer submitted',
321
+ 'ds.questionFrom': 'From {server}',
322
+ }
323
+ }
324
+ </script>
325
+ <script src="i18n.js"></script>
326
+ <script src="desktop.js"></script>
327
+ </body>
328
+ </html>