dsh-remote-plugin 0.6.18 → 0.6.20

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.
@@ -4,7 +4,7 @@
4
4
  <meta charset="utf-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1">
6
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>
7
+ <script>/* 首帧前应用皮肤 */ (function(){try{var t=localStorage.getItem('dshTheme');if(t!=='default'&&t!=='dark'&&t!=='light'&&t!=='neutral'&&t!=='mono'){t=window.matchMedia('(prefers-color-scheme: light)').matches?'light':'default'}document.documentElement.setAttribute('data-theme',t)}catch(e){}})()</script>
8
8
  <link rel="stylesheet" href="../theme-vars.css">
9
9
  <link rel="stylesheet" href="desktop.css">
10
10
  </head>
@@ -50,7 +50,7 @@
50
50
  </div>
51
51
  <button class="ds-nav-item" data-view="view-overview"><span class="ds-nav-ico">◌</span><span data-i18n="ds.overview">主页</span></button>
52
52
  <button class="ds-nav-item" data-view="view-files"><span class="ds-nav-ico">⇅</span><span data-i18n="ds.files">文件传输</span></button>
53
- <button class="ds-nav-item" data-view="view-settings"><span class="ds-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><span data-i18n="ds.settings">设置</span></button>
53
+ <button class="ds-nav-item" data-view="view-settings"><span class="ds-nav-ico"><svg class="settings-gear" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.09a2 2 0 0 1 1 1.73v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.38a2 2 0 0 0-.73-2.73l-.15-.09a2 2 0 0 1-1-1.74v-.51a2 2 0 0 1 1-1.73l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2Z"/><circle cx="12" cy="12" r="3"/></svg></span><span data-i18n="ds.settings">设置</span></button>
54
54
  <div class="ds-feedback">
55
55
  <button id="btn-feedback" class="ds-feedback-btn" aria-haspopup="menu" aria-expanded="false" data-i18n-title="ds.feedback" data-i18n-aria="ds.feedback">
56
56
  <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>
@@ -81,10 +81,11 @@
81
81
  </div>
82
82
  </div>
83
83
  </aside>
84
+ <button id="ds-sidebar-backdrop" class="ds-sidebar-backdrop hidden" type="button" data-i18n-aria="ds.close" aria-label="关闭菜单"></button>
84
85
 
85
86
  <div class="ds-main">
86
87
  <header class="ds-topbar">
87
- <button id="btn-mobile-nav" class="ds-icon-btn" data-i18n-aria="ds.menu" data-i18n-title="ds.menu"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" aria-hidden="true"><path d="M4 6h16M4 12h16M4 18h16"/></svg></button>
88
+ <button id="btn-mobile-nav" class="ds-icon-btn" aria-controls="ds-sidebar" aria-expanded="false" data-i18n-aria="ds.menu" data-i18n-title="ds.menu"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" aria-hidden="true"><path d="M4 6h16M4 12h16M4 18h16"/></svg></button>
88
89
  <div class="ds-title" id="ds-title" data-i18n="ds.sessions">会话</div>
89
90
  <span id="ds-session-status" class="ds-session-status hidden"></span>
90
91
  <button id="btn-rename-session" class="ds-icon-btn hidden" data-i18n-title="ds.sessionRename" data-i18n-aria="ds.sessionRename">✎</button>
@@ -150,6 +151,7 @@
150
151
  <section id="view-chat" class="ds-view">
151
152
  <div id="session-cards" class="ds-session-cards"></div>
152
153
  <div id="queue-dock" class="ds-queue-dock hidden" aria-live="polite"></div>
154
+ <section id="session-pending" class="ds-session-pending hidden" aria-live="polite"></section>
153
155
  <div id="history" class="ds-history" aria-live="polite"></div>
154
156
  <div class="ds-composer">
155
157
  <div id="composer-status" class="ds-composer-status hidden" role="status" aria-live="polite"><span class="ds-composer-status-dot" aria-hidden="true"></span><span data-i18n="ds.composerRunning">运行中…</span></div>
@@ -202,22 +204,22 @@
202
204
  <div class="ds-section-label" data-i18n="ds.settings">设置</div>
203
205
  <div id="settings-home">
204
206
  <div class="ds-settings">
205
- <div class="ds-setting-row" data-settings-group="general" style="cursor:pointer">
207
+ <button type="button" class="ds-setting-row ds-setting-link" data-settings-group="general">
206
208
  <div><div class="ds-setting-name" data-i18n="ds.groupGeneral">通用</div><div class="ds-setting-desc" data-i18n="ds.groupGeneralDesc">工具调用、预设提示词</div></div>
207
209
  <span class="ds-setting-arrow" aria-hidden="true">›</span>
208
- </div>
209
- <div class="ds-setting-row" data-settings-group="servers" style="cursor:pointer">
210
+ </button>
211
+ <button type="button" class="ds-setting-row ds-setting-link" data-settings-group="servers">
210
212
  <div><div class="ds-setting-name" data-i18n="ds.groupServers">服务器</div><div class="ds-setting-desc" data-i18n="ds.groupServersDesc">服务器地址</div></div>
211
213
  <span class="ds-setting-arrow" aria-hidden="true">›</span>
212
- </div>
213
- <div class="ds-setting-row" data-settings-group="theme" style="cursor:pointer">
214
+ </button>
215
+ <button type="button" class="ds-setting-row ds-setting-link" data-settings-group="theme">
214
216
  <div><div class="ds-setting-name" data-i18n="ds.groupTheme">皮肤</div><div class="ds-setting-desc" data-i18n="ds.groupThemeDesc">配色主题</div></div>
215
217
  <span class="ds-setting-arrow" aria-hidden="true">›</span>
216
- </div>
217
- <div class="ds-setting-row" data-settings-group="about" style="cursor:pointer">
218
+ </button>
219
+ <button type="button" class="ds-setting-row ds-setting-link" data-settings-group="about">
218
220
  <div><div class="ds-setting-name" data-i18n="ds.groupAbout">关于</div><div class="ds-setting-desc" data-i18n="ds.groupAboutDesc">版本信息</div></div>
219
221
  <span class="ds-setting-arrow" aria-hidden="true">›</span>
220
- </div>
222
+ </button>
221
223
  </div>
222
224
  <div class="ds-settings">
