dsh-remote-plugin 0.6.0 → 0.6.1

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.
@@ -63,6 +63,7 @@
63
63
  <div class="ds-top-right">
64
64
  <span class="ds-conn" id="conn-badge" title="">●</span>
65
65
  <span class="ds-server" id="server-badge"></span>
66
+ <button id="btn-donate" class="ds-icon-btn" data-i18n-aria="ds.donate" data-i18n-title="ds.donate">🎁</button>
66
67
  <button id="btn-stats-top" class="ds-icon-btn" data-i18n-aria="ds.stats" data-i18n-title="ds.stats">📊</button>
67
68
  </div>
68
69
  </header>
@@ -79,6 +80,21 @@
79
80
  <div id="history" class="ds-history" aria-live="polite"></div>
80
81
  <div class="ds-composer">
81
82
  <textarea id="composer" rows="1" data-i18n-placeholder="ds.composerPlaceholder" placeholder="输入消息…"></textarea>
83
+ <div style="position:relative">
84
+ <button id="btn-cmd" class="ds-btn" data-i18n="ds.commands">指令</button>
85
+ <div id="cmd-menu" class="ds-group-menu hidden" style="width:280px; left:auto; right:0; top:auto; bottom:calc(100% + 8px)">
86
+ <button class="ds-feedback-item" data-ds-cmd="/compact" data-i18n="ds.cmdCompact">/compact 压缩对话历史</button>
87
+ <button class="ds-feedback-item" data-ds-cmd="/export" data-i18n="ds.cmdExport">/export 导出会话日志 ZIP</button>
88
+ <button class="ds-feedback-item" data-ds-cmd="/feedback" data-i18n="ds.cmdFeedback">/feedback 反馈当前会话</button>
89
+ <button class="ds-feedback-item" data-ds-cmd="/goal" data-i18n="ds.cmdGoal">/goal 设置/查看任务目标</button>
90
+ <button class="ds-feedback-item" data-ds-cmd="/permission" data-i18n="ds.cmdPermission">/permission 切换权限预设</button>
91
+ <button class="ds-feedback-item" data-ds-cmd="/plan" data-i18n="ds.cmdPlan">/plan 进入/退出计划模式</button>
92
+ </div>
93
+ </div>
94
+ <div style="position:relative">
95
+ <button id="btn-preset" class="ds-btn" data-i18n="ds.presets">预设</button>
96
+ <div id="preset-menu" class="ds-group-menu hidden" style="width:260px; left:auto; right:0; top:auto; bottom:calc(100% + 8px)"></div>
97
+ </div>
82
98
  <button id="btn-send" class="ds-btn primary" data-i18n="ds.send">发送</button>
83
99
  </div>
84
100
  </section>
@@ -97,7 +113,64 @@
97
113
  <!-- 设置 -->
98
114
  <section id="view-settings" class="ds-view hidden">
99
115
  <div class="ds-section-label" data-i18n="ds.settings">设置</div>
