dsh-remote-plugin 0.6.11 → 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/README.en.md +3 -2
- package/README.md +3 -2
- package/apk/dsh-remote.apk +0 -0
- package/gateway.cjs +760 -82
- package/index.mjs +32 -6
- package/package.json +1 -1
- package/public/admin.html +157 -8
- package/public/admin.js +264 -5
- package/public/announcements.json +35 -0
- package/public/app.js +475 -55
- 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 +79 -16
- package/public/styles.css +64 -2
- package/public/update.json +13 -13
- package/public/version.json +1 -1
|
@@ -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
|
}
|
|
@@ -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
|
@@ -196,6 +196,17 @@
|
|
|
196
196
|
</div>
|
|
197
197
|
</div>
|
|
198
198
|
|
|
199
|
+
<section id="overview-announcement-board" class="overview-board" aria-labelledby="overview-board-title">
|
|
200
|
+
<div class="overview-board-head">
|
|
201
|
+
<div>
|
|
202
|
+
<div class="overview-eyebrow" data-i18n="overview.communityEyebrow">COMMUNITY BOARD</div>
|
|
203
|
+
<h2 id="overview-board-title" data-i18n="overview.communityTitle">公告与投票</h2>
|
|
204
|
+
</div>
|
|
205
|
+
<button id="overview-announcement-history" class="mini-btn" type="button" data-i18n="overview.communityHistory">历史公告</button>
|
|
206
|
+
</div>
|
|
207
|
+
<div id="overview-announcement-list" class="overview-announcement-list" aria-live="polite"></div>
|
|
208
|
+
</section>
|
|
209
|
+
|
|
199
210
|
<div class="overview-section-head"><span data-i18n="overview.attention">需要你处理</span><span id="overview-attention-count" class="overview-section-meta"></span></div>
|
|
200
211
|
<div id="overview-attention-list" class="overview-attention-list"></div>
|
|
201
212
|
|
|
@@ -228,23 +239,18 @@
|
|
|
228
239
|
<div class="settings-group">
|
|
229
240
|
<div class="setting-row" data-settings-group="general" style="cursor:pointer">
|
|
230
241
|
<div><div class="setting-name" data-i18n="settings.groupGeneral">通用</div><div class="setting-desc" data-i18n="settings.groupGeneralDesc">显示工具调用、预设提示词</div></div>
|
|
231
|
-
<span class="sc-arrow">›</span>
|
|
232
242
|
</div>
|
|
233
243
|
<div class="setting-row" data-settings-group="servers" style="cursor:pointer">
|
|
234
244
|
<div><div class="setting-name" data-i18n="settings.groupServers">服务器</div><div class="setting-desc" data-i18n="settings.groupServersDesc">服务器地址、扫码连接</div></div>
|
|
235
|
-
<span class="sc-arrow">›</span>
|
|
236
245
|
</div>
|
|
237
246
|
<div class="setting-row" data-settings-group="notify" style="cursor:pointer">
|
|
238
247
|
<div><div class="setting-name" data-i18n="settings.groupNotify">通知</div><div class="setting-desc" data-i18n="settings.groupNotifyDesc">通知、提醒、后台轮询</div></div>
|
|
239
|
-
<span class="sc-arrow">›</span>
|
|
240
248
|
</div>
|
|
241
249
|
<div class="setting-row" data-settings-group="theme" style="cursor:pointer">
|
|
242
250
|
<div><div class="setting-name" data-i18n="settings.groupTheme">皮肤</div><div class="setting-desc" data-i18n="settings.groupThemeDesc">配色主题</div></div>
|
|
243
|
-
<span class="sc-arrow">›</span>
|
|
244
251
|
</div>
|
|
245
252
|
<div class="setting-row" data-settings-group="about" style="cursor:pointer">
|
|
246
253
|
<div><div class="setting-name" data-i18n="settings.groupAbout">关于</div><div class="setting-desc" data-i18n="settings.groupAboutDesc">DSH 状态、检查更新</div></div>
|
|
247
|
-
<span class="sc-arrow">›</span>
|
|
248
254
|
</div>
|
|
249
255
|
</div>
|
|
250
256
|
<div class="settings-group">
|
|
@@ -424,7 +430,7 @@
|
|
|
424
430
|
<button id="btn-host-describe" class="mini-btn" data-i18n="settings.probe">探测</button>
|
|
425
431
|
</div>
|
|
426
432
|
<div class="setting-row">
|
|
427
|
-
<div><div class="setting-name" data-i18n="settings.dshControlTitle">远程启动 DSH</div><div class="setting-desc" id="dsh-control-desc" data-i18n="settings.dshControlProbing">检查本机服务状态…</div></div>
|
|
433
|
+
<div><div class="setting-name" data-i18n="settings.dshControlTitle">远程启动 DSH</div><div class="setting-desc expanded" id="dsh-control-desc" data-i18n="settings.dshControlProbing">检查本机服务状态…</div><div id="dsh-control-steps" class="dsh-control-steps hidden" aria-live="polite"></div></div>
|
|
428
434
|
<div class="setting-actions">
|
|
429
435
|
<button id="btn-dsh-start" class="mini-btn" data-i18n="settings.dshStart">启动</button>
|
|
430
436
|
<button id="btn-dsh-restart" class="mini-btn" data-i18n="settings.dshRestart">重启</button>
|
|
@@ -553,6 +559,20 @@
|
|
|
553
559
|
</div>
|
|
554
560
|
</div>
|
|
555
561
|
|
|
562
|
+
<!-- 反馈发送成功确认 -->
|
|
563
|
+
<div id="modal-feedback-success" class="modal hidden" role="dialog" aria-modal="true" aria-labelledby="feedback-success-title">
|
|
564
|
+
<div class="modal-card feedback-success-card">
|
|
565
|
+
<div class="feedback-success-icon" aria-hidden="true">
|
|
566
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="m6 12 4 4 8-9"/></svg>
|
|
567
|
+
</div>
|
|
568
|
+
<div id="feedback-success-title" class="modal-title feedback-success-title" data-i18n="feedback.successTitle">反馈发送成功</div>
|
|
569
|
+
<div class="modal-body feedback-success-body" data-i18n="feedback.successBody">服务器已确认收到这条反馈,感谢你的帮助。</div>
|
|
570
|
+
<div class="modal-actions">
|
|
571
|
+
<button id="feedback-success-confirm" class="btn primary" data-i18n="feedback.successConfirm">知道了</button>
|
|
572
|
+
</div>
|
|
573
|
+
</div>
|
|
574
|
+
</div>
|
|
575
|
+
|
|
556
576
|
<!-- 新建工作区模态 -->
|
|
557
577
|
<div id="modal-workspace" class="modal hidden" role="dialog" aria-modal="true">
|
|
558
578
|
<div class="modal-card">
|
|
@@ -576,6 +596,10 @@
|
|
|
576
596
|
<div class="modal-body">
|
|
577
597
|
<label class="field-label" for="new-session-workspace" data-i18n="newSession.workspace">选择工作区</label>
|
|
578
598
|
<select id="new-session-workspace" class="workspace-select full" data-i18n-aria="workspace.select"></select>
|
|
599
|
+
<div class="workspace-name-block">
|
|
600
|
+
<span data-i18n="newSession.name">工作区名称</span>
|
|
601
|
+
<strong id="new-session-workspace-name"></strong>
|
|
602
|
+
</div>
|
|
579
603
|
<div class="workspace-path-block">
|
|
580
604
|
<span data-i18n="newSession.path">工作区地址</span>
|
|
581
605
|
<code id="new-session-workspace-path" class="workspace-path-scroll"></code>
|
|
@@ -674,6 +698,25 @@
|
|
|
674
698
|
</div>
|
|
675
699
|
</div>
|
|
676
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
|
+
|
|
677
720
|
<!-- 更新内容模态 -->
|
|
678
721
|
<div id="modal-notes" class="modal hidden" role="dialog" aria-modal="true">
|
|
679
722
|
<div class="modal-card">
|
|
@@ -742,6 +785,8 @@
|
|
|
742
785
|
'overview.activeSessions': '活跃会话', 'overview.connectionMode': '连接模式', 'overview.ws': '实时 WS', 'overview.poll': '降级轮询', 'overview.noActivity': '暂无后台活动', 'overview.noSession': '暂无会话',
|
|
743
786
|
'overview.refresh': '刷新总览', 'overview.none': '暂无事项', 'overview.pendingCount': '{n} 项待处理', 'overview.activeCount': '{n} 个活跃会话',
|
|
744
787
|
'overview.action.openSession': '打开最近会话', 'overview.action.newSession': '新建会话', 'overview.action.attention': '处理待办', 'overview.action.connect': '去设置连接', 'overview.action.refresh': '重新检查连接',
|
|
788
|
+
'overview.communityEyebrow': 'COMMUNITY BOARD', 'overview.communityTitle': '公告与投票', 'overview.communityHistory': '历史公告', 'overview.communityEmpty': '暂无未读公告',
|
|
789
|
+
'overview.communityPoll': '投票', 'overview.communityNotice': '公告', 'overview.communityNew': '新', 'overview.communityVote': '参与投票', 'overview.communityView': '查看公告', 'overview.communityVoted': '已参与',
|
|
745
790
|
'statsPage.title': 'Token 统计',
|
|
746
791
|
'statsPage.todayTokens': '今日 Token', 'statsPage.todayCost': '今日费用', 'statsPage.peakShare': '高峰占比',
|
|
747
792
|
'statsPage.input': '未缓存输入', 'statsPage.cacheRead': '缓存命中', 'statsPage.cacheWrite': '缓存写入', 'statsPage.output': '输出',
|
|
@@ -753,7 +798,7 @@
|
|
|
753
798
|
'workspace.createTitle': '新建工作区', 'workspace.createDesc': '将在当前文件目录下创建文件夹,并自动打开新会话。', 'workspace.parent': '父目录', 'workspace.namePlaceholder': '例如:my-project', 'workspace.cancel': '取消', 'workspace.create': '创建并打开', 'workspace.nameRequired': '请输入工作区名称', 'workspace.exists': '该目录已存在', 'workspace.invalidName': '名称不能包含路径分隔符', 'workspace.createFailed': '创建工作区失败:{msg}', 'workspace.created': '工作区已创建', 'workspace.createdNoSession': '工作区已创建,但新会话未能打开',
|
|
754
799
|
'workspace.createTitle': '新建工作区', 'workspace.createDesc': '将在当前文件目录下创建文件夹,并自动打开新会话。', 'workspace.parent': '父目录', 'workspace.namePlaceholder': '例如:my-project', 'workspace.cancel': '取消', 'workspace.create': '创建并打开', 'workspace.nameRequired': '请输入工作区名称', 'workspace.created': '工作区已创建', 'workspace.sessionFailed': '目录已创建,但新会话打开失败:{msg}',
|
|
755
800
|
'workspace.current': '当前工作区', 'workspace.select': '选择工作区', 'workspace.all': '全部工作区', 'workspace.ungrouped': '未分组会话', 'workspace.files': '查看文件', 'workspace.allPath': '显示全部 DSH 工作区中的会话',
|
|
756
|
-
'newSession.title': '新建会话', 'newSession.workspace': '选择工作区', 'newSession.path': '工作区地址', 'newSession.create': '创建会话', 'newSession.noWorkspaces': '还没有 DSH 工作区,请先新建工作区。', 'newSession.chooseWorkspace': '请先选择工作区',
|
|
801
|
+
'newSession.title': '新建会话', 'newSession.workspace': '选择工作区', 'newSession.name': '工作区名称', 'newSession.path': '工作区地址', 'newSession.create': '创建会话', 'newSession.noWorkspaces': '还没有 DSH 工作区,请先新建工作区。', 'newSession.chooseWorkspace': '请先选择工作区',
|
|
757
802
|
'sessions.running': '运行中', 'sessions.statRunning': '运行中', 'sessions.statPending': '待处理', 'sessions.statTotal': '会话总数',
|
|
758
803
|
'sessions.sortLabel': '会话排序', 'sessions.sortTime': '按时间', 'sessions.sortWorkspace': '按工作区', 'sessions.workspace': '工作区', 'sessions.workspaceUnknown': '未指定工作区',
|
|
759
804
|
'sessions.queueBadge': '队列 {n}', 'sessions.goalBadge': '目标·{phase}',
|
|
@@ -797,7 +842,7 @@
|
|
|
797
842
|
'tool.default': '工具', 'tool.unknown': '未知工具', 'tool.result': '结果',
|
|
798
843
|
'event.taskStart': '▶ 任务开始', 'event.taskEnd': '■ 任务结束',
|
|
799
844
|
'event.systemReminder': '系统提示',
|
|
800
|
-
'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}',
|
|
801
846
|
'truncated': '…(截断)',
|
|
802
847
|
'stats.title': '本轮统计', 'stats.roundTitle': '本轮统计', 'stats.turnsSteps': '轮次 / 步骤',
|
|
803
848
|
'stats.llmTime': '模型耗时', 'stats.minutes': ' 分钟', 'stats.outputCache': '输出 / 缓存读',
|
|
@@ -812,7 +857,7 @@
|
|
|
812
857
|
'send.failed': '发送失败', 'send.commandSent': '指令已发送', 'send.sent': '已发送', 'send.imageSent': '图片已发送', 'send.commandExecuted': '命令已执行',
|
|
813
858
|
'models.loading': '模型加载中…', 'models.loadFailed': '模型列表加载失败:{msg}', 'models.unavailable': '不可用', 'models.none': '没有可用模型',
|
|
814
859
|
'models.switchFailed': '切换模型失败', 'models.switched': '已切换模型:{model}',
|
|
815
|
-
'models.effortFailed': '切换思考深度失败', 'models.effortSwitched': '思考深度:{effort}',
|
|
860
|
+
'models.effortFailed': '切换思考深度失败', 'models.effortSwitched': '思考深度:{effort}', 'models.effortLow': '低', 'models.effortHigh': '高', 'models.effortMax': '极高', 'models.effortOff': '关闭', 'models.effortCustomHint': '该路由未公布档位,按 DSH 兼容值尝试;不支持时不会更改当前设置。',
|
|
816
861
|
'menu.commandsTitle': '输入指令',
|
|
817
862
|
'menu.compact': '/compact 压缩对话历史', 'menu.export': '/export 导出会话日志 ZIP',
|
|
818
863
|
'menu.feedback': '/feedback 反馈当前会话', 'menu.goal': '/goal 设置/查看任务目标',
|
|
@@ -831,7 +876,7 @@
|
|
|
831
876
|
'fs.workspace': 'DSH 工作区', 'fs.root': '文件根目录', 'fs.previewTitle': '文件预览', 'fs.previewLoading': '正在加载文件…', 'fs.previewFailed': '无法预览:{msg}', 'fs.previewUnsupported': '该文件类型暂不支持预览', 'fs.previewTooLarge': '文件超过 1 MB,请下载后查看', 'fs.source': '源码', 'fs.preview': '预览', 'fs.download': '下载',
|
|
832
877
|
'fs.pullDown': '下拉刷新', 'fs.pullRelease': '松开刷新', 'fs.refreshing': '刷新中…',
|
|
833
878
|
'fs.noToken': '未设置令牌', 'fs.goSettings': '请先到「设置」页粘贴网关令牌',
|
|
834
|
-
'fs.notFound': '目录不存在', 'fs.forbidden': '
|
|
879
|
+
'fs.notFound': '目录不存在', 'fs.forbidden': '路径不在允许范围内;DSH 已登记工作区会自动放行,其他目录请配置 DSH_REMOTE_FS_ROOT',
|
|
835
880
|
'fs.loadFailed': '加载失败:{msg}', 'fs.loadFailedToast': '文件列表加载失败:{msg}',
|
|
836
881
|
'fs.emptyDir': '空目录', 'fs.dir': '目录',
|
|
837
882
|
'fs.downloadStarted': '开始下载到「下载/dsh-remote」目录', 'fs.downloadFailed': '无法启动下载:{msg}',
|
|
@@ -854,6 +899,9 @@
|
|
|
854
899
|
'update.corrupted': '下载文件损坏,请重试', 'update.serverFileMissing': '服务器上还没有对应版本的文件,请稍后再试',
|
|
855
900
|
'update.installUnsupported': '当前版本不支持 App 内安装,已转浏览器下载',
|
|
856
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': '立即检查更新',
|
|
857
905
|
'notes.title': '更新内容', 'notes.close': '关闭', 'notes.page': '版本号 {current}/{total}',
|
|
858
906
|
'scan.imageLoadFailed': '图片加载失败', 'scan.decodeUnsupported': '当前设备不支持图片解码',
|
|
859
907
|
'scan.browserHint': '浏览器请打开主机管理页,用手机相机扫码', 'scan.unsupported': '当前 App 版本不支持拍照扫码,请先更新 App',
|
|
@@ -901,7 +949,11 @@
|
|
|
901
949
|
'theme.panelTitle': '选择配色', 'theme.close': '关闭',
|
|
902
950
|
'settings.tokenTitle': '访问令牌', 'settings.change': '更换',
|
|
903
951
|
'settings.hostTitle': 'DSH 状态', 'settings.hostProbing': '探测中…', 'settings.probe': '探测', 'settings.probeFailed': '探测失败',
|
|
904
|
-
'settings.dshControlTitle': '远程启动 DSH', 'settings.dshControlProbing': '
|
|
952
|
+
'settings.dshControlTitle': '远程启动 DSH', 'settings.dshControlProbing': '正在向网关查询本机 systemd 服务…', 'settings.dshRunning': '运行中', 'settings.dshStopped': '已停止', 'settings.dshUnsupported': '当前系统未配置可控的 DSH 服务', 'settings.dshStart': '启动', 'settings.dshRestart': '重启', 'settings.dshStarting': '正在{action} DSH…', 'settings.dshStarted': 'DSH 已{action}', 'settings.dshFailed': 'DSH 操作失败:{msg}',
|
|
953
|
+
'settings.dshRunningDetail': '运行中 · {service} · PID {pid} · {state}', 'settings.dshStoppedDetail': '已停止 · {service} · {state}', 'settings.dshStatusFailed': '服务状态查询失败:{msg}',
|
|
954
|
+
'settings.dshStageQueued': '请求已到达网关,等待检查服务', 'settings.dshStageChecking': '正在检查 systemd 用户服务 {service}', 'settings.dshStageCommand': '服务已确认,正在提交{action}命令', 'settings.dshStageWaitingService': '命令已接受,等待服务进程运行({seconds} 秒)', 'settings.dshStageWaitingUpstream': '服务进程已运行,等待 DSH HTTP 接口恢复({seconds} 秒)', 'settings.dshStageWaitingEvents': 'DSH HTTP 已恢复,等待实时消息通道连接({seconds} 秒)',
|
|
955
|
+
'settings.dshSuccessDetail': 'DSH {action}成功 · PID {pid} · HTTP {status} · 用时 {seconds} 秒', 'settings.dshAlreadyRunning': 'DSH 已在运行 · PID {pid}', 'settings.dshResultUnknown': '与网关的连接在操作中断开,最终结果未知;请重新进入此页查看服务状态',
|
|
956
|
+
'settings.dshErrorServiceNotFound': '未找到 systemd 用户服务 {service},请先安装或配置 DSH_REMOTE_DSH_SERVICE', 'settings.dshErrorInvalidService': 'DSH 服务名配置不合法', 'settings.dshErrorSystemctlNotFound': '系统中找不到 systemctl', 'settings.dshErrorSystemdUnavailable': '无法连接当前用户的 systemd 会话', 'settings.dshErrorPermissionDenied': '当前用户无权控制 DSH 服务', 'settings.dshErrorCommandTimeout': 'systemctl 命令执行超时', 'settings.dshErrorCommandFailed': 'systemctl 拒绝或未能执行 DSH 控制命令', 'settings.dshErrorServiceFailed': 'DSH 服务启动后进入 failed 状态', 'settings.dshErrorServiceTimeout': 'DSH 服务未在限时内进入运行状态', 'settings.dshErrorUpstreamTimeout': 'DSH 进程已运行,但 HTTP 接口未在限时内恢复', 'settings.dshErrorEventsTimeout': 'DSH 服务和 HTTP 已恢复,但实时消息通道未在限时内连接', 'settings.dshErrorOperationNotFound': '网关找不到该操作,可能在期间重启过', 'settings.dshErrorOperationProgress': '已有一个 DSH 启停操作正在执行', 'settings.dshErrorGeneric': '{msg}', 'settings.dshErrorDetail': '详情:{detail}',
|
|
905
957
|
'settings.hostDesc': 'DSH {version} · {cwd} · 附加会话 {n}',
|
|
906
958
|
'settings.updateTitle': '检查更新', 'settings.updateLoading': '加载中…', 'settings.downloadUpdate': '下载并安装更新', 'settings.check': '检查',
|
|
907
959
|
'settings.feedbackTitle': '反馈渠道', 'settings.feedbackDesc': 'GitHub / Gitee / B站:反馈 bug、提建议、唠嗑',
|
|
@@ -915,6 +967,7 @@
|
|
|
915
967
|
'feedback.cancel': '取消', 'feedback.submit': '提交',
|
|
916
968
|
'feedback.empty': '请填写描述内容', 'feedback.tooLong': '描述不能超过 2000 字',
|
|
917
969
|
'feedback.submitted': '已提交,感谢反馈',
|
|
970
|
+
'feedback.successTitle': '反馈发送成功', 'feedback.successBody': '服务器已确认收到这条反馈,感谢你的帮助。', 'feedback.successConfirm': '知道了',
|
|
918
971
|
'feedback.rateLimited': '提交太频繁,请稍后再试', 'feedback.rateLimitedAt': '提交太频繁,请 {n} 秒后再试', 'feedback.submitFailed': '提交失败:{msg}', 'feedback.networkError': '网络错误',
|
|
919
972
|
'settings.resetTitle': '清空本地数据', 'settings.resetDesc': '令牌与缓存', 'settings.reset': '重置',
|
|
920
973
|
'settings.confirmReset': '清除本地令牌、服务器与缓存?', 'settings.notifyDenied': '通知权限未开启',
|
|
@@ -943,6 +996,8 @@
|
|
|
943
996
|
'overview.activeSessions': 'Active sessions', 'overview.connectionMode': 'Connection mode', 'overview.ws': 'Live WS', 'overview.poll': 'Degraded polling', 'overview.noActivity': 'No background activity', 'overview.noSession': 'No sessions yet',
|
|
944
997
|
'overview.refresh': 'Refresh overview', 'overview.none': 'Nothing pending', 'overview.pendingCount': '{n} pending', 'overview.activeCount': '{n} active sessions',
|
|
945
998
|
'overview.action.openSession': 'Open recent session', 'overview.action.newSession': 'New session', 'overview.action.attention': 'Review pending', 'overview.action.connect': 'Set up connection', 'overview.action.refresh': 'Check connection',
|
|
999
|
+
'overview.communityEyebrow': 'COMMUNITY BOARD', 'overview.communityTitle': 'Announcements & polls', 'overview.communityHistory': 'History', 'overview.communityEmpty': 'No unread announcements',
|
|
1000
|
+
'overview.communityPoll': 'Poll', 'overview.communityNotice': 'Notice', 'overview.communityNew': 'New', 'overview.communityVote': 'Vote now', 'overview.communityView': 'View notice', 'overview.communityVoted': 'Voted',
|
|
946
1001
|
'statsPage.title': 'Token stats',
|
|
947
1002
|
'statsPage.todayTokens': 'Tokens today', 'statsPage.todayCost': 'Cost today', 'statsPage.peakShare': 'Peak share',
|
|
948
1003
|
'statsPage.input': 'Uncached input', 'statsPage.cacheRead': 'Cache read', 'statsPage.cacheWrite': 'Cache write', 'statsPage.output': 'Output',
|
|
@@ -954,7 +1009,7 @@
|
|
|
954
1009
|
'workspace.createTitle': 'New workspace', 'workspace.createDesc': 'Create a folder in the current file directory and open a new session there.', 'workspace.parent': 'Parent folder', 'workspace.namePlaceholder': 'For example: my-project', 'workspace.cancel': 'Cancel', 'workspace.create': 'Create & open', 'workspace.nameRequired': 'Enter a workspace name', 'workspace.exists': 'That folder already exists', 'workspace.invalidName': 'The name cannot contain path separators', 'workspace.createFailed': 'Could not create workspace: {msg}', 'workspace.created': 'Workspace created', 'workspace.createdNoSession': 'Workspace created, but the new session could not be opened',
|
|
955
1010
|
'workspace.createTitle': 'New workspace', 'workspace.createDesc': 'Create a folder in the current file directory and open a new session there.', 'workspace.parent': 'Parent folder', 'workspace.namePlaceholder': 'For example: my-project', 'workspace.cancel': 'Cancel', 'workspace.create': 'Create & open', 'workspace.nameRequired': 'Enter a workspace name', 'workspace.created': 'Workspace created', 'workspace.sessionFailed': 'Folder created, but the new session could not be opened: {msg}',
|
|
956
1011
|
'workspace.current': 'Current workspace', 'workspace.select': 'Select workspace', 'workspace.all': 'All workspaces', 'workspace.ungrouped': 'Ungrouped sessions', 'workspace.files': 'View files', 'workspace.allPath': 'Showing sessions from every DSH workspace',
|
|
957
|
-
'newSession.title': 'New session', 'newSession.workspace': 'Choose workspace', 'newSession.path': 'Workspace path', 'newSession.create': 'Create session', 'newSession.noWorkspaces': 'No DSH workspaces yet. Create a workspace first.', 'newSession.chooseWorkspace': 'Choose a workspace first',
|
|
1012
|
+
'newSession.title': 'New session', 'newSession.workspace': 'Choose workspace', 'newSession.name': 'Workspace name', 'newSession.path': 'Workspace path', 'newSession.create': 'Create session', 'newSession.noWorkspaces': 'No DSH workspaces yet. Create a workspace first.', 'newSession.chooseWorkspace': 'Choose a workspace first',
|
|
958
1013
|
'sessions.running': 'Running', 'sessions.statRunning': 'Running', 'sessions.statPending': 'Pending', 'sessions.statTotal': 'Sessions',
|
|
959
1014
|
'sessions.sortLabel': 'Session sort', 'sessions.sortTime': 'By time', 'sessions.sortWorkspace': 'By workspace', 'sessions.workspace': 'Workspace', 'sessions.workspaceUnknown': 'No workspace',
|
|
960
1015
|
'sessions.queueBadge': 'Queue {n}', 'sessions.goalBadge': 'Goal·{phase}',
|
|
@@ -998,7 +1053,7 @@
|
|
|
998
1053
|
'tool.default': 'tool', 'tool.unknown': 'Unknown tool', 'tool.result': 'Result',
|
|
999
1054
|
'event.taskStart': '▶ Task started', 'event.taskEnd': '■ Task finished',
|
|
1000
1055
|
'event.systemReminder': 'System reminder',
|
|
1001
|
-
'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}',
|
|
1002
1057
|
'truncated': '…(truncated)',
|
|
1003
1058
|
'stats.title': 'This round', 'stats.roundTitle': 'This round', 'stats.turnsSteps': 'Turns / steps',
|
|
1004
1059
|
'stats.llmTime': 'LLM time', 'stats.minutes': ' min', 'stats.outputCache': 'Output / cache read',
|
|
@@ -1013,7 +1068,7 @@
|
|
|
1013
1068
|
'send.failed': 'Send failed', 'send.commandSent': 'Command sent', 'send.sent': 'Sent', 'send.imageSent': 'Image sent', 'send.commandExecuted': 'Command executed',
|
|
1014
1069
|
'models.loading': 'Loading models…', 'models.loadFailed': 'Failed to load models: {msg}', 'models.unavailable': 'unavailable', 'models.none': 'No models available',
|
|
1015
1070
|
'models.switchFailed': 'Model switch failed', 'models.switched': 'Switched model: {model}',
|
|
1016
|
-
'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.',
|
|
1017
1072
|
'menu.commandsTitle': 'Commands',
|
|
1018
1073
|
'menu.compact': '/compact Compress conversation history', 'menu.export': '/export Export session log ZIP',
|
|
1019
1074
|
'menu.feedback': '/feedback Feedback current session', 'menu.goal': '/goal Set/view task goal',
|
|
@@ -1032,7 +1087,7 @@
|
|
|
1032
1087
|
'fs.workspace': 'DSH workspace', 'fs.root': 'File root', 'fs.previewTitle': 'File preview', 'fs.previewLoading': 'Loading file…', 'fs.previewFailed': 'Could not preview: {msg}', 'fs.previewUnsupported': 'This file type cannot be previewed', 'fs.previewTooLarge': 'File is larger than 1 MB; download it to view', 'fs.source': 'Source', 'fs.preview': 'Preview', 'fs.download': 'Download',
|
|
1033
1088
|
'fs.pullDown': 'Pull to refresh', 'fs.pullRelease': 'Release to refresh', 'fs.refreshing': 'Refreshing…',
|
|
1034
1089
|
'fs.noToken': 'No token set', 'fs.goSettings': 'Go to Settings and paste the gateway token first',
|
|
1035
|
-
'fs.notFound': 'Directory not found', 'fs.forbidden': 'Path is outside the allowed roots',
|
|
1090
|
+
'fs.notFound': 'Directory not found', 'fs.forbidden': 'Path is outside the allowed roots. Registered DSH workspaces are allowed automatically; configure DSH_REMOTE_FS_ROOT for other directories.',
|
|
1036
1091
|
'fs.loadFailed': 'Load failed: {msg}', 'fs.loadFailedToast': 'Failed to load file list: {msg}',
|
|
1037
1092
|
'fs.emptyDir': 'Empty directory', 'fs.dir': 'Directory',
|
|
1038
1093
|
'fs.downloadStarted': 'Downloading to "Download/dsh-remote"', 'fs.downloadFailed': 'Could not start download: {msg}',
|
|
@@ -1055,6 +1110,9 @@
|
|
|
1055
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',
|
|
1056
1111
|
'update.installUnsupported': 'This version cannot install in-app, opening browser download',
|
|
1057
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',
|
|
1058
1116
|
'notes.title': 'What\'s New', 'notes.close': 'Close', 'notes.page': 'Version {current}/{total}',
|
|
1059
1117
|
'scan.imageLoadFailed': 'Image failed to load', 'scan.decodeUnsupported': 'This device cannot decode images',
|
|
1060
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',
|
|
@@ -1102,7 +1160,11 @@
|
|
|
1102
1160
|
'theme.panelTitle': 'Choose theme', 'theme.close': 'Close',
|
|
1103
1161
|
'settings.tokenTitle': 'Access token', 'settings.change': 'Change',
|
|
1104
1162
|
'settings.hostTitle': 'DSH status', 'settings.hostProbing': 'Probing…', 'settings.probe': 'Probe', 'settings.probeFailed': 'Probe failed',
|
|
1105
|
-
'settings.dshControlTitle': 'Remote DSH control', 'settings.dshControlProbing': '
|
|
1163
|
+
'settings.dshControlTitle': 'Remote DSH control', 'settings.dshControlProbing': 'Querying the gateway for the local systemd service…', 'settings.dshRunning': 'Running', 'settings.dshStopped': 'Stopped', 'settings.dshUnsupported': 'No controllable DSH service is configured', 'settings.dshStart': 'Start', 'settings.dshRestart': 'Restart', 'settings.dshStarting': '{action}ing DSH…', 'settings.dshStarted': 'DSH {action}ed', 'settings.dshFailed': 'DSH operation failed: {msg}',
|
|
1164
|
+
'settings.dshRunningDetail': 'Running · {service} · PID {pid} · {state}', 'settings.dshStoppedDetail': 'Stopped · {service} · {state}', 'settings.dshStatusFailed': 'Service status query failed: {msg}',
|
|
1165
|
+
'settings.dshStageQueued': 'Request reached the gateway; waiting to inspect the service', 'settings.dshStageChecking': 'Checking systemd user service {service}', 'settings.dshStageCommand': 'Service confirmed; submitting the {action} command', 'settings.dshStageWaitingService': 'Command accepted; waiting for the service process ({seconds}s)', 'settings.dshStageWaitingUpstream': 'Service process is running; waiting for the DSH HTTP endpoint ({seconds}s)', 'settings.dshStageWaitingEvents': 'DSH HTTP is ready; waiting for the realtime message channels ({seconds}s)',
|
|
1166
|
+
'settings.dshSuccessDetail': 'DSH {action} succeeded · PID {pid} · HTTP {status} · {seconds}s', 'settings.dshAlreadyRunning': 'DSH is already running · PID {pid}', 'settings.dshResultUnknown': 'The gateway connection was interrupted during the operation, so the final result is unknown. Reopen this page to check the service state.',
|
|
1167
|
+
'settings.dshErrorServiceNotFound': 'systemd user service {service} was not found. Install it or configure DSH_REMOTE_DSH_SERVICE first.', 'settings.dshErrorInvalidService': 'The configured DSH service name is invalid', 'settings.dshErrorSystemctlNotFound': 'systemctl is not available on this host', 'settings.dshErrorSystemdUnavailable': 'Could not connect to the current user systemd session', 'settings.dshErrorPermissionDenied': 'The current user cannot control the DSH service', 'settings.dshErrorCommandTimeout': 'The systemctl command timed out', 'settings.dshErrorCommandFailed': 'systemctl rejected or could not run the DSH control command', 'settings.dshErrorServiceFailed': 'The DSH service entered the failed state after startup', 'settings.dshErrorServiceTimeout': 'The DSH service did not enter the running state before timeout', 'settings.dshErrorUpstreamTimeout': 'The DSH process is running, but its HTTP endpoint did not recover before timeout', 'settings.dshErrorEventsTimeout': 'The DSH service and HTTP endpoint recovered, but the realtime message channels did not reconnect before timeout', 'settings.dshErrorOperationNotFound': 'The gateway no longer knows this operation; it may have restarted', 'settings.dshErrorOperationProgress': 'Another DSH start/restart operation is already running', 'settings.dshErrorGeneric': '{msg}', 'settings.dshErrorDetail': 'Detail: {detail}',
|
|
1106
1168
|
'settings.hostDesc': 'DSH {version} · {cwd} · {n} attached sessions',
|
|
1107
1169
|
'settings.updateTitle': 'Check for updates', 'settings.updateLoading': 'Loading…', 'settings.downloadUpdate': 'Download & install update', 'settings.check': 'Check',
|
|
1108
1170
|
'settings.feedbackTitle': 'Feedback', 'settings.feedbackDesc': 'GitHub / Gitee / Bilibili: report bugs, suggest features, or just chat',
|
|
@@ -1116,6 +1178,7 @@
|
|
|
1116
1178
|
'feedback.cancel': 'Cancel', 'feedback.submit': 'Submit',
|
|
1117
1179
|
'feedback.empty': 'Please fill in the description', 'feedback.tooLong': 'Description must be ≤2000 characters',
|
|
1118
1180
|
'feedback.submitted': 'Submitted — thanks!',
|
|
1181
|
+
'feedback.successTitle': 'Feedback sent', 'feedback.successBody': 'The server confirmed receipt of your feedback. Thank you for helping improve DSH Remote.', 'feedback.successConfirm': 'Got it',
|
|
1119
1182
|
'feedback.rateLimited': 'Too frequent, try again later', 'feedback.rateLimitedAt': 'Too frequent, try again in {n}s', 'feedback.submitFailed': 'Submit failed: {msg}', 'feedback.networkError': 'Network error',
|
|
1120
1183
|
'settings.resetTitle': 'Clear local data', 'settings.resetDesc': 'Token and caches', 'settings.reset': 'Reset',
|
|
1121
1184
|
'settings.confirmReset': 'Clear local token, servers and caches?', 'settings.notifyDenied': 'Notification permission not granted',
|