223
225
  <div class="ds-setting-row">
@@ -231,7 +233,7 @@
231
233
  </div>
232
234
  </div>
233
235
 
234
- <div id="settings-page-general" class="ds-settings hidden">
236
+ <div id="settings-page-general" class="ds-settings hidden" data-motion-panel>
235
237
  <div class="ds-setting-row" data-settings-back style="cursor:pointer">
236
238
  <button class="ds-btn" data-i18n="ds.back">‹ 返回</button>
237
239
  <div class="ds-setting-name" data-i18n="ds.groupGeneral">通用</div>
@@ -242,7 +244,7 @@
242
244
  </div>
243
245
  </div>
244
246
 
245
- <div id="settings-page-servers" class="ds-settings hidden">
247
+ <div id="settings-page-servers" class="ds-settings hidden" data-motion-panel>
246
248
  <div class="ds-setting-row" data-settings-back style="cursor:pointer">
247
249
  <button class="ds-btn" data-i18n="ds.back">‹ 返回</button>
248
250
  <div class="ds-setting-name" data-i18n="ds.groupServers">服务器</div>
@@ -266,18 +268,18 @@
266
268
  </div>
267
269
  </div>
268
270
 
269
- <div id="settings-page-theme" class="ds-settings hidden">
271
+ <div id="settings-page-theme" class="ds-settings hidden" data-motion-panel>
270
272
  <div class="ds-setting-row" data-settings-back style="cursor:pointer">
271
273
  <button class="ds-btn" data-i18n="ds.back">‹ 返回</button>
272
274
  <div class="ds-setting-name" data-i18n="ds.groupTheme">皮肤</div>
273
275
  </div>
274
276
  <div class="ds-setting-row">
275
- <div><div class="ds-setting-name" data-i18n="ds.themeTitle">皮肤</div><div class="ds-setting-desc" data-i18n="ds.themeDesc">四套皮肤</div></div>
277
+ <div><div class="ds-setting-name" data-i18n="ds.themeTitle">皮肤</div><div class="ds-setting-desc" data-i18n="ds.themeDesc">五套皮肤</div></div>
276
278
  <button id="btn-theme" class="ds-btn">默认深空</button>
277
279
  </div>
278
280
  </div>
279
281
 
280
- <div id="settings-page-about" class="ds-settings hidden">
282
+ <div id="settings-page-about" class="ds-settings hidden" data-motion-panel>
281
283
  <div class="ds-setting-row" data-settings-back style="cursor:pointer">
282
284
  <button class="ds-btn" data-i18n="ds.back">‹ 返回</button>
283
285
  <div class="ds-setting-name" data-i18n="ds.groupAbout">关于</div>
@@ -475,7 +477,7 @@
475
477
  'ds.back': '‹ 返回', 'ds.emptyGroup': '暂无设置项',
476
478
  'ds.serversTitle': '服务器(分组)', 'ds.speedTest': '测速', 'ds.currentGroup': '当前组',
477
479
  'ds.addGroup': '+ 组', 'ds.addServer': '添加', 'ds.tokenTitle': '访问令牌', 'ds.copyToken': '复制',
478
- 'ds.themeTitle': '皮肤', 'ds.themeDesc': '默认深空 / 落日 / 易北爱乐厅 / 草原孤塔', 'ds.langTitle': '语言 / Language',
480
+ 'ds.themeTitle': '皮肤', 'ds.themeDesc': '默认深空 / 落日 / 易北爱乐厅 / 草原孤塔 / 黑曜白', 'ds.langTitle': '语言 / Language',
479
481
  'ds.feedbackTitle': '反馈渠道', 'ds.feedbackDesc': 'GitHub / Gitee / B站:反馈 bug、提建议、唠嗑',
480
482
  'ds.feedback': '反馈', 'ds.feedbackGithubDesc': '反馈 bug / 提建议', 'ds.feedbackGiteeDesc': '国内镜像,无需代理',
481
483
  'ds.feedbackBiliDesc': 'UP 动态页交流', 'ds.feedbackCopyLink': '复制项目链接', 'ds.feedbackCopyDesc': '手动分享给朋友',
@@ -526,7 +528,7 @@
526
528
  'ds.groupsPromptRename': '重命名组「{group}」:', 'ds.groupsRenamed': '组已重命名',
527
529
  'ds.groupsCannotDeleteDefault': '默认组不可删除',
528
530
  'ds.groupsConfirmDelete': '删除组「{group}」?组内服务器将归入默认组。', 'ds.groupsDeleted': '组已删除',
529
- 'ds.theme.default': '默认深空', 'ds.theme.dark': '落日', 'ds.theme.light': '易北爱乐厅', 'ds.theme.neutral': '草原孤塔',
531
+ 'ds.theme.default': '默认深空', 'ds.theme.dark': '落日', 'ds.theme.light': '易北爱乐厅', 'ds.theme.neutral': '草原孤塔', 'ds.theme.mono': '黑曜白',
530
532
  'ds.role.me': '我', 'ds.role.dsh': 'DSH', 'ds.toolDefault': '工具',
531
533
  'ds.eventSystemReminder': '系统提示',
532
534
  'ds.questionOptions': '选项', 'ds.questionCustom': '自定义回答',
@@ -540,6 +542,7 @@
540
542
  'presets.nameEmpty': '名称不能为空', 'presets.nameTooLong': '名称不能超过 20 字', 'presets.textTooLong': '内容不能超过 2000 字', 'presets.limit': '最多 20 条预设', 'presets.added': '预设已添加',
541
543
  'presets.confirmDelete': '删除预设「{name}」?', 'presets.saved': '预设已保存', 'presets.deleted': '预设已删除',
542
544
  'goal.title': '目标', 'goal.pause': '暂停', 'goal.resume': '继续', 'goal.complete': '完成', 'goal.edit': '改目标', 'goal.clear': '清除',
545
+ 'goal.collapse': '收起目标到侧边', 'goal.collapseShort': '收起', 'goal.expand': '展开目标', 'goal.collapsedHint': '仍在运行',
543
546
  'goal.none': '当前会话没有目标', 'goal.confirmClear': '清除当前目标?(不会删除会话)', 'goal.confirmComplete': '将目标标记为完成?',
