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
package/public/app.js
CHANGED
|
@@ -55,6 +55,7 @@ const state = {
|
|
|
55
55
|
autoSelect: { '默认': true }, // 组内自动测速选优 / 手动指定
|
|
56
56
|
groupActive: { '默认': '' }, // 每组当前生效的 server id(手动模式)
|
|
57
57
|
serverLatency: {}, // url -> 最近一次 /health 测速毫秒数
|
|
58
|
+
gatewayHealth: {}, // url -> /health 协议版本与能力声明
|
|
58
59
|
selectingServer: false, // 防重入: 测速/切换中
|
|
59
60
|
sessions: [],
|
|
60
61
|
sessionSort: LS.get('sessionSort', 'time') === 'workspace' ? 'workspace' : 'time',
|
|
@@ -64,6 +65,7 @@ const state = {
|
|
|
64
65
|
hostInfo: null,
|
|
65
66
|
localVersion: '',
|
|
66
67
|
updateInfo: null,
|
|
68
|
+
warnedGatewayVersions: new Set(),
|
|
67
69
|
announcement: null,
|
|
68
70
|
announcements: [],
|
|
69
71
|
approvals: [], // 待处理审批
|
|
@@ -355,6 +357,7 @@ async function getWsTicket() {
|
|
|
355
357
|
const token = state.token
|
|
356
358
|
const server = state.server
|
|
357
359
|
wsTicketPromise = (async () => {
|
|
360
|
+
if (activeGatewayCapability('wsTicket') === false) throw new Error('ws ticket unsupported')
|
|
358
361
|
const res = await fetch(apiUrl('/api/ws-ticket'), {
|
|
359
362
|
method: 'POST',
|
|
360
363
|
headers: {
|
|
@@ -621,7 +624,10 @@ async function pingServer(base) {
|
|
|
621
624
|
const timer = setTimeout(() => ctrl.abort(), 3500)
|
|
622
625
|
try {
|
|
623
626
|
const res = await fetch(u + '/health?t=' + Date.now(), { signal: ctrl.signal, cache: 'no-store' })
|
|
624
|
-
|
|
627
|
+
if (!res.ok) return Infinity
|
|
628
|
+
const health = await res.json().catch(() => null)
|
|
629
|
+
if (health && typeof health === 'object') state.gatewayHealth[u] = health
|
|
630
|
+
return Math.round(performance.now() - t0)
|
|
625
631
|
} catch {
|
|
626
632
|
return Infinity
|
|
627
633
|
} finally {
|
|
@@ -629,6 +635,13 @@ async function pingServer(base) {
|
|
|
629
635
|
}
|
|
630
636
|
}
|
|
631
637
|
|
|
638
|
+
function activeGatewayCapability(name) {
|
|
639
|
+
const key = String(state.server || location.origin || '').replace(/\/+$/, '')
|
|
640
|
+
const capabilities = state.gatewayHealth[key]?.capabilities
|
|
641
|
+
if (!capabilities || !Object.prototype.hasOwnProperty.call(capabilities, name)) return null
|
|
642
|
+
return Number(capabilities[name]) > 0
|
|
643
|
+
}
|
|
644
|
+
|
|
632
645
|
async function selectFastestServer({ silent = false, reconnect = true } = {}) {
|
|
633
646
|
if (state.selectingServer) return null
|
|
634
647
|
state.selectingServer = true
|
|
@@ -677,6 +690,7 @@ async function selectFastestServer({ silent = false, reconnect = true } = {}) {
|
|
|
677
690
|
else if (chosen) toast(t('speed.manualUsing', { url: chosen, ms: Number.isFinite(ms) ? ms : '—' }), 'ok')
|
|
678
691
|
else toast(t('speed.allDown'), 'err')
|
|
679
692
|
}
|
|
693
|
+
await maybeWarnAppBehindGateway()
|
|
680
694
|
return chosen
|
|
681
695
|
} finally {
|
|
682
696
|
state.selectingServer = false
|
|
@@ -1343,6 +1357,10 @@ function applyProjection(sessionId, key, value, seq) {
|
|
|
1343
1357
|
}
|
|
1344
1358
|
function titleOf(s) { return proj(s, 'title') || (s?.sessionId ? short(s.sessionId) : t('session.unknown')) }
|
|
1345
1359
|
function short(id) { return '…' + String(id).slice(-8) }
|
|
1360
|
+
function isTopLevelSession(session) {
|
|
1361
|
+
return !!session && !session.parentSessionId && session.origin !== 'subagent'
|
|
1362
|
+
}
|
|
1363
|
+
function topLevelSessions() { return state.sessions.filter(isTopLevelSession) }
|
|
1346
1364
|
const GOAL_TERMINAL_PHASES = new Set(['complete', 'cleared'])
|
|
1347
1365
|
function isGoalTerminal(goal) {
|
|
1348
1366
|
return !!goal && GOAL_TERMINAL_PHASES.has(goal.phase)
|
|
@@ -1523,7 +1541,7 @@ function renderWorkbench() {
|
|
|
1523
1541
|
panel.innerHTML = projects.map(w => {
|
|
1524
1542
|
const id = String(w.workspaceId || '')
|
|
1525
1543
|
const open = !!state.wbOpenProjects[id]
|
|
1526
|
-
const sessions = (w.sessionIds || []).map(sid => state.byId.get(sid)).filter(
|
|
1544
|
+
const sessions = (w.sessionIds || []).map(sid => state.byId.get(sid)).filter(isTopLevelSession).filter(s => !archivedSet.has(s.sessionId))
|
|
1527
1545
|
const body = open ? `<div class="wb-sessions">${sessions.length ? sessions.map(s => `
|
|
1528
1546
|
<div class="session-swipe" data-session-swipe data-id="${esc(s.sessionId)}">
|
|
1529
1547
|
<button class="wb-session" type="button" data-wb-session="${esc(s.sessionId)}">
|
|
@@ -1559,7 +1577,7 @@ function workspaceDisplayName(label) {
|
|
|
1559
1577
|
return parts[parts.length - 1] || value
|
|
1560
1578
|
}
|
|
1561
1579
|
function sortedSessions() {
|
|
1562
|
-
const items =
|
|
1580
|
+
const items = topLevelSessions()
|
|
1563
1581
|
if (state.sessionSort === 'workspace') {
|
|
1564
1582
|
return items.sort((a, b) => {
|
|
1565
1583
|
const aw = sessionCwd(a) || '\uffff'
|
|
@@ -1626,7 +1644,7 @@ function renderSessions() {
|
|
|
1626
1644
|
const sort = $('session-sort')
|
|
1627
1645
|
if (sort) { sort.value = state.sessionSort; syncCustomSelect(sort) }
|
|
1628
1646
|
$('home-empty').classList.toggle('hidden', visible.length > 0)
|
|
1629
|
-
const running =
|
|
1647
|
+
const running = topLevelSessions().filter(s => s.running).length
|
|
1630
1648
|
const pending = state.approvals.length + state.questions.length
|
|
1631
1649
|
$('stat-strip').innerHTML = `
|
|
1632
1650
|
<div class="stat running"><div class="v">${running}</div><div class="k">${t('sessions.statRunning')}</div></div>
|
|
@@ -1668,7 +1686,7 @@ function bindNativeBack() {
|
|
|
1668
1686
|
if ($('composer-wrap')?.classList.contains('fs')) { setComposerFullscreen(false); return }
|
|
1669
1687
|
if (customSelectCurrent) { closeCustomSelect(); return }
|
|
1670
1688
|
const openModal = [...document.querySelectorAll('.modal')].find(m => !m.classList.contains('hidden'))
|
|
1671
|
-
if (openModal) { if (openModal.id === 'modal-notes') closeNotesModal(); else if (openModal.id === 'modal-archive') closeArchiveConfirm(); else openModal.classList.add('hidden'); return } // 先关弹窗
|
|
1689
|
+
if (openModal) { if (openModal.id === 'modal-notes') closeNotesModal(); else if (openModal.id === 'modal-archive') closeArchiveConfirm(); else if (openModal.id === 'modal-app-version-warning') closeAppVersionWarning(false); else openModal.classList.add('hidden'); return } // 先关弹窗
|
|
1672
1690
|
if (document.body.classList.contains('in-session')) { closeSession(); return } // 会话页 → 回主页
|
|
1673
1691
|
if (!$('view-files').classList.contains('hidden')) { // 文件页 → 上级目录 → 主页
|
|
1674
1692
|
if (state.fs.path && state.fs.initial && state.fs.path !== state.fs.initial) { fsUp(); return }
|
|
@@ -1716,10 +1734,78 @@ const HISTORY_MAX_VISIBLE = 5000 // 已加载的可显示事件上限(消息/
|
|
|
1716
1734
|
function emptyHistory() {
|
|
1717
1735
|
return {
|
|
1718
1736
|
visible: [], seqs: new Set(), minSeq: Infinity,
|
|
1719
|
-
hasMore: false, loading: false, renderStart: 0, renderEnd: 0
|
|
1737
|
+
hasMore: false, loading: false, renderStart: 0, renderEnd: 0,
|
|
1738
|
+
partialReasoning: new Map()
|
|
1720
1739
|
}
|
|
1721
1740
|
}
|
|
1722
1741
|
|
|
1742
|
+
function reasoningStreamKey(data, index) {
|
|
1743
|
+
return `${data?.turn ?? '?'}:${data?.step ?? '?'}:${index ?? '?'}`
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1746
|
+
/**
|
|
1747
|
+
* DSH 的实时思考以 assistant/chunk 下发,历史尾页可能把增量压成
|
|
1748
|
+
* reasoning-chunks。最终 assistant/message 到达后再由正式消息接管展示。
|
|
1749
|
+
*/
|
|
1750
|
+
function applyReasoningStreamEvent(event) {
|
|
1751
|
+
const h = state.history
|
|
1752
|
+
const data = event?.data || {}
|
|
1753
|
+
let changed = false
|
|
1754
|
+
if (event?.type === 'assistant/chunk') {
|
|
1755
|
+
const chunk = data.chunk || {}
|
|
1756
|
+
const key = reasoningStreamKey(data, chunk.index)
|
|
1757
|
+
if (chunk.type === 'block-start' && chunk.blockType === 'reasoning') {
|
|
1758
|
+
h.partialReasoning.set(key, { turn: data.turn, step: data.step, index: chunk.index, text: '' })
|
|
1759
|
+
changed = true
|
|
1760
|
+
} else if (chunk.type === 'reasoning-delta') {
|
|
1761
|
+
const item = h.partialReasoning.get(key) || { turn: data.turn, step: data.step, index: chunk.index, text: '' }
|
|
1762
|
+
item.text += String(chunk.text || '')
|
|
1763
|
+
h.partialReasoning.set(key, item)
|
|
1764
|
+
changed = true
|
|
1765
|
+
} else if (chunk.type === 'block-end' && chunk.block?.type === 'reasoning') {
|
|
1766
|
+
h.partialReasoning.set(key, {
|
|
1767
|
+
turn: data.turn, step: data.step, index: chunk.index,
|
|
1768
|
+
text: String(chunk.block.text ?? chunk.block.content ?? '')
|
|
1769
|
+
})
|
|
1770
|
+
changed = true
|
|
1771
|
+
}
|
|
1772
|
+
} else if (event?.type === 'reasoning-chunks') {
|
|
1773
|
+
const key = reasoningStreamKey(data, data.index)
|
|
1774
|
+
const item = h.partialReasoning.get(key) || { turn: data.turn, step: data.step, index: data.index, text: '' }
|
|
1775
|
+
item.text += Array.isArray(data.texts) ? data.texts.join('') : String(data.text || '')
|
|
1776
|
+
h.partialReasoning.set(key, item)
|
|
1777
|
+
changed = true
|
|
1778
|
+
} else if (event?.type === 'assistant/message') {
|
|
1779
|
+
for (const [key, item] of h.partialReasoning) {
|
|
1780
|
+
if (item.turn === data.turn && item.step === data.step) {
|
|
1781
|
+
h.partialReasoning.delete(key)
|
|
1782
|
+
changed = true
|
|
1783
|
+
}
|
|
1784
|
+
}
|
|
1785
|
+
}
|
|
1786
|
+
return changed
|
|
1787
|
+
}
|
|
1788
|
+
|
|
1789
|
+
let reasoningRenderTimer = null
|
|
1790
|
+
function scheduleReasoningRender() {
|
|
1791
|
+
if (reasoningRenderTimer) return
|
|
1792
|
+
reasoningRenderTimer = setTimeout(() => {
|
|
1793
|
+
reasoningRenderTimer = null
|
|
1794
|
+
if (!state.current) return
|
|
1795
|
+
const box = $('history')
|
|
1796
|
+
const nearBottom = box.scrollHeight - box.scrollTop - box.clientHeight < 240
|
|
1797
|
+
renderHistory(false, nearBottom ? 'bottom' : 'fixed')
|
|
1798
|
+
}, 80)
|
|
1799
|
+
}
|
|
1800
|
+
|
|
1801
|
+
function partialReasoningHtml() {
|
|
1802
|
+
return [...state.history.partialReasoning.values()]
|
|
1803
|
+
.filter(item => item.text)
|
|
1804
|
+
.sort((a, b) => (a.turn ?? 0) - (b.turn ?? 0) || (a.step ?? 0) - (b.step ?? 0) || (a.index ?? 0) - (b.index ?? 0))
|
|
1805
|
+
.map(item => `<div class="msg assistant reasoning-live"><div class="role">${esc(t('role.dsh'))}</div><details class="tool" open><summary>${esc(t('block.thinkingLive'))}</summary><div class="tool-text">${esc(truncate(item.text, 12000))}</div></details></div>`)
|
|
1806
|
+
.join('')
|
|
1807
|
+
}
|
|
1808
|
+
|
|
1723
1809
|
function trimVisible() {
|
|
1724
1810
|
const h = state.history
|
|
1725
1811
|
if (h.visible.length <= HISTORY_MAX_VISIBLE) return
|
|
@@ -1806,9 +1892,11 @@ async function loadHistory(reset) {
|
|
|
1806
1892
|
|
|
1807
1893
|
const incoming = v.events || []
|
|
1808
1894
|
let added = 0
|
|
1895
|
+
if (reset) state.history.partialReasoning.clear()
|
|
1809
1896
|
for (const entry of incoming) {
|
|
1810
1897
|
const ev = entry?.event
|
|
1811
1898
|
const seq = ev?.seq
|
|
1899
|
+
applyReasoningStreamEvent(ev)
|
|
1812
1900
|
if (seq == null || state.history.seqs.has(seq)) continue
|
|
1813
1901
|
if (!shouldShowEvent(ev.type)) continue // chunk 等内部事件不保留
|
|
1814
1902
|
state.history.seqs.add(seq)
|
|
@@ -1835,8 +1923,16 @@ async function loadHistory(reset) {
|
|
|
1835
1923
|
|
|
1836
1924
|
function insertLiveEvent(event) {
|
|
1837
1925
|
const h = state.history
|
|
1926
|
+
const reasoningChanged = applyReasoningStreamEvent(event)
|
|
1927
|
+
if (event?.type === 'assistant/chunk' || event?.type === 'reasoning-chunks') {
|
|
1928
|
+
if (reasoningChanged) scheduleReasoningRender()
|
|
1929
|
+
return
|
|
1930
|
+
}
|
|
1838
1931
|
const seq = event?.seq
|
|
1839
|
-
if (seq == null || h.seqs.has(seq) || !shouldShowEvent(event.type))
|
|
1932
|
+
if (seq == null || h.seqs.has(seq) || !shouldShowEvent(event.type)) {
|
|
1933
|
+
if (reasoningChanged) scheduleReasoningRender()
|
|
1934
|
+
return
|
|
1935
|
+
}
|
|
1840
1936
|
h.seqs.add(seq)
|
|
1841
1937
|
h.visible.push({ seq, event })
|
|
1842
1938
|
h.visible.sort((a, b) => a.seq - b.seq)
|
|
@@ -1870,7 +1966,8 @@ function renderHistory(reset, mode = 'bottom') {
|
|
|
1870
1966
|
const h = state.history
|
|
1871
1967
|
const filtered = filteredEntries()
|
|
1872
1968
|
const len = filtered.length
|
|
1873
|
-
|
|
1969
|
+
const reasoningHtml = partialReasoningHtml()
|
|
1970
|
+
if (!len && !reasoningHtml) {
|
|
1874
1971
|
box.innerHTML = '<div class="empty">' + t('history.empty') + '</div>'
|
|
1875
1972
|
h.renderStart = 0; h.renderEnd = 0
|
|
1876
1973
|
updateRail()
|
|
@@ -1891,7 +1988,7 @@ function renderHistory(reset, mode = 'bottom') {
|
|
|
1891
1988
|
const d = e.event.data || {}
|
|
1892
1989
|
if (d.callId && d.name) toolNames.set(d.callId, d.name)
|
|
1893
1990
|
}
|
|
1894
|
-
box.innerHTML = filtered.slice(start, end).map(e => eventHtml(e, { toolNames })).join('')
|
|
1991
|
+
box.innerHTML = filtered.slice(start, end).map(e => eventHtml(e, { toolNames })).join('') + reasoningHtml
|
|
1895
1992
|
if (reset || mode === 'bottom') box.scrollTop = box.scrollHeight
|
|
1896
1993
|
else if (mode === 'keep') box.scrollTop = Math.max(0, oldTop + (box.scrollHeight - oldH))
|
|
1897
1994
|
else if (mode === 'fixed') box.scrollTop = oldTop
|
|
@@ -2393,16 +2490,37 @@ function renderEffortMenu() {
|
|
|
2393
2490
|
const cur = state.models.current
|
|
2394
2491
|
const provider = (state.models.groups || []).find(g => g.id === cur?.provider)
|
|
2395
2492
|
const model = (provider?.models || []).find(m => m.id === cur?.model)
|
|
2396
|
-
const efforts = model
|
|
2397
|
-
group.classList.toggle('hidden', !efforts.length)
|
|
2493
|
+
const { efforts, defaultEffort, custom } = reasoningEffortOptions(model)
|
|
2494
|
+
group.classList.toggle('hidden', !cur || !efforts.length)
|
|
2398
2495
|
box.innerHTML = efforts.map(e => {
|
|
2399
|
-
const isCur = cur?.reasoningEffort === e.id || (!cur?.reasoningEffort && e.id ===
|
|
2496
|
+
const isCur = cur?.reasoningEffort === e.id || (!cur?.reasoningEffort && e.id === defaultEffort)
|
|
2400
2497
|
return `<button class="menu-chip ${isCur ? 'current' : ''}" data-effort="${esc(e.id)}" title="${esc(e.description || '')}">${esc(e.name || e.id)}</button>`
|
|
2401
|
-
}).join('')
|
|
2498
|
+
}).join('') + (custom ? `<span class="effort-hint">${esc(t('models.effortCustomHint'))}</span>` : '')
|
|
2402
2499
|
box.querySelectorAll('[data-effort]').forEach(btn =>
|
|
2403
2500
|
btn.addEventListener('click', () => selectSessionEffort(btn.dataset.effort)))
|
|
2404
2501
|
}
|
|
2405
2502
|
|
|
2503
|
+
function reasoningEffortOptions(model) {
|
|
2504
|
+
const raw = Array.isArray(model?.reasoning?.efforts) && model.reasoning.efforts.length
|
|
2505
|
+
? model.reasoning.efforts
|
|
2506
|
+
: (Array.isArray(model?.reasoningEfforts) && model.reasoningEfforts.length ? model.reasoningEfforts : null)
|
|
2507
|
+
const names = {
|
|
2508
|
+
low: t('models.effortLow'), high: t('models.effortHigh'), max: t('models.effortMax'), off: t('models.effortOff')
|
|
2509
|
+
}
|
|
2510
|
+
if (raw) {
|
|
2511
|
+
return {
|
|
2512
|
+
efforts: raw.map(e => typeof e === 'string' ? { id: e, name: names[e] || e } : e),
|
|
2513
|
+
defaultEffort: model?.reasoning?.defaultEffort,
|
|
2514
|
+
custom: !model?.reasoning?.efforts
|
|
2515
|
+
}
|
|
2516
|
+
}
|
|
2517
|
+
return {
|
|
2518
|
+
efforts: ['low', 'high', 'max'].map(id => ({ id, name: names[id] })),
|
|
2519
|
+
defaultEffort: undefined,
|
|
2520
|
+
custom: true
|
|
2521
|
+
}
|
|
2522
|
+
}
|
|
2523
|
+
|
|
2406
2524
|
async function selectSessionEffort(effortId) {
|
|
2407
2525
|
const cur = state.models.current
|
|
2408
2526
|
if (!state.current || !cur) return
|
|
@@ -2615,8 +2733,9 @@ function renderOverview() {
|
|
|
2615
2733
|
btn.addEventListener('click', () => openQuestionModal(state.questions.find(q => q.rpcId === btn.dataset.overviewQuestion)))
|
|
2616
2734
|
})
|
|
2617
2735
|
|
|
2618
|
-
const
|
|
2619
|
-
const
|
|
2736
|
+
const topSessions = topLevelSessions()
|
|
2737
|
+
const running = topSessions.filter(s => s.running).length
|
|
2738
|
+
const sessions = topSessions.sort((a, b) => Number(b.running) - Number(a.running) || (new Date(b.updatedAt || 0) - new Date(a.updatedAt || 0))).slice(0, 4)
|
|
2620
2739
|
const primary = $('overview-primary-action')
|
|
2621
2740
|
if (primary) {
|
|
2622
2741
|
let action = 'new'
|
|
@@ -3385,6 +3504,54 @@ function cmpVersion(a, b) {
|
|
|
3385
3504
|
return 0
|
|
3386
3505
|
}
|
|
3387
3506
|
|
|
3507
|
+
function isComparableVersion(value) {
|
|
3508
|
+
return /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(String(value || '').trim())
|
|
3509
|
+
}
|
|
3510
|
+
|
|
3511
|
+
function isAppBehindGateway(appVersion, gatewayVersion) {
|
|
3512
|
+
return isComparableVersion(appVersion) && isComparableVersion(gatewayVersion) && cmpVersion(gatewayVersion, appVersion) > 0
|
|
3513
|
+
}
|
|
3514
|
+
|
|
3515
|
+
function isAppVersionWarningOpen() {
|
|
3516
|
+
const modal = $('modal-app-version-warning')
|
|
3517
|
+
return !!modal && !modal.classList.contains('hidden')
|
|
3518
|
+
}
|
|
3519
|
+
|
|
3520
|
+
async function maybeWarnAppBehindGateway({ probe = false } = {}) {
|
|
3521
|
+
if (!CAP?.isNativePlatform?.() || !state.localVersion) return false
|
|
3522
|
+
const base = String(state.server || '').replace(/\/+$/, '')
|
|
3523
|
+
if (!base) return false
|
|
3524
|
+
let health = state.gatewayHealth[base]
|
|
3525
|
+
if (!health && probe) {
|
|
3526
|
+
await pingServer(base)
|
|
3527
|
+
health = state.gatewayHealth[base]
|
|
3528
|
+
}
|
|
3529
|
+
const gatewayVersion = String(health?.version || '').trim()
|
|
3530
|
+
if (!isAppBehindGateway(state.localVersion, gatewayVersion)) return false
|
|
3531
|
+
const warningKey = `${state.localVersion}\u0000${gatewayVersion}`
|
|
3532
|
+
if (state.warnedGatewayVersions.has(warningKey)) return false
|
|
3533
|
+
const anotherModal = [...document.querySelectorAll('.modal')]
|
|
3534
|
+
.some(modal => modal.id !== 'modal-app-version-warning' && !modal.classList.contains('hidden'))
|
|
3535
|
+
if (anotherModal) return false
|
|
3536
|
+
state.warnedGatewayVersions.add(warningKey)
|
|
3537
|
+
$('app-version-current').textContent = 'v' + state.localVersion
|
|
3538
|
+
$('app-version-gateway').textContent = 'v' + gatewayVersion
|
|
3539
|
+
$('modal-app-version-warning').classList.remove('hidden')
|
|
3540
|
+
setTimeout(() => $('app-version-update')?.focus(), 50)
|
|
3541
|
+
return true
|
|
3542
|
+
}
|
|
3543
|
+
|
|
3544
|
+
function closeAppVersionWarning(checkNow = false) {
|
|
3545
|
+
$('modal-app-version-warning').classList.add('hidden')
|
|
3546
|
+
if (checkNow) {
|
|
3547
|
+
showView('view-settings')
|
|
3548
|
+
showSettingsPage('about')
|
|
3549
|
+
void checkUpdate(false)
|
|
3550
|
+
} else {
|
|
3551
|
+
scheduleStartupNotices(150)
|
|
3552
|
+
}
|
|
3553
|
+
}
|
|
3554
|
+
|
|
3388
3555
|
function resetUpdateExpand() {
|
|
3389
3556
|
const desc = $('update-desc')
|
|
3390
3557
|
if (desc) desc.classList.remove('expanded')
|
|
@@ -3672,7 +3839,7 @@ function closeAnnouncement(markSeen) {
|
|
|
3672
3839
|
}
|
|
3673
3840
|
async function fetchAnnouncements() {
|
|
3674
3841
|
const base = updateBase()
|
|
3675
|
-
if (!base || !state.localVersion) return false
|
|
3842
|
+
if (!base || !state.localVersion || isAppVersionWarningOpen()) return false
|
|
3676
3843
|
try {
|
|
3677
3844
|
const signal = typeof AbortSignal?.timeout === 'function' ? AbortSignal.timeout(8000) : undefined
|
|
3678
3845
|
const url = base + '/announcements.json?t=' + Date.now()
|
|
@@ -3713,6 +3880,16 @@ function startAnnouncementPolling() {
|
|
|
3713
3880
|
window.addEventListener('online', () => { void checkAnnouncements() })
|
|
3714
3881
|
}
|
|
3715
3882
|
|
|
3883
|
+
let startupNoticeTimer = null
|
|
3884
|
+
function scheduleStartupNotices(delay = 4000) {
|
|
3885
|
+
clearTimeout(startupNoticeTimer)
|
|
3886
|
+
startupNoticeTimer = setTimeout(async () => {
|
|
3887
|
+
if (isAppVersionWarningOpen()) return
|
|
3888
|
+
const shown = await checkAnnouncements()
|
|
3889
|
+
if (!shown && state.token && !isAppVersionWarningOpen()) checkUpdate(true)
|
|
3890
|
+
}, delay)
|
|
3891
|
+
}
|
|
3892
|
+
|
|
3716
3893
|
/* ---------------- 更新内容弹窗 ---------------- */
|
|
3717
3894
|
const NOTES_KEY = 'seenNotesVersion'
|
|
3718
3895
|
let notesVersion = ''
|
|
@@ -4523,6 +4700,10 @@ function renderDshControlStatus(value) {
|
|
|
4523
4700
|
|
|
4524
4701
|
async function loadDshControl() {
|
|
4525
4702
|
if (!state.token || !$('dsh-control-desc')) return
|
|
4703
|
+
if (activeGatewayCapability('dshLifecycle') === false) {
|
|
4704
|
+
renderDshControlStatus({ supported: false, message: t('settings.dshUnsupported') })
|
|
4705
|
+
return
|
|
4706
|
+
}
|
|
4526
4707
|
try {
|
|
4527
4708
|
const res = await fetch(adminApiUrl('/admin/api/dsh'), { headers: { authorization: 'Bearer ' + state.token }, cache: 'no-store' })
|
|
4528
4709
|
if (res.status === 401) return authFailure()
|
|
@@ -4775,6 +4956,7 @@ function bindUi() {
|
|
|
4775
4956
|
document.addEventListener('keydown', (e) => {
|
|
4776
4957
|
if (e.key === 'Escape' && !$('feedback-sheet').classList.contains('hidden')) { closeFeedbackSheet(); $('btn-feedback').focus() }
|
|
4777
4958
|
else if (e.key === 'Escape' && !$('modal-feedback-success').classList.contains('hidden')) closeFeedbackSuccess()
|
|
4959
|
+
else if (e.key === 'Escape' && isAppVersionWarningOpen()) closeAppVersionWarning(false)
|
|
4778
4960
|
})
|
|
4779
4961
|
$('btn-new-session').addEventListener('click', newSession)
|
|
4780
4962
|
$('session-workspace-filter').addEventListener('change', (e) => {
|
|
@@ -4965,6 +5147,11 @@ function bindUi() {
|
|
|
4965
5147
|
$('btn-check-update').addEventListener('click', () => checkUpdate(false))
|
|
4966
5148
|
$('btn-download-update').addEventListener('click', downloadUpdate)
|
|
4967
5149
|
$('btn-update-expand').addEventListener('click', toggleUpdateExpand)
|
|
5150
|
+
$('app-version-later').addEventListener('click', () => closeAppVersionWarning(false))
|
|
5151
|
+
$('app-version-update').addEventListener('click', () => closeAppVersionWarning(true))
|
|
5152
|
+
$('modal-app-version-warning').addEventListener('click', (e) => {
|
|
5153
|
+
if (e.target === $('modal-app-version-warning')) closeAppVersionWarning(false)
|
|
5154
|
+
})
|
|
4968
5155
|
$('btn-reset').addEventListener('click', () => {
|
|
4969
5156
|
if (!confirm(t('settings.confirmReset'))) return
|
|
4970
5157
|
LS.del('token'); LS.del('notify'); LS.del('server'); LS.del('mobileEnterAction'); LS.del(ANNOUNCEMENTS_KEY); LS.del(ANNOUNCEMENT_HISTORY_KEY); LS.del(ANNOUNCEMENT_VOTES_KEY)
|
|
@@ -5119,6 +5306,7 @@ async function boot() {
|
|
|
5119
5306
|
} else {
|
|
5120
5307
|
// 多服务器: 启动时静默测速一次, 选最快的连接(同源页面也参与比较)
|
|
5121
5308
|
await selectFastestServer({ silent: true, reconnect: false })
|
|
5309
|
+
await maybeWarnAppBehindGateway({ probe: true })
|
|
5122
5310
|
openStreams()
|
|
5123
5311
|
await refreshAll()
|
|
5124
5312
|
const host = await safeRpc('host.describe', {}, '')
|
|
@@ -5128,10 +5316,7 @@ async function boot() {
|
|
|
5128
5316
|
// 网关从中央 HTTPS 公告源读取并在不可达时回退内置文件。前台每 30 秒检查,
|
|
5129
5317
|
// 回到前台或网络恢复时立即补查;公告优先,避免启动时两个弹窗重叠。
|
|
5130
5318
|
startAnnouncementPolling()
|
|
5131
|
-
|
|
5132
|
-
const shown = await checkAnnouncements()
|
|
5133
|
-
if (!shown && state.token) checkUpdate(true)
|
|
5134
|
-
}, 4000)
|
|
5319
|
+
scheduleStartupNotices()
|
|
5135
5320
|
renderPending()
|
|
5136
5321
|
}
|
|
5137
5322
|
|
|
@@ -283,6 +283,11 @@ button.ds-overview-attention-item, button.ds-overview-session-item { cursor:poin
|
|
|
283
283
|
.ds-model-chip.current { background: var(--dsr-accent-soft); border-color: var(--dsr-accent-line); color: var(--dsr-accent-strong); font-weight: 600; }
|
|
284
284
|
.ds-model-effort-group { padding-top: 2px; border-top: 1px solid var(--dsr-divider); margin-top: 2px; }
|
|
285
285
|
.ds-model-effort-group.hidden { display: none; }
|
|
286
|
+
.ds-effort-hint { flex-basis: 100%; padding: 0 2px; color: var(--dsr-muted); font-size: 10.5px; line-height: 1.45; }
|
|
287
|
+
.ds-reasoning-live { border-color: var(--dsr-accent-line); }
|
|
288
|
+
.ds-reasoning-live summary { cursor: pointer; color: var(--dsr-accent-strong); }
|
|
289
|
+
.ds-reasoning-live summary::after { content: ''; display: inline-block; width: 6px; height: 6px; margin-left: 7px; border-radius: 50%; background: var(--dsr-accent-strong); animation: ds-reasoning-pulse 1.2s ease-in-out infinite; }
|
|
290
|
+
@keyframes ds-reasoning-pulse { 0%, 100% { opacity: .35; transform: scale(.8); } 50% { opacity: 1; transform: scale(1); } }
|
|
286
291
|
.ds-composer { flex: none; display: flex; flex-direction: column; gap: 8px; padding: 12px 16px; border-top: 1px solid var(--dsr-line); background: var(--dsr-panel); }
|
|
287
292
|
.ds-composer textarea { width: 100%; min-width: 0; resize: none; background: var(--dsr-bg-2); color: var(--dsr-text); border: 1px solid var(--dsr-line); border-radius: 12px; padding: 10px 14px; font: inherit; font-size: 13.5px; line-height: 1.5; outline: none; min-height: 44px; max-height: 120px; box-sizing: border-box; }
|
|
288
293
|
.ds-composer-actions { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
|
|
@@ -524,7 +524,8 @@
|
|
|
524
524
|
'subagent.confirmInterrupt': '中断这个子代理当前回合?', 'subagent.interruptFailed': '中断失败', 'subagent.interruptSubmitted': '中断请求已提交',
|
|
525
525
|
'models.loading': '模型加载中…', 'models.loadFailed': '模型列表加载失败:{msg}', 'models.unavailable': '不可用', 'models.none': '没有可用模型',
|
|
526
526
|
'models.switchFailed': '切换模型失败', 'models.switched': '已切换模型:{model}',
|
|
527
|
-
'models.effortFailed': '切换思考深度失败', 'models.effortSwitched': '思考深度:{effort}',
|
|
527
|
+
'models.effortFailed': '切换思考深度失败', 'models.effortSwitched': '思考深度:{effort}', 'models.effortLow': '低', 'models.effortHigh': '高', 'models.effortMax': '极高', 'models.effortOff': '关闭', 'models.effortCustomHint': '该路由未公布档位,按 DSH 兼容值尝试;不支持时不会更改当前设置。',
|
|
528
|
+
'block.thinking': '🧠 思考过程', 'block.thinkingLive': '🧠 思考中…',
|
|
528
529
|
'menu.modelTitle': '模型切换', 'menu.effortTitle': '思考深度',
|
|
529
530
|
},
|
|
530
531
|
en: {
|
|
@@ -624,7 +625,8 @@
|
|
|
624
625
|
'subagent.confirmInterrupt': 'Interrupt this subagent\'s current turn?', 'subagent.interruptFailed': 'Interrupt failed', 'subagent.interruptSubmitted': 'Interrupt requested',
|
|
625
626
|
'models.loading': 'Loading models…', 'models.loadFailed': 'Failed to load models: {msg}', 'models.unavailable': 'unavailable', 'models.none': 'No models available',
|
|
626
627
|
'models.switchFailed': 'Model switch failed', 'models.switched': 'Switched model: {model}',
|
|
627
|
-
'models.effortFailed': 'Failed to switch reasoning effort', 'models.effortSwitched': 'Reasoning effort: {effort}',
|
|
628
|
+
'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.',
|
|
629
|
+
'block.thinking': '🧠 Thinking', 'block.thinkingLive': '🧠 Thinking…',
|
|
628
630
|
'menu.modelTitle': 'Model', 'menu.effortTitle': 'Reasoning effort',
|
|
629
631
|
}
|
|
630
632
|
}
|