100
- <div class="ds-settings">
116
+ <div id="settings-home">
117
+ <div class="ds-settings">
118
+ <div class="ds-setting-row" data-settings-group="general" style="cursor:pointer">
119
+ <div><div class="ds-setting-name" data-i18n="ds.groupGeneral">通用</div><div class="ds-setting-desc" data-i18n="ds.groupGeneralDesc">工具调用、预设提示词</div></div>
120
+ <span class="ds-feedback-item" style="font-size:18px">›</span>
121
+ </div>
122
+ <div class="ds-setting-row" data-settings-group="servers" style="cursor:pointer">
123
+ <div><div class="ds-setting-name" data-i18n="ds.groupServers">服务器</div><div class="ds-setting-desc" data-i18n="ds.groupServersDesc">服务器地址</div></div>
124
+ <span class="ds-feedback-item" style="font-size:18px">›</span>
125
+ </div>
126
+ <div class="ds-setting-row" data-settings-group="notify" style="cursor:pointer">
127
+ <div><div class="ds-setting-name" data-i18n="ds.groupNotify">通知</div><div class="ds-setting-desc" data-i18n="ds.groupNotifyDesc">通知与提醒</div></div>
128
+ <span class="ds-feedback-item" style="font-size:18px">›</span>
129
+ </div>
130
+ <div class="ds-setting-row" data-settings-group="theme" style="cursor:pointer">
131
+ <div><div class="ds-setting-name" data-i18n="ds.groupTheme">皮肤</div><div class="ds-setting-desc" data-i18n="ds.groupThemeDesc">配色主题</div></div>
132
+ <span class="ds-feedback-item" style="font-size:18px">›</span>
133
+ </div>
134
+ <div class="ds-setting-row" data-settings-group="about" style="cursor:pointer">
135
+ <div><div class="ds-setting-name" data-i18n="ds.groupAbout">关于</div><div class="ds-setting-desc" data-i18n="ds.groupAboutDesc">版本信息</div></div>
136
+ <span class="ds-feedback-item" style="font-size:18px">›</span>
137
+ </div>
138
+ </div>
139
+ <div class="ds-settings">
140
+ <div class="ds-setting-row">
141
+ <div><div class="ds-setting-name" data-i18n="ds.tokenTitle">访问令牌</div><div class="ds-setting-desc" id="token-desc"></div></div>
142
+ <button id="btn-copy-token" class="ds-btn" data-i18n="ds.copyToken">复制</button>
143
+ </div>
144
+ <div class="ds-setting-row">
145
+ <div><div class="ds-setting-name" data-i18n="ds.langTitle">语言 / Language</div></div>
146
+ <button id="btn-lang" class="ds-btn">EN</button>
147
+ </div>
148
+ <div class="ds-setting-row">
149
+ <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>
150
+ </div>
151
+ <div class="ds-feedback-links">
152
+ <a class="ds-btn" href="https://github.com/Blank-not-black/dsh-Remote" target="_blank" rel="noopener">GitHub</a>
153
+ <a class="ds-btn" href="https://gitee.com/Blankneverfails/dsh-Remote" target="_blank" rel="noopener">Gitee</a>
154
+ <a class="ds-btn" href="https://space.bilibili.com/419009275/dynamic" target="_blank" rel="noopener">B站</a>
155
+ </div>
156
+ </div>
157
+ </div>
158
+
159
+ <div id="settings-page-general" class="ds-settings hidden">
160
+ <div class="ds-setting-row" data-settings-back style="cursor:pointer">
161
+ <button class="ds-btn" data-i18n="ds.back">‹ 返回</button>
162
+ <div class="ds-setting-name" data-i18n="ds.groupGeneral">通用</div>
163
+ </div>
164
+ <div class="ds-setting-row">
165
+ <div><div class="ds-setting-name" data-i18n="ds.emptyGroup">暂无设置项</div></div>
166
+ </div>
167
+ </div>
168
+
169
+ <div id="settings-page-servers" class="ds-settings hidden">
170
+ <div class="ds-setting-row" data-settings-back style="cursor:pointer">
171
+ <button class="ds-btn" data-i18n="ds.back">‹ 返回</button>
172
+ <div class="ds-setting-name" data-i18n="ds.groupServers">服务器</div>
173
+ </div>
101
174
  <div class="ds-setting-row">
102
175
  <div><div class="ds-setting-name" data-i18n="ds.serversTitle">服务器(分组)</div><div class="ds-setting-desc" id="server-desc"></div></div>
103
176
  <button id="btn-server-speed" class="ds-btn" data-i18n="ds.speedTest">测速</button>
@@ -115,25 +188,37 @@
115
188
  <input id="server-input" type="url" placeholder="http://IP:8787">
116
189
  <button id="btn-server-add" class="ds-btn" data-i18n="ds.addServer">添加</button>
117
190
  </div>
191
+ </div>
192
+
193
+ <div id="settings-page-notify" class="ds-settings hidden">
194
+ <div class="ds-setting-row" data-settings-back style="cursor:pointer">
195
+ <button class="ds-btn" data-i18n="ds.back">‹ 返回</button>
196
+ <div class="ds-setting-name" data-i18n="ds.groupNotify">通知</div>
197
+ </div>
118
198
  <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>
199
+ <div><div class="ds-setting-name" data-i18n="ds.emptyGroup">暂无设置项</div></div>
200
+ </div>
201
+ </div>
202
+
203
+ <div id="settings-page-theme" class="ds-settings hidden">
204
+ <div class="ds-setting-row" data-settings-back style="cursor:pointer">
205
+ <button class="ds-btn" data-i18n="ds.back">‹ 返回</button>
206
+ <div class="ds-setting-name" data-i18n="ds.groupTheme">皮肤</div>
121
207
  </div>