544
547
  'goal.actionFailed': '目标操作失败', 'goal.actionSubmitted': '目标操作已提交',
545
548
  'goal.cannotEmpty': '目标不能为空', 'goal.updateFailed': '更新失败', 'goal.updated': '目标已更新', 'goal.editPrompt': '修改目标:',
@@ -577,7 +580,7 @@
577
580
  'ds.back': '‹ Back', 'ds.emptyGroup': 'No settings here',
578
581
  'ds.serversTitle': 'Servers (groups)', 'ds.speedTest': 'Test', 'ds.currentGroup': 'Current group',
579
582
  'ds.addGroup': '+ Group', 'ds.addServer': 'Add', 'ds.tokenTitle': 'Access token', 'ds.copyToken': 'Copy',
580
- 'ds.themeTitle': 'Theme', 'ds.themeDesc': 'Deep Space / Sunset / Elbphilharmonie / Prairie Tower', 'ds.langTitle': 'Language',
583
+ 'ds.themeTitle': 'Theme', 'ds.themeDesc': 'Deep Space / Sunset / Elbphilharmonie / Prairie Tower / Monochrome', 'ds.langTitle': 'Language',
581
584
  'ds.feedbackTitle': 'Feedback', 'ds.feedbackDesc': 'GitHub / Gitee / Bilibili: report bugs, suggest features, or just chat',
582
585
  'ds.feedback': 'Feedback', 'ds.feedbackGithubDesc': 'Report bugs · suggest features', 'ds.feedbackGiteeDesc': 'Mirror in China, no proxy needed',
583
586
  'ds.feedbackBiliDesc': 'Chat on the UP\'s Bilibili page', 'ds.feedbackCopyLink': 'Copy project link', 'ds.feedbackCopyDesc': 'Share it manually',
@@ -628,7 +631,7 @@
628
631
  'ds.groupsPromptRename': 'Rename group "{group}":', 'ds.groupsRenamed': 'Group renamed',
629
632
  'ds.groupsCannotDeleteDefault': 'The default group cannot be deleted',
630
633
  'ds.groupsConfirmDelete': 'Delete group "{group}"? Its servers move to the default group.', 'ds.groupsDeleted': 'Group deleted',
631
- 'ds.theme.default': 'Deep Space', 'ds.theme.dark': 'Sunset', 'ds.theme.light': 'Elbphilharmonie', 'ds.theme.neutral': 'Prairie Tower',
634
+ 'ds.theme.default': 'Deep Space', 'ds.theme.dark': 'Sunset', 'ds.theme.light': 'Elbphilharmonie', 'ds.theme.neutral': 'Prairie Tower', 'ds.theme.mono': 'Monochrome',
632
635
  'ds.role.me': 'Me', 'ds.role.dsh': 'DSH', 'ds.toolDefault': 'tool',
633
636
  'ds.eventSystemReminder': 'System reminder',
634
637
  'ds.questionOptions': 'Options', 'ds.questionCustom': 'Custom answer',
@@ -642,6 +645,7 @@
642
645
  'presets.nameEmpty': 'Name cannot be empty', 'presets.nameTooLong': 'Name must be ≤20 characters', 'presets.textTooLong': 'Content must be ≤2000 characters', 'presets.limit': 'At most 20 presets', 'presets.added': 'Preset added',
643
646
  'presets.confirmDelete': 'Delete preset "{name}"?', 'presets.saved': 'Preset saved', 'presets.deleted': 'Preset deleted',
644
647
  'goal.title': 'Goal', 'goal.pause': 'Pause', 'goal.resume': 'Resume', 'goal.complete': 'Complete', 'goal.edit': 'Edit goal', 'goal.clear': 'Clear',
648
+ 'goal.collapse': 'Collapse goal to the side', 'goal.collapseShort': 'Collapse', 'goal.expand': 'Expand goal', 'goal.collapsedHint': 'Still running',
645
649
  'goal.none': 'This session has no goal', 'goal.confirmClear': 'Clear the current goal? (the session stays)', 'goal.confirmComplete': 'Mark the goal as completed?',
646
650
  'goal.actionFailed': 'Goal action failed', 'goal.actionSubmitted': 'Goal action submitted',
647
651
  'goal.cannotEmpty': 'Goal cannot be empty', 'goal.updateFailed': 'Update failed', 'goal.updated': 'Goal updated', 'goal.editPrompt': 'Edit goal:',
@@ -27,10 +27,11 @@ const CAP = window.Capacitor || null
27
27
 
28
28
  /* ---------------- 皮肤 ---------------- */
29
29
  const THEME_META = [
30
- { id: 'default', sw: ['#0B0E1A', '#151B33', '#5B8CFF'] },
31
- { id: 'dark', sw: ['#05348B', '#0D438F', '#F9A647'] },
32
- { id: 'light', sw: ['#EFEEEC', '#FAF8F5', '#E6BC7B'] },
33
- { id: 'neutral', sw: ['#DDD4B8', '#585818', '#832D15'] }
30
+ { id: 'default', sw: ['#0D1117', '#21262D', '#58A6FF'] },
31
+ { id: 'dark', sw: ['#161316', '#302323', '#FFB86B'] },
32
+ { id: 'light', sw: ['#F5F7F8', '#FFFFFF', '#176B87'] },
33
+ { id: 'neutral', sw: ['#EEF1E8', '#FAFBF6', '#47643C'] },
34
+ { id: 'mono', sw: ['#050505', '#333333', '#F5F5F5'] }
34
35
  ]
