chore: 初始化 dsh-enhancements 留档仓库

- 插件源码:dsh-usage(用户头像 + DeepSeek 官方 API 消耗面板)
- 工具:dl-gh.mjs(node fetch 下载 gh,绕开沙箱 curl 限制)
- 文档:PLAN.md 路线图 + mmx 视觉桥接补丁说明
- 资产:DeepSeek 鲸鱼图标
This commit is contained in:
江晨
2026-08-17 11:54:40 +08:00
commit fdafd5d6b3
10 changed files with 394 additions and 0 deletions
+111
View File
@@ -0,0 +1,111 @@
// dsh-usage 插件 — Client 半
// 注册两处 UI
// sidebar.footer.action 'dsh-usage-avatar' — 设置旁的 🐋 用户头像按钮
// shell.overlay 'dsh-usage-modal' — 点击头像弹出的官方余额模态面板
// 两个组件通过插件内微型 storeopen + listeners)共享开关状态。
return {
apply(ctx) {
const slots = ctx.get('slots')
if (slots === undefined) return
styles.insert(`
.dsh-ua-avatar { display:flex; align-items:center; gap:8px; width:100%; padding:7px 10px; background:transparent; border:0; border-radius:8px; color:var(--dsw-alias-label-primary); cursor:pointer; font:inherit; }
.dsh-ua-avatar:hover { background:var(--dsw-alias-bg-layer-2); }
.dsh-ua-avatar.active { background:var(--dsw-alias-bg-layer-2); }
.dsh-ua-avatar-icon { width:24px; height:24px; border-radius:50%; display:flex; align-items:center; justify-content:center; font-size:15px; flex:none; background:var(--dsw-alias-bg-layer-2); border:1px solid var(--dsw-alias-border-l1); }
.dsh-ua-avatar-label { font-size:12px; color:var(--dsw-alias-label-secondary); }
.dsh-ua-backdrop { position:fixed; inset:0; z-index:9999; background:rgba(0,0,0,.35); display:flex; align-items:center; justify-content:center; pointer-events:auto; }
.dsh-ua-modal { width:380px; max-width:92vw; background:var(--dsw-alias-bg-overlay); border:1px solid var(--dsw-alias-border-l1); border-radius:12px; box-shadow:0 12px 40px rgba(0,0,0,.3); padding:16px; color:var(--dsw-alias-label-primary); pointer-events:auto; font-size:13px; }
.dsh-ua-modal h3 { margin:0 0 12px; font-size:15px; display:flex; align-items:center; gap:8px; }
.dsh-ua-row { display:flex; justify-content:space-between; padding:8px 0; border-bottom:1px solid var(--dsw-alias-border-l1); }
.dsh-ua-row:last-child { border-bottom:0; }
.dsh-ua-row .k { color:var(--dsw-alias-label-secondary); }
.dsh-ua-row .v { font-weight:600; }
.dsh-ua-ok { color:var(--dsw-alias-state-success-primary); }
.dsh-ua-err { color:var(--dsw-alias-state-error-primary); }
.dsh-ua-btns { display:flex; gap:8px; margin-top:14px; }
.dsh-ua-btn { flex:1; padding:7px 10px; border-radius:8px; border:1px solid var(--dsw-alias-border-l2); background:var(--dsw-alias-bg-layer-1); color:var(--dsw-alias-label-primary); cursor:pointer; font:inherit; font-size:12px; }
.dsh-ua-btn:hover { background:var(--dsw-alias-bg-layer-2); }
.dsh-ua-btn.primary { background:var(--dsw-alias-brand-primary); color:#fff; border-color:transparent; }
`)
const store = { open: false, listeners: new Set() }
const setOpen = (v) => { store.open = v; store.listeners.forEach((fn) => fn()) }
const useOpen = () => {
const [s, setS] = React.useState(store.open)
React.useEffect(() => { store.listeners.add(setS); return () => store.listeners.delete(setS) }, [])
return s
}
const UsageModal = (props) => {
const [state, setState] = React.useState({ loading: true, data: null, error: null })
const load = () => {
setState({ loading: true, data: null, error: null })
host.call('usage/balance', {}).then((data) => {
setState({ loading: false, data: data, error: null })
}).catch((e) => {
setState({ loading: false, data: null, error: String((e && e.message) || e) })
})
}
React.useEffect(() => { load() }, [])
const openPortal = () => { host.call('usage/openPortal', {}) }
const data = state.data
const rows = []
if (data && Array.isArray(data.balance_infos)) {
data.balance_infos.forEach((b) => {
rows.push(React.createElement('div', { className: 'dsh-ua-row', key: b.currency + '-total' },
React.createElement('span', { className: 'k' }, '总额 (' + b.currency + ')'),
React.createElement('span', { className: 'v' }, String(b.total_balance))))
rows.push(React.createElement('div', { className: 'dsh-ua-row', key: b.currency + '-granted' },
React.createElement('span', { className: 'k' }, '赠送余额'),
React.createElement('span', { className: 'v' }, String(b.granted_balance))))
rows.push(React.createElement('div', { className: 'dsh-ua-row', key: b.currency + '-topped' },
React.createElement('span', { className: 'k' }, '充值余额'),
React.createElement('span', { className: 'v' }, String(b.topped_up_balance))))
})
}
let body = null
if (state.loading) {
body = React.createElement('div', { className: 'dsh-ua-row' }, React.createElement('span', { className: 'k' }, '正在查询官方余额…'))
} else if (state.error || (data && data.error)) {
body = React.createElement('div', { className: 'dsh-ua-err' }, String(state.error || (data && data.error)))
} else {
body = React.createElement('div', null,
React.createElement('div', { className: 'dsh-ua-row' },
React.createElement('span', { className: 'k' }, '账户可用'),
React.createElement('span', { className: 'v ' + (data.is_available ? 'dsh-ua-ok' : 'dsh-ua-err') }, data.is_available ? '可用' : '不可用')),
rows)
}
return React.createElement('div', { className: 'dsh-ua-backdrop', onClick: props.onClose },
React.createElement('div', { className: 'dsh-ua-modal', onClick: (e) => e.stopPropagation() },
React.createElement('h3', null, '🐋 DeepSeek API 用量',
React.createElement('span', { style: { marginLeft: 'auto', fontSize: '12px', color: 'var(--dsw-alias-label-secondary)' } }, '官方余额')),
body,
React.createElement('div', { className: 'dsh-ua-btns' },
React.createElement('button', { className: 'dsh-ua-btn', onClick: load }, '刷新'),
React.createElement('button', { className: 'dsh-ua-btn', onClick: openPortal }, '打开官网用量页'),
React.createElement('button', { className: 'dsh-ua-btn primary', onClick: props.onClose }, '关闭'))))
}
slots.inject('sidebar.footer.action', () => slots.register(
{ name: 'sidebar.footer.action', id: 'dsh-usage-avatar', order: 100, label: 'API 用量' },
(props) => {
const open = useOpen()
return React.createElement('button', {
className: 'dsh-ua-avatar' + (open ? ' active' : ''),
title: 'API 用量 / 余额',
onClick: () => setOpen(true),
},
React.createElement('span', { className: 'dsh-ua-avatar-icon' }, '🐋'),
props.wide ? React.createElement('span', { className: 'dsh-ua-avatar-label' }, 'API 用量') : null)
}
))
slots.inject('shell.overlay', () => slots.register(
{ name: 'shell.overlay', id: 'dsh-usage-modal', order: 100 },
() => {
const open = useOpen()
if (!open) return null
return React.createElement(UsageModal, { onClose: () => setOpen(false) })
}
))
},
}