dsh-remote-plugin 0.6.12 → 0.6.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/apk/dsh-remote.apk +0 -0
- package/gateway.cjs +294 -17
- package/index.mjs +30 -5
- package/package.json +1 -1
- package/public/admin.html +157 -8
- package/public/admin.js +264 -5
- package/public/announcements.json +27 -0
- package/public/app.js +205 -20
- package/public/desktop/desktop.css +5 -0
- package/public/desktop/desktop.html +4 -2
- package/public/desktop/desktop.js +103 -14
- package/public/index.html +29 -4
- package/public/styles.css +23 -0
- package/public/update.json +8 -8
- package/public/version.json +1 -1
|
@@ -48,6 +48,9 @@ function themeSet(id) { LS.set('dshTheme', id); themeApply() }
|
|
|
48
48
|
themeApply()
|
|
49
49
|
|
|
50
50
|
/* ---------------- 状态 ---------------- */
|
|
51
|
+
function emptyDesktopHistory() {
|
|
52
|
+
return { seqs: new Set(), visible: [], hasMore: false, loading: false, minSeq: Infinity, partialReasoning: new Map() }
|
|
53
|
+
}
|
|
51
54
|
const state = {
|
|
52
55
|
token: LS.get('token', ''),
|
|
53
56
|
wsTicket: { token: '', server: '', value: '', expiresAt: 0 },
|
|
@@ -58,13 +61,14 @@ const state = {
|
|
|
58
61
|
autoSelect: { '默认': true },
|
|
59
62
|
groupActive: { '默认': '' },
|
|
60
63
|
serverLatency: {},
|
|
64
|
+
gatewayHealth: {},
|
|
61
65
|
selectingServer: false,
|
|
62
66
|
sessions: [],
|
|
63
67
|
sessionSort: LS.get('sessionSort', 'time') === 'workspace' ? 'workspace' : 'time',
|
|
64
68
|
byId: new Map(),
|
|
65
69
|
current: null,
|
|
66
70
|
hostInfo: null,
|
|
67
|
-
history:
|
|
71
|
+
history: emptyDesktopHistory(),
|
|
68
72
|
approvals: [],
|
|
69
73
|
questions: [],
|
|
70
74
|
questionModal: null,
|
|
@@ -280,16 +284,31 @@ function renderEffortMenu() {
|
|
|
280
284
|
const cur = state.models.current
|
|
281
285
|
const provider = (state.models.groups || []).find(g => g.id === cur?.provider)
|
|
282
286
|
const model = (provider?.models || []).find(m => m.id === cur?.model)
|
|
283
|
-
const efforts = model
|
|
284
|
-
group.classList.toggle('hidden', !efforts.length)
|
|
287
|
+
const { efforts, defaultEffort, custom } = reasoningEffortOptions(model)
|
|
288
|
+
group.classList.toggle('hidden', !cur || !efforts.length)
|
|
285
289
|
box.innerHTML = efforts.map(e => {
|
|
286
|
-
const isCur = cur?.reasoningEffort === e.id || (!cur?.reasoningEffort && e.id ===
|
|
290
|
+
const isCur = cur?.reasoningEffort === e.id || (!cur?.reasoningEffort && e.id === defaultEffort)
|
|
287
291
|
return `<button class="ds-model-chip ${isCur ? 'current' : ''}" data-effort="${esc(e.id)}" title="${esc(e.description || '')}">${esc(e.name || e.id)}</button>`
|
|
288
|
-
}).join('')
|
|
292
|
+
}).join('') + (custom ? `<span class="ds-effort-hint">${esc(t('models.effortCustomHint'))}</span>` : '')
|
|
289
293
|
box.querySelectorAll('[data-effort]').forEach(btn =>
|
|
290
294
|
btn.addEventListener('click', () => selectSessionEffort(btn.dataset.effort)))
|
|
291
295
|
}
|
|
292
296
|
|
|
297
|
+
function reasoningEffortOptions(model) {
|
|
298
|
+
const raw = Array.isArray(model?.reasoning?.efforts) && model.reasoning.efforts.length
|
|
299
|
+
? model.reasoning.efforts
|
|
300
|
+
: (Array.isArray(model?.reasoningEfforts) && model.reasoningEfforts.length ? model.reasoningEfforts : null)
|
|
301
|
+
const names = { low: t('models.effortLow'), high: t('models.effortHigh'), max: t('models.effortMax'), off: t('models.effortOff') }
|
|
302
|
+
if (raw) {
|
|
303
|
+
return {
|
|
304
|
+
efforts: raw.map(e => typeof e === 'string' ? { id: e, name: names[e] || e } : e),
|
|
305
|
+
defaultEffort: model?.reasoning?.defaultEffort,
|
|
306
|
+
custom: !model?.reasoning?.efforts
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return { efforts: ['low', 'high', 'max'].map(id => ({ id, name: names[id] })), defaultEffort: undefined, custom: true }
|
|
310
|
+
}
|
|
311
|
+
|
|
293
312
|
async function selectSessionEffort(effortId) {
|
|
294
313
|
const cur = state.models.current
|
|
295
314
|
if (!state.current || !cur) return
|
|
@@ -523,6 +542,7 @@ async function getWsTicket() {
|
|
|
523
542
|
const token = state.token
|
|
524
543
|
const server = state.server
|
|
525
544
|
wsTicketPromise = (async () => {
|
|
545
|
+
if (activeGatewayCapability('wsTicket') === false) throw new Error('ws ticket unsupported')
|
|
526
546
|
const res = await fetch(apiUrl('/api/ws-ticket'), {
|
|
527
547
|
method: 'POST',
|
|
528
548
|
headers: { authorization: 'Bearer ' + token, 'x-dsh-remote-client': 'web' }
|
|
@@ -645,9 +665,18 @@ async function pingServer(base) {
|
|
|
645
665
|
const timer = setTimeout(() => ctrl.abort(), 3500)
|
|
646
666
|
try {
|
|
647
667
|
const res = await fetch(u + '/health?t=' + Date.now(), { signal: ctrl.signal, cache: 'no-store' })
|
|
648
|
-
|
|
668
|
+
if (!res.ok) return Infinity
|
|
669
|
+
const health = await res.json().catch(() => null)
|
|
670
|
+
if (health && typeof health === 'object') state.gatewayHealth[u] = health
|
|
671
|
+
return Math.round(performance.now() - t0)
|
|
649
672
|
} catch { return Infinity } finally { clearTimeout(timer) }
|
|
650
673
|
}
|
|
674
|
+
function activeGatewayCapability(name) {
|
|
675
|
+
const key = String(state.server || location.origin || '').replace(/\/+$/, '')
|
|
676
|
+
const capabilities = state.gatewayHealth[key]?.capabilities
|
|
677
|
+
if (!capabilities || !Object.prototype.hasOwnProperty.call(capabilities, name)) return null
|
|
678
|
+
return Number(capabilities[name]) > 0
|
|
679
|
+
}
|
|
651
680
|
async function selectFastestServer({ silent = false, reconnect = true } = {}) {
|
|
652
681
|
if (state.selectingServer) return null
|
|
653
682
|
state.selectingServer = true
|
|
@@ -1163,6 +1192,10 @@ function applyProjection(sessionId, key, value, seq) {
|
|
|
1163
1192
|
}
|
|
1164
1193
|
function proj(s, key, d) { return s?.projections?.values?.[key] ?? d }
|
|
1165
1194
|
function titleOf(s) { return proj(s, 'title') || (s?.sessionId ? short(s.sessionId) : t('ds.sessions')) }
|
|
1195
|
+
function isTopLevelSession(session) {
|
|
1196
|
+
return !!session && !session.parentSessionId && session.origin !== 'subagent'
|
|
1197
|
+
}
|
|
1198
|
+
function topLevelSessions() { return state.sessions.filter(isTopLevelSession) }
|
|
1166
1199
|
const GOAL_TERMINAL_PHASES = new Set(['complete', 'cleared'])
|
|
1167
1200
|
function isGoalTerminal(goal) {
|
|
1168
1201
|
return !!goal && GOAL_TERMINAL_PHASES.has(goal.phase)
|
|
@@ -1175,6 +1208,11 @@ function goalOf(s) {
|
|
|
1175
1208
|
function onSessionEvent(sessionId, event) {
|
|
1176
1209
|
if (state.current === sessionId && event) {
|
|
1177
1210
|
const h = state.history
|
|
1211
|
+
const reasoningChanged = applyReasoningStreamEvent(event)
|
|
1212
|
+
if (event.type === 'assistant/chunk' || event.type === 'reasoning-chunks') {
|
|
1213
|
+
if (reasoningChanged) scheduleReasoningRender()
|
|
1214
|
+
return
|
|
1215
|
+
}
|
|
1178
1216
|
const seq = event.seq
|
|
1179
1217
|
if (seq != null && !h.seqs.has(seq) && shouldShowEvent(event.type)) {
|
|
1180
1218
|
h.seqs.add(seq)
|
|
@@ -1209,7 +1247,7 @@ function workspaceDisplayName(label) {
|
|
|
1209
1247
|
return parts[parts.length - 1] || value
|
|
1210
1248
|
}
|
|
1211
1249
|
function sortedSessions() {
|
|
1212
|
-
const items =
|
|
1250
|
+
const items = topLevelSessions()
|
|
1213
1251
|
if (state.sessionSort === 'workspace') {
|
|
1214
1252
|
return items.sort((a, b) => {
|
|
1215
1253
|
const aw = sessionCwd(a) || '\uffff'
|
|
@@ -1272,7 +1310,7 @@ function renderSessions() {
|
|
|
1272
1310
|
|
|
1273
1311
|
async function openSession(id) {
|
|
1274
1312
|
state.current = id
|
|
1275
|
-
state.history =
|
|
1313
|
+
state.history = emptyDesktopHistory()
|
|
1276
1314
|
state.models = { loaded: false, loading: false, groups: [], current: null, failures: [] }
|
|
1277
1315
|
showView('view-chat')
|
|
1278
1316
|
$('ds-title').textContent = titleOf(state.byId.get(id)) || t('ds.sessions')
|
|
@@ -1283,7 +1321,7 @@ async function openSession(id) {
|
|
|
1283
1321
|
}
|
|
1284
1322
|
function closeSession() {
|
|
1285
1323
|
state.current = null
|
|
1286
|
-
state.history =
|
|
1324
|
+
state.history = emptyDesktopHistory()
|
|
1287
1325
|
const cards = $('session-cards')
|
|
1288
1326
|
if (cards) cards.innerHTML = ''
|
|
1289
1327
|
showView('view-sessions')
|
|
@@ -1303,6 +1341,7 @@ async function loadHistory() {
|
|
|
1303
1341
|
for (const entry of v.events || []) {
|
|
1304
1342
|
const ev = entry?.event
|
|
1305
1343
|
const seq = ev?.seq
|
|
1344
|
+
applyReasoningStreamEvent(ev)
|
|
1306
1345
|
if (seq == null || state.history.seqs.has(seq)) continue
|
|
1307
1346
|
if (!shouldShowEvent(ev.type)) continue
|
|
1308
1347
|
state.history.seqs.add(seq)
|
|
@@ -1322,11 +1361,59 @@ const INTERESTING_EVENTS = new Set([
|
|
|
1322
1361
|
'approval/asked', 'approval/resolved', 'session/title', 'title'
|
|
1323
1362
|
])
|
|
1324
1363
|
function shouldShowEvent(type) { return INTERESTING_EVENTS.has(type) }
|
|
1364
|
+
function reasoningStreamKey(data, index) { return `${data?.turn ?? '?'}:${data?.step ?? '?'}:${index ?? '?'}` }
|
|
1365
|
+
function applyReasoningStreamEvent(event) {
|
|
1366
|
+
const h = state.history
|
|
1367
|
+
const data = event?.data || {}
|
|
1368
|
+
let changed = false
|
|
1369
|
+
if (event?.type === 'assistant/chunk') {
|
|
1370
|
+
const chunk = data.chunk || {}
|
|
1371
|
+
const key = reasoningStreamKey(data, chunk.index)
|
|
1372
|
+
if (chunk.type === 'block-start' && chunk.blockType === 'reasoning') {
|
|
1373
|
+
h.partialReasoning.set(key, { turn: data.turn, step: data.step, index: chunk.index, text: '' })
|
|
1374
|
+
changed = true
|
|
1375
|
+
} else if (chunk.type === 'reasoning-delta') {
|
|
1376
|
+
const item = h.partialReasoning.get(key) || { turn: data.turn, step: data.step, index: chunk.index, text: '' }
|
|
1377
|
+
item.text += String(chunk.text || '')
|
|
1378
|
+
h.partialReasoning.set(key, item)
|
|
1379
|
+
changed = true
|
|
1380
|
+
} else if (chunk.type === 'block-end' && chunk.block?.type === 'reasoning') {
|
|
1381
|
+
h.partialReasoning.set(key, { turn: data.turn, step: data.step, index: chunk.index, text: String(chunk.block.text ?? chunk.block.content ?? '') })
|
|
1382
|
+
changed = true
|
|
1383
|
+
}
|
|
1384
|
+
} else if (event?.type === 'reasoning-chunks') {
|
|
1385
|
+
const key = reasoningStreamKey(data, data.index)
|
|
1386
|
+
const item = h.partialReasoning.get(key) || { turn: data.turn, step: data.step, index: data.index, text: '' }
|
|
1387
|
+
item.text += Array.isArray(data.texts) ? data.texts.join('') : String(data.text || '')
|
|
1388
|
+
h.partialReasoning.set(key, item)
|
|
1389
|
+
changed = true
|
|
1390
|
+
} else if (event?.type === 'assistant/message') {
|
|
1391
|
+
for (const [key, item] of h.partialReasoning) {
|
|
1392
|
+
if (item.turn === data.turn && item.step === data.step) { h.partialReasoning.delete(key); changed = true }
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
return changed
|
|
1396
|
+
}
|
|
1397
|
+
let reasoningRenderTimer = null
|
|
1398
|
+
function scheduleReasoningRender() {
|
|
1399
|
+
if (reasoningRenderTimer) return
|
|
1400
|
+
reasoningRenderTimer = setTimeout(() => {
|
|
1401
|
+
reasoningRenderTimer = null
|
|
1402
|
+
if (state.current) renderHistory()
|
|
1403
|
+
}, 80)
|
|
1404
|
+
}
|
|
1405
|
+
function partialReasoningHtml() {
|
|
1406
|
+
return [...state.history.partialReasoning.values()]
|
|
1407
|
+
.filter(item => item.text)
|
|
1408
|
+
.sort((a, b) => (a.turn ?? 0) - (b.turn ?? 0) || (a.step ?? 0) - (b.step ?? 0) || (a.index ?? 0) - (b.index ?? 0))
|
|
1409
|
+
.map(item => `<div class="ds-msg assistant ds-reasoning-live"><div class="role">${esc(t('ds.role.dsh'))}</div><details open><summary>${esc(t('block.thinkingLive'))}</summary><div>${esc(item.text)}</div></details></div>`)
|
|
1410
|
+
.join('')
|
|
1411
|
+
}
|
|
1325
1412
|
function safeJson(v) { try { return JSON.stringify(v, null, 2) } catch { return String(v) } }
|
|
1326
1413
|
function blockHtml(b) {
|
|
1327
1414
|
if (!b) return ''
|
|
1328
1415
|
if (b.type === 'text') return `<div class="md">${window.mdToHtml ? window.mdToHtml(b.text ?? '') : esc(b.text ?? '')}</div>`
|
|
1329
|
-
if (b.type === 'reasoning') return `<
|
|
1416
|
+
if (b.type === 'thinking' || b.type === 'reasoning') return `<details><summary>${esc(t('block.thinking'))}</summary><div style="opacity:.82">${esc(b.text ?? b.content ?? '')}</div></details>`
|
|
1330
1417
|
if (b.type === 'tool-call') return `<div>🔧 ${esc(b.name || '')}</div>`
|
|
1331
1418
|
if (b.type === 'tool-result') return `<div>📦</div>`
|
|
1332
1419
|
if (b.type === 'image') return `<div>🖼</div>`
|
|
@@ -1373,7 +1460,8 @@ function eventHtml(entry) {
|
|
|
1373
1460
|
function renderHistory() {
|
|
1374
1461
|
const box = $('history')
|
|
1375
1462
|
const items = state.history.visible
|
|
1376
|
-
|
|
1463
|
+
const html = items.map(eventHtml).join('') + partialReasoningHtml()
|
|
1464
|
+
box.innerHTML = html || `<div class="ds-empty">${t('ds.historyEmpty')}</div>`
|
|
1377
1465
|
box.scrollTop = box.scrollHeight
|
|
1378
1466
|
}
|
|
1379
1467
|
|
|
@@ -1838,7 +1926,7 @@ function renderWorkbench() {
|
|
|
1838
1926
|
let html = `<div class="ds-wb-panel-title">${esc(t('wb.projects'))}</div>`
|
|
1839
1927
|
html += projects.length ? projects.map(w => {
|
|
1840
1928
|
const id = String(w.workspaceId || '')
|
|
1841
|
-
const sessions = (w.sessionIds || []).map(sid => state.byId.get(sid)).filter(
|
|
1929
|
+
const sessions = (w.sessionIds || []).map(sid => state.byId.get(sid)).filter(isTopLevelSession).filter(s => !archivedSet.has(s.sessionId)).sort((a, b) => (b.updatedAt || 0) - (a.updatedAt || 0))
|
|
1842
1930
|
const open = state.wb.open === id
|
|
1843
1931
|
return `<div class="ds-wb-project ${open ? 'open' : ''}">
|
|
1844
1932
|
<button type="button" class="ds-wb-project-head" data-wb-head="${esc(id)}">
|
|
@@ -2060,8 +2148,9 @@ function renderOverviewDesktop() {
|
|
|
2060
2148
|
$('ds-overview-attention-list').querySelectorAll('[data-ds-overview-approve]').forEach(btn => btn.addEventListener('click', () => approveApproval(btn.closest('[data-ds-overview-approval]')?.dataset.dsOverviewApproval || '', btn.dataset.dsOverviewApprove === '1')))
|
|
2061
2149
|
$('ds-overview-attention-list').querySelectorAll('[data-ds-overview-question]').forEach(btn => btn.addEventListener('click', () => openQuestionModal(state.questions.find(q => q.rpcId === btn.dataset.dsOverviewQuestion))))
|
|
2062
2150
|
|
|
2063
|
-
const
|
|
2064
|
-
const
|
|
2151
|
+
const topSessions = topLevelSessions()
|
|
2152
|
+
const running = topSessions.filter(s => s.running).length
|
|
2153
|
+
const sessions = topSessions.sort((a, b) => Number(b.running) - Number(a.running) || (new Date(b.updatedAt || 0) - new Date(a.updatedAt || 0))).slice(0, 6)
|
|
2065
2154
|
const primary = $('ds-overview-primary-action')
|
|
2066
2155
|
if (primary) {
|
|
2067
2156
|
let action = 'new'
|
package/public/index.html
CHANGED
|
@@ -698,6 +698,25 @@
|
|
|
698
698
|
</div>
|
|
699
699
|
</div>
|
|
700
700
|
|
|
701
|
+
<!-- App / 网关版本差异提醒 -->
|
|
702
|
+
<div id="modal-app-version-warning" class="modal hidden" role="dialog" aria-modal="true" aria-labelledby="app-version-warning-title">
|
|
703
|
+
<div class="modal-card app-version-warning-card">
|
|
704
|
+
<div class="app-version-warning-icon" aria-hidden="true">↑</div>
|
|
705
|
+
<div id="app-version-warning-title" class="modal-title app-version-warning-title" data-i18n="update.gatewayAheadTitle">App 版本需要更新</div>
|
|
706
|
+
<div class="modal-body app-version-warning-body" data-i18n="update.gatewayAheadBody">网关已经更新,但当前 App 仍是较旧版本,部分功能可能缺失或显示异常。</div>
|
|
707
|
+
<div class="app-version-comparison" aria-label="App and gateway versions">
|
|
708
|
+
<div><span data-i18n="update.appVersion">App 版本</span><strong id="app-version-current">—</strong></div>
|
|
709
|
+
<i aria-hidden="true">→</i>
|
|
710
|
+
<div><span data-i18n="update.gatewayVersion">网关版本</span><strong id="app-version-gateway">—</strong></div>
|
|
711
|
+
</div>
|
|
712
|
+
<div class="app-version-warning-hint" data-i18n="update.gatewayAheadHint">建议同步更新 App,以保持与网关功能一致。</div>
|
|
713
|
+
<div class="modal-actions">
|
|
714
|
+
<button id="app-version-later" class="btn subtle" data-i18n="update.remindLater">稍后提醒</button>
|
|
715
|
+
<button id="app-version-update" class="btn primary" data-i18n="update.checkNow">立即检查更新</button>
|
|
716
|
+
</div>
|
|
717
|
+
</div>
|
|
718
|
+
</div>
|
|
719
|
+
|
|
701
720
|
<!-- 更新内容模态 -->
|
|
702
721
|
<div id="modal-notes" class="modal hidden" role="dialog" aria-modal="true">
|
|
703
722
|
<div class="modal-card">
|
|
@@ -823,7 +842,7 @@
|
|
|
823
842
|
'tool.default': '工具', 'tool.unknown': '未知工具', 'tool.result': '结果',
|
|
824
843
|
'event.taskStart': '▶ 任务开始', 'event.taskEnd': '■ 任务结束',
|
|
825
844
|
'event.systemReminder': '系统提示',
|
|
826
|
-
'block.image': '图片', 'block.thinking': '🧠 思考过程', 'block.toolCall': '工具调用', 'block.toolResult': '工具结果', 'block.unknown': '块 · {type}',
|
|
845
|
+
'block.image': '图片', 'block.thinking': '🧠 思考过程', 'block.thinkingLive': '🧠 思考中…', 'block.toolCall': '工具调用', 'block.toolResult': '工具结果', 'block.unknown': '块 · {type}',
|
|
827
846
|
'truncated': '…(截断)',
|
|
828
847
|
'stats.title': '本轮统计', 'stats.roundTitle': '本轮统计', 'stats.turnsSteps': '轮次 / 步骤',
|
|
829
848
|
'stats.llmTime': '模型耗时', 'stats.minutes': ' 分钟', 'stats.outputCache': '输出 / 缓存读',
|
|
@@ -838,7 +857,7 @@
|
|
|
838
857
|
'send.failed': '发送失败', 'send.commandSent': '指令已发送', 'send.sent': '已发送', 'send.imageSent': '图片已发送', 'send.commandExecuted': '命令已执行',
|
|
839
858
|
'models.loading': '模型加载中…', 'models.loadFailed': '模型列表加载失败:{msg}', 'models.unavailable': '不可用', 'models.none': '没有可用模型',
|
|
840
859
|
'models.switchFailed': '切换模型失败', 'models.switched': '已切换模型:{model}',
|
|
841
|
-
'models.effortFailed': '切换思考深度失败', 'models.effortSwitched': '思考深度:{effort}',
|
|
860
|
+
'models.effortFailed': '切换思考深度失败', 'models.effortSwitched': '思考深度:{effort}', 'models.effortLow': '低', 'models.effortHigh': '高', 'models.effortMax': '极高', 'models.effortOff': '关闭', 'models.effortCustomHint': '该路由未公布档位,按 DSH 兼容值尝试;不支持时不会更改当前设置。',
|
|
842
861
|
'menu.commandsTitle': '输入指令',
|
|
843
862
|
'menu.compact': '/compact 压缩对话历史', 'menu.export': '/export 导出会话日志 ZIP',
|
|
844
863
|
'menu.feedback': '/feedback 反馈当前会话', 'menu.goal': '/goal 设置/查看任务目标',
|
|
@@ -880,6 +899,9 @@
|
|
|
880
899
|
'update.corrupted': '下载文件损坏,请重试', 'update.serverFileMissing': '服务器上还没有对应版本的文件,请稍后再试',
|
|
881
900
|
'update.installUnsupported': '当前版本不支持 App 内安装,已转浏览器下载',
|
|
882
901
|
'update.expand': '展开', 'update.collapse': '收起',
|
|
902
|
+
'update.gatewayAheadTitle': 'App 版本需要更新', 'update.gatewayAheadBody': '网关已经更新,但当前 App 仍是较旧版本,部分功能可能缺失或显示异常。',
|
|
903
|
+
'update.appVersion': 'App 版本', 'update.gatewayVersion': '网关版本', 'update.gatewayAheadHint': '建议同步更新 App,以保持与网关功能一致。',
|
|
904
|
+
'update.remindLater': '稍后提醒', 'update.checkNow': '立即检查更新',
|
|
883
905
|
'notes.title': '更新内容', 'notes.close': '关闭', 'notes.page': '版本号 {current}/{total}',
|
|
884
906
|
'scan.imageLoadFailed': '图片加载失败', 'scan.decodeUnsupported': '当前设备不支持图片解码',
|
|
885
907
|
'scan.browserHint': '浏览器请打开主机管理页,用手机相机扫码', 'scan.unsupported': '当前 App 版本不支持拍照扫码,请先更新 App',
|
|
@@ -1031,7 +1053,7 @@
|
|
|
1031
1053
|
'tool.default': 'tool', 'tool.unknown': 'Unknown tool', 'tool.result': 'Result',
|
|
1032
1054
|
'event.taskStart': '▶ Task started', 'event.taskEnd': '■ Task finished',
|
|
1033
1055
|
'event.systemReminder': 'System reminder',
|
|
1034
|
-
'block.image': 'Image', 'block.thinking': '🧠 Thinking', 'block.toolCall': 'Tool call', 'block.toolResult': 'Tool result', 'block.unknown': 'Block · {type}',
|
|
1056
|
+
'block.image': 'Image', 'block.thinking': '🧠 Thinking', 'block.thinkingLive': '🧠 Thinking…', 'block.toolCall': 'Tool call', 'block.toolResult': 'Tool result', 'block.unknown': 'Block · {type}',
|
|
1035
1057
|
'truncated': '…(truncated)',
|
|
1036
1058
|
'stats.title': 'This round', 'stats.roundTitle': 'This round', 'stats.turnsSteps': 'Turns / steps',
|
|
1037
1059
|
'stats.llmTime': 'LLM time', 'stats.minutes': ' min', 'stats.outputCache': 'Output / cache read',
|
|
@@ -1046,7 +1068,7 @@
|
|
|
1046
1068
|
'send.failed': 'Send failed', 'send.commandSent': 'Command sent', 'send.sent': 'Sent', 'send.imageSent': 'Image sent', 'send.commandExecuted': 'Command executed',
|
|
1047
1069
|
'models.loading': 'Loading models…', 'models.loadFailed': 'Failed to load models: {msg}', 'models.unavailable': 'unavailable', 'models.none': 'No models available',
|
|
1048
1070
|
'models.switchFailed': 'Model switch failed', 'models.switched': 'Switched model: {model}',
|
|
1049
|
-
'models.effortFailed': 'Failed to switch reasoning effort', 'models.effortSwitched': 'Reasoning effort: {effort}',
|
|
1071
|
+
'models.effortFailed': 'Failed to switch reasoning effort', 'models.effortSwitched': 'Reasoning effort: {effort}', 'models.effortLow': 'Low', 'models.effortHigh': 'High', 'models.effortMax': 'Max', 'models.effortOff': 'Off', 'models.effortCustomHint': 'This route does not publish effort metadata. DSH compatibility values are tried; unsupported values leave the current setting unchanged.',
|
|
1050
1072
|
'menu.commandsTitle': 'Commands',
|
|
1051
1073
|
'menu.compact': '/compact Compress conversation history', 'menu.export': '/export Export session log ZIP',
|
|
1052
1074
|
'menu.feedback': '/feedback Feedback current session', 'menu.goal': '/goal Set/view task goal',
|
|
@@ -1088,6 +1110,9 @@
|
|
|
1088
1110
|
'update.corrupted': 'Downloaded file is corrupted, please retry', 'update.serverFileMissing': 'The file for this version is not on the server yet, please try again later',
|
|
1089
1111
|
'update.installUnsupported': 'This version cannot install in-app, opening browser download',
|
|
1090
1112
|
'update.expand': 'Expand', 'update.collapse': 'Collapse',
|
|
1113
|
+
'update.gatewayAheadTitle': 'App update recommended', 'update.gatewayAheadBody': 'The gateway is newer than this App. Some features may be missing or display incorrectly.',
|
|
1114
|
+
'update.appVersion': 'App version', 'update.gatewayVersion': 'Gateway version', 'update.gatewayAheadHint': 'Update the App to keep its features aligned with the gateway.',
|
|
1115
|
+
'update.remindLater': 'Remind me later', 'update.checkNow': 'Check for updates',
|
|
1091
1116
|
'notes.title': 'What\'s New', 'notes.close': 'Close', 'notes.page': 'Version {current}/{total}',
|
|
1092
1117
|
'scan.imageLoadFailed': 'Image failed to load', 'scan.decodeUnsupported': 'This device cannot decode images',
|
|
1093
1118
|
'scan.browserHint': 'In a browser, open the host admin page and scan with your phone camera', 'scan.unsupported': 'This app version cannot scan, please update first',
|
package/public/styles.css
CHANGED
|
@@ -3,6 +3,12 @@
|
|
|
3
3
|
|
|
4
4
|
@import 'theme-vars.css';
|
|
5
5
|
|
|
6
|
+
/* 思考流与兼容档位提示 */
|
|
7
|
+
.effort-hint { flex-basis: 100%; color: var(--dsr-muted); font-size: 11px; line-height: 1.45; }
|
|
8
|
+
.msg.reasoning-live { border-color: var(--dsr-accent-line); box-shadow: 0 0 0 1px var(--dsr-accent-soft) inset; }
|
|
9
|
+
.msg.reasoning-live summary::after { content: ''; display: inline-block; width: 6px; height: 6px; margin-left: 7px; border-radius: 50%; background: var(--dsr-accent); animation: reasoning-pulse 1.2s ease-in-out infinite; }
|
|
10
|
+
@keyframes reasoning-pulse { 0%, 100% { opacity: .35; transform: scale(.8); } 50% { opacity: 1; transform: scale(1); } }
|
|
11
|
+
|
|
6
12
|
|
|
7
13
|
/* ============ 基础 ============ */
|
|
8
14
|
* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
|
|
@@ -900,6 +906,23 @@ button.overview-attention-item, button.overview-session-item { cursor:pointer; }
|
|
|
900
906
|
.feedback-success-card .modal-actions { justify-content: stretch; }
|
|
901
907
|
.feedback-success-card .modal-actions .btn { flex: 1; min-height: 42px; }
|
|
902
908
|
|
|
909
|
+
.app-version-warning-card { max-width: 390px; text-align: center; padding: 22px 20px 18px; border-top: 3px solid var(--dsr-accent); }
|
|
910
|
+
.app-version-warning-icon {
|
|
911
|
+
width: 50px; height: 50px; display: grid; place-items: center; margin: 0 auto 12px; border-radius: 15px;
|
|
912
|
+
color: var(--dsr-accent-strong); background: var(--dsr-accent-soft); border: 1px solid var(--dsr-accent-line);
|
|
913
|
+
font-size: 28px; font-weight: 800; line-height: 1;
|
|
914
|
+
}
|
|
915
|
+
.app-version-warning-title { margin-bottom: 7px; font-size: 18px; }
|
|
916
|
+
.app-version-warning-body, .app-version-warning-hint { color: var(--dsr-muted); line-height: 1.6; }
|
|
917
|
+
.app-version-comparison { display: grid; grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); align-items: center; gap: 10px; margin: 16px 0 12px; }
|
|
918
|
+
.app-version-comparison > div { display: grid; gap: 5px; min-width: 0; padding: 11px 8px; border: 1px solid var(--dsr-line); border-radius: 12px; background: var(--dsr-panel); }
|
|
919
|
+
.app-version-comparison span { color: var(--dsr-muted); font-size: 11px; }
|
|
920
|
+
.app-version-comparison strong { overflow-wrap: anywhere; color: var(--dsr-text); font-size: 14px; }
|
|
921
|
+
.app-version-comparison > i { color: var(--dsr-accent-strong); font-style: normal; font-weight: 800; }
|
|
922
|
+
.app-version-warning-hint { font-size: 12px; }
|
|
923
|
+
.app-version-warning-card .modal-actions { justify-content: stretch; }
|
|
924
|
+
.app-version-warning-card .modal-actions .btn { flex: 1; min-height: 42px; }
|
|
925
|
+
|
|
903
926
|
/* ---------- 模态 ---------- */
|
|
904
927
|
.modal {
|
|
905
928
|
position: fixed; inset: 0; z-index: 40; display: grid; place-items: center;
|
package/public/update.json
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.6.
|
|
2
|
+
"version": "0.6.13",
|
|
3
3
|
"apkUrl": "dsh-remote.apk",
|
|
4
|
-
"sha256": "
|
|
5
|
-
"releasedAt": "2026-08-
|
|
6
|
-
"notes": "0.6.
|
|
4
|
+
"sha256": "6cee7bfb1425fb26bcfbcd5c5f932b8ff26ca9d5026ac199b5393a00d0c343b1",
|
|
5
|
+
"releasedAt": "2026-08-24T05:49:23.534Z",
|
|
6
|
+
"notes": "0.6.13 正式版:新增首次连接 Doctor,集中检查 DSH 服务、远程网关、局域网地址、防火墙、终端配对和实时消息通道;网关控制台新增可选的独立设备密钥,支持设备备注、最近 IP、二维码、令牌轮换、复制和退出,共享令牌在启用后仅保留管理权限;手机端和桌面端支持实时思考内容,并为未声明推理档位的模型提供 low、high、max 三档选择;普通会话列表、工作区树和主页统计不再混入子代理内部会话;Android App 落后于网关版本时显示明确更新提醒;新增网关协议与能力协商并兼容旧网关,同时补充设备隔离、重启持久化、推理显示、会话过滤和版本差异回归测试。",
|
|
7
7
|
"history": [
|
|
8
|
+
{
|
|
9
|
+
"version": "0.6.13",
|
|
10
|
+
"notes": "0.6.13 正式版:新增首次连接 Doctor,集中检查 DSH 服务、远程网关、局域网地址、防火墙、终端配对和实时消息通道;网关控制台新增可选的独立设备密钥,支持设备备注、最近 IP、二维码、令牌轮换、复制和退出,共享令牌在启用后仅保留管理权限;手机端和桌面端支持实时思考内容,并为未声明推理档位的模型提供 low、high、max 三档选择;普通会话列表、工作区树和主页统计不再混入子代理内部会话;Android App 落后于网关版本时显示明确更新提醒;新增网关协议与能力协商并兼容旧网关,同时补充设备隔离、重启持久化、推理显示、会话过滤和版本差异回归测试。"
|
|
11
|
+
},
|
|
8
12
|
{
|
|
9
13
|
"version": "0.6.12",
|
|
10
14
|
"notes": "0.6.12:修复远程启动和重启 DSH 时统一报 HTTP 502 且无法确认结果的问题;改为异步追踪 systemd 服务检查、命令提交、进程启动、DSH HTTP 恢复和 mux/host 实时通道重连阶段,成功时显示 PID、HTTP 状态和用时,失败时区分服务不存在、systemd 不可用、权限不足、命令失败、服务失败、启动超时、HTTP 恢复超时和实时通道恢复超时;优化工作区会话筛选、文件预览和应用内选择器;新增中央投票公告与反馈成功确认;主页公告栏常驻并在无未读公告时显示空状态,同时修正设置页异常右箭头、主页刷新图标居中和周末谷时提醒。"
|
|
@@ -40,10 +44,6 @@
|
|
|
40
44
|
{
|
|
41
45
|
"version": "0.6.4",
|
|
42
46
|
"notes": "扫码配对改为显式双按钮(拍照/相册),修复小米等 ROM 选择器错乱;会话历史加载超时放宽至 45 秒,失败可重试;网关端口自定义(插件管理页可改,环境变量仍优先)+ 启动前端口占用检测;峰谷计费提醒改前台服务驱动,后台按时必达;消息 Markdown 渲染(标题/代码块/列表/链接);修复 DSH 重启后网关上游端口不刷新(Issue #1);新增测试通知按钮。"
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
"version": "0.6.3",
|
|
46
|
-
"notes": "修复 DSH 重启后网关 upstream 端口不刷新(Issue #1):/health 增加 upstream/upstreamOk/pid 探测;插件 ensureGateway 检测上游变化或不可达时自动杀旧网关并按新 DSH_UPSTREAM 重启;启动/刷新写插件日志与 PID 文件。"
|
|
47
47
|
}
|
|
48
48
|
]
|
|
49
49
|
}
|
package/public/version.json
CHANGED