35
36
  function themeGet() {
36
37
  let v = LS.get('dshTheme', '')
@@ -1381,6 +1382,24 @@ function goalOf(s) {
1381
1382
  if (!p) return null
1382
1383
  return p.goal && typeof p.goal === 'object' ? p.goal : p
1383
1384
  }
1385
+ const COLLAPSED_GOALS_KEY = 'dshCollapsedGoalsV1'
1386
+ function collapsedGoals() {
1387
+ try {
1388
+ const value = JSON.parse(LS.get(COLLAPSED_GOALS_KEY, '[]'))
1389
+ return Array.isArray(value) ? value.filter(item => item && typeof item.sessionId === 'string' && typeof item.goalId === 'string') : []
1390
+ } catch { return [] }
1391
+ }
1392
+ function goalDisplayId(goal) { return String(goal?.id || '__current__') }
1393
+ function isGoalCollapsed(sessionId, goal) {
1394
+ const goalId = goalDisplayId(goal)
1395
+ return collapsedGoals().some(item => item.sessionId === sessionId && item.goalId === goalId)
1396
+ }
1397
+ function setGoalCollapsed(sessionId, goal, collapsed) {
1398
+ const goalId = goalDisplayId(goal)
1399
+ const next = collapsedGoals().filter(item => item.sessionId !== sessionId)
1400
+ if (collapsed) next.push({ sessionId, goalId })
1401
+ LS.set(COLLAPSED_GOALS_KEY, JSON.stringify(next.slice(-100)))
1402
+ }
1384
1403
  function onSessionEvent(sessionId, event) {
1385
1404
  if (event?.type === 'turn/start' || event?.type === 'turn/end') {
1386
1405
  noteSessionTurnTime(sessionId, event)
@@ -1547,6 +1566,7 @@ async function openSession(id) {
1547
1566
  renderSessions()
1548
1567
  renderSessionCards()
1549
1568
  renderQueue()
1569
+ renderSessionPendingDesktop()
1550
1570
  updateComposerStatus()
1551
1571
  await loadHistory()
1552
1572
  }
@@ -1557,6 +1577,7 @@ function closeSession() {
1557
1577
  const cards = $('session-cards')
1558
1578
  if (cards) cards.innerHTML = ''
1559
1579
  renderQueue()
1580
+ renderSessionPendingDesktop()
1560
1581
  updateComposerStatus()
1561
1582
  updateSessionActions()
1562
1583
  showView('view-sessions')
@@ -1738,15 +1759,23 @@ async function renderSessionCards() {
1738
1759
  const todos = proj(s, 'todos')
1739
1760
  let html = ''
1740
1761
  if (goal && !isGoalTerminal(goal)) {
1741
- html += `<div class="ds-card ds-goal-card"><div class="ds-card-title">${t('goal.title')}</div>
1742
- <div class="ds-goal-obj">${esc(goal.objective || '')}</div>
1743
- <div class="ds-goal-phase">phase: ${esc(goal.phase || '?')} · revision ${goal.revision ?? '?'}</div>
1744
- <div class="ds-goal-actions">
1745
- ${goal.phase === 'active' ? `<button class="ds-mini-btn" data-goal="pause">${t('goal.pause')}</button>` : `<button class="ds-mini-btn" data-goal="resume">${t('goal.resume')}</button>`}
1746
- <button class="ds-mini-btn" data-goal="complete">${t('goal.complete')}</button>
1747
- <button class="ds-mini-btn" data-goal="edit">${t('goal.edit')}</button>
1748
- <button class="ds-mini-btn" data-goal="clear">${t('goal.clear')}</button>
1749
- </div></div>`
1762
+ const collapsed = isGoalCollapsed(sessionId, goal)
1763
+ html += `<div class="ds-goal-disclosure${collapsed ? ' is-collapsed' : ''}">
1764
+ <div id="goal-panel-desktop" class="ds-card ds-goal-card${collapsed ? ' hidden' : ''}">
1765
+ <div class="ds-goal-card-head"><div class="ds-card-title">${t('goal.title')}</div>
1766
+ <button type="button" class="ds-goal-collapse-btn" data-goal-collapse="1" aria-expanded="true" aria-controls="goal-panel-desktop" title="${esc(t('goal.collapse'))}"><span aria-hidden="true">›</span>${t('goal.collapseShort')}</button>
1767
+ </div>
1768
+ <div class="ds-goal-obj">${esc(goal.objective || '')}</div>
1769
+ <div class="ds-goal-phase">phase: ${esc(goal.phase || '?')} · revision ${goal.revision ?? '?'}</div>
1770
+ <div class="ds-goal-actions">
1771
+ ${goal.phase === 'active' ? `<button class="ds-mini-btn" data-goal="pause">${t('goal.pause')}</button>` : `<button class="ds-mini-btn" data-goal="resume">${t('goal.resume')}</button>`}
1772
+ <button class="ds-mini-btn" data-goal="complete">${t('goal.complete')}</button>
1773
+ <button class="ds-mini-btn" data-goal="edit">${t('goal.edit')}</button>
1774
+ <button class="ds-mini-btn" data-goal="clear">${t('goal.clear')}</button>
1775
+ </div>
1776
+ </div>
1777
+ <button type="button" class="ds-goal-side-tab${collapsed ? '' : ' hidden'}" data-goal-collapse="0" aria-expanded="false" aria-controls="goal-panel-desktop" aria-label="${esc(t('goal.expand'))}" title="${esc(t('goal.expand'))}"><span aria-hidden="true">‹</span><span>${t('goal.title')}</span><small>${t('goal.collapsedHint')}</small></button>
1778
+ </div>`
1750
1779
  }
1751
1780
  if (todos?.items?.length) {
1752
1781
  html += `<div class="ds-card"><div class="ds-card-title">${t('todos.title')}</div>${todos.items.map(item =>
@@ -1756,6 +1785,15 @@ async function renderSessionCards() {
1756
1785
  box.innerHTML = html
1757
1786
  box.querySelectorAll('[data-goal]').forEach(btn =>
1758
1787
  btn.addEventListener('click', () => goalAction(btn.dataset.goal)))
1788
+ box.querySelectorAll('[data-goal-collapse]').forEach(btn =>
1789
+ btn.addEventListener('click', () => {
1790
+ const collapse = btn.dataset.goalCollapse === '1'
1791
+ const currentGoal = goalOf(state.byId.get(state.current))
1792
+ if (!currentGoal) return
1793
+ setGoalCollapsed(state.current, currentGoal, collapse)
1794
+ void renderSessionCards()
1795
+ requestAnimationFrame(() => box.querySelector(`[data-goal-collapse="${collapse ? '0' : '1'}"]`)?.focus())
1796
+ }))
1759
1797
 
1760
1798
  const sub = await safeRpc('subagent.list', { parentSessionId: sessionId }, '')
1761
1799
  if (renderGeneration !== sessionCardsRenderGeneration || state.current !== sessionId) return
@@ -1974,6 +2012,39 @@ function serverLabel() {
1974
2012
  const cur = currentServerEntry()
1975
2013
  return cur ? (cur.note || cur.url) : (state.server || location.host)
1976
2014
  }
2015
+ function desktopApprovalDetail(a) {
2016
+ const lines = []
2017
+ if (a?.reason) lines.push(String(a.reason))
2018
+ if (a?.arguments !== undefined) lines.push(safeJson(a.arguments))
2019
+ if (a?.callId) lines.push(`callId: ${a.callId}`)
2020
+ return lines.join('\n') || t('ds.approvalReason', { reason: '' })
2021
+ }
2022
+ function renderSessionPendingDesktop() {
2023
+ const box = $('session-pending')
2024
+ if (!box) return
2025
+ const sessionId = state.current
2026
+ const items = [
2027
+ ...state.approvals.filter(a => a.sessionId === sessionId).map(a => ({ kind: 'approval', item: a })),
2028
+ ...state.questions.filter(q => q.sessionId === sessionId).map(q => ({ kind: 'question', item: q }))
2029
+ ]
2030
+ box.classList.toggle('hidden', !sessionId || !items.length)
2031
+ if (!sessionId || !items.length) { box.innerHTML = ''; return }
2032
+ box.innerHTML = `<div class="ds-session-pending-head"><span>${esc(t('ds.attention'))}</span><span>${items.length}</span></div><div class="ds-session-pending-list">${items.map(({ kind, item }) => {
2033
+ if (kind === 'approval') return `<article class="ds-notif-card" data-session-approval="${esc(item.approvalId)}">
2034
+ <div class="ds-notif-head">🔐 ${t('ds.approvalTitle')}</div>
2035
+ <div class="ds-notif-title">${esc(item.toolName || t('ds.toolDefault'))}</div>
2036
+ <div class="ds-notif-body">${esc(desktopApprovalDetail(item))}</div>
2037
+ <div class="ds-notif-actions"><button type="button" class="ds-btn allow" data-session-approve="1">${t('ds.allow')}</button><button type="button" class="ds-btn reject" data-session-approve="0">${t('ds.reject')}</button></div>
2038
+ </article>`
2039
+ return `<article class="ds-notif-card question" data-session-question="${esc(item.rpcId)}">
2040
+ <div class="ds-notif-head">❓ ${t('ds.questionNotify')}</div>
2041
+ <div class="ds-notif-title">${esc((item.questions || []).map(question => question.question).filter(Boolean).join(' / '))}</div>
2042
+ <div class="ds-notif-actions"><button type="button" class="ds-btn" data-session-answer>${t('ds.submit')}</button></div>
2043
+ </article>`
2044
+ }).join('')}</div>`
2045
+ box.querySelectorAll('[data-session-approve]').forEach(button => button.addEventListener('click', () => approveApproval(button.closest('[data-session-approval]')?.dataset.sessionApproval || '', button.dataset.sessionApprove === '1')))
2046
+ box.querySelectorAll('[data-session-answer]').forEach(button => button.addEventListener('click', () => openQuestionModal(state.questions.find(q => q.rpcId === button.closest('[data-session-question]')?.dataset.sessionQuestion))))
2047
+ }
1977
2048
  function renderNotifStack() {
1978
2049
  const stack = $('notif-stack')
1979
2050
  const items = [
@@ -1983,7 +2054,7 @@ function renderNotifStack() {
1983
2054
  stack.innerHTML = items.map(it => {
1984
2055
  if (it.kind === 'approval') {
1985
2056
  const a = it.a
1986
- const reason = a.reason || a.arguments ? safeJson(a.arguments ?? a.reason ?? '') : ''
2057
+ const reason = desktopApprovalDetail(a)
1987
2058
  return `<div class="ds-notif-card" data-approval="${esc(a.approvalId)}" tabindex="0">
1988
2059
  <div class="ds-notif-head">🔐 ${t('ds.approvalTitle')} · ${esc(serverLabel())} · ${fmtTime(a.time || Date.now())}</div>
1989
2060
  <div class="ds-notif-title">${esc(a.toolName || t('ds.toolDefault'))}</div>
@@ -2014,6 +2085,7 @@ function renderNotifStack() {
2014
2085
  stack.querySelectorAll('.ds-notif-card').forEach(card => card.addEventListener('keydown', (e) => {
2015
2086
  if (e.key === 'Escape') toast(t('ds.ignored'), 'ok')
2016
2087
  }))
2088
+ renderSessionPendingDesktop()
2017
2089
  renderOverviewDesktop()
2018
2090
  }
2019
2091
  async function approveApproval(id, allow) {
@@ -2672,7 +2744,7 @@ function renderOverviewDesktop() {
2672
2744
  $('ds-overview-attention-list').innerHTML = pending.length ? pending.slice(0, 4).map(({ kind, item }) => {
2673
2745
  const title = titleOf(state.byId.get(item.sessionId))
2674
2746
  if (kind === 'approval') return `<div class="ds-overview-attention-item" data-ds-overview-approval="${esc(item.approvalId)}">
2675
- <span class="ds-overview-mark">⌁</span><span class="ds-overview-copy"><span class="ds-overview-item-title">${esc(item.toolName || t('ds.toolDefault'))}</span><span class="ds-overview-item-desc">${esc(item.reason || t('ds.approvalReason', { reason: '' }))} · ${esc(title)}</span></span>
2747
+ <span class="ds-overview-mark">⌁</span><span class="ds-overview-copy"><span class="ds-overview-item-title">${esc(item.toolName || t('ds.toolDefault'))}</span><span class="ds-overview-item-desc">${esc(desktopApprovalDetail(item))} · ${esc(title)}</span></span>
2676
2748
  <span class="ds-overview-actions"><button class="ds-btn allow" data-ds-overview-approve="1">${t('ds.allow')}</button><button class="ds-btn reject" data-ds-overview-approve="0">${t('ds.reject')}</button></span>
2677
2749
  </div>`
2678
2750
  return `<button type="button" class="ds-overview-attention-item question" data-ds-overview-question="${esc(item.rpcId)}">
@@ -2719,10 +2791,25 @@ function renderOverviewDesktop() {
2719
2791
  $('ds-overview-session-list').querySelectorAll('[data-ds-overview-session]').forEach(btn => btn.addEventListener('click', () => openSession(btn.dataset.dsOverviewSession)))
2720
2792
  }
2721
2793
 
2794
+ const narrowSidebarQuery = window.matchMedia('(max-width: 1023px)')
2795
+ function setMobileSidebar(open) {
2796
+ const sidebar = $('ds-sidebar')
2797
+ const backdrop = $('ds-sidebar-backdrop')
2798
+ const trigger = $('btn-mobile-nav')
2799
+ if (!sidebar || !backdrop || !trigger) return
2800
+ const visible = !!open && narrowSidebarQuery.matches
2801
+ sidebar.classList.toggle('mobile-open', visible)
2802
+ backdrop.classList.toggle('hidden', !visible)
2803
+ trigger.setAttribute('aria-expanded', String(visible))
2804
+ sidebar.inert = narrowSidebarQuery.matches && !visible
2805
+ $('ds-app')?.classList.toggle('mobile-nav-open', visible)
2806
+ }
2807
+
2722
2808
  function showView(id) {
2723
2809
  state.view = id
2724
2810
  for (const v of ['view-overview', 'view-sessions', 'view-chat', 'view-files', 'view-settings']) $(v).classList.toggle('hidden', v !== id)
2725
2811
  document.querySelectorAll('.ds-nav-item').forEach(b => b.classList.toggle('active', b.dataset.view === id))
2812
+ if (narrowSidebarQuery.matches) setMobileSidebar(false)
2726
2813
  window.DshMotion?.view($(id))
2727
2814
  const titles = { 'view-overview': 'ds.overview', 'view-sessions': 'ds.sessions', 'view-chat': 'ds.sessions', 'view-files': 'ds.files', 'view-settings': 'ds.settings' }
2728
2815
  if (id === 'view-chat') { const s = state.byId.get(state.current); $('ds-title').textContent = s ? titleOf(s) : t('ds.sessions') }
@@ -2825,9 +2912,14 @@ function bindUi() {
2825
2912
  LS.set('sessionSort', state.sessionSort)
2826
2913
  renderSessions()
2827
2914
  })
2828
- $('btn-mobile-nav').addEventListener('click', () => {
2829
- const list = $('mobile-session-list')
2830
- list.style.display = list.style.display === 'none' ? 'flex' : 'none'
2915
+ $('btn-mobile-nav').addEventListener('click', () => setMobileSidebar(!$('ds-sidebar').classList.contains('mobile-open')))
2916
+ $('ds-sidebar-backdrop').addEventListener('click', () => setMobileSidebar(false))
2917
+ narrowSidebarQuery.addEventListener?.('change', () => setMobileSidebar(false))
2918
+ document.addEventListener('keydown', (event) => {
2919
+ if (event.key === 'Escape' && $('ds-sidebar').classList.contains('mobile-open')) {
2920
+ setMobileSidebar(false)
2921
+ $('btn-mobile-nav').focus()
2922
+ }
2831
2923
  })
2832
2924
  document.querySelectorAll('.ds-nav-item').forEach(b => b.addEventListener('click', () => showView(b.dataset.view)))
2833
2925
  $('ds-overview-refresh').addEventListener('click', async () => {
package/public/index.html CHANGED
@@ -3,12 +3,12 @@
3
3
  <head>
4
4
  <meta charset="utf-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
6
- <meta name="theme-color" content="#05348B">
6
+ <meta name="theme-color" content="#0D1117">
7
7
  <meta name="mobile-web-app-capable" content="yes">
8
8
  <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
9
9
  <title>DSH Remote</title>
10
10
  <script>/* 统一入口: 桌面浏览器(宽屏且 UA 非移动)访问根地址自动切换到桌面端, 手机/窄窗口继续 App 界面 */ (function(){try{var proto=location.protocol;var ua=navigator.userAgent||'';var mobile=/Android|iPhone|iPad|iPod|Mobile|Windows Phone/i.test(ua);if((proto==='http:'||proto==='https:')&&!mobile&&window.innerWidth>=1024){var p=location.pathname;if(p==='/'||/\/index\.html$/.test(p)){location.replace('desktop/desktop.html'+location.search)}}}catch(e){}})()</script>
11
- <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>
11
+ <script>/* 首帧前应用皮肤, 避免闪白/闪黑 */ (function(){try{var t=localStorage.getItem('dshTheme');if(t!=='default'&&t!=='dark'&&t!=='light'&&t!=='neutral'&&t!=='mono'){t=window.matchMedia('(prefers-color-scheme: light)').matches?'light':'default'}document.documentElement.setAttribute('data-theme',t)}catch(e){}})()</script>
12
12
  <link rel="manifest" href="manifest.webmanifest">
13
13
  <link rel="icon" href="icon.svg" type="image/svg+xml">
14
14
  <link rel="stylesheet" href="styles.css">
@@ -104,6 +104,8 @@
104
104
 
105
105
  <div id="queue-dock" class="queue-dock hidden" aria-live="polite"></div>
106
106
 
107
+ <section id="session-pending" class="session-pending hidden" aria-live="polite"></section>
108
+
107
109
  <div class="section-head small">
108
110
  <span data-i18n="session.history">对话</span>
109
111
  <span id="history-hint" class="muted"></span>
@@ -276,27 +278,27 @@
276
278
  <section id="view-settings" class="view hidden">
277
279
  <div id="settings-home">
278
280
  <div class="settings-group">
279
- <div class="setting-row" data-settings-group="general" style="cursor:pointer">
281
+ <button type="button" class="setting-row setting-link" data-settings-group="general">
280
282
  <div><div class="setting-name" data-i18n="settings.groupGeneral">通用</div><div class="setting-desc" data-i18n="settings.groupGeneralDesc">显示工具调用、预设提示词</div></div>
281
- </div>
282
- <div class="setting-row" data-settings-group="model" style="cursor:pointer">
283
+ </button>
284
+ <button type="button" class="setting-row setting-link" data-settings-group="model">
283
285
  <div><div class="setting-name" data-i18n="settings.groupModel">模型</div><div class="setting-desc" data-i18n="settings.groupModelDesc">提供方、API 密钥和模型目录</div></div>
284
- </div>
285
- <div class="setting-row" data-settings-group="tests" style="cursor:pointer">
286
+ </button>
287
+ <button type="button" class="setting-row setting-link" data-settings-group="tests">
286
288
  <div><div class="setting-name" data-i18n="settings.groupTests">功能测试</div><div class="setting-desc" data-i18n="settings.groupTestsDesc">验证 Android 和 DSH 原生能力</div></div>
287
- </div>
288
- <div class="setting-row" data-settings-group="servers" style="cursor:pointer">
289
+ </button>
290
+ <button type="button" class="setting-row setting-link" data-settings-group="servers">
289
291
  <div><div class="setting-name" data-i18n="settings.groupServers">服务器</div><div class="setting-desc" data-i18n="settings.groupServersDesc">服务器地址、扫码连接</div></div>
290
- </div>
291
- <div class="setting-row" data-settings-group="notify" style="cursor:pointer">
292
+ </button>
293
+ <button type="button" class="setting-row setting-link" data-settings-group="notify">
292
294
  <div><div class="setting-name" data-i18n="settings.groupNotify">通知</div><div class="setting-desc" data-i18n="settings.groupNotifyDesc">通知、提醒、后台轮询</div></div>
293
- </div>
294
- <div class="setting-row" data-settings-group="theme" style="cursor:pointer">
295
+ </button>
296
+ <button type="button" class="setting-row setting-link" data-settings-group="theme">
295
297
  <div><div class="setting-name" data-i18n="settings.groupTheme">皮肤</div><div class="setting-desc" data-i18n="settings.groupThemeDesc">配色主题</div></div>
296
- </div>
297
- <div class="setting-row" data-settings-group="about" style="cursor:pointer">
298
+ </button>
299
+ <button type="button" class="setting-row setting-link" data-settings-group="about">
298
300
  <div><div class="setting-name" data-i18n="settings.groupAbout">关于</div><div class="setting-desc" data-i18n="settings.groupAboutDesc">DSH 状态、检查更新</div></div>
299
- </div>
301
+ </button>
300
302
  </div>
301
303
  <div class="settings-group">
302
304
  <div class="setting-row">
@@ -336,7 +338,7 @@
336
338
  <p class="about" data-i18n="settings.about">DSH Remote · 局域网 / Tailscale 均可访问<br>仅作为 DSH 本机 3080 端口的带认证代理</p>
337
339
  </div>
338
340
 
339
- <div id="settings-page-general" class="settings-subpage hidden">
341
+ <div id="settings-page-general" class="settings-subpage hidden" data-motion-panel>
340
342
  <div class="settings-group">
341
343
  <div class="setting-row" data-settings-back style="cursor:pointer">
342
344
  <button class="mini-btn" data-i18n="settings.back">‹ 返回</button>
@@ -363,7 +365,7 @@
363
365
  </div>
364
366
  </div>
365
367
 
366
- <div id="settings-page-model" class="settings-subpage hidden">
368
+ <div id="settings-page-model" class="settings-subpage hidden" data-motion-panel>
367
369
  <div class="settings-group">
368
370
  <div class="setting-row" data-settings-back style="cursor:pointer">
369
371
  <button class="mini-btn" data-i18n="settings.back">‹ 返回</button>
@@ -386,7 +388,7 @@
386
388
  </div>
387
389
  </div>
388
390
 
389
- <div id="settings-page-tests" class="settings-subpage hidden">
391
+ <div id="settings-page-tests" class="settings-subpage hidden" data-motion-panel>
390
392
  <div class="settings-group">
391
393
  <div class="setting-row" data-settings-back style="cursor:pointer">
392
394
  <button class="mini-btn" data-i18n="settings.back">‹ 返回</button>
@@ -415,7 +417,7 @@
415
417
  </div>
416
418
  </div>
417
419
 
418
- <div id="settings-page-servers" class="settings-subpage hidden">
420
+ <div id="settings-page-servers" class="settings-subpage hidden" data-motion-panel>
419
421
  <div class="settings-group">
420
422
  <div class="setting-row" data-settings-back style="cursor:pointer">
421
423
  <button class="mini-btn" data-i18n="settings.back">‹ 返回</button>
@@ -452,7 +454,7 @@
452
454
  </div>
453
455
  </div>
454
456
 
455
- <div id="settings-page-notify" class="settings-subpage hidden">
457
+ <div id="settings-page-notify" class="settings-subpage hidden" data-motion-panel>
456
458
  <div class="settings-group">
457
459
  <div class="setting-row" data-settings-back style="cursor:pointer">
458
460
  <button class="mini-btn" data-i18n="settings.back">‹ 返回</button>
@@ -496,7 +498,7 @@
496
498
  </div>
497
499
  </div>
498
500
 
499
- <div id="settings-page-theme" class="settings-subpage hidden">
501
+ <div id="settings-page-theme" class="settings-subpage hidden" data-motion-panel>
500
502
  <div class="settings-group">
501
503
  <div class="setting-row" data-settings-back style="cursor:pointer">
502
504
  <button class="mini-btn" data-i18n="settings.back">‹ 返回</button>
@@ -505,13 +507,13 @@
505
507
  </div>
506
508
  <div class="settings-group">
507
509
  <div class="setting-row">
508
- <div><div class="setting-name" data-i18n="settings.themeTitle">皮肤</div><div class="setting-desc" data-i18n="settings.themeDesc">默认深空 / 落日 / 易北爱乐厅 / 草原孤塔</div></div>
510
+ <div><div class="setting-name" data-i18n="settings.themeTitle">皮肤</div><div class="setting-desc" data-i18n="settings.themeDesc">默认深空 / 落日 / 易北爱乐厅 / 草原孤塔 / 黑曜白</div></div>
509
511
  <button id="btn-theme" class="mini-btn" style="min-width:104px">默认深空</button>
510
512
  </div>
511
513
  </div>
512
514
  </div>
513
515
 
514
- <div id="settings-page-about" class="settings-subpage hidden">
516
+ <div id="settings-page-about" class="settings-subpage hidden" data-motion-panel>
515
517
  <div class="settings-group">
516
518
  <div class="setting-row" data-settings-back style="cursor:pointer">
517
519
  <button class="mini-btn" data-i18n="settings.back">‹ 返回</button>
@@ -607,7 +609,7 @@
607
609
  <span data-i18n="nav.stats">统计</span>
608
610
  </button>
609
611
  <button data-view="view-settings" class="nav-btn">
610
- <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>
612
+ <span class="nav-ico"><svg class="settings-gear" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.09a2 2 0 0 1 1 1.73v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.38a2 2 0 0 0-.73-2.73l-.15-.09a2 2 0 0 1-1-1.74v-.51a2 2 0 0 1 1-1.73l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2Z"/><circle cx="12" cy="12" r="3"/></svg></span>
611
613
  <span data-i18n="nav.settings">设置</span>
612
614
  </button>
613
615
  </nav>
@@ -975,6 +977,7 @@
975
977
  'stats.llmTime': '模型耗时', 'stats.minutes': ' 分钟', 'stats.outputCache': '输出 / 缓存读',
976
978
  'stats.ctxPressure': '上下文压力', 'stats.permission': '权限', 'stats.empty': '暂无统计数据',
977
979
  'goal.title': '目标', 'goal.pause': '暂停', 'goal.resume': '继续', 'goal.complete': '完成', 'goal.edit': '改目标', 'goal.clear': '清除',
980
+ 'goal.collapse': '收起目标到侧边', 'goal.collapseShort': '收起', 'goal.expand': '展开目标', 'goal.collapsedHint': '仍在运行',
978
981
  'goal.none': '当前会话没有目标', 'goal.confirmClear': '清除当前目标?(不会删除会话)', 'goal.confirmComplete': '将目标标记为完成?',
979
982
  'goal.actionFailed': '目标操作失败', 'goal.actionSubmitted': '目标操作已提交',
980
983
  'goal.cannotEmpty': '目标不能为空', 'goal.updateFailed': '更新失败', 'goal.updated': '目标已更新',
@@ -1087,8 +1090,8 @@
1087
1090
  'settings.toolsTitle': '显示工具调用', 'settings.toolsDesc': '关闭后隐藏 🔧/📦 工具调用与结果',
1088
1091
  'settings.mobileEnterTitle': '手机回车行为', 'settings.mobileEnterDesc': '选择手机端按 Enter 时换行或直接发送', 'settings.mobileEnterNewline': '回车换行', 'settings.mobileEnterSend': '回车发送',
1089
1092
  'settings.langTitle': '语言 / Language', 'settings.langDesc': '界面语言,跟随系统或手动切换',
1090
- 'settings.themeTitle': '皮肤', 'settings.themeDesc': '默认深空 / 落日 / 易北爱乐厅 / 草原孤塔',
1091
- 'theme.default': '默认深空', 'theme.dark': '落日', 'theme.light': '易北爱乐厅', 'theme.neutral': '草原孤塔',
1093
+ 'settings.themeTitle': '皮肤', 'settings.themeDesc': '默认深空 / 落日 / 易北爱乐厅 / 草原孤塔 / 黑曜白',
1094
+ 'theme.default': '默认深空', 'theme.dark': '落日', 'theme.light': '易北爱乐厅', 'theme.neutral': '草原孤塔', 'theme.mono': '黑曜白',
1092
1095
  'theme.panelTitle': '选择配色', 'theme.close': '关闭',
1093
1096
  'settings.tokenTitle': '访问令牌', 'settings.change': '更换',
1094
1097
  'settings.hostTitle': 'DSH 状态', 'settings.hostProbing': '探测中…', 'settings.probe': '探测', 'settings.probeFailed': '探测失败',
@@ -1204,6 +1207,7 @@
1204
1207
  'stats.llmTime': 'LLM time', 'stats.minutes': ' min', 'stats.outputCache': 'Output / cache read',
1205
1208
  'stats.ctxPressure': 'Context pressure', 'stats.permission': 'Permission', 'stats.empty': 'No stats yet',
1206
1209
  'goal.title': 'Goal', 'goal.pause': 'Pause', 'goal.resume': 'Resume', 'goal.complete': 'Complete', 'goal.edit': 'Edit goal', 'goal.clear': 'Clear',
1210
+ 'goal.collapse': 'Collapse goal to the side', 'goal.collapseShort': 'Collapse', 'goal.expand': 'Expand goal', 'goal.collapsedHint': 'Still running',
1207
1211
  'goal.none': 'This session has no goal', 'goal.confirmClear': 'Clear the current goal? (the session stays)', 'goal.confirmComplete': 'Mark the goal as completed?',
1208
1212
  'goal.actionFailed': 'Goal action failed', 'goal.actionSubmitted': 'Goal action submitted',
1209
1213
  'goal.cannotEmpty': 'Goal cannot be empty', 'goal.updateFailed': 'Update failed', 'goal.updated': 'Goal updated',
@@ -1316,8 +1320,8 @@
1316
1320
  'settings.toolsTitle': 'Show tool calls', 'settings.toolsDesc': 'Hide 🔧/📦 tool calls and results when off',
1317
1321
  'settings.mobileEnterTitle': 'Mobile Enter behavior', 'settings.mobileEnterDesc': 'Choose whether Enter inserts a newline or sends', 'settings.mobileEnterNewline': 'Enter = newline', 'settings.mobileEnterSend': 'Enter = send',
1318
1322
  'settings.langTitle': 'Language / 语言', 'settings.langDesc': 'UI language, follows system or switch manually',
1319
- 'settings.themeTitle': 'Theme', 'settings.themeDesc': 'Default deep space / Sunset / Elbphilharmonie / Prairie Tower',
1320
- 'theme.default': 'Default', 'theme.dark': 'Sunset', 'theme.light': 'Elbphilharmonie', 'theme.neutral': 'Prairie Tower',
1323
+ 'settings.themeTitle': 'Theme', 'settings.themeDesc': 'Deep Space / Sunset / Elbphilharmonie / Prairie Tower / Monochrome',
1324
+ 'theme.default': 'Default', 'theme.dark': 'Sunset', 'theme.light': 'Elbphilharmonie', 'theme.neutral': 'Prairie Tower', 'theme.mono': 'Monochrome',
1321
1325
  'theme.panelTitle': 'Choose theme', 'theme.close': 'Close',
1322
1326
  'settings.tokenTitle': 'Access token', 'settings.change': 'Change',
1323
1327
  'settings.hostTitle': 'DSH status', 'settings.hostProbing': 'Probing…', 'settings.probe': 'Probe', 'settings.probeFailed': 'Probe failed',