122
208
  <div class="ds-setting-row">
123
209
  <div><div class="ds-setting-name" data-i18n="ds.themeTitle">皮肤</div><div class="ds-setting-desc" data-i18n="ds.themeDesc">四套皮肤</div></div>
124
210
  <button id="btn-theme" class="ds-btn">默认深空</button>
125
211
  </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>
212
+ </div>
213
+
214
+ <div id="settings-page-about" class="ds-settings hidden">
215
+ <div class="ds-setting-row" data-settings-back style="cursor:pointer">
216
+ <button class="ds-btn" data-i18n="ds.back">‹ 返回</button>
217
+ <div class="ds-setting-name" data-i18n="ds.groupAbout">关于</div>
132
218
  </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>
219
+ <div class="ds-setting-row" id="btn-donate-about" style="cursor:pointer">
220
+ <div><div class="ds-setting-name" data-i18n="ds.donate">赞赏支持</div><div class="ds-setting-desc" data-i18n="ds.donateDesc">如果 dsh-remote 帮到了你,欢迎赞赏支持开发 ☕</div></div>
221
+ <span class="ds-feedback-item" style="font-size:18px">›</span>
137
222
  </div>
138
223
  </div>
139
224
  </section>
@@ -191,6 +276,38 @@
191
276
  </div>
192
277
  </div>
193
278
 
279
+ <!-- 赞赏支持模态 -->
280
+ <div id="modal-donate" class="ds-modal hidden">
281
+ <div class="ds-modal-card">
282
+ <div class="ds-modal-title" data-i18n="ds.donate">赞赏支持</div>
283
+ <div class="ds-modal-body donate-body">
284
+ <img src="../donate.png" alt="" class="donate-img" style="display:block;margin:0 auto;max-width:100%;max-height:60vh;border-radius:8px">
285
+ <p class="donate-thanks" data-i18n="ds.donateThanks" style="text-align:center;margin-top:12px">感谢你的支持 ☕</p>
286
+ </div>
287
+ <div class="ds-modal-actions">
288
+ <button id="btn-donate-close" class="ds-btn" data-i18n="ds.donateClose">关闭</button>
289
+ </div>
290
+ </div>
291
+ </div>
292
+
293
+ <!-- 更新内容模态 -->
294
+ <div id="modal-notes" class="ds-modal hidden">
295
+ <div class="ds-modal-card">
296
+ <div class="ds-modal-title"><span id="notes-version" style="opacity:.75"></span> <span data-i18n="ds.notesTitle">更新内容</span></div>
297
+ <div class="ds-modal-body notes-body">
298
+ <div id="notes-pages" class="notes-pages" style="display:flex;overflow-x:auto;scroll-snap-type:x mandatory;gap:12px;scrollbar-width:none;-ms-overflow-style:none;"></div>
299
+ <div class="notes-nav" style="display:flex;align-items:center;justify-content:center;gap:12px;margin-top:8px;">
300
+ <button id="notes-prev" class="ds-btn" aria-label="‹">‹</button>
301
+ <span id="notes-page" style="opacity:.75">1/1</span>
302
+ <button id="notes-next" class="ds-btn" aria-label="›">›</button>
303
+ </div>
304
+ </div>
305
+ <div class="ds-modal-actions">
306
+ <button id="notes-close" class="ds-btn" data-i18n="ds.notesClose">关闭</button>
307
+ </div>
308
+ </div>
309
+ </div>
310
+
194
311
  <div id="toast" class="ds-toast hidden"></div>
195
312
  <div id="ds-tip" class="ds-tip hidden" role="tooltip" aria-hidden="true"></div>
196
313
 
