Files
dsh_shenxian/assets/inject/assist.js
T

211 lines
9.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
(function () {
if (window.__dshAssist) return
window.__dshAssist = 1
function portalOrigin() {
try {
var l = location.hostname.split('.')
if (l.length > 2) return location.protocol + '//' + l.slice(1).join('.')
} catch (e) {}
return location.origin
}
var P = portalOrigin()
function mk(tag, style, text) {
var e = document.createElement(tag)
if (style) e.setAttribute('style', style)
if (text !== undefined) e.textContent = text
return e
}
function j(u) {
return fetch(u, { credentials: 'include' }).then(function (r) {
if (!r.ok) throw new Error('HTTP ' + r.status)
return r.json()
})
}
function fmtSize(n) {
if (typeof n !== 'number') return ''
if (n >= 1048576) return (n / 1048576).toFixed(1) + ' MB'
if (n >= 1024) return (n / 1024).toFixed(0) + ' KB'
return n + ' B'
}
var CHIP =
'cursor:pointer;border:1px solid rgba(127,127,127,.45);background:rgba(127,127,127,.16);color:var(--dsw-alias-label-primary,#e8e8ec);border-radius:999px;padding:5px 10px;font:12px/1.4 system-ui,-apple-system,"Segoe UI",sans-serif'
var PANEL =
'position:fixed;right:10px;bottom:52px;z-index:2147483000;width:min(620px,92vw);max-height:72vh;overflow:auto;background:var(--dsw-alias-bg-layer-2,#1e1e24);color:var(--dsw-alias-label-primary,#e8e8ec);border:1px solid rgba(127,127,127,.35);border-radius:12px;box-shadow:0 10px 34px rgba(0,0,0,.35);padding:12px 14px;font:13px/1.6 system-ui,-apple-system,"Segoe UI",sans-serif'
var BTN = 'cursor:pointer;border:1px solid rgba(127,127,127,.4);background:transparent;color:inherit;border-radius:8px;padding:3px 8px;font:12px/1.4 inherit'
function closePanel() {
var p = document.getElementById('__dshAssistPanel')
if (p) p.remove()
}
function panel(title, render) {
closePanel()
var p = mk('div', PANEL)
p.id = '__dshAssistPanel'
var head = mk('div', 'display:flex;justify-content:space-between;align-items:center;margin-bottom:8px')
head.appendChild(mk('strong', '', title))
var x = mk('button', 'cursor:pointer;border:0;background:transparent;color:inherit;font-size:14px', '✕')
x.onclick = closePanel
head.appendChild(x)
p.appendChild(head)
var body = mk('div')
p.appendChild(body)
document.body.appendChild(p)
render(body)
}
function row(label, hint) {
var d = mk('div', 'padding:5px 0;border-bottom:1px solid rgba(127,127,127,.14)')
d.appendChild(mk('div', 'font-weight:600', label))
if (hint) d.appendChild(mk('div', 'opacity:.72;font-size:12px;white-space:pre-wrap', hint))
return d
}
// ── 我的文件:浏览工作区 + 下载(走平台代理,不暴露宿主路径)──
function openFiles(path) {
panel('我的文件(工作区)', function (b) {
b.textContent = '加载中…'
j(P + '/api/desktop/tree?path=' + encodeURIComponent(path || ''))
.then(function (d) {
b.innerHTML = ''
var bar = mk('div', 'display:flex;gap:8px;align-items:center;margin-bottom:8px')
var up = mk('button', BTN, '⬆ 上级')
up.onclick = function () {
var parts = (path || '').split('/').filter(Boolean)
parts.pop()
openFiles(parts.join('/'))
}
bar.appendChild(up)
bar.appendChild(mk('span', 'opacity:.7;font-size:12px', '/' + (path || '')))
b.appendChild(bar)
var entries = (d && d.entries) || []
entries.sort(function (a, c) {
if ((a.type === 'dir') !== (c.type === 'dir')) return a.type === 'dir' ? -1 : 1
return String(a.name).localeCompare(String(c.name))
})
if (!entries.length) b.appendChild(mk('div', 'opacity:.7', '(空目录)'))
entries.forEach(function (e) {
var full = (path ? path + '/' : '') + e.name
var line = mk('div', 'display:flex;justify-content:space-between;gap:10px;align-items:center;padding:5px 0;border-bottom:1px solid rgba(127,127,127,.14)')
var name = mk('span', 'overflow:hidden;text-overflow:ellipsis;white-space:nowrap', (e.type === 'dir' ? '📁 ' : '📄 ') + e.name)
name.title = full
line.appendChild(name)
var right = mk('span', 'display:flex;gap:8px;align-items:center;flex:0 0 auto')
if (e.type === 'dir') {
var open = mk('button', BTN, '打开')
open.onclick = function () {
openFiles(full)
}
right.appendChild(open)
} else {
right.appendChild(mk('span', 'opacity:.65;font-size:12px', fmtSize(e.size)))
var a = mk('a', BTN + ';text-decoration:none', '下载')
a.href = P + '/api/fs/download?path=' + encodeURIComponent(full)
a.setAttribute('download', e.name)
right.appendChild(a)
}
line.appendChild(right)
b.appendChild(line)
})
var foot = mk('div', 'margin-top:10px;opacity:.65;font-size:12px')
foot.textContent = 'AI 产出的文件都在你自己的工作区里;下载走平台代理,不需要服务器路径。'
b.appendChild(foot)
})
.catch(function (err) {
b.textContent = '读取失败:' + err.message
})
})
}
// ── 能力清单:读平台生成的 abilities JSON(与实例内 skill 同源)──
function openCaps() {
panel('实例能力清单', function (b) {
b.textContent = '加载中…'
j(P + '/api/capabilities')
.then(function (c) {
b.innerHTML = ''
b.appendChild(row('权限档位', c.permissionMode + ' —— ' + (c.note || '')))
b.appendChild(row('可读', (c.read && c.read.allowed || []).join(';') + '\n不可读:' + ((c.read && c.read.denied) || []).join(';')))
b.appendChild(row('可写', (c.write && c.write.allowed || []).join(';') + '\n不可写:' + ((c.write && c.write.denied) || []).join(';')))
b.appendChild(
row(
'网络',
'出网:' + ((c.network && c.network.egress) || '') + '\n不可达:' + ((c.network && c.network.denied) || []).join(';') + '\n' + ((c.network && c.network.hint) || ''),
),
)
var t = c.tools || {}
b.appendChild(row('可用工具', Object.keys(t).map(function (k) { return k + '=' + t[k] }).join(' ')))
var sk = c.skills || {}
b.appendChild(row('已注册技能', '共享层:' + (((sk.shared) || []).join(', ') || '(空)') + '\n个人层:' + JSON.stringify(sk.perUser || {}) + '\n' + (sk.hint || '')))
if (c.fileDelivery) b.appendChild(row('把文件交给用户', (c.fileDelivery.hint || '') + '\n❌ ' + ((c.fileDelivery.avoid) || []).join(';')))
b.appendChild(mk('div', 'margin-top:8px;opacity:.6;font-size:12px', '生成时间:' + (c.generatedAt || '—')))
})
.catch(function (err) {
b.textContent = '读取失败:' + err.message + '(能力清单可能尚未生成)'
})
})
}
// ── 档位提示:老会话仍 workspace-write 时给一条可操作的横幅 ──
function banner(text, key) {
try {
if (sessionStorage.getItem(key)) return
} catch (e) {}
var d = mk(
'div',
'position:fixed;left:50%;transform:translateX(-50%);top:10px;z-index:2147483000;max-width:min(760px,94vw);background:rgba(180,120,20,.16);border:1px solid rgba(200,140,30,.5);color:var(--dsw-alias-label-primary,#e8e8ec);border-radius:10px;padding:9px 12px;font:13px/1.6 system-ui,-apple-system,"Segoe UI",sans-serif;display:flex;gap:10px;align-items:flex-start',
)
d.appendChild(mk('div', 'flex:1;white-space:pre-wrap', text))
var x = mk('button', 'cursor:pointer;border:0;background:transparent;color:inherit;font-size:14px;flex:0 0 auto', '✕')
x.onclick = function () {
try {
sessionStorage.setItem(key, '1')
} catch (e) {}
d.remove()
}
d.appendChild(x)
document.body.appendChild(d)
}
function checkPermission() {
j(P + '/api/dsh/session-permission')
.then(function (r) {
if (!r || !r.stale) return
banner(
'当前会话的权限档位是「' +
r.session.preset +
'」,而本机没有可用的沙箱后端 —— AI 将无法执行任何命令(bash 会被拒绝)。\n' +
'修法:在 dsh 界面把权限档位切到「完全权限」(或新建一个会话,新会话默认已是完全权限)。',
'__dshPermWarn:' + (r.session && r.session.sessionId ? r.session.sessionId : 'x'),
)
})
.catch(function () {})
}
function mount() {
if (!document.body) return
if (document.getElementById('__dshAssistBar')) return
var bar = mk('div', 'position:fixed;right:10px;bottom:10px;z-index:2147483000;display:flex;gap:6px')
bar.id = '__dshAssistBar'
var f = mk('button', CHIP, '📁 我的文件')
f.onclick = function () {
openFiles('')
}
var c = mk('button', CHIP, '🧭 能力')
c.onclick = openCaps
bar.appendChild(f)
bar.appendChild(c)
document.body.appendChild(bar)
}
var tries = 0
var timer = setInterval(function () {
tries++
mount()
if (tries > 20) clearInterval(timer)
}, 1500)
mount()
checkPermission()
})()