dsh-remote-plugin 0.6.19 → 0.6.21

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.
@@ -840,10 +840,79 @@ function renderGroupSelect() {
840
840
  function toggleGroupMenu() { $('group-select-menu').classList.toggle('hidden') }
841
841
  function closeGroupMenu() { $('group-select-menu').classList.add('hidden') }
842
842
 
843
+ let desktopGroupDrawerReturnFocus = null
844
+
845
+ function desktopGroupDrawerOpen() {
846
+ return !$('ds-group-drawer')?.classList.contains('hidden')
847
+ }
848
+
849
+ function renderDesktopGroupDrawer() {
850
+ const trigger = $('ds-group-drawer-trigger')
851
+ const brandGroup = $('ds-brand-group-name')
852
+ const current = $('ds-group-drawer-current')
853
+ const list = $('ds-group-drawer-list')
854
+ if (brandGroup) brandGroup.textContent = state.activeGroup
855
+ if (trigger) trigger.setAttribute('aria-label', t('ds.groupsDrawerOpen') + ':' + state.activeGroup)
856
+ if (current) current.textContent = state.activeGroup
857
+ if (!list) return
858
+ list.innerHTML = state.groups.map(group => {
859
+ const servers = groupServers(group)
860
+ const selected = group === state.activeGroup
861
+ return `<button type="button" class="ds-group-drawer-option ${selected ? 'current' : ''}" data-ds-quick-group="${esc(group)}" ${selected ? 'aria-current="true"' : ''}>
862
+ <span class="ds-group-drawer-mark" aria-hidden="true"></span>
863
+ <span class="ds-group-drawer-copy"><span class="ds-group-drawer-name">${esc(group)}</span><span class="ds-group-drawer-meta">${t('ds.groupsServerCount', { count: servers.length })}</span></span>
864
+ <span class="ds-group-drawer-check" aria-hidden="true">${selected ? '✓' : ''}</span>
865
+ </button>`
866
+ }).join('')
867
+ }
868
+
869
+ function openDesktopGroupDrawer() {
870
+ const drawer = $('ds-group-drawer')
871
+ const backdrop = $('ds-group-drawer-backdrop')
872
+ const trigger = $('ds-group-drawer-trigger')
873
+ if (!drawer || !backdrop || !trigger) return
874
+ renderDesktopGroupDrawer()
875
+ desktopGroupDrawerReturnFocus = document.activeElement instanceof HTMLElement ? document.activeElement : trigger
876
+ drawer.classList.remove('hidden')
877
+ backdrop.classList.remove('hidden')
878
+ trigger.setAttribute('aria-expanded', 'true')
879
+ requestAnimationFrame(() => (drawer.querySelector('[data-ds-quick-group].current') || drawer.querySelector('[data-ds-quick-group]') || drawer).focus({ preventScroll: true }))
880
+ }
881
+
882
+ function closeDesktopGroupDrawer({ restoreFocus = true } = {}) {
883
+ const drawer = $('ds-group-drawer')
884
+ const backdrop = $('ds-group-drawer-backdrop')
885
+ const trigger = $('ds-group-drawer-trigger')
886
+ if (!drawer || !backdrop || drawer.classList.contains('hidden')) return
887
+ drawer.classList.add('hidden')
888
+ backdrop.classList.add('hidden')
889
+ trigger?.setAttribute('aria-expanded', 'false')
890
+ if (restoreFocus) (desktopGroupDrawerReturnFocus || trigger)?.focus({ preventScroll: true })
891
+ desktopGroupDrawerReturnFocus = null
892
+ }
893
+
894
+ function keepDesktopGroupDrawerFocus(event) {
895
+ if (!desktopGroupDrawerOpen()) return
896
+ if (event.key === 'Escape') {
897
+ event.preventDefault()
898
+ closeDesktopGroupDrawer()
899
+ return
900
+ }
901
+ if (event.key !== 'Tab') return
902
+ const drawer = $('ds-group-drawer')
903
+ const targets = [...drawer.querySelectorAll('button:not([disabled]), [href], [tabindex]:not([tabindex="-1"])')]
904
+ if (!targets.length) return
905
+ const first = targets[0]
906
+ const last = targets[targets.length - 1]
907
+ if (event.shiftKey && document.activeElement === first) { event.preventDefault(); last.focus() }
908
+ else if (!event.shiftKey && document.activeElement === last) { event.preventDefault(); first.focus() }
909
+ }
910
+
843
911
  function renderServers() {
844
912
  const box = $('server-list')
845
913
  if (!box) return
846
914
  renderGroupSelect()
915
+ renderDesktopGroupDrawer()
847
916
  box.innerHTML = state.groups.map(g => {
848
917
  const list = groupServers(g)
849
918
  const auto = state.autoSelect[g] !== false
@@ -1566,6 +1635,7 @@ async function openSession(id) {
1566
1635
  renderSessions()
1567
1636
  renderSessionCards()
1568
1637
  renderQueue()
1638
+ renderSessionPendingDesktop()
1569
1639
  updateComposerStatus()
1570
1640
  await loadHistory()
1571
1641
  }
@@ -1576,6 +1646,7 @@ function closeSession() {
1576
1646
  const cards = $('session-cards')
1577
1647
  if (cards) cards.innerHTML = ''
1578
1648
  renderQueue()
1649
+ renderSessionPendingDesktop()
1579
1650
  updateComposerStatus()
1580
1651
  updateSessionActions()
1581
1652
  showView('view-sessions')
@@ -2010,6 +2081,39 @@ function serverLabel() {
2010
2081
  const cur = currentServerEntry()
2011
2082
  return cur ? (cur.note || cur.url) : (state.server || location.host)
2012
2083
  }
2084
+ function desktopApprovalDetail(a) {
2085
+ const lines = []
2086
+ if (a?.reason) lines.push(String(a.reason))
2087
+ if (a?.arguments !== undefined) lines.push(safeJson(a.arguments))
2088
+ if (a?.callId) lines.push(`callId: ${a.callId}`)
2089
+ return lines.join('\n') || t('ds.approvalReason', { reason: '' })
2090
+ }
2091
+ function renderSessionPendingDesktop() {
2092
+ const box = $('session-pending')
2093
+ if (!box) return
2094
+ const sessionId = state.current
2095
+ const items = [
2096
+ ...state.approvals.filter(a => a.sessionId === sessionId).map(a => ({ kind: 'approval', item: a })),
2097
+ ...state.questions.filter(q => q.sessionId === sessionId).map(q => ({ kind: 'question', item: q }))
2098
+ ]
2099
+ box.classList.toggle('hidden', !sessionId || !items.length)
2100
+ if (!sessionId || !items.length) { box.innerHTML = ''; return }
2101
+ 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 }) => {
2102
+ if (kind === 'approval') return `<article class="ds-notif-card" data-session-approval="${esc(item.approvalId)}">
2103
+ <div class="ds-notif-head">🔐 ${t('ds.approvalTitle')}</div>
2104
+ <div class="ds-notif-title">${esc(item.toolName || t('ds.toolDefault'))}</div>
2105
+ <div class="ds-notif-body">${esc(desktopApprovalDetail(item))}</div>
2106
+ <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>
2107
+ </article>`
2108
+ return `<article class="ds-notif-card question" data-session-question="${esc(item.rpcId)}">
2109
+ <div class="ds-notif-head">❓ ${t('ds.questionNotify')}</div>
2110
+ <div class="ds-notif-title">${esc((item.questions || []).map(question => question.question).filter(Boolean).join(' / '))}</div>
2111
+ <div class="ds-notif-actions"><button type="button" class="ds-btn" data-session-answer>${t('ds.submit')}</button></div>
2112
+ </article>`
2113
+ }).join('')}</div>`
2114
+ box.querySelectorAll('[data-session-approve]').forEach(button => button.addEventListener('click', () => approveApproval(button.closest('[data-session-approval]')?.dataset.sessionApproval || '', button.dataset.sessionApprove === '1')))
2115
+ box.querySelectorAll('[data-session-answer]').forEach(button => button.addEventListener('click', () => openQuestionModal(state.questions.find(q => q.rpcId === button.closest('[data-session-question]')?.dataset.sessionQuestion))))
2116
+ }
2013
2117
  function renderNotifStack() {
2014
2118
  const stack = $('notif-stack')
2015
2119
  const items = [
@@ -2019,7 +2123,7 @@ function renderNotifStack() {
2019
2123
  stack.innerHTML = items.map(it => {
2020
2124
  if (it.kind === 'approval') {
2021
2125
  const a = it.a
2022
- const reason = a.reason || a.arguments ? safeJson(a.arguments ?? a.reason ?? '') : ''
2126
+ const reason = desktopApprovalDetail(a)
2023
2127
  return `<div class="ds-notif-card" data-approval="${esc(a.approvalId)}" tabindex="0">
2024
2128
  <div class="ds-notif-head">🔐 ${t('ds.approvalTitle')} · ${esc(serverLabel())} · ${fmtTime(a.time || Date.now())}</div>
2025
2129
  <div class="ds-notif-title">${esc(a.toolName || t('ds.toolDefault'))}</div>
@@ -2050,6 +2154,7 @@ function renderNotifStack() {
2050
2154
  stack.querySelectorAll('.ds-notif-card').forEach(card => card.addEventListener('keydown', (e) => {
2051
2155
  if (e.key === 'Escape') toast(t('ds.ignored'), 'ok')
2052
2156
  }))
2157
+ renderSessionPendingDesktop()
2053
2158
  renderOverviewDesktop()
2054
2159
  }
2055
2160
  async function approveApproval(id, allow) {
@@ -2708,7 +2813,7 @@ function renderOverviewDesktop() {
2708
2813
  $('ds-overview-attention-list').innerHTML = pending.length ? pending.slice(0, 4).map(({ kind, item }) => {
2709
2814
  const title = titleOf(state.byId.get(item.sessionId))
2710
2815
  if (kind === 'approval') return `<div class="ds-overview-attention-item" data-ds-overview-approval="${esc(item.approvalId)}">
2711
- <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>
2816
+ <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>
2712
2817
  <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>
2713
2818
  </div>`
2714
2819
  return `<button type="button" class="ds-overview-attention-item question" data-ds-overview-question="${esc(item.rpcId)}">
@@ -3041,6 +3146,22 @@ function bindUi() {
3041
3146
  $('btn-server-add').addEventListener('click', addServer)
3042
3147
  $('btn-group-add').addEventListener('click', addGroup)
3043
3148
  $('group-select-btn').addEventListener('click', (e) => { e.stopPropagation(); toggleGroupMenu() })
3149
+ $('ds-group-drawer-trigger').addEventListener('click', () => { desktopGroupDrawerOpen() ? closeDesktopGroupDrawer() : openDesktopGroupDrawer() })
3150
+ $('ds-group-drawer-close').addEventListener('click', () => closeDesktopGroupDrawer())
3151
+ $('ds-group-drawer-backdrop').addEventListener('click', () => closeDesktopGroupDrawer())
3152
+ $('ds-group-drawer-list').addEventListener('click', (e) => {
3153
+ const option = e.target.closest('[data-ds-quick-group]')
3154
+ if (!option) return
3155
+ const group = option.dataset.dsQuickGroup
3156
+ closeDesktopGroupDrawer({ restoreFocus: false })
3157
+ if (group !== state.activeGroup) switchGroup(group)
3158
+ })
3159
+ $('ds-group-drawer-manage').addEventListener('click', () => {
3160
+ closeDesktopGroupDrawer({ restoreFocus: false })
3161
+ showView('view-settings')
3162
+ showSettingsPage('servers')
3163
+ })
3164
+ document.addEventListener('keydown', keepDesktopGroupDrawerFocus)
3044
3165
  document.addEventListener('click', (e) => { if (!e.target.closest('#group-select')) closeGroupMenu() })
3045
3166
  $('server-input').addEventListener('keydown', (e) => { if (e.key === 'Enter' && !e.isComposing) { e.preventDefault(); addServer() } })
3046
3167
 
package/public/index.html CHANGED
@@ -15,10 +15,11 @@
15
15
  </head>
16
16
  <body>
17
17
  <header class="topbar">
18
- <div class="brand">
18
+ <button id="group-drawer-trigger" class="brand brand-trigger" type="button" aria-haspopup="dialog" aria-controls="group-drawer" aria-expanded="false" data-i18n-aria="groups.drawerOpen">
19
19
  <span class="brand-dot"></span>
20
- <span class="brand-name">DSH Remote</span>
21
- </div>
20
+ <span class="brand-copy"><span class="brand-name">DSH Remote</span><span id="brand-group-name" class="brand-group-name">默认</span></span>
21
+ <svg class="brand-caret" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m7 10 5 5 5-5"/></svg>
22
+ </button>
22
23
  <div class="topbar-right">
23
24
  <button id="btn-donate" class="topbar-btn icon-btn" data-i18n-title="settings.donate" data-i18n-aria="settings.donate"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 8h11v6a4 4 0 0 1-4 4H10a4 4 0 0 1-4-4Z"/><path d="M17 10h1.5a2.5 2.5 0 0 1 0 5H17M9 4c0 1 1 1.2 1 2M13 4c0 1 1 1.2 1 2M5 20h13"/></svg></button>
24
25
  <button id="btn-feedback" class="topbar-btn icon-btn" data-i18n-title="feedback.title" data-i18n-aria="feedback.title" aria-haspopup="menu" aria-expanded="false"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 11.5a7.5 7.5 0 0 1-7.5 7.5H7l-3 2 .8-3.1A7.5 7.5 0 1 1 20 11.5Z"/><path d="M8 11.5h.01M12 11.5h.01M16 11.5h.01"/></svg></button>
@@ -27,6 +28,20 @@
27
28
  </div>
28
29
  </header>
29
30
 
31
+ <button id="group-drawer-backdrop" class="group-drawer-backdrop hidden" type="button" tabindex="-1" aria-hidden="true"></button>
32
+ <aside id="group-drawer" class="group-drawer hidden" role="dialog" aria-modal="true" aria-labelledby="group-drawer-title" tabindex="-1">
33
+ <div class="group-drawer-head">
34
+ <div><div class="group-drawer-eyebrow" data-i18n="groups.drawerEyebrow">QUICK SWITCH</div><h2 id="group-drawer-title" data-i18n="groups.drawerTitle">切换服务器组</h2></div>
35
+ <button id="group-drawer-close" class="group-drawer-close" type="button" data-i18n-aria="groups.drawerClose" data-i18n-title="groups.drawerClose">×</button>
36
+ </div>
37
+ <div class="group-drawer-current"><span data-i18n="groups.drawerCurrent">当前连接组</span><strong id="group-drawer-current">默认</strong></div>
38
+ <div id="group-drawer-list" class="group-drawer-list" role="group" data-i18n-aria="groups.drawerTitle"></div>
39
+ <div class="group-drawer-foot">
40
+ <button id="group-drawer-manage" class="group-drawer-manage" type="button"><span data-i18n="groups.drawerManage">管理服务器与组</span><span aria-hidden="true">›</span></button>
41
+ <p data-i18n="groups.drawerManageDesc">编辑、测速和调整组</p>
42
+ </div>
43
+ </aside>
44
+
30
45
  <main id="main" class="main">
31
46
  <!-- 会话列表 -->
32
47
  <section id="view-home" class="view">
@@ -104,6 +119,8 @@
104
119
 
105
120
  <div id="queue-dock" class="queue-dock hidden" aria-live="polite"></div>
106
121
 
122
+ <section id="session-pending" class="session-pending hidden" aria-live="polite"></section>
123
+
107
124
  <div class="section-head small">
108
125
  <span data-i18n="session.history">对话</span>
109
126
  <span id="history-hint" class="muted"></span>
@@ -956,6 +973,8 @@
956
973
  'groups.added': '已创建组:{group}', 'groups.promptRename': '重命名组「{group}」:',
957
974
  'groups.renamed': '组已重命名', 'groups.cannotDeleteDefault': '默认组不可删除',
958
975
  'groups.confirmDelete': '删除组「{group}」?组内服务器将归入默认组。', 'groups.deleted': '组已删除',
976
+ 'groups.drawerOpen': '切换服务器组', 'groups.drawerClose': '关闭组列表', 'groups.drawerEyebrow': '快速切换', 'groups.drawerTitle': '切换服务器组', 'groups.drawerCurrent': '当前连接组',
977
+ 'groups.drawerManage': '管理服务器与组', 'groups.drawerManageDesc': '编辑、测速和调整组', 'groups.serverCount': '{count} 台服务器',
959
978
  'stream.error': '事件流错误:{msg}',
960
979
  'session.loading': '加载中…', 'session.unknown': '未知会话', 'session.running': '运行中', 'session.interrupted': '已中断',
961
980
  'session.stop': '停止本轮', 'session.history': '对话', 'session.errorMsg': '会话出错:{msg}', 'session.recovering': '恢复会话中…', 'session.recoveryReady': '会话已恢复', 'session.recoveryFailed': '会话恢复失败', 'session.recoveryCached': '正在查看缓存历史',
@@ -1186,6 +1205,8 @@
1186
1205
  'groups.added': 'Created group: {group}', 'groups.promptRename': 'Rename group "{group}":',
1187
1206
  'groups.renamed': 'Group renamed', 'groups.cannotDeleteDefault': 'The default group cannot be deleted',
1188
1207
  'groups.confirmDelete': 'Delete group "{group}"? Its servers move to the default group.', 'groups.deleted': 'Group deleted',
1208
+ 'groups.drawerOpen': 'Switch server group', 'groups.drawerClose': 'Close group list', 'groups.drawerEyebrow': 'QUICK SWITCH', 'groups.drawerTitle': 'Switch server group', 'groups.drawerCurrent': 'Current connection group',
1209
+ 'groups.drawerManage': 'Manage servers and groups', 'groups.drawerManageDesc': 'Edit, test, and organize groups', 'groups.serverCount': '{count} servers',
1189
1210
  'stream.error': 'Event stream error: {msg}',
1190
1211
  'session.loading': 'Loading…', 'session.unknown': 'Unknown session', 'session.running': 'Running', 'session.interrupted': 'Interrupted',
1191
1212
  'session.stop': 'Stop turn', 'session.history': 'Conversation', 'session.errorMsg': 'Session error: {msg}', 'session.recovering': 'Restoring session…', 'session.recoveryReady': 'Session ready', 'session.recoveryFailed': 'Session restore failed', 'session.recoveryCached': 'Viewing cached history',
package/public/styles.css CHANGED
@@ -40,7 +40,15 @@ body { padding-bottom: calc(92px + var(--dsr-safe-b)); }
40
40
  background: var(--dsr-bg);
41
41
  border-bottom: 1px solid var(--dsr-line);
42
42
  }
43
- .brand { display: flex; align-items: center; gap: 8px; font-weight: 700; letter-spacing: .5px; }
43
+ .brand { display: flex; align-items: center; gap: 8px; min-width: 0; font-weight: 700; letter-spacing: .5px; }
44
+ .brand-trigger { padding: 4px 5px 4px 2px; border-radius: 11px; text-align: left; color: var(--dsr-text); }
45
+ .brand-trigger:active { background: var(--dsr-accent-soft); }
46
+ .brand-trigger:focus-visible { outline: 2px solid var(--dsr-accent); outline-offset: 2px; }
47
+ .brand-copy { min-width: 0; display: flex; flex-direction: column; align-items: flex-start; gap: 0; line-height: 1.14; }
48
+ .brand-name { white-space: nowrap; }
49
+ .brand-group-name { max-width: 16ch; overflow: hidden; color: var(--dsr-muted); font-size: 10.5px; font-weight: 600; letter-spacing: .15px; text-overflow: ellipsis; white-space: nowrap; }
50
+ .brand-caret { width: 14px; height: 14px; flex: none; color: var(--dsr-muted); transition: transform .18s ease, color .18s ease; }
51
+ .brand-trigger[aria-expanded="true"] .brand-caret { color: var(--dsr-accent-strong); transform: rotate(180deg); }
44
52
  .brand-dot {
45
53
  width: 10px; height: 10px; border-radius: 50%;
46
54
  background: linear-gradient(135deg, var(--dsr-brand-a), var(--dsr-brand-b));
@@ -77,6 +85,34 @@ button {
77
85
  .topbar-right #btn-feedback { color: var(--dsr-info); }
78
86
  .topbar-right #btn-refresh { color: var(--dsr-success); }
79
87
  .topbar-right .topbar-btn.conn-badge { padding: 0 14px; border-radius: 999px; font-size: 12.5px; }
88
+
89
+ /* ---------- 顶栏组切换抽屉 ---------- */
90
+ .group-drawer-backdrop { position: fixed; inset: 0; z-index: 70; border: 0; background: var(--dsr-overlay); }
91
+ .group-drawer { position: fixed; inset: 0 auto 0 0; z-index: 71; display: flex; flex-direction: column; width: min(360px, calc(100vw - 18px)); background: var(--dsr-panel); border-right: 1px solid var(--dsr-line); box-shadow: 18px 0 48px var(--dsr-shadow); animation: group-drawer-in .22s cubic-bezier(.2,.8,.2,1); }
92
+ .group-drawer.hidden, .group-drawer-backdrop.hidden { display: none; }
93
+ @keyframes group-drawer-in { from { opacity: .01; transform: translateX(-16px); } to { opacity: 1; transform: translateX(0); } }
94
+ body.group-drawer-open { overflow: hidden; }
95
+ .group-drawer-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 14px; padding: calc(var(--dsr-safe-top) + 18px) 18px 15px; border-bottom: 1px solid var(--dsr-line); }
96
+ .group-drawer-eyebrow { margin-bottom: 3px; color: var(--dsr-accent-strong); font-size: 10px; font-weight: 750; letter-spacing: 1.5px; }
97
+ .group-drawer-head h2 { margin: 0; color: var(--dsr-text); font-size: 20px; line-height: 1.2; }
98
+ .group-drawer-close { width: 38px; height: 38px; display: grid; flex: none; place-items: center; border: 1px solid var(--dsr-line); border-radius: 12px; background: var(--dsr-bg-2); color: var(--dsr-muted); font-size: 24px; line-height: 1; }
99
+ .group-drawer-close:active { background: var(--dsr-accent-soft); color: var(--dsr-accent-strong); }
100
+ .group-drawer-current { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin: 14px 14px 8px; padding: 11px 12px; border: 1px solid var(--dsr-accent-line); border-radius: 12px; background: var(--dsr-accent-soft); color: var(--dsr-muted); font-size: 12px; }
101
+ .group-drawer-current strong { min-width: 0; overflow: hidden; color: var(--dsr-accent-strong); font-size: 13px; text-overflow: ellipsis; white-space: nowrap; }
102
+ .group-drawer-list { flex: 1; min-height: 0; overflow-y: auto; padding: 4px 10px 14px; display: flex; flex-direction: column; gap: 4px; overscroll-behavior: contain; scrollbar-width: thin; scrollbar-color: var(--dsr-line) transparent; }
103
+ .group-drawer-option { width: 100%; min-height: 58px; display: flex; align-items: center; gap: 11px; padding: 9px 11px; border: 1px solid transparent; border-radius: 13px; color: var(--dsr-text); text-align: left; }
104
+ .group-drawer-option:active { background: var(--dsr-bg-2); }
105
+ .group-drawer-option.current { border-color: var(--dsr-accent-line); background: linear-gradient(100deg, var(--dsr-accent-soft), transparent); }
106
+ .group-drawer-option-mark { width: 10px; height: 10px; flex: none; border: 2px solid var(--dsr-line); border-radius: 50%; }
107
+ .group-drawer-option.current .group-drawer-option-mark { border-color: var(--dsr-accent); background: var(--dsr-accent); box-shadow: 0 0 0 4px var(--dsr-accent-soft); }
108
+ .group-drawer-option-copy { min-width: 0; flex: 1; display: flex; flex-direction: column; gap: 2px; }
109
+ .group-drawer-option-name { overflow: hidden; font-size: 14px; font-weight: 700; text-overflow: ellipsis; white-space: nowrap; }
110
+ .group-drawer-option-meta { overflow: hidden; color: var(--dsr-muted); font-size: 11px; text-overflow: ellipsis; white-space: nowrap; }
111
+ .group-drawer-option-check { flex: none; color: var(--dsr-accent-strong); font-size: 18px; font-weight: 750; }
112
+ .group-drawer-foot { flex: none; padding: 12px 14px calc(12px + var(--dsr-safe-b)); border-top: 1px solid var(--dsr-line); background: var(--dsr-bg-2); }
113
+ .group-drawer-manage { width: 100%; min-height: 44px; display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 0 12px; border: 1px solid var(--dsr-line); border-radius: 11px; background: var(--dsr-panel); color: var(--dsr-accent-strong); font-weight: 700; text-align: left; }
114
+ .group-drawer-manage:active { background: var(--dsr-accent-soft); }
115
+ .group-drawer-foot p { margin: 7px 2px 0; color: var(--dsr-muted); font-size: 11px; }
80
116
  .mini-btn {
81
117
  padding: 5px 11px; border-radius: 9px; font-size: 13px;
82
118
  background: var(--dsr-panel); border: 1px solid var(--dsr-line); color: var(--dsr-accent-strong);
@@ -424,6 +460,13 @@ body.in-session .view {
424
460
  .queue-dock-item:last-child { border-bottom: 0; }
425
461
  .queue-dock-preview { flex: 1; min-width: 0; color: var(--dsr-text); font-size: 13px; line-height: 1.4; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
426
462
  .queue-dock-action { flex: 0 0 auto; }
463
+ .session-pending { flex:0 0 auto; margin:0 0 10px; padding:10px; border:1px solid var(--dsr-warning-line); border-radius:13px; background:var(--dsr-warning-soft); }
464
+ .session-pending-head { display:flex; align-items:center; justify-content:space-between; gap:8px; margin-bottom:8px; color:var(--dsr-text); font-size:12px; font-weight:750; }
465
+ .session-pending-list { display:flex; flex-direction:column; gap:8px; }
466
+ .session-pending .pending-card { min-width:0; background:var(--dsr-panel); }
467
+ .session-pending .pc-title { line-height:1.45; overflow-wrap:anywhere; word-break:break-word; }
468
+ .session-pending .pc-desc { line-height:1.5; white-space:pre-wrap; overflow-wrap:anywhere; }
469
+ .session-pending .goal-actions { justify-content:flex-end; }
427
470
  .md-table-wrap { max-width: 100%; overflow-x: auto; margin: 8px 0; }
428
471
  .md-table-wrap table { width: max-content; min-width: 100%; border-collapse: collapse; font-size: .94em; }
429
472
  .md-table-wrap th, .md-table-wrap td { padding: 6px 9px; border: 1px solid var(--dsr-line); text-align: left; white-space: nowrap; }
@@ -711,16 +754,17 @@ body.in-session .main { padding-bottom: 0; }
711
754
  .overview-section-meta { color:var(--dsr-muted); font-size:11px; font-weight:500; }
712
755
  .overview-attention-list, .overview-session-list { display:flex; flex-direction:column; gap:7px; }
713
756
  .overview-attention-item, .overview-session-item { width:100%; display:flex; align-items:center; gap:10px; min-width:0; padding:11px 12px; border:1px solid var(--dsr-line); border-radius:13px; background:var(--dsr-panel); color:var(--dsr-text); text-align:left; }
757
+ .overview-attention-item { align-items:flex-start; }
714
758
  button.overview-attention-item, button.overview-session-item { cursor:pointer; }
715
759
  .overview-attention-item { border-left:3px solid var(--dsr-warning); }
716
760
  .overview-attention-item.question { border-left-color:var(--dsr-accent-2); }
717
761
  .overview-item-mark { flex:0 0 auto; width:25px; height:25px; display:grid; place-items:center; border-radius:8px; background:var(--dsr-warning-soft); color:var(--dsr-warning); font-size:12px; }
718
762
  .overview-attention-item.question .overview-item-mark { background:var(--dsr-accent-2-soft); color:var(--dsr-accent-2); }
719
763
  .overview-item-copy { min-width:0; flex:1; }
720
- .overview-item-title { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-size:12.5px; font-weight:650; }
721
- .overview-item-desc { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; margin-top:2px; color:var(--dsr-muted); font-size:11px; }
764
+ .overview-item-title { font-size:12.5px; font-weight:650; line-height:1.45; overflow-wrap:anywhere; word-break:break-word; }
765
+ .overview-item-desc { margin-top:3px; color:var(--dsr-muted); font-size:11px; line-height:1.45; white-space:normal; overflow-wrap:anywhere; }
722
766
  .overview-item-arrow { color:var(--dsr-muted); font-size:18px; }
723
- .overview-item-actions { flex:0 0 auto; display:flex; gap:4px; }
767
+ .overview-item-actions { flex:0 0 auto; display:flex; flex-wrap:wrap; justify-content:flex-end; gap:4px; }
724
768
  .overview-item-actions .mini-btn { padding:4px 7px; font-size:10px; }
725
769
  .overview-metric-grid { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:7px; }
726
770
  .overview-metric { min-width:0; padding:11px 12px; border:1px solid var(--dsr-line); border-radius:13px; background:var(--dsr-panel); }
@@ -1207,6 +1251,8 @@ button.overview-attention-item, button.overview-session-item { cursor:pointer; }
1207
1251
  .section-actions .session-sort { max-width: 98px; }
1208
1252
  .section-actions .mini-btn { max-width: 92px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1209
1253
  .topbar { padding-left: 10px; padding-right: 10px; }
1254
+ .brand { gap: 6px; }
1255
+ .brand-group-name { max-width: 10ch; }
1210
1256
  .topbar-right { gap: 4px; min-width: 0; }
1211
1257
  .topbar-right .topbar-btn.icon-btn { width: 38px; height: 38px; }
1212
1258
  .topbar-right .topbar-btn.conn-badge { max-width: 72px; padding-left: 8px; padding-right: 8px; overflow: hidden; text-overflow: ellipsis; }
@@ -1,10 +1,18 @@
1
1
  {
2
- "version": "0.6.19",
2
+ "version": "0.6.21",
3
3
  "apkUrl": "dsh-remote.apk",
4
- "sha256": "d79c732efa12a8f303a89fd9e707fdcd4c0e30a29515b222157ae48a6c07260b",
5
- "releasedAt": "2026-08-30T04:41:52.453Z",
6
- "notes": "0.6.19:全面优化手机端、桌面端、管理页和插件页的布局、动效与响应式体验,并支持减少动态效果偏好;重做蓝色、草原、音乐厅等主题的明暗层级和文字图标对比度,新增利落的黑白配色;统一主页图标配色并修正设置齿轮中心孔偏移;目标面板支持收起为始终可见的侧边入口,保留运行状态、可随时恢复,并按会话与目标在本地记忆。",
4
+ "sha256": "46fc5409dbe59300e352ae6dddfbc001856a349a85b81be025e958316a1c5dc0",
5
+ "releasedAt": "2026-08-31T04:10:09.396Z",
6
+ "notes": "0.6.21:左上角 DSH Remote 名称可展开服务器组快捷切换抽屉,当前组、组内服务器数量和管理入口一目了然,手机与桌面端均可一键切换;修复部分新版 DSH 命令执行成功返回 void 时被误判为未执行、继而把 /命令作为普通文本发送的问题,同时兼容旧版三参数、新版四参数及默认参数签名。",
7
7
  "history": [
8
+ {
9
+ "version": "0.6.21",
10
+ "notes": "0.6.21:左上角 DSH Remote 名称可展开服务器组快捷切换抽屉,当前组、组内服务器数量和管理入口一目了然,手机与桌面端均可一键切换;修复部分新版 DSH 命令执行成功返回 void 时被误判为未执行、继而把 /命令作为普通文本发送的问题,同时兼容旧版三参数、新版四参数及默认参数签名。"
11
+ },
12
+ {
13
+ "version": "0.6.20",
14
+ "notes": "0.6.20:适配 DSH 0.1.2-alpha.1 的新版客户端模块、认证、斜杠 RPC 与 remote.mux 实时协议,同时保留旧版 DSH 的自动回退;修复斜杠命令因 commands.execute 参数变化触发 aborted 报错,以及新版历史、工作区、模型、目标和子代理等接口不兼容问题;审批与用户提问现在会在所属对话内完整显示并可直接处理,主页仍保留全局待办入口,长工具参数、原因和调用标识不再被截断。"
15
+ },
8
16
  {
9
17
  "version": "0.6.19",
10
18
  "notes": "0.6.19:全面优化手机端、桌面端、管理页和插件页的布局、动效与响应式体验,并支持减少动态效果偏好;重做蓝色、草原、音乐厅等主题的明暗层级和文字图标对比度,新增利落的黑白配色;统一主页图标配色并修正设置齿轮中心孔偏移;目标面板支持收起为始终可见的侧边入口,保留运行状态、可随时恢复,并按会话与目标在本地记忆。"
@@ -36,14 +44,6 @@
36
44
  {
37
45
  "version": "0.6.12",
38
46
  "notes": "0.6.12:修复远程启动和重启 DSH 时统一报 HTTP 502 且无法确认结果的问题;改为异步追踪 systemd 服务检查、命令提交、进程启动、DSH HTTP 恢复和 mux/host 实时通道重连阶段,成功时显示 PID、HTTP 状态和用时,失败时区分服务不存在、systemd 不可用、权限不足、命令失败、服务失败、启动超时、HTTP 恢复超时和实时通道恢复超时;优化工作区会话筛选、文件预览和应用内选择器;新增中央投票公告与反馈成功确认;主页公告栏常驻并在无未读公告时显示空状态,同时修正设置页异常右箭头、主页刷新图标居中和周末谷时提醒。"
39
- },
40
- {
41
- "version": "0.6.11",
42
- "notes": "0.6.11 正式版:新增按 DSH 工作区筛选、切换和创建会话,文件页支持切换工作区并直接预览常见文本与 Markdown;修复工作区筛选未正确收敛会话、工作区位于默认文件根外时无法查看,以及新会话工作区名称与完整路径显示异常;新增投票公告及隐私化汇总;统一手机端应用内选择抽屉;周末全天按谷时并修复重复峰谷提醒;插件侧栏图标采用亮色背景并随插件自身四套皮肤切换配色;加强网关安装、连接与故障排查引导。"
43
- },
44
- {
45
- "version": "0.6.10",
46
- "notes": "0.6.10 正式版:修复 DSH/网关重启后事件通道不自动恢复、启动阶段网卡读取异常、同源状态误报、双通道总览卡在 3/4、图片后实时回复不可见及子代理重复;新增完整用户链路与重启生命周期测试;公网反馈切换到安全的 HTTPS 临时入口并加强来源 IP 防伪造、节流与隐私保护。"
47
47
  }
48
48
  ]
49
49
  }
@@ -1,3 +1,3 @@
1
1
  {
2
- "version": "0.6.19"
2
+ "version": "0.6.21"
3
3
  }