@@ -199,8 +316,20 @@
199
316
  zh: {
200
317
  'ds.repo': 'GitHub 仓库', 'ds.newSession': '+ 新会话', 'ds.sessions': '会话',
201
318
  'ds.files': '文件传输', 'ds.settings': '设置', 'ds.stats': '统计', 'ds.menu': '导航',
202
- 'ds.send': '发送', 'ds.composerPlaceholder': '输入消息,Enter 发送…',
319
+ 'ds.send': '发送', 'ds.composerPlaceholder': '输入消息,Enter 发送…', 'ds.presets': '预设',
320
+ 'ds.commands': '指令',
321
+ 'ds.cmdCompact': '/compact 压缩对话历史', 'ds.cmdExport': '/export 导出会话日志 ZIP',
322
+ 'ds.cmdFeedback': '/feedback 反馈当前会话', 'ds.cmdGoal': '/goal 设置/查看任务目标',
323
+ 'ds.cmdPermission': '/permission 切换权限预设', 'ds.cmdPlan': '/plan 进入/退出计划模式',
203
324
  'ds.fsUp': '上级', 'ds.fsRefresh': '刷新', 'ds.fsEmpty': '目录为空',
325
+ 'ds.groupGeneral': '通用', 'ds.groupGeneralDesc': '工具调用、预设提示词',
326
+ 'ds.groupServers': '服务器', 'ds.groupServersDesc': '服务器地址',
327
+ 'ds.groupNotify': '通知', 'ds.groupNotifyDesc': '通知与提醒',
328
+ 'ds.groupTheme': '皮肤', 'ds.groupThemeDesc': '配色主题',
329
+ 'ds.groupAbout': '关于', 'ds.groupAboutDesc': '版本信息',
330
+ 'ds.donate': '赞赏支持', 'ds.donateDesc': '如果 dsh-remote 帮到了你,欢迎赞赏支持开发 ☕', 'ds.donateThanks': '感谢你的支持 ☕', 'ds.donateClose': '关闭',
331
+ 'ds.notesTitle': '更新内容', 'ds.notesClose': '关闭', 'ds.notesPage': '{current}/{total}',
332
+ 'ds.back': '‹ 返回', 'ds.emptyGroup': '暂无设置项',
204
333
  'ds.serversTitle': '服务器(分组)', 'ds.speedTest': '测速', 'ds.currentGroup': '当前组',
205
334
  'ds.addGroup': '+ 组', 'ds.addServer': '添加', 'ds.tokenTitle': '访问令牌', 'ds.copyToken': '复制',
206
335
  'ds.themeTitle': '皮肤', 'ds.themeDesc': '默认深空 / 落日 / 易北爱乐厅 / 草原孤塔', 'ds.langTitle': '语言 / Language',
@@ -255,6 +384,7 @@
255
384
  'ds.groupsConfirmDelete': '删除组「{group}」?组内服务器将归入默认组。', 'ds.groupsDeleted': '组已删除',
256
385
  'ds.theme.default': '默认深空', 'ds.theme.dark': '落日', 'ds.theme.light': '易北爱乐厅', 'ds.theme.neutral': '草原孤塔',
257
386
  'ds.role.me': '我', 'ds.role.dsh': 'DSH', 'ds.toolDefault': '工具',
387
+ 'ds.eventSystemReminder': '系统提示',
258
388
  'ds.questionOptions': '选项', 'ds.questionCustom': '自定义回答',
259
389
  'ds.questionNeedAnswer': '请选择一个选项或填写自定义回答', 'ds.questionSubmitted': '已提交回答',
260
390
  'ds.questionFrom': '来自 {server}',
@@ -262,8 +392,20 @@
262
392
  en: {
263
393
  'ds.repo': 'GitHub repo', 'ds.newSession': '+ New session', 'ds.sessions': 'Sessions',
264
394
  'ds.files': 'Files', 'ds.settings': 'Settings', 'ds.stats': 'Stats', 'ds.menu': 'Menu',
265
- 'ds.send': 'Send', 'ds.composerPlaceholder': 'Type a message, Enter to send…',
395
+ 'ds.send': 'Send', 'ds.composerPlaceholder': 'Type a message, Enter to send…', 'ds.presets': 'Presets',
396
+ 'ds.commands': 'Commands',
397
+ 'ds.cmdCompact': '/compact Compress conversation history', 'ds.cmdExport': '/export Export session log ZIP',
398
+ 'ds.cmdFeedback': '/feedback Feedback current session', 'ds.cmdGoal': '/goal Set/view task goal',
399
+ 'ds.cmdPermission': '/permission Switch permission preset', 'ds.cmdPlan': '/plan Enter/exit plan mode',
266
400
  'ds.fsUp': 'Up', 'ds.fsRefresh': 'Refresh', 'ds.fsEmpty': 'Empty directory',
401
+ 'ds.groupGeneral': 'General', 'ds.groupGeneralDesc': 'Tool calls, prompt presets',
402
+ 'ds.groupServers': 'Servers', 'ds.groupServersDesc': 'Server address',
403
+ 'ds.groupNotify': 'Notifications', 'ds.groupNotifyDesc': 'Notifications & reminders',
404
+ 'ds.groupTheme': 'Theme', 'ds.groupThemeDesc': 'Color themes',
405
+ 'ds.groupAbout': 'About', 'ds.groupAboutDesc': 'Version info',
406
+ 'ds.donate': 'Support', 'ds.donateDesc': 'If dsh-remote has been helpful to you, feel free to support development with a coffee ☕', 'ds.donateThanks': 'Thanks for your support ☕', 'ds.donateClose': 'Close',
407
+ 'ds.notesTitle': 'What\'s New', 'ds.notesClose': 'Close', 'ds.notesPage': '{current}/{total}',
408
+ 'ds.back': '‹ Back', 'ds.emptyGroup': 'No settings here',
267
409
  'ds.serversTitle': 'Servers (groups)', 'ds.speedTest': 'Test', 'ds.currentGroup': 'Current group',
268
410
  'ds.addGroup': '+ Group', 'ds.addServer': 'Add', 'ds.tokenTitle': 'Access token', 'ds.copyToken': 'Copy',
269
411
  'ds.themeTitle': 'Theme', 'ds.themeDesc': 'Deep Space / Sunset / Elbphilharmonie / Prairie Tower', 'ds.langTitle': 'Language',
@@ -318,6 +460,7 @@
318
460
  'ds.groupsConfirmDelete': 'Delete group "{group}"? Its servers move to the default group.', 'ds.groupsDeleted': 'Group deleted',
319
461
  'ds.theme.default': 'Deep Space', 'ds.theme.dark': 'Sunset', 'ds.theme.light': 'Elbphilharmonie', 'ds.theme.neutral': 'Prairie Tower',
320
462
  'ds.role.me': 'Me', 'ds.role.dsh': 'DSH', 'ds.toolDefault': 'tool',
463
+ 'ds.eventSystemReminder': 'System reminder',
321
464
  'ds.questionOptions': 'Options', 'ds.questionCustom': 'Custom answer',
322
465
  'ds.questionNeedAnswer': 'Choose an option or write a custom answer', 'ds.questionSubmitted': 'Answer submitted',
323
466
  'ds.questionFrom': 'From {server}',
@@ -96,6 +96,35 @@ function toast(text, kind = '') {
96
96
  toast._t = setTimeout(() => el.classList.add('hidden'), 2600)
97
97
  }
98
98
 
99
+ /* ---------------- 预设提示词 ---------------- */
100
+ const PRESETS_KEY = 'dshPromptPresets'
101
+ function readPresets() {
102
+ try {
103
+ const v = JSON.parse(LS.get(PRESETS_KEY, '[]') || '[]')
104
+ return Array.isArray(v) ? v.filter(p => p && typeof p.id === 'string' && typeof p.name === 'string' && typeof p.text === 'string') : []
105
+ } catch { return [] }
106
+ }
107
+ function renderPresetMenuDesktop() {
108
+ const btn = $('btn-preset')
109
+ const menu = $('preset-menu')
110
+ if (!btn || !menu) return
111
+ const list = readPresets()
112
+ btn.style.display = list.length ? '' : 'none'
113
+ menu.classList.add('hidden')
114
+ menu.innerHTML = list.map(p => `<button class="ds-feedback-item" data-ds-preset="${esc(p.id)}">${esc(p.name)}</button>`).join('')
115
+ }
116
+ function togglePresetMenuDesktop() {
117
+ const menu = $('preset-menu')
118
+ if (!menu) return
119
+ if (!readPresets().length) return
120
+ menu.classList.toggle('hidden')
121
+ }
122
+ function toggleCmdMenuDesktop() {
123
+ const menu = $('cmd-menu')
124
+ if (!menu) return
125
+ menu.classList.toggle('hidden')
126
+ }
127
+
99
128
  /* ---------------- 反馈 ---------------- */
100
129
  const FEEDBACK_LINKS = {
101
130
  githubIssues: 'https://github.com/Blank-not-black/dsh-Remote/issues',
@@ -134,6 +163,64 @@ function openFeedbackModal() {
134
163
  setTimeout(() => $('fb-msg').focus(), 50)
135
164
  }
136
165
  function closeFeedbackModal() { $('modal-feedback').classList.add('hidden') }
166
+ function openDonateModal() {
167
+ const m = $('modal-donate')
168
+ if (m) m.classList.remove('hidden')
169
+ }
170
+ /* ---------------- 更新内容弹窗 ---------------- */
171
+ const NOTES_KEY = 'seenNotesVersion'
172
+ let notesVersion = ''
173
+ let notesPages = []
174
+ let notesPage = 0
175
+ function splitNotes(notes) {
176
+ return String(notes || '').split(/[;;]/).map(s => s.trim()).filter(Boolean)
177
+ }
178
+ function renderNotesPages(items) {
179
+ const box = $('notes-pages')
180
+ if (!box) return
181
+ const pages = []
182
+ for (let i = 0; i < items.length; i += 3) pages.push(items.slice(i, i + 3))
183
+ notesPages = pages
184
+ notesPage = 0
185
+ box.innerHTML = pages.map(page => `<div class="notes-page" style="flex:0 0 100%;scroll-snap-align:start;box-sizing:border-box;min-width:0;">${page.map(item => `<div class="notes-item" style="padding:6px 0;line-height:1.5;">${esc(item)}</div>`).join('')}</div>`).join('')
186
+ box.scrollLeft = 0
187
+ updateNotesPage()
188
+ }
189
+ function updateNotesPage() {
190
+ const box = $('notes-pages')
191
+ const pageEl = $('notes-page')
192
+ if (!box || !pageEl) return
193
+ const total = notesPages.length || 1
194
+ const idx = Math.min(Math.max(0, Math.round(box.scrollLeft / Math.max(1, box.clientWidth))), total - 1)
195
+ notesPage = idx
196
+ pageEl.textContent = t('ds.notesPage', { current: idx + 1, total })
197
+ }
198
+ function scrollNotes(dir) {
199
+ const box = $('notes-pages')
200
+ if (box) box.scrollBy({ left: dir * box.clientWidth, behavior: 'smooth' })
201
+ }
202
+ function openNotesModal(info) {
203
+ if (!info?.version || String(info.version).includes('-rc')) return
204
+ if (LS.get(NOTES_KEY) === info.version) return
205
+ const items = splitNotes(info.notes)
206
+ if (!items.length) return
207
+ notesVersion = info.version
208
+ const vEl = $('notes-version')
209
+ if (vEl) vEl.textContent = 'v' + info.version
210
+ renderNotesPages(items)
211
+ $('modal-notes').classList.remove('hidden')
212
+ }
213
+ function closeNotesModal() {
214
+ $('modal-notes').classList.add('hidden')
215
+ if (notesVersion) { LS.set(NOTES_KEY, notesVersion); notesVersion = '' }
216
+ }
217
+ async function checkNotesOnStart() {
218
+ try {
219
+ const res = await fetch('../update.json?t=' + Date.now())
220
+ if (!res.ok) return
221
+ openNotesModal(await res.json())
222
+ } catch {}
223
+ }
137
224
  async function submitFeedback() {
138
225
  const type = document.querySelector('#fb-chips .ds-fb-chip.current')?.dataset.fbType || 'bug'
139
226
  const message = $('fb-msg').value.trim()
@@ -735,6 +822,13 @@ function blockHtml(b) {
735
822
  if (b.type === 'image') return `<div>🖼</div>`
736
823
  return ''
737
824
  }
825
+ function systemReminderText(blocks) {
826
+ if (!Array.isArray(blocks)) return ''
827
+ return blocks
828
+ .filter(b => b && typeof b === 'object' && b.type === 'text' && String(b.text ?? '').trimStart().startsWith('<system-reminder>'))
829
+ .map(b => String(b.text ?? ''))
830
+ .join('\n')
831
+ }
738
832
  function eventHtml(entry) {
739
833
  const ev = entry.event || {}
740
834
  const data = ev.data || {}
@@ -744,6 +838,11 @@ function eventHtml(entry) {
744
838
  const msg = data.message || {}
745
839
  const role = data.role || msg.role || (type.startsWith('user') ? 'user' : 'assistant')
746
840
  const blocks = msg.content || data.content || []
841
+ const sysText = type === 'user/message' ? systemReminderText(blocks) : ''
842
+ if (sysText) {
843
+ const shown = sysText.length > 400 ? sysText.slice(0, 400) + '…' : sysText
844
+ return `<details class="event ds-tool"><summary>${esc(t('ds.eventSystemReminder'))}</summary><pre>${esc(shown)}</pre></details>`
845
+ }
747
846
  const text = blocks.map(blockHtml).join('')
748
847
  return `<div class="ds-msg ${esc(role)}"><div class="role">${esc(role === 'user' ? t('ds.role.me') : t('ds.role.dsh'))}</div>${text || '<span style="opacity:.6">…</span>'}</div>`
749
848
  }
@@ -767,10 +866,35 @@ function renderHistory() {
767
866
  box.innerHTML = items.map(eventHtml).join('') || `<div class="ds-empty">${t('ds.historyEmpty')}</div>`
768
867
  box.scrollTop = box.scrollHeight
769
868
  }
869
+ async function runSlashCommand(text) {
870
+ const clean = String(text || '').trim()
871
+ if (!clean.startsWith('/') || !state.current) return false
872
+ try {
873
+ const signal = typeof AbortSignal !== 'undefined' && typeof AbortSignal.timeout === 'function'
874
+ ? AbortSignal.timeout(20000)
875
+ : undefined
876
+ const res = await fetch(apiUrl('/remote/api/command'), {
877
+ method: 'POST',
878
+ headers: { 'content-type': 'application/json', authorization: 'Bearer ' + state.token, 'x-dsh-remote-client': 'web' },
879
+ body: JSON.stringify({ sessionId: state.current, line: clean }),
880
+ ...(signal ? { signal } : {})
881
+ })
882
+ if (res.status === 401) { toast(t('ds.toastAuth'), 'err'); return true }
883
+ if (!res.ok) return false
884
+ const data = await res.json().catch(() => null)
885
+ if (data?.ok === false) return true
886
+ return data?.ok === true && data.executed === true
887
+ } catch (e) {
888
+ console.error('slash command bridge failed', e)
889
+ }
890
+ return false
891
+ }
892
+
770
893
  async function sendMessage() {
771
894
  const input = $('composer')
772
895
  const text = input.value.trim()
773
896
  if (!text || !state.current) return
897
+ if (await runSlashCommand(text)) { input.value = ''; return }
774
898
  input.value = ''
775
899
  const v = await safeRpc('session.prompt', { sessionId: state.current, text }, '')
776
900
  if (v) toast(t('ds.toastSent'), 'ok')
@@ -993,6 +1117,21 @@ function showView(id) {
993
1117
  if (id === 'view-chat') { const s = state.byId.get(state.current); $('ds-title').textContent = s ? titleOf(s) : t('ds.sessions') }
994
1118
  else $('ds-title').textContent = t(titles[id])
995
1119
  if (id === 'view-files' && !state.fs.loaded) loadFs(null, true)
1120
+ if (id === 'view-settings') showSettingsHome()
1121
+ }
1122
+
1123
+ const SETTINGS_GROUPS = ['general', 'servers', 'notify', 'theme', 'about']
1124
+ function showSettingsHome() {
1125
+ const home = $('settings-home')
1126
+ if (!home) return
1127
+ home.classList.remove('hidden')
1128
+ for (const name of SETTINGS_GROUPS) $('settings-page-' + name)?.classList.add('hidden')
1129
+ }
1130
+ function showSettingsPage(name) {
1131
+ const home = $('settings-home')
1132
+ if (!home || !SETTINGS_GROUPS.includes(name)) return
1133
+ home.classList.add('hidden')
1134
+ for (const g of SETTINGS_GROUPS) $('settings-page-' + g)?.classList.toggle('hidden', g !== name)
996
1135
  }
997
1136
  function updateConn() {
998
1137
  const el = $('conn-badge')
@@ -1033,8 +1172,46 @@ function bindUi() {
1033
1172
  $('composer').addEventListener('keydown', (e) => {
1034
1173
  if (e.key === 'Enter' && !e.shiftKey && !e.isComposing) { e.preventDefault(); sendMessage() }
1035
1174
  })
1175
+ renderPresetMenuDesktop()
1176
+ $('btn-cmd').addEventListener('click', (e) => { e.stopPropagation(); toggleCmdMenuDesktop() })
1177
+ $('cmd-menu').addEventListener('click', (e) => {
1178
+ const item = e.target.closest('[data-ds-cmd]')
1179
+ if (item) {
1180
+ const input = $('composer')
1181
+ input.value = item.dataset.dsCmd + ' '
1182
+ input.focus()
1183
+ $('cmd-menu').classList.add('hidden')
1184
+ }
1185
+ })
1186
+ $('btn-preset').addEventListener('click', (e) => { e.stopPropagation(); togglePresetMenuDesktop() })
1187
+ $('preset-menu').addEventListener('click', (e) => {
1188
+ const item = e.target.closest('[data-ds-preset]')
1189
+ if (item) {
1190
+ const found = readPresets().find(x => x.id === item.dataset.dsPreset)
1191
+ if (found) {
1192
+ const input = $('composer')
1193
+ input.value = found.text
1194
+ input.focus()
1195
+ }
1196
+ $('preset-menu').classList.add('hidden')
1197
+ }
1198
+ })
1199
+ document.addEventListener('click', (e) => {
1200
+ if (!e.target.closest('#preset-menu') && !e.target.closest('#btn-preset')) $('preset-menu')?.classList.add('hidden')
1201
+ if (!e.target.closest('#cmd-menu') && !e.target.closest('#btn-cmd')) $('cmd-menu')?.classList.add('hidden')
1202
+ })
1036
1203
  $('btn-stats-top').addEventListener('click', toggleStatsDrawer)
1037
1204
  $('stats-drawer-close').addEventListener('click', toggleStatsDrawer)
1205
+ // 赞赏支持
1206
+ $('btn-donate').addEventListener('click', openDonateModal)
1207
+ $('btn-donate-about').addEventListener('click', openDonateModal)
1208
+ $('btn-donate-close').addEventListener('click', () => $('modal-donate').classList.add('hidden'))
1209
+ // 更新内容弹窗
1210
+ $('notes-close').addEventListener('click', closeNotesModal)
1211
+ $('notes-prev').addEventListener('click', () => scrollNotes(-1))
1212
+ $('notes-next').addEventListener('click', () => scrollNotes(1))
1213
+ $('notes-pages').addEventListener('scroll', updateNotesPage)
1214
+ $('modal-notes').addEventListener('click', (e) => { if (e.target === $('modal-notes')) closeNotesModal() })
1038
1215
  // 反馈
1039
1216
  $('btn-feedback').addEventListener('click', (e) => { e.stopPropagation(); toggleFeedbackMenu() })
1040
1217
  $('feedback-menu').addEventListener('click', (e) => {
@@ -1065,6 +1242,11 @@ function bindUi() {
1065
1242
  })
1066
1243
  $('stats-chart').addEventListener('mouseleave', hideTip)
1067
1244
 
1245
+ $('view-settings').addEventListener('click', (e) => {
1246
+ const group = e.target.closest('[data-settings-group]')
1247
+ if (group) { showSettingsPage(group.dataset.settingsGroup); return }
1248
+ if (e.target.closest('[data-settings-back]')) { showSettingsHome(); return }
1249
+ })
1068
1250
  $('btn-server-speed').addEventListener('click', () => selectFastestServer({ silent: false }))
1069
1251
  $('btn-server-add').addEventListener('click', addServer)
1070
1252
  $('btn-group-add').addEventListener('click', addGroup)
@@ -1105,6 +1287,7 @@ async function start() {
1105
1287
  $('token-desc').textContent = state.token ? '● ' + state.token.slice(0, 12) + '…' : t('ds.toastAuth')
1106
1288
  bindUi()
1107
1289
  updateConn()
1290
+ checkNotesOnStart()
1108
1291
  if (state.token) {
1109
1292
  if (state.servers.length) await selectFastestServer({ silent: true, reconnect: false })
1110
1293
  openStreams()
Binary file