dsh-remote-plugin 0.6.0 → 0.6.2
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 +5 -0
- package/index.mjs +68 -3
- package/package.json +1 -1
- package/public/admin.html +17 -0
- package/public/admin.js +9 -0
- package/public/app.js +343 -11
- package/public/desktop/desktop.css +62 -8
- package/public/desktop/desktop.html +204 -18
- package/public/desktop/desktop.js +529 -1
- package/public/donate.png +0 -0
- package/public/index.html +267 -85
- package/public/update.json +10 -4
- package/public/version.json +1 -1
package/public/app.js
CHANGED
|
@@ -269,11 +269,15 @@ function renderStats(days) {
|
|
|
269
269
|
}).join('')
|
|
270
270
|
}
|
|
271
271
|
async function rpc(method, payload = {}) {
|
|
272
|
-
const
|
|
272
|
+
const opts = {
|
|
273
273
|
method: 'POST',
|
|
274
274
|
headers: { 'content-type': 'application/json', authorization: 'Bearer ' + state.token, 'x-dsh-remote-client': CAP?.isNativePlatform?.() ? 'app' : 'web' },
|
|
275
275
|
body: JSON.stringify({ type: 'client-request', rpcId: uuid(), method, payload })
|
|
276
|
-
}
|
|
276
|
+
}
|
|
277
|
+
if (typeof AbortSignal !== 'undefined' && typeof AbortSignal.timeout === 'function') {
|
|
278
|
+
opts.signal = AbortSignal.timeout(20000)
|
|
279
|
+
}
|
|
280
|
+
const res = await fetch(apiUrl('/api/' + method), opts)
|
|
277
281
|
if (res.status === 401) throw new Error('AUTH')
|
|
278
282
|
if (!res.ok) throw new Error('HTTP ' + res.status)
|
|
279
283
|
const full = await res.json()
|
|
@@ -451,6 +455,7 @@ async function selectFastestServer({ silent = false, reconnect = true } = {}) {
|
|
|
451
455
|
if (srv) state.groupActive[state.activeGroup] = srv.id
|
|
452
456
|
}
|
|
453
457
|
saveServers()
|
|
458
|
+
syncBgConfig()
|
|
454
459
|
if (!silent) {
|
|
455
460
|
if (chosen) toast(t('speed.switched', { url: chosen, ms: Number.isFinite(ms) ? ms : 0 }), 'ok')
|
|
456
461
|
else toast(t('speed.switchedOrigin'), 'ok')
|
|
@@ -976,6 +981,10 @@ function applyProjection(sessionId, key, value, seq) {
|
|
|
976
981
|
else renderSessions()
|
|
977
982
|
}function titleOf(s) { return proj(s, 'title') || short(s.sessionId) }
|
|
978
983
|
function short(id) { return '…' + String(id).slice(-8) }
|
|
984
|
+
const GOAL_TERMINAL_PHASES = new Set(['complete', 'cleared'])
|
|
985
|
+
function isGoalTerminal(goal) {
|
|
986
|
+
return !!goal && GOAL_TERMINAL_PHASES.has(goal.phase)
|
|
987
|
+
}
|
|
979
988
|
function goalOf(s) {
|
|
980
989
|
const p = proj(s, 'goal')
|
|
981
990
|
if (!p) return null
|
|
@@ -1028,8 +1037,10 @@ async function openSession(id) {
|
|
|
1028
1037
|
state.history = emptyHistory()
|
|
1029
1038
|
document.body.classList.add('in-session')
|
|
1030
1039
|
showView('view-session')
|
|
1040
|
+
$('session-cards').innerHTML = ''
|
|
1031
1041
|
renderSessionTitle(); renderSessionSub(); updateCancelBtn(); updateSessionStatus()
|
|
1032
1042
|
$('history').innerHTML = '<div class="empty">' + t('history.loading') + '</div>'
|
|
1043
|
+
restoreCachedHistory()
|
|
1033
1044
|
await loadHistory(true)
|
|
1034
1045
|
renderSessionCards()
|
|
1035
1046
|
refreshSessions()
|
|
@@ -1049,7 +1060,7 @@ function bindNativeBack() {
|
|
|
1049
1060
|
try {
|
|
1050
1061
|
CAP.Plugins?.App?.addListener?.('backButton', () => {
|
|
1051
1062
|
const openModal = [...document.querySelectorAll('.modal')].find(m => !m.classList.contains('hidden'))
|
|
1052
|
-
if (openModal) { openModal.classList.add('hidden'); return } // 先关弹窗
|
|
1063
|
+
if (openModal) { if (openModal.id === 'modal-notes') closeNotesModal(); else openModal.classList.add('hidden'); return } // 先关弹窗
|
|
1053
1064
|
if (document.body.classList.contains('in-session')) { closeSession(); return } // 会话页 → 回主页
|
|
1054
1065
|
if (!$('view-files').classList.contains('hidden')) { // 文件页 → 上级目录 → 主页
|
|
1055
1066
|
if (state.fs.path && state.fs.initial && state.fs.path !== state.fs.initial) { fsUp(); return }
|
|
@@ -1192,8 +1203,12 @@ async function loadHistory(reset) {
|
|
|
1192
1203
|
trimVisible()
|
|
1193
1204
|
state.history.hasMore = !!v.hasMore
|
|
1194
1205
|
state.history.loading = false
|
|
1195
|
-
|
|
1196
|
-
|
|
1206
|
+
try {
|
|
1207
|
+
if (reset) renderHistory(true)
|
|
1208
|
+
else if (added) renderHistory(false, 'keep')
|
|
1209
|
+
} catch (e) {
|
|
1210
|
+
console.error('renderHistory failed', e)
|
|
1211
|
+
}
|
|
1197
1212
|
if (moreBtn) moreBtn.classList.toggle('hidden', !state.history.hasMore)
|
|
1198
1213
|
$('history-hint').textContent = state.history.visible.length ? t('history.count', { n: state.history.visible.length }) : ''
|
|
1199
1214
|
scheduleHistoryCacheSave()
|
|
@@ -1338,6 +1353,13 @@ function shouldShowEvent(type) {
|
|
|
1338
1353
|
if (INTERESTING_EVENTS.has(type)) return true
|
|
1339
1354
|
return false
|
|
1340
1355
|
}
|
|
1356
|
+
function systemReminderText(blocks) {
|
|
1357
|
+
if (!Array.isArray(blocks)) return ''
|
|
1358
|
+
return blocks
|
|
1359
|
+
.filter(b => b && typeof b === 'object' && b.type === 'text' && String(b.text ?? '').trimStart().startsWith('<system-reminder>'))
|
|
1360
|
+
.map(b => String(b.text ?? ''))
|
|
1361
|
+
.join('\n')
|
|
1362
|
+
}
|
|
1341
1363
|
function eventHtml(entry, ctx = {}) {
|
|
1342
1364
|
const seq = entry.seq
|
|
1343
1365
|
const ev = entry.event || {}
|
|
@@ -1350,7 +1372,12 @@ function eventHtml(entry, ctx = {}) {
|
|
|
1350
1372
|
const msg = data.message || {}
|
|
1351
1373
|
const role = data.role || msg.role || (type.startsWith('user') ? 'user' : 'assistant')
|
|
1352
1374
|
const blocks = msg.content || data.content || []
|
|
1353
|
-
|
|
1375
|
+
const sysText = type === 'user/message' ? systemReminderText(blocks) : ''
|
|
1376
|
+
if (sysText) {
|
|
1377
|
+
inner = `<details class="event" data-seq="${seq}"><summary>${esc(t('event.systemReminder'))}</summary><pre>${esc(truncate(sysText, 400))}</pre></details>`
|
|
1378
|
+
} else {
|
|
1379
|
+
inner = `<div class="msg ${esc(role)}" data-seq="${seq}"><div class="role">${esc(role === 'user' ? t('role.me') : t('role.dsh'))}</div>${blocks.map(blockHtml).join('')}</div>`
|
|
1380
|
+
}
|
|
1354
1381
|
} else if (type === 'tool/call') {
|
|
1355
1382
|
const name = data.name || data.toolName || t('tool.default')
|
|
1356
1383
|
const step = (data.turn != null ? ` · turn ${data.turn}` : '') + (data.step != null ? `.${data.step}` : '')
|
|
@@ -1436,12 +1463,13 @@ async function renderSessionCards() {
|
|
|
1436
1463
|
const box = $('session-cards')
|
|
1437
1464
|
const statsBox = $('stats-body')
|
|
1438
1465
|
if (!s) { box.innerHTML = ''; if (statsBox) statsBox.innerHTML = ''; return }
|
|
1439
|
-
if (statsBox) statsBox.innerHTML = statsHtml(s)
|
|
1466
|
+
if (statsBox) { try { statsBox.innerHTML = statsHtml(s) } catch {} }
|
|
1467
|
+
box.innerHTML = ''
|
|
1440
1468
|
const goal = goalOf(s)
|
|
1441
1469
|
const todos = proj(s, 'todos')
|
|
1442
1470
|
let html = ''
|
|
1443
1471
|
|
|
1444
|
-
if (goal) {
|
|
1472
|
+
if (goal && !isGoalTerminal(goal)) {
|
|
1445
1473
|
html += `<div class="card"><div class="card-title">${t('goal.title')}</div>
|
|
1446
1474
|
<div class="goal-obj">${esc(goal.objective || '')}</div>
|
|
1447
1475
|
<div class="goal-phase">phase: ${esc(goal.phase || '?')} · revision ${goal.revision ?? '?'}</div>
|
|
@@ -1476,6 +1504,16 @@ async function renderSessionCards() {
|
|
|
1476
1504
|
}
|
|
1477
1505
|
}
|
|
1478
1506
|
|
|
1507
|
+
function setGoalPhaseLocal(phase) {
|
|
1508
|
+
const s = state.byId.get(state.current)
|
|
1509
|
+
const p = s && proj(s, 'goal')
|
|
1510
|
+
const goal = p && typeof p === 'object' && p.goal && typeof p.goal === 'object' ? p.goal : p
|
|
1511
|
+
if (!goal) return
|
|
1512
|
+
goal.phase = phase
|
|
1513
|
+
renderSessions()
|
|
1514
|
+
renderSessionCards()
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1479
1517
|
async function goalAction(kind) {
|
|
1480
1518
|
const s = state.byId.get(state.current)
|
|
1481
1519
|
const goal = goalOf(s)
|
|
@@ -1488,6 +1526,8 @@ async function goalAction(kind) {
|
|
|
1488
1526
|
if (kind === 'clear' && !confirm(t('goal.confirmClear'))) return
|
|
1489
1527
|
if (kind === 'complete' && !confirm(t('goal.confirmComplete'))) return
|
|
1490
1528
|
await safeRpc(method, { sessionId: state.current, ref }, t('goal.actionFailed'))
|
|
1529
|
+
if (kind === 'complete') setGoalPhaseLocal('complete')
|
|
1530
|
+
if (kind === 'clear') setGoalPhaseLocal('cleared')
|
|
1491
1531
|
toast(t('goal.actionSubmitted'), 'ok')
|
|
1492
1532
|
scheduleRefresh()
|
|
1493
1533
|
}
|
|
@@ -1500,9 +1540,34 @@ async function interruptSubagent(childId) {
|
|
|
1500
1540
|
}
|
|
1501
1541
|
|
|
1502
1542
|
/* ---------------- 发送 / 取消 / 快捷菜单 ---------------- */
|
|
1543
|
+
async function runSlashCommand(text) {
|
|
1544
|
+
const clean = String(text || '').trim()
|
|
1545
|
+
if (!clean.startsWith('/') || !state.current) return false
|
|
1546
|
+
try {
|
|
1547
|
+
const signal = typeof AbortSignal !== 'undefined' && typeof AbortSignal.timeout === 'function'
|
|
1548
|
+
? AbortSignal.timeout(20000)
|
|
1549
|
+
: undefined
|
|
1550
|
+
const res = await fetch(apiUrl('/remote/api/command'), {
|
|
1551
|
+
method: 'POST',
|
|
1552
|
+
headers: { 'content-type': 'application/json', authorization: 'Bearer ' + state.token, 'x-dsh-remote-client': CAP?.isNativePlatform?.() ? 'app' : 'web' },
|
|
1553
|
+
body: JSON.stringify({ sessionId: state.current, line: clean }),
|
|
1554
|
+
...(signal ? { signal } : {})
|
|
1555
|
+
})
|
|
1556
|
+
if (res.status === 401) { authFailure(); return true }
|
|
1557
|
+
if (!res.ok) return false
|
|
1558
|
+
const data = await res.json().catch(() => null)
|
|
1559
|
+
if (data?.ok === false) { toast(data.message || t('send.failed'), 'err'); return true }
|
|
1560
|
+
if (data?.ok && data.executed === true) { toast(t('send.commandExecuted'), 'ok'); return true }
|
|
1561
|
+
} catch (e) {
|
|
1562
|
+
console.error('slash command bridge failed', e)
|
|
1563
|
+
}
|
|
1564
|
+
return false
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1503
1567
|
async function sendSessionText(text) {
|
|
1504
1568
|
const clean = String(text || '').trim()
|
|
1505
1569
|
if (!clean || !state.current) return false
|
|
1570
|
+
if (await runSlashCommand(clean)) return true
|
|
1506
1571
|
$('btn-send').disabled = true
|
|
1507
1572
|
const v = await safeRpc('session.prompt', {
|
|
1508
1573
|
sessionId: state.current,
|
|
@@ -2240,6 +2305,83 @@ async function loadLocalVersion() {
|
|
|
2240
2305
|
$('update-desc').textContent = state.localVersion ? t('update.currentV', { version: state.localVersion }) : t('update.noVersion')
|
|
2241
2306
|
}
|
|
2242
2307
|
|
|
2308
|
+
/* ---------------- 更新内容弹窗 ---------------- */
|
|
2309
|
+
const NOTES_KEY = 'seenNotesVersion'
|
|
2310
|
+
let notesVersion = ''
|
|
2311
|
+
let notesPages = []
|
|
2312
|
+
let notesPage = 0
|
|
2313
|
+
function splitNotes(notes) {
|
|
2314
|
+
return String(notes || '').split(/[;;]/).map(s => s.trim()).filter(Boolean)
|
|
2315
|
+
}
|
|
2316
|
+
function renderNotesPages(items) {
|
|
2317
|
+
const box = $('notes-pages')
|
|
2318
|
+
if (!box) return
|
|
2319
|
+
const pages = []
|
|
2320
|
+
for (let i = 0; i < items.length; i += 3) pages.push(items.slice(i, i + 3))
|
|
2321
|
+
notesPages = pages
|
|
2322
|
+
notesPage = 0
|
|
2323
|
+
box.innerHTML = pages.map(page => `<div class="notes-page" style="flex:0 0 100%;scroll-snap-align:start;box-sizing:border-box;min-width:0;">${page.map(item => `<div class="notes-item" style="padding:6px 0;line-height:1.5;">${esc(item)}</div>`).join('')}</div>`).join('')
|
|
2324
|
+
box.scrollLeft = 0
|
|
2325
|
+
updateNotesPage()
|
|
2326
|
+
}
|
|
2327
|
+
function renderNotesVersionPages(entries) {
|
|
2328
|
+
const box = $('notes-pages')
|
|
2329
|
+
if (!box) return
|
|
2330
|
+
notesPages = entries
|
|
2331
|
+
notesPage = 0
|
|
2332
|
+
box.innerHTML = entries.map(entry => {
|
|
2333
|
+
const items = splitNotes(entry.notes)
|
|
2334
|
+
return `<div class="notes-page" style="flex:0 0 100%;scroll-snap-align:start;box-sizing:border-box;min-width:0;">
|
|
2335
|
+
<div class="notes-version-title" style="font-weight:700;margin-bottom:6px;opacity:.9;">v${esc(entry.version)}</div>
|
|
2336
|
+
${items.length ? items.map(item => `<div class="notes-item" style="padding:6px 0;line-height:1.5;">${esc(item)}</div>`).join('') : `<div class="notes-item" style="padding:6px 0;line-height:1.5;">${esc(entry.notes || '')}</div>`}
|
|
2337
|
+
</div>`
|
|
2338
|
+
}).join('')
|
|
2339
|
+
box.scrollLeft = 0
|
|
2340
|
+
updateNotesPage()
|
|
2341
|
+
}
|
|
2342
|
+
function updateNotesPage() {
|
|
2343
|
+
const box = $('notes-pages')
|
|
2344
|
+
const pageEl = $('notes-page')
|
|
2345
|
+
if (!box || !pageEl) return
|
|
2346
|
+
const total = notesPages.length || 1
|
|
2347
|
+
const idx = Math.min(Math.max(0, Math.round(box.scrollLeft / Math.max(1, box.clientWidth))), total - 1)
|
|
2348
|
+
notesPage = idx
|
|
2349
|
+
pageEl.textContent = t('notes.page', { current: idx + 1, total })
|
|
2350
|
+
}
|
|
2351
|
+
function scrollNotes(dir) {
|
|
2352
|
+
const box = $('notes-pages')
|
|
2353
|
+
if (box) box.scrollBy({ left: dir * box.clientWidth, behavior: 'smooth' })
|
|
2354
|
+
}
|
|
2355
|
+
function openNotesModal(info) {
|
|
2356
|
+
if (!info?.version) return
|
|
2357
|
+
const history = Array.isArray(info.history) ? info.history.filter(h => h && typeof h.version === 'string' && typeof h.notes === 'string' && !String(h.version).includes('-rc')) : []
|
|
2358
|
+
const latestStable = history[0]?.version || info.version
|
|
2359
|
+
if (history.length) {
|
|
2360
|
+
const entries = history.filter(h => cmpVersion(h.version, state.localVersion) > 0)
|
|
2361
|
+
if (!entries.length) return
|
|
2362
|
+
if (LS.get(NOTES_KEY) === latestStable) return
|
|
2363
|
+
notesVersion = latestStable
|
|
2364
|
+
const vEl = $('notes-version')
|
|
2365
|
+
if (vEl) vEl.textContent = 'v' + latestStable
|
|
2366
|
+
renderNotesVersionPages(entries.reverse())
|
|
2367
|
+
$('modal-notes').classList.remove('hidden')
|
|
2368
|
+
return
|
|
2369
|
+
}
|
|
2370
|
+
if (String(info.version).includes('-rc')) return
|
|
2371
|
+
if (LS.get(NOTES_KEY) === info.version) return
|
|
2372
|
+
const items = splitNotes(info.notes)
|
|
2373
|
+
if (!items.length) return
|
|
2374
|
+
notesVersion = info.version
|
|
2375
|
+
const vEl = $('notes-version')
|
|
2376
|
+
if (vEl) vEl.textContent = 'v' + info.version
|
|
2377
|
+
renderNotesPages(items)
|
|
2378
|
+
$('modal-notes').classList.remove('hidden')
|
|
2379
|
+
}
|
|
2380
|
+
function closeNotesModal() {
|
|
2381
|
+
$('modal-notes').classList.add('hidden')
|
|
2382
|
+
if (notesVersion) { LS.set(NOTES_KEY, notesVersion); notesVersion = '' }
|
|
2383
|
+
}
|
|
2384
|
+
|
|
2243
2385
|
async function checkUpdate(silent) {
|
|
2244
2386
|
if (!state.localVersion) {
|
|
2245
2387
|
$('update-desc').textContent = t('update.noVersion')
|
|
@@ -2259,6 +2401,7 @@ async function checkUpdate(silent) {
|
|
|
2259
2401
|
const res = await fetch(base + '/update.json?t=' + Date.now() + '&local=' + encodeURIComponent(state.localVersion))
|
|
2260
2402
|
if (!res.ok) throw new Error('HTTP ' + res.status)
|
|
2261
2403
|
const info = await res.json()
|
|
2404
|
+
openNotesModal(info)
|
|
2262
2405
|
if (info.version && cmpVersion(info.version, state.localVersion) > 0) {
|
|
2263
2406
|
state.updateInfo = info
|
|
2264
2407
|
const hasNotes = !!(info.notes && String(info.notes).trim())
|
|
@@ -2401,6 +2544,108 @@ function notify(title, body) {
|
|
|
2401
2544
|
} catch {}
|
|
2402
2545
|
}
|
|
2403
2546
|
|
|
2547
|
+
/* ---------------- 后台轮询(Android 前台服务) ---------------- */
|
|
2548
|
+
function bgBridge() { return window.NativeBackground }
|
|
2549
|
+
function bgBase() { return (state.server || location.origin || '').replace(/\/+$/, '') }
|
|
2550
|
+
function applyBgConfigFromNative() {
|
|
2551
|
+
const b = bgBridge()
|
|
2552
|
+
if (!b?.getBackgroundConfig) return
|
|
2553
|
+
try {
|
|
2554
|
+
const cfg = JSON.parse(b.getBackgroundConfig() || '{}')
|
|
2555
|
+
$('opt-bg-poll').checked = !!cfg.enabled
|
|
2556
|
+
const v = String(cfg.intervalMin ?? 1)
|
|
2557
|
+
const opts = Array.from($('bg-interval')?.options || [])
|
|
2558
|
+
if (opts.some(o => o.value === v)) $('bg-interval').value = v
|
|
2559
|
+
if ($('opt-task-done')) $('opt-task-done').checked = cfg.notifyTaskDone !== false
|
|
2560
|
+
$('bg-auth-status')?.classList.toggle('hidden', !cfg.loginExpired)
|
|
2561
|
+
} catch {}
|
|
2562
|
+
}
|
|
2563
|
+
function saveBgConfig(enabled) {
|
|
2564
|
+
const b = bgBridge()
|
|
2565
|
+
if (!b?.saveBackgroundConfig) return false
|
|
2566
|
+
const base = bgBase()
|
|
2567
|
+
const intervalMin = parseFloat($('bg-interval')?.value || '1') || 1
|
|
2568
|
+
const notifyTaskDone = $('opt-task-done')?.checked !== false
|
|
2569
|
+
b.saveBackgroundConfig(JSON.stringify({ enabled, intervalMin, base, token: state.token || '', notifyTaskDone }))
|
|
2570
|
+
if (enabled) $('bg-auth-status')?.classList.add('hidden')
|
|
2571
|
+
return true
|
|
2572
|
+
}
|
|
2573
|
+
function syncBgConfig() {
|
|
2574
|
+
if ($('opt-bg-poll')?.checked && state.token) saveBgConfig(true)
|
|
2575
|
+
}
|
|
2576
|
+
|
|
2577
|
+
/* ---------------- 预设提示词 ---------------- */
|
|
2578
|
+
const PRESETS_KEY = 'dshPromptPresets'
|
|
2579
|
+
const PRESET_NAME_MAX = 20
|
|
2580
|
+
const PRESET_TEXT_MAX = 2000
|
|
2581
|
+
const PRESET_LIMIT = 20
|
|
2582
|
+
function readPresets() {
|
|
2583
|
+
try {
|
|
2584
|
+
const v = JSON.parse(LS.get(PRESETS_KEY, '[]') || '[]')
|
|
2585
|
+
return Array.isArray(v) ? v.filter(p => p && typeof p.id === 'string' && typeof p.name === 'string' && typeof p.text === 'string') : []
|
|
2586
|
+
} catch { return [] }
|
|
2587
|
+
}
|
|
2588
|
+
function writePresets(list) {
|
|
2589
|
+
LS.set(PRESETS_KEY, JSON.stringify(list))
|
|
2590
|
+
renderPresets()
|
|
2591
|
+
renderPresetMenu()
|
|
2592
|
+
}
|
|
2593
|
+
function renderPresets() {
|
|
2594
|
+
const box = $('preset-list')
|
|
2595
|
+
if (!box) return
|
|
2596
|
+
const list = readPresets()
|
|
2597
|
+
if (!list.length) {
|
|
2598
|
+
box.innerHTML = `<div class="server-empty">${esc(t('presets.empty'))}</div>`
|
|
2599
|
+
return
|
|
2600
|
+
}
|
|
2601
|
+
box.innerHTML = list.map(p => `<div class="server-row">
|
|
2602
|
+
<div class="server-main"><div class="server-note">${esc(p.name)}</div><div class="server-url">${esc((p.text || '').slice(0, 60))}</div></div>
|
|
2603
|
+
<button class="mini-btn" data-preset-edit="${esc(p.id)}">${t('presets.edit')}</button>
|
|
2604
|
+
<button class="mini-btn" data-preset-del="${esc(p.id)}">${t('presets.delete')}</button>
|
|
2605
|
+
</div>`).join('')
|
|
2606
|
+
box.querySelectorAll('[data-preset-edit]').forEach(b => b.addEventListener('click', () => editPreset(b.dataset.presetEdit)))
|
|
2607
|
+
box.querySelectorAll('[data-preset-del]').forEach(b => b.addEventListener('click', () => deletePreset(b.dataset.presetDel)))
|
|
2608
|
+
}
|
|
2609
|
+
function renderPresetMenu() {
|
|
2610
|
+
const group = $('preset-menu-group')
|
|
2611
|
+
const listBox = $('preset-menu-list')
|
|
2612
|
+
if (!group || !listBox) return
|
|
2613
|
+
const list = readPresets()
|
|
2614
|
+
group.classList.toggle('hidden', !list.length)
|
|
2615
|
+
listBox.innerHTML = list.map(p => `<button class="menu-chip" data-preset="${esc(p.id)}">${esc(p.name)}</button>`).join('')
|
|
2616
|
+
}
|
|
2617
|
+
function promptPreset(id) {
|
|
2618
|
+
const list = readPresets()
|
|
2619
|
+
const existing = id ? list.find(p => p.id === id) : null
|
|
2620
|
+
const name = prompt(t('presets.namePrompt'), existing?.name || '')
|
|
2621
|
+
if (name == null) return
|
|
2622
|
+
const text = prompt(t('presets.textPrompt'), existing?.text || '')
|
|
2623
|
+
if (text == null) return
|
|
2624
|
+
const n = (name || '').trim()
|
|
2625
|
+
if (!n) return toast(t('presets.nameEmpty'), 'err')
|
|
2626
|
+
if (n.length > PRESET_NAME_MAX) return toast(t('presets.nameTooLong'), 'err')
|
|
2627
|
+
if (text.length > PRESET_TEXT_MAX) return toast(t('presets.textTooLong'), 'err')
|
|
2628
|
+
if (existing) {
|
|
2629
|
+
existing.name = n
|
|
2630
|
+
existing.text = text
|
|
2631
|
+
} else {
|
|
2632
|
+
if (list.length >= PRESET_LIMIT) return toast(t('presets.limit'), 'err')
|
|
2633
|
+
list.push({ id: 'p' + Date.now().toString(36) + Math.random().toString(36).slice(2, 6), name: n, text })
|
|
2634
|
+
}
|
|
2635
|
+
writePresets(list)
|
|
2636
|
+
toast(existing ? t('presets.saved') : t('presets.added'), 'ok')
|
|
2637
|
+
}
|
|
2638
|
+
function addPreset() { promptPreset(null) }
|
|
2639
|
+
function editPreset(id) { promptPreset(id) }
|
|
2640
|
+
function deletePreset(id) {
|
|
2641
|
+
const list = readPresets()
|
|
2642
|
+
const p = list.find(x => x.id === id)
|
|
2643
|
+
if (!p) return
|
|
2644
|
+
if (!confirm(t('presets.confirmDelete', { name: p.name }))) return
|
|
2645
|
+
writePresets(list.filter(x => x.id !== id))
|
|
2646
|
+
toast(t('presets.deleted'), 'ok')
|
|
2647
|
+
}
|
|
2648
|
+
|
|
2404
2649
|
/* ---------------- 峰谷计费提醒(每天 9/12/14/18 点本地通知) ---------------- */
|
|
2405
2650
|
const PEAK_REMIND_NOTIFS = [
|
|
2406
2651
|
{ id: 8801, hour: 9, periodKey: 'peak0912', enterKey: 'enterPeak' },
|
|
@@ -2446,6 +2691,23 @@ function showView(id) {
|
|
|
2446
2691
|
window.scrollTo(0, 0)
|
|
2447
2692
|
if (id === 'view-files' && !state.fs.loaded) loadFs(null, { silent: true })
|
|
2448
2693
|
if (id === 'view-stats') loadStats()
|
|
2694
|
+
if (id === 'view-settings') showSettingsHome()
|
|
2695
|
+
}
|
|
2696
|
+
|
|
2697
|
+
const SETTINGS_GROUPS = ['general', 'servers', 'notify', 'theme', 'about']
|
|
2698
|
+
function showSettingsHome() {
|
|
2699
|
+
const home = $('settings-home')
|
|
2700
|
+
if (!home) return
|
|
2701
|
+
home.classList.remove('hidden')
|
|
2702
|
+
for (const name of SETTINGS_GROUPS) $('settings-page-' + name)?.classList.add('hidden')
|
|
2703
|
+
window.scrollTo(0, 0)
|
|
2704
|
+
}
|
|
2705
|
+
function showSettingsPage(name) {
|
|
2706
|
+
const home = $('settings-home')
|
|
2707
|
+
if (!home || !SETTINGS_GROUPS.includes(name)) return
|
|
2708
|
+
home.classList.add('hidden')
|
|
2709
|
+
for (const g of SETTINGS_GROUPS) $('settings-page-' + g)?.classList.toggle('hidden', g !== name)
|
|
2710
|
+
window.scrollTo(0, 0)
|
|
2449
2711
|
}
|
|
2450
2712
|
|
|
2451
2713
|
function updateConn() {
|
|
@@ -2489,6 +2751,7 @@ function applyPairUrl(url) {
|
|
|
2489
2751
|
saveServers()
|
|
2490
2752
|
renderServers()
|
|
2491
2753
|
$('token-desc').textContent = t('token.savedScan')
|
|
2754
|
+
syncBgConfig()
|
|
2492
2755
|
return true
|
|
2493
2756
|
} catch {
|
|
2494
2757
|
return false
|
|
@@ -2629,6 +2892,11 @@ function openThemePanel() {
|
|
|
2629
2892
|
$('modal-theme').classList.remove('hidden')
|
|
2630
2893
|
}
|
|
2631
2894
|
|
|
2895
|
+
function openDonateModal() {
|
|
2896
|
+
const m = $('modal-donate')
|
|
2897
|
+
if (m) m.classList.remove('hidden')
|
|
2898
|
+
}
|
|
2899
|
+
|
|
2632
2900
|
function bindUi() {
|
|
2633
2901
|
renderLangBtn()
|
|
2634
2902
|
renderThemeBtn()
|
|
@@ -2649,6 +2917,17 @@ function bindUi() {
|
|
|
2649
2917
|
})
|
|
2650
2918
|
$('btn-theme').addEventListener('click', openThemePanel)
|
|
2651
2919
|
$('theme-close').addEventListener('click', () => $('modal-theme').classList.add('hidden'))
|
|
2920
|
+
$('btn-donate').addEventListener('click', openDonateModal)
|
|
2921
|
+
$('donate-close').addEventListener('click', () => $('modal-donate').classList.add('hidden'))
|
|
2922
|
+
document.addEventListener('click', (e) => {
|
|
2923
|
+
if (e.target.closest('[data-donate-open]')) openDonateModal()
|
|
2924
|
+
})
|
|
2925
|
+
// 更新内容弹窗
|
|
2926
|
+
$('notes-close').addEventListener('click', closeNotesModal)
|
|
2927
|
+
$('notes-prev').addEventListener('click', () => scrollNotes(-1))
|
|
2928
|
+
$('notes-next').addEventListener('click', () => scrollNotes(1))
|
|
2929
|
+
$('notes-pages').addEventListener('scroll', updateNotesPage)
|
|
2930
|
+
$('modal-notes').addEventListener('click', (e) => { if (e.target === $('modal-notes')) closeNotesModal() })
|
|
2652
2931
|
renderServers()
|
|
2653
2932
|
// 底部导航
|
|
2654
2933
|
document.querySelectorAll('.nav-btn').forEach(b =>
|
|
@@ -2691,9 +2970,27 @@ function bindUi() {
|
|
|
2691
2970
|
$('btn-cancel').addEventListener('click', cancelSession)
|
|
2692
2971
|
$('btn-send').addEventListener('click', sendMessage)
|
|
2693
2972
|
$('btn-plus').addEventListener('click', toggleComposerMenu)
|
|
2694
|
-
$('composer-menu').addEventListener('click', (e) => {
|
|
2973
|
+
$('composer-menu').addEventListener('click', async (e) => {
|
|
2695
2974
|
const chip = e.target.closest('[data-cmd]')
|
|
2696
|
-
if (chip) {
|
|
2975
|
+
if (chip) {
|
|
2976
|
+
const input = $('composer-input')
|
|
2977
|
+
input.value = chip.dataset.cmd + ' '
|
|
2978
|
+
input.focus()
|
|
2979
|
+
autosize(input)
|
|
2980
|
+
hideComposerMenu()
|
|
2981
|
+
return
|
|
2982
|
+
}
|
|
2983
|
+
const preset = e.target.closest('[data-preset]')
|
|
2984
|
+
if (preset) {
|
|
2985
|
+
const found = readPresets().find(x => x.id === preset.dataset.preset)
|
|
2986
|
+
if (found) {
|
|
2987
|
+
const input = $('composer-input')
|
|
2988
|
+
input.value = found.text
|
|
2989
|
+
input.focus()
|
|
2990
|
+
autosize(input)
|
|
2991
|
+
}
|
|
2992
|
+
hideComposerMenu()
|
|
2993
|
+
}
|
|
2697
2994
|
})
|
|
2698
2995
|
$('btn-model-refresh').addEventListener('click', loadSessionModels)
|
|
2699
2996
|
const input = $('composer-input')
|
|
@@ -2718,10 +3015,15 @@ function bindUi() {
|
|
|
2718
3015
|
$('goal-close').addEventListener('click', () => $('modal-goal').classList.add('hidden'))
|
|
2719
3016
|
$('goal-edit').addEventListener('click', submitGoalEdit)
|
|
2720
3017
|
// 设置
|
|
3018
|
+
$('view-settings').addEventListener('click', (e) => {
|
|
3019
|
+
const group = e.target.closest('[data-settings-group]')
|
|
3020
|
+
if (group) { showSettingsPage(group.dataset.settingsGroup); return }
|
|
3021
|
+
if (e.target.closest('[data-settings-back]')) { showSettingsHome(); return }
|
|
3022
|
+
})
|
|
2721
3023
|
$('btn-scan-pair').addEventListener('click', scanPair)
|
|
2722
3024
|
$('btn-change-token').addEventListener('click', () => {
|
|
2723
3025
|
const input = prompt(t('token.prompt'), state.token)
|
|
2724
|
-
if (input && input.trim()) { state.token = input.trim(); LS.set('token', input.trim()); $('token-desc').textContent = t('token.saved'); toast(t('token.savedReconnect'), 'ok'); openStreams(); refreshAll() }
|
|
3026
|
+
if (input && input.trim()) { state.token = input.trim(); LS.set('token', input.trim()); $('token-desc').textContent = t('token.saved'); toast(t('token.savedReconnect'), 'ok'); openStreams(); refreshAll(); syncBgConfig() }
|
|
2725
3027
|
})
|
|
2726
3028
|
$('btn-server-speed').addEventListener('click', () => selectFastestServer({ silent: false }))
|
|
2727
3029
|
$('btn-server-add').addEventListener('click', addServer)
|
|
@@ -2746,6 +3048,7 @@ function bindUi() {
|
|
|
2746
3048
|
$('btn-reset').addEventListener('click', () => {
|
|
2747
3049
|
if (!confirm(t('settings.confirmReset'))) return
|
|
2748
3050
|
LS.del('token'); LS.del('notify'); LS.del('server')
|
|
3051
|
+
if (bgBridge()?.saveBackgroundConfig) saveBgConfig(false)
|
|
2749
3052
|
location.reload()
|
|
2750
3053
|
})
|
|
2751
3054
|
$('opt-notify').checked = LS.get('notify', '0') === '1'
|
|
@@ -2775,6 +3078,35 @@ function bindUi() {
|
|
|
2775
3078
|
})
|
|
2776
3079
|
// 已开启则启动时重新调度, 防止系统清理后丢失
|
|
2777
3080
|
if (peakRemindOn() && CAP?.isNativePlatform?.()) schedulePeakReminders()
|
|
3081
|
+
applyBgConfigFromNative()
|
|
3082
|
+
$('opt-bg-poll').addEventListener('change', async (e) => {
|
|
3083
|
+
if (e.target.checked) {
|
|
3084
|
+
if (!CAP?.isNativePlatform?.() || !bgBridge()?.saveBackgroundConfig) {
|
|
3085
|
+
e.target.checked = false
|
|
3086
|
+
return toast(t('settings.bgNativeOnly'), 'err')
|
|
3087
|
+
}
|
|
3088
|
+
if (!state.token) {
|
|
3089
|
+
e.target.checked = false
|
|
3090
|
+
return toast(t('settings.bgNeedToken'), 'err')
|
|
3091
|
+
}
|
|
3092
|
+
const ok = await ensureNotify()
|
|
3093
|
+
if (!ok) { e.target.checked = false; return toast(t('settings.notifyDenied')) }
|
|
3094
|
+
saveBgConfig(true)
|
|
3095
|
+
toast(t('settings.bgOn'), 'ok')
|
|
3096
|
+
} else {
|
|
3097
|
+
saveBgConfig(false)
|
|
3098
|
+
toast(t('settings.bgOff'), 'ok')
|
|
3099
|
+
}
|
|
3100
|
+
})
|
|
3101
|
+
$('bg-interval').addEventListener('change', () => {
|
|
3102
|
+
if ($('opt-bg-poll')?.checked) saveBgConfig(true)
|
|
3103
|
+
})
|
|
3104
|
+
$('opt-task-done')?.addEventListener('change', () => {
|
|
3105
|
+
if (bgBridge()?.saveBackgroundConfig) saveBgConfig($('opt-bg-poll')?.checked)
|
|
3106
|
+
})
|
|
3107
|
+
renderPresets()
|
|
3108
|
+
renderPresetMenu()
|
|
3109
|
+
$('btn-preset-add').addEventListener('click', addPreset)
|
|
2778
3110
|
$('opt-tools').checked = LS.get('showTools', '1') !== '0'
|
|
2779
3111
|
$('opt-tools').addEventListener('change', (e) => {
|
|
2780
3112
|
LS.set('showTools', e.target.checked ? '1' : '0')
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/* DSH Remote 桌面端专用样式 · 零依赖 · 只引用 --dsr-* 皮肤变量 */
|
|
2
2
|
html, body { height: 100%; margin: 0; overflow: hidden; overscroll-behavior: none; background: var(--dsr-bg); color: var(--dsr-text); }
|
|
3
|
+
.hidden { display: none !important; }
|
|
3
4
|
.ds-app { position: fixed; inset: 0; display: flex; overflow: hidden; }
|
|
4
5
|
.ds-sidebar { width: 280px; flex: none; display: flex; flex-direction: column; border-right: 1px solid var(--dsr-line); background: var(--dsr-panel); }
|
|
5
6
|
.ds-brand { display: flex; align-items: center; gap: 8px; padding: 14px 14px 10px; font-weight: 700; font-size: 15px; }
|
|
@@ -52,12 +53,10 @@ a.ds-btn { text-decoration: none; }
|
|
|
52
53
|
font: inherit; text-align: left; cursor: pointer; text-decoration: none;
|
|
53
54
|
}
|
|
54
55
|
.ds-feedback-item:hover, .ds-feedback-item:focus-visible { background: var(--dsr-bg); outline: none; }
|
|
55
|
-
.ds-feedback-item.primary { background: var(--dsr-accent-soft); border: 1px solid var(--dsr-accent-line); }
|
|
56
56
|
.ds-feedback-ico {
|
|
57
57
|
width: 32px; height: 32px; border-radius: 9px; flex-shrink: 0;
|
|
58
58
|
display: grid; place-items: center; background: var(--dsr-panel); border: 1px solid var(--dsr-line); color: var(--dsr-accent-strong);
|
|
59
59
|
}
|
|
60
|
-
.ds-feedback-item.primary .ds-feedback-ico { background: var(--dsr-accent-strong); border-color: var(--dsr-accent-strong); color: var(--dsr-on-accent); }
|
|
61
60
|
.ds-feedback-ico svg { width: 16px; height: 16px; fill: currentColor; }
|
|
62
61
|
.ds-feedback-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
|
|
63
62
|
.ds-feedback-name { font-size: 13px; font-weight: 600; }
|
|
@@ -100,16 +99,47 @@ a.ds-btn { text-decoration: none; }
|
|
|
100
99
|
/* 会话视图 */
|
|
101
100
|
#view-chat { padding: 0; }
|
|
102
101
|
.ds-history { flex: 1; min-height: 0; overflow-y: auto; overscroll-behavior: contain; padding: 18px 22px; display: flex; flex-direction: column; gap: 10px; }
|
|
103
|
-
.ds-msg { max-width: 78
|
|
102
|
+
.ds-msg { max-width: min(78%, 680px); width: fit-content; box-sizing: border-box; padding: 9px 12px; border-radius: 12px; font-size: 13.5px; line-height: 1.6; white-space: pre-wrap; word-break: break-word; }
|
|
104
103
|
.ds-msg.user { align-self: flex-end; background: var(--dsr-accent-soft); border: 1px solid var(--dsr-accent-line); color: var(--dsr-accent-strong); }
|
|
105
104
|
.ds-msg.assistant { align-self: flex-start; background: var(--dsr-panel); border: 1px solid var(--dsr-line); color: var(--dsr-text); }
|
|
106
105
|
.ds-msg .role { font-size: 10px; color: var(--dsr-muted); margin-bottom: 3px; }
|
|
107
|
-
.ds-tool { align-self: flex-start; max-width: 78
|
|
106
|
+
.ds-tool { align-self: flex-start; max-width: min(78%, 680px); width: fit-content; box-sizing: border-box; background: var(--dsr-bg-2); border: 1px solid var(--dsr-line); border-radius: 10px; padding: 8px 11px; font-size: 12px; }
|
|
108
107
|
.ds-tool summary { cursor: pointer; color: var(--dsr-muted); }
|
|
109
108
|
.ds-tool pre { margin: 6px 0 0; white-space: pre-wrap; word-break: break-all; font-size: 11px; color: var(--dsr-text); }
|
|
110
109
|
.ds-empty { color: var(--dsr-muted); text-align: center; padding: 30px 0; font-size: 13px; }
|
|
111
|
-
.ds-
|
|
112
|
-
.ds-
|
|
110
|
+
.ds-presets-guide { padding: 2px 8px 10px; font-size: 11px; line-height: 1.5; }
|
|
111
|
+
.ds-session-cards { flex: none; max-height: 220px; overflow-y: auto; padding: 10px 22px 0; display: flex; flex-direction: column; gap: 8px; }
|
|
112
|
+
.ds-session-cards:empty { display: none; }
|
|
113
|
+
.ds-card { background: var(--dsr-panel); border: 1px solid var(--dsr-line); border-radius: 12px; padding: 10px 12px; }
|
|
114
|
+
.ds-card-title { font-size: 11px; font-weight: 700; color: var(--dsr-muted); letter-spacing: .6px; margin-bottom: 6px; text-transform: uppercase; }
|
|
115
|
+
.ds-goal-obj { font-size: 13px; line-height: 1.55; word-break: break-word; }
|
|
116
|
+
.ds-goal-phase { font-size: 11px; color: var(--dsr-muted); margin-top: 3px; }
|
|
117
|
+
.ds-goal-actions { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 8px; }
|
|
118
|
+
.ds-mini-btn { display: inline-flex; align-items: center; justify-content: center; min-height: 26px; padding: 2px 9px; border-radius: 7px; background: var(--dsr-bg-2); border: 1px solid var(--dsr-line); color: var(--dsr-text); font: inherit; font-size: 11.5px; cursor: pointer; }
|
|
119
|
+
.ds-mini-btn:hover { filter: brightness(1.08); }
|
|
120
|
+
.ds-todo-row { display: flex; align-items: flex-start; gap: 8px; font-size: 12.5px; line-height: 1.5; padding: 2px 0; }
|
|
121
|
+
.ds-pill { flex: none; font-size: 10.5px; line-height: 16px; padding: 0 7px; border-radius: 999px; background: var(--dsr-bg-2); border: 1px solid var(--dsr-line); color: var(--dsr-muted); }
|
|
122
|
+
.ds-pill.active { background: var(--dsr-accent-soft); border-color: var(--dsr-accent-line); color: var(--dsr-accent-strong); }
|
|
123
|
+
.ds-pill.done { background: var(--dsr-success-soft); border-color: var(--dsr-success-line); color: var(--dsr-success); }
|
|
124
|
+
.ds-card-row { display: flex; align-items: center; gap: 10px; font-size: 12.5px; padding: 3px 0; }
|
|
125
|
+
.ds-card-k { flex: 1; min-width: 0; word-break: break-word; }
|
|
126
|
+
.ds-card-v { color: var(--dsr-muted); font-size: 11.5px; white-space: nowrap; }
|
|
127
|
+
.ds-model-head { font-size: 11px; font-weight: 700; color: var(--dsr-muted); letter-spacing: .6px; padding: 4px 6px 2px; }
|
|
128
|
+
.ds-model-group { padding: 2px 0 4px; }
|
|
129
|
+
.ds-model-provider { font-size: 11px; color: var(--dsr-muted); padding: 2px 6px; }
|
|
130
|
+
.ds-model-chips { display: flex; flex-wrap: wrap; gap: 6px; padding: 2px 6px 4px; }
|
|
131
|
+
.ds-model-chip { display: inline-flex; align-items: center; min-height: 26px; padding: 2px 9px; border-radius: 999px; background: var(--dsr-bg-2); border: 1px solid var(--dsr-line); color: var(--dsr-text); font: inherit; font-size: 11.5px; cursor: pointer; white-space: nowrap; }
|
|
132
|
+
.ds-model-chip:hover { filter: brightness(1.08); }
|
|
133
|
+
.ds-model-chip.current { background: var(--dsr-accent-soft); border-color: var(--dsr-accent-line); color: var(--dsr-accent-strong); font-weight: 600; }
|
|
134
|
+
.ds-model-effort-group { padding-top: 2px; border-top: 1px solid var(--dsr-divider); margin-top: 2px; }
|
|
135
|
+
.ds-model-effort-group.hidden { display: none; }
|
|
136
|
+
.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); }
|
|
137
|
+
.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; }
|
|
138
|
+
.ds-composer-actions { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
|
|
139
|
+
.ds-composer-left { display: flex; align-items: center; gap: 8px; min-width: 0; }
|
|
140
|
+
.ds-composer-right { display: flex; align-items: center; flex: none; }
|
|
141
|
+
.ds-composer .ds-btn { min-height: 34px; box-sizing: border-box; }
|
|
142
|
+
.ds-send-btn { width: 36px; height: 36px; min-height: 36px; padding: 0; border-radius: 50%; background: var(--dsr-accent-strong); border-color: var(--dsr-accent-strong); color: var(--dsr-on-accent); font-size: 18px; line-height: 1; justify-content: center; }
|
|
113
143
|
|
|
114
144
|
/* 文件传输 */
|
|
115
145
|
.ds-fs-bar { display: flex; align-items: center; gap: 8px; margin: 8px 0; }
|
|
@@ -122,12 +152,26 @@ a.ds-btn { text-decoration: none; }
|
|
|
122
152
|
.ds-fs-size { color: var(--dsr-muted); font-size: 12px; white-space: nowrap; }
|
|
123
153
|
|
|
124
154
|
/* 设置 */
|
|
155
|
+
#view-settings { overflow-y: auto; }
|
|
125
156
|
.ds-settings { max-width: 720px; display: flex; flex-direction: column; gap: 10px; }
|
|
126
|
-
.ds-setting-row { display: flex; align-items: center; gap:
|
|
157
|
+
.ds-setting-row { display: flex; align-items: center; gap: 10px; background: var(--dsr-panel); border: 1px solid var(--dsr-line); border-radius: 12px; padding: 10px 14px; }
|
|
127
158
|
.ds-setting-row > div:first-child { flex: 1; min-width: 0; }
|
|
159
|
+
.ds-setting-arrow,
|
|
160
|
+
#settings-home .ds-setting-row .ds-btn {
|
|
161
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
162
|
+
min-height: 32px; padding: 0 10px; border-radius: 8px;
|
|
163
|
+
background: transparent; border: 1px solid transparent; color: var(--dsr-muted);
|
|
164
|
+
font-size: 12.5px; cursor: pointer; white-space: nowrap; text-decoration: none;
|
|
165
|
+
}
|
|
166
|
+
.ds-setting-arrow { font-size: 16px; padding: 0 12px; }
|
|
167
|
+
.ds-setting-arrow:hover,
|
|
168
|
+
#settings-home .ds-setting-row .ds-btn:hover,
|
|
169
|
+
#settings-home .ds-setting-row .ds-btn:focus-visible {
|
|
170
|
+
background: var(--dsr-bg-2); border-color: var(--dsr-line); color: var(--dsr-text);
|
|
171
|
+
}
|
|
128
172
|
.ds-setting-name { font-size: 13.5px; font-weight: 600; }
|
|
129
173
|
.ds-setting-desc { font-size: 12px; color: var(--dsr-muted); margin-top: 2px; word-break: break-all; }
|
|
130
|
-
|
|
174
|
+
#token-desc { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%; }
|
|
131
175
|
.ds-group-bar { display: flex; align-items: center; gap: 10px; padding: 2px 0; }
|
|
132
176
|
.ds-group-bar > label { font-size: 12px; color: var(--dsr-muted); white-space: nowrap; }
|
|
133
177
|
.ds-group-select { flex: 1; min-width: 0; position: relative; }
|
|
@@ -222,6 +266,16 @@ a.ds-btn { text-decoration: none; }
|
|
|
222
266
|
.ds-q-option .muted { font-size: 11px; color: var(--dsr-muted); }
|
|
223
267
|
.ds-modal-body textarea { width: 100%; box-sizing: border-box; margin-top: 6px; background: var(--dsr-bg-2); color: var(--dsr-text); border: 1px solid var(--dsr-line); border-radius: 9px; padding: 7px 9px; font: inherit; font-size: 13px; outline: none; }
|
|
224
268
|
|
|
269
|
+
/* 预设提示词管理 */
|
|
270
|
+
.ds-preset-toolbar { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 8px; }
|
|
271
|
+
.ds-preset-count { font-size: 12px; color: var(--dsr-muted); }
|
|
272
|
+
.ds-preset-list { display: flex; flex-direction: column; gap: 8px; }
|
|
273
|
+
.ds-preset-row { display: flex; align-items: center; gap: 8px; background: var(--dsr-bg-2); border: 1px solid var(--dsr-line); border-radius: 10px; padding: 8px 10px; }
|
|
274
|
+
.ds-preset-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
|
|
275
|
+
.ds-preset-name { font-size: 13px; font-weight: 600; word-break: break-word; }
|
|
276
|
+
.ds-preset-preview { font-size: 11.5px; color: var(--dsr-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
277
|
+
.ds-preset-empty { font-size: 12.5px; color: var(--dsr-muted); text-align: center; padding: 18px 0; }
|
|
278
|
+
|
|
225
279
|
.ds-toast { position: fixed; left: 50%; bottom: 22px; transform: translateX(-50%); z-index: 150; background: var(--dsr-panel); border: 1px solid var(--dsr-line); border-radius: 10px; padding: 9px 15px; font-size: 13px; box-shadow: 0 8px 24px rgba(0,0,0,.3); }
|
|
226
280
|
.ds-toast.hidden { display: none; }
|
|
227
281
|
|