@zaofan/dsh-qqbot 1.10.29 → 1.10.31
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/client/qqbot-settings.js +957 -952
- package/package.json +1 -1
- package/settings-host.js +31 -17
package/client/qqbot-settings.js
CHANGED
|
@@ -6019,949 +6019,954 @@ var QQS_THEME_CSS = [
|
|
|
6019
6019
|
closePanel()
|
|
6020
6020
|
}
|
|
6021
6021
|
|
|
6022
|
-
|
|
6023
|
-
// ═════════════════════════════════════════════════════════════════════════
|
|
6024
|
-
// 「QQ机器人扩展」会话页签(2026-10-24)
|
|
6025
|
-
//
|
|
6026
|
-
// 会话页顶部与「对话 / 轨迹 / 记忆 / 白板看板」并列,进会话就能看到当前工作区
|
|
6027
|
-
// 装了哪些扩展,不用切到设置页。数据来源 GET /api/qqbot-settings/extensions。
|
|
6028
|
-
//
|
|
6029
|
-
// 两块来源:
|
|
6030
|
-
// · 内置扩展 {dataRoot}/.qqbot-extensions/{tools,commands,...}/
|
|
6031
|
-
// · 扩展包 {dataRoot}/extension-packs/{包名}/{同样的子目录}/
|
|
6032
|
-
// 包目录里放 qqbot-pack.json 可声明 name/author/desc/version
|
|
6033
|
-
// ═════════════════════════════════════════════════════════════════════════
|
|
6034
|
-
var _lastCwd = ''
|
|
6035
|
-
var _lastSid = ''
|
|
6036
|
-
// 当前会话工作区(跟随 GUI 切换工作区):与 dsh-auto-memory 的 currentWs() 同一套取法
|
|
6037
|
-
// sessions.list.getSnapshot().byId[current].cwd 是官方 buildListSnapshot 投影出来的权威字段
|
|
6038
|
-
/** 取当前会话 id(cwd 拿不到时后端靠它反查工作区) */
|
|
6039
|
-
function curSessionId() {
|
|
6040
|
-
try {
|
|
6041
|
-
var svc = _extSessionsSvc
|
|
6042
|
-
if (svc && svc.list && typeof svc.list.getSnapshot === 'function') {
|
|
6043
|
-
var snap = svc.list.getSnapshot()
|
|
6044
|
-
if (snap && snap.current) return String(snap.current)
|
|
6045
|
-
}
|
|
6046
|
-
} catch (e) {}
|
|
6047
|
-
try {
|
|
6048
|
-
var raw = localStorage.getItem('dsh.sessions.current')
|
|
6049
|
-
if (raw) { var o = JSON.parse(raw); if (o && o.sessionId) return String(o.sessionId) }
|
|
6050
|
-
} catch (e) {}
|
|
6051
|
-
return ''
|
|
6052
|
-
}
|
|
6053
|
-
|
|
6054
|
-
function curWorkspaceCwd() {
|
|
6055
|
-
try {
|
|
6056
|
-
var svc = _extSessionsSvc
|
|
6057
|
-
if (svc && svc.list && typeof svc.list.getSnapshot === 'function') {
|
|
6058
|
-
var snap = svc.list.getSnapshot()
|
|
6059
|
-
if (!snap) return ''
|
|
6060
|
-
var id = snap.current
|
|
6061
|
-
if (id === undefined || id === null) return ''
|
|
6062
|
-
var row = snap.byId && snap.byId[id]
|
|
6063
|
-
if (row && typeof row.cwd === 'string') return row.cwd
|
|
6064
|
-
}
|
|
6065
|
-
} catch (e) {}
|
|
6066
|
-
// 兜底:宿主持久化镜像(dock 球也在用这个键)
|
|
6067
|
-
try {
|
|
6068
|
-
var raw = localStorage.getItem('dsh.sessions.current')
|
|
6069
|
-
if (raw) { var o = JSON.parse(raw); if (o && o.cwd) return String(o.cwd) }
|
|
6070
|
-
} catch (e) {}
|
|
6071
|
-
return ''
|
|
6072
|
-
}
|
|
6073
|
-
|
|
6074
|
-
var EXT_KIND_META = [
|
|
6075
|
-
{ key: 'tools', label: '扩展工具', icon: '🔧', hint: '模型可调用的工具' },
|
|
6076
|
-
{ key: 'commands', label: '斜杠命令', icon: '⌘', hint: '/ 开头的命令' },
|
|
6077
|
-
{ key: 'botplay', label: '游戏裁判', icon: '🎲', hint: '群内小游戏' },
|
|
6078
|
-
{ key: 'hooks', label: '事件钩子', icon: '🪝', hint: '生命周期回调' },
|
|
6079
|
-
{ key: 'inbound', label: '入站处理', icon: '📥', hint: '消息进模型前' },
|
|
6080
|
-
{ key: 'outbound', label: '出站处理', icon: '📤', hint: '模型输出后' },
|
|
6081
|
-
]
|
|
6082
|
-
|
|
6083
|
-
function ExtPanel() {
|
|
6084
|
-
var _a = useState(null), data = _a[0], setData = _a[1]
|
|
6085
|
-
var _b = useState(true), loading = _b[0], setLoading = _b[1]
|
|
6086
|
-
var _c = useState(''), err = _c[0], setErr = _c[1]
|
|
6087
|
-
var _d = useState('builtin'), tab = _d[0], setTab = _d[1]
|
|
6088
|
-
var _e = useState(0), tick = _e[0], setTick = _e[1]
|
|
6089
|
-
var _sel = useState(null), sel = _sel[0], setSel = _sel[1] // 正在编辑哪个扩展的设置
|
|
6090
|
-
function openSettings(info) { setSel(info) }
|
|
6091
|
-
// 首次进市场页签 -> 拉一次索引
|
|
6092
|
-
useEffect(function () { if (tab === 'market' && !market && !loadingMarket) loadMarket() }, [tab])
|
|
6093
|
-
var _filter = useState(''), filter = _filter[0], setFilter = _filter[1]
|
|
6094
|
-
var _tog = useState(false), toggling = _tog[0], setToggling = _tog[1]
|
|
6095
|
-
function toggleExt(info) {
|
|
6096
|
-
setToggling(true)
|
|
6097
|
-
var body = { kind: info.kind, file: info.file, enabled: info.enabled }
|
|
6098
|
-
var cw = curWorkspaceCwd()
|
|
6099
|
-
if (cw) body.cwd = cw
|
|
6100
|
-
fetch('/api/qqbot-settings/ext-toggle', {
|
|
6101
|
-
method: 'POST',
|
|
6102
|
-
credentials: 'same-origin',
|
|
6103
|
-
headers: { 'Content-Type': 'application/json' },
|
|
6104
|
-
body: JSON.stringify(body),
|
|
6105
|
-
})
|
|
6106
|
-
.then(function (r) { return r.json() })
|
|
6107
|
-
.then(function (d) {
|
|
6108
|
-
if (!d || d.ok === false) { console.warn('[ext-toggle] 失败', d); }
|
|
6109
|
-
else if (d.reloaded) {
|
|
6110
|
-
var r = d.reloaded
|
|
6111
|
-
var bits = []
|
|
6112
|
-
if (r.commands) bits.push('命令 ' + r.commands)
|
|
6113
|
-
if (r.inbound) bits.push('入站 ' + r.inbound)
|
|
6114
|
-
if (r.outbound) bits.push('出站 ' + r.outbound)
|
|
6115
|
-
var tail = r.tools ? ';工具' + r.tools : ''
|
|
6116
|
-
console.log('[ext-toggle] 热重载:', bits.join(' / ') + tail)
|
|
6117
|
-
if (typeof window !== 'undefined' && window.__qqsExtToast) window.__qqsExtToast(bits.join(' / ') + tail)
|
|
6118
|
-
}
|
|
6119
|
-
setTick(function (n) { return n + 1 })
|
|
6120
|
-
})
|
|
6121
|
-
.catch(function (e) { console.warn('[ext-toggle] 异常', e) })
|
|
6122
|
-
.then(function () { setToggling(false) })
|
|
6123
|
-
}
|
|
6124
|
-
// ── 插件市场(2026-10-24)──
|
|
6125
|
-
var _market = useState(null), market = _market[0], setMarket = _market[1]
|
|
6126
|
-
var _loadingMarket = useState(false), loadingMarket = _loadingMarket[0], setLoadingMarket = _loadingMarket[1]
|
|
6127
|
-
var _searchMarket = useState(''), searchMarket = _searchMarket[0], setSearchMarket = _searchMarket[1]
|
|
6128
|
-
var _busyId = useState(''), busyId = _busyId[0], setBusyId = _busyId[1]
|
|
6129
|
-
var marketCount = (market && market.index && market.index.plugins) ? market.index.plugins.length : 0
|
|
6130
|
-
|
|
6131
|
-
function loadMarket() {
|
|
6132
|
-
setLoadingMarket(true)
|
|
6133
|
-
var cw = curWorkspaceCwd()
|
|
6134
|
-
var qs = cw ? ('?cwd=' + encodeURIComponent(cw)) : ''
|
|
6135
|
-
fetch('/api/qqbot-settings/market' + qs, { credentials: 'same-origin' })
|
|
6136
|
-
.then(function (r) { return r.json() })
|
|
6137
|
-
.then(function (d) { if (d && d.ok) setMarket(d); else console.warn('[market] 读取失败', d) })
|
|
6138
|
-
.catch(function (e) { console.warn('[market] 异常', e) })
|
|
6139
|
-
.then(function () { setLoadingMarket(false) })
|
|
6140
|
-
}
|
|
6141
|
-
function marketPost(path, body) {
|
|
6142
|
-
var cw = curWorkspaceCwd()
|
|
6143
|
-
if (cw) body.cwd = cw
|
|
6144
|
-
return fetch(path, {
|
|
6145
|
-
method: 'POST',
|
|
6146
|
-
credentials: 'same-origin',
|
|
6147
|
-
headers: { 'Content-Type': 'application/json' },
|
|
6148
|
-
body: JSON.stringify(body),
|
|
6149
|
-
}) // 返回原始 Response:调用方要看 r.ok 判断 HTTP 状态,并自己 r.text()
|
|
6150
|
-
}
|
|
6151
|
-
function onInstall(info) {
|
|
6152
|
-
setBusyId(info.id)
|
|
6153
|
-
marketPost('/api/qqbot-settings/market/install', { id: info.id })
|
|
6154
|
-
.then(function (r) {
|
|
6155
|
-
return r.text().then(function (txt) {
|
|
6156
|
-
var d = null
|
|
6157
|
-
try { d = JSON.parse(txt) } catch (e) { d = { error: String(txt).slice(0, 200) } }
|
|
6158
|
-
if (!r.ok || !d || d.ok === false) {
|
|
6159
|
-
console.warn('[market] 安装失败 HTTP', r.status, d)
|
|
6160
|
-
var why = (d && (d.error || '')) || ('HTTP ' + r.status)
|
|
6161
|
-
var tag = (d && d.stage) ? ('[' + d.stage + ']') : ''
|
|
6162
|
-
if (window.__qqsExtToast) window.__qqsExtToast('安装失败' + tag + ':' + why)
|
|
6163
|
-
return
|
|
6164
|
-
}
|
|
6165
|
-
if (window.__qqsExtToast) window.__qqsExtToast('已安装 ' + info.id + '(' + d.written + '/' + d.total + ' 个文件)')
|
|
6166
|
-
})
|
|
6167
|
-
})
|
|
6168
|
-
.catch(function (e) { console.warn('[market] 安装异常', e); if (window.__qqsExtToast) window.__qqsExtToast('安装异常:' + e) })
|
|
6169
|
-
.then(function () { setBusyId(''); loadMarket(); setTick(function (n) { return n + 1 }) })
|
|
6170
|
-
}
|
|
6171
|
-
// ── 市场代理配置(2026-10-24 主人要求:墙内要能填代理)──
|
|
6172
|
-
var _proxyInput = useState(''), proxyInput = _proxyInput[0], setProxyInput = _proxyInput[1]
|
|
6173
|
-
var _savingProxy = useState(false), savingProxy = _savingProxy[0], setSavingProxy = _savingProxy[1]
|
|
6174
|
-
var _proxyInited = useState(false), proxyInited = _proxyInited[0], setProxyInited = _proxyInited[1]
|
|
6175
|
-
useEffect(function () {
|
|
6176
|
-
if (market && !proxyInited) { setProxyInput(market.proxy || ''); setProxyInited(true) }
|
|
6177
|
-
}, [market])
|
|
6178
|
-
function onSaveProxy() {
|
|
6179
|
-
setSavingProxy(true)
|
|
6180
|
-
var cw = curWorkspaceCwd()
|
|
6181
|
-
var body = { proxy: String(proxyInput || '').trim() }
|
|
6182
|
-
if (cw) body.cwd = cw
|
|
6183
|
-
fetch('/api/qqbot-settings/market/config', {
|
|
6184
|
-
method: 'POST',
|
|
6185
|
-
credentials: 'same-origin',
|
|
6186
|
-
headers: { 'Content-Type': 'application/json' },
|
|
6187
|
-
body: JSON.stringify(body),
|
|
6188
|
-
})
|
|
6189
|
-
.then(function (r) { return r.json() })
|
|
6190
|
-
.then(function (d) {
|
|
6191
|
-
if (!d || d.ok === false) {
|
|
6192
|
-
if (window.__qqsExtToast) window.__qqsExtToast('代理保存失败:' + ((d && d.error) || '未知'))
|
|
6193
|
-
} else {
|
|
6194
|
-
if (window.__qqsExtToast) window.__qqsExtToast(body.proxy ? ('代理已设为 ' + body.proxy + ',重新拉索引…') : '已切回直连,重新拉索引…')
|
|
6195
|
-
setProxyInited(false)
|
|
6196
|
-
setMarket(null)
|
|
6197
|
-
loadMarket()
|
|
6198
|
-
}
|
|
6199
|
-
})
|
|
6200
|
-
.catch(function (e) { if (window.__qqsExtToast) window.__qqsExtToast('保存异常:' + e) })
|
|
6201
|
-
.then(function () { setSavingProxy(false) })
|
|
6202
|
-
}
|
|
6203
|
-
|
|
6204
|
-
function onRemove(info) {
|
|
6205
|
-
setBusyId(info.id)
|
|
6206
|
-
marketPost('/api/qqbot-settings/market/remove', { id: info.id })
|
|
6207
|
-
.then(function (r) {
|
|
6208
|
-
return r.text().then(function (txt) {
|
|
6209
|
-
var d = null
|
|
6210
|
-
try { d = JSON.parse(txt) } catch (e) { d = { error: String(txt).slice(0, 200) } }
|
|
6211
|
-
if (!r.ok || !d || d.ok === false) {
|
|
6212
|
-
var why = (d && (d.error || '')) || ('HTTP ' + r.status)
|
|
6213
|
-
if (window.__qqsExtToast) window.__qqsExtToast('卸载失败:' + why)
|
|
6214
|
-
return
|
|
6215
|
-
}
|
|
6216
|
-
if (window.__qqsExtToast) window.__qqsExtToast('已卸载 ' + info.id)
|
|
6217
|
-
})
|
|
6218
|
-
})
|
|
6219
|
-
.catch(function (e) { console.warn('[market] 卸载异常', e) })
|
|
6220
|
-
.then(function () { setBusyId(''); loadMarket(); setTick(function (n) { return n + 1 }) })
|
|
6221
|
-
}
|
|
6222
|
-
|
|
6223
|
-
// 工作区切换后自动重载:cwd 一变就刷新
|
|
6224
|
-
useEffect(function () {
|
|
6225
|
-
var nowCwd = curWorkspaceCwd()
|
|
6226
|
-
var nowSid = curSessionId()
|
|
6227
|
-
if ((nowCwd && nowCwd !== _lastCwd) || (nowSid && nowSid !== _lastSid)) {
|
|
6228
|
-
_lastCwd = nowCwd; _lastSid = nowSid
|
|
6229
|
-
setTick(function (n) { return n + 1 })
|
|
6230
|
-
}
|
|
6231
|
-
})
|
|
6232
|
-
|
|
6233
|
-
useEffect(function () {
|
|
6234
|
-
var dead = false
|
|
6235
|
-
setLoading(true)
|
|
6236
|
-
var _cwd = curWorkspaceCwd()
|
|
6237
|
-
var _sid = curSessionId()
|
|
6238
|
-
var _qs = []
|
|
6239
|
-
if (_cwd) _qs.push('cwd=' + encodeURIComponent(_cwd))
|
|
6240
|
-
if (_sid) _qs.push('sid=' + encodeURIComponent(_sid))
|
|
6241
|
-
var _url = '/api/qqbot-settings/extensions' + (_qs.length ? ('?' + _qs.join('&')) : '')
|
|
6242
|
-
fetch(_url, { credentials: 'same-origin' })
|
|
6243
|
-
.then(function (r) { return r.json() })
|
|
6244
|
-
.then(function (d) { if (!dead) { setData(d); setErr(d && d.ok === false ? (d.error || '未知错误') : ''); } })
|
|
6245
|
-
.catch(function (e) { if (!dead) setErr(String((e && e.message) || e)) })
|
|
6246
|
-
.then(function () { if (!dead) setLoading(false) })
|
|
6247
|
-
return function () { dead = true }
|
|
6248
|
-
}, [tick])
|
|
6249
|
-
|
|
6250
|
-
var S = extStyles()
|
|
6251
|
-
|
|
6252
|
-
if (loading && !data) return h('div', { style: S.wrap }, h('div', { style: S.hint }, '正在读取扩展…'))
|
|
6253
|
-
if (err) return h('div', { style: S.wrap },
|
|
6254
|
-
h('div', { style: S.errBox }, '读取失败:' + err),
|
|
6255
|
-
h('button', { style: S.btn, onClick: function () { setTick(tick + 1) } }, '重试'))
|
|
6256
|
-
if (!data) return h('div', { style: S.wrap }, h('div', { style: S.hint }, '暂无数据'))
|
|
6257
|
-
|
|
6258
|
-
// 汇总计数
|
|
6259
|
-
var builtinTotal = data.builtin ? data.builtin.total : 0
|
|
6260
|
-
var packList = data.packs || []
|
|
6261
|
-
var packTotal = packList.reduce(function (n, p) { return n + (p.total || 0) }, 0)
|
|
6262
|
-
var settingsCount = (data.settingsPages || []).length
|
|
6263
|
-
|
|
6264
|
-
// 当前 tab 的内容块列表
|
|
6265
|
-
var groups = []
|
|
6266
|
-
if (tab === 'builtin') {
|
|
6267
|
-
groups = [{ title: '内置扩展', subtitle: data.builtinRoot || '', kinds: (data.builtin || {}).kinds || {}, isPack: false }]
|
|
6268
|
-
} else if (tab === 'packs') {
|
|
6269
|
-
groups = packList.map(function (p) {
|
|
6270
|
-
var meta = p.meta || {}
|
|
6271
|
-
return {
|
|
6272
|
-
title: meta.name || p.dirName,
|
|
6273
|
-
subtitle: [meta.author ? ('作者 ' + meta.author) : '', meta.version ? ('v' + meta.version) : '', meta.desc || ''].filter(Boolean).join(' · ') || p.dir,
|
|
6274
|
-
kinds: p.kinds || {},
|
|
6275
|
-
isPack: true,
|
|
6276
|
-
raw: p,
|
|
6277
|
-
}
|
|
6278
|
-
})
|
|
6279
|
-
} else {
|
|
6280
|
-
// 设置页 tab
|
|
6281
|
-
groups = []
|
|
6282
|
-
}
|
|
6283
|
-
|
|
6284
|
-
function kindRows(kinds, kindMeta, onOpen) {
|
|
6285
|
-
var list = (kinds && kinds[kindMeta.key]) || []
|
|
6286
|
-
if (!list.length) return null
|
|
6287
|
-
return h('div', { key: kindMeta.key, style: S.kindBlock },
|
|
6288
|
-
h('div', { style: S.kindHead },
|
|
6289
|
-
h('span', { style: S.kindIcon }, kindMeta.icon),
|
|
6290
|
-
h('span', { style: S.kindLabel }, kindMeta.label),
|
|
6291
|
-
h('span', { style: S.kindCount }, String(list.length)),
|
|
6292
|
-
h('span', { style: S.kindHint }, kindMeta.hint)),
|
|
6293
|
-
h('div', { style: S.chipWrap },
|
|
6294
|
-
list.map(function (it) {
|
|
6295
|
-
return h('span', {
|
|
6296
|
-
key: it.id,
|
|
6297
|
-
style: it.hasSettings ? S.chipClickable : S.chip,
|
|
6298
|
-
title: it.name + ' · ' + Math.round(it.size / 102.4) / 10 + ' KB' + (it.hasSettings ? '(点击打开设置)' : ''),
|
|
6299
|
-
onClick: it.hasSettings ? function () { onOpen({ kind: kindMeta.key, file: it.name }) } : undefined,
|
|
6300
|
-
}, it.id, it.hasSettings ? h('span', { style: S.gear }, ' ⚙') : null)
|
|
6301
|
-
})))
|
|
6302
|
-
}
|
|
6303
|
-
|
|
6304
|
-
return h('div', { style: S.wrap },
|
|
6305
|
-
// 头部
|
|
6306
|
-
h('div', { style: S.head },
|
|
6307
|
-
h('div', null,
|
|
6308
|
-
h('div', { style: S.title }, 'QQ 机器人扩展'),
|
|
6309
|
-
h('div', { style: S.sub }, '当前工作区已装的扩展;放独立目录的扩展包也能在这里看到')),
|
|
6310
|
-
h('button', { style: S.btn, onClick: function () { setTick(tick + 1) } }, loading ? '刷新中…' : '刷新')),
|
|
6311
|
-
|
|
6312
|
-
// 统计卡
|
|
6313
|
-
h('div', { style: S.stats },
|
|
6314
|
-
statCard(S, '内置扩展', builtinTotal, '个文件'),
|
|
6315
|
-
statCard(S, '扩展包', packList.length, '个包 · 共 ' + packTotal + ' 个扩展'),
|
|
6316
|
-
statCard(S, '带设置页', settingsCount, '个扩展可配置')),
|
|
6317
|
-
|
|
6318
|
-
// 路径提示
|
|
6319
|
-
h('div', { style: S.pathBox },
|
|
6320
|
-
h('div', { style: S.pathRow }, h('span', { style: S.pathKey }, '当前工作区'), h('code', { style: S.code }, (data.workspaceCwd || curWorkspaceCwd() || '(未识别,回退到默认数据根)'),
|
|
6321
|
-
data.workspaceFrom && data.workspaceFrom !== 'default'
|
|
6322
|
-
? h('span', { style: { fontSize: 10.5, opacity: .6, marginLeft: 6 } }, 'via ' + data.workspaceFrom)
|
|
6323
|
-
: null)),
|
|
6324
|
-
h('div', { style: S.pathRow }, h('span', { style: S.pathKey }, '内置目录'), h('code', { style: S.code }, data.builtinRoot || '—')),
|
|
6325
|
-
h('div', { style: S.pathRow }, h('span', { style: S.pathKey }, '扩展包目录'), h('code', { style: S.code }, data.packsRoot || '—'))),
|
|
6326
|
-
|
|
6327
|
-
// 页签
|
|
6328
|
-
h('div', { style: S.tabs },
|
|
6329
|
-
tabBtn(S, '内置扩展', 'builtin', tab, setTab, builtinTotal),
|
|
6330
|
-
tabBtn(S, '扩展包', 'packs', tab, setTab, packList.length),
|
|
6331
|
-
tabBtn(S, '设置中心', 'settings', tab, setTab, settingsCount),
|
|
6332
|
-
tabBtn(S, '市场', 'market', tab, setTab, marketCount)),
|
|
6333
|
-
|
|
6334
|
-
// 内容
|
|
6335
|
-
tab === 'market'
|
|
6336
|
-
? renderMarket(S, market, loadingMarket, searchMarket, setSearchMarket, onInstall, onRemove, busyId, proxyInput, setProxyInput, onSaveProxy, savingProxy)
|
|
6337
|
-
: tab === 'settings'
|
|
6338
|
-
? renderSettingsCenter(S, data, filter, setFilter, openSettings, toggleExt, toggling)
|
|
6339
|
-
: (groups.length
|
|
6340
|
-
? groups.map(function (g, gi) {
|
|
6341
|
-
var hasAny = EXT_KIND_META.some(function (k) { return ((g.kinds || {})[k.key] || []).length > 0 })
|
|
6342
|
-
return h('div', { key: gi, style: S.group },
|
|
6343
|
-
h('div', { style: S.groupHead },
|
|
6344
|
-
h('span', { style: S.groupTitle }, g.title),
|
|
6345
|
-
h('span', { style: S.groupSub }, g.subtitle)),
|
|
6346
|
-
hasAny
|
|
6347
|
-
? EXT_KIND_META.map(function (k) { return kindRows(g.kinds, k, openSettings) })
|
|
6348
|
-
: h('div', { style: S.empty }, '这个' + (g.isPack ? '包' : '目录') + '里还没放扩展'))
|
|
6349
|
-
})
|
|
6350
|
-
: h('div', { style: S.empty }, tab === 'packs' ? '还没有扩展包。在 ' + (data.packsRoot || '') + ' 下建一个子文件夹,里面按 tools/commands/… 放扩展即可。' : '没有扩展'))
|
|
6351
|
-
,
|
|
6352
|
-
sel ? h(ExtSettings, { kind: sel.kind, file: sel.file, cwd: (data && data.workspaceCwd) || '', onClose: function () { setSel(null) } }) : null
|
|
6353
|
-
)
|
|
6354
|
-
}
|
|
6355
|
-
|
|
6356
|
-
/**
|
|
6357
|
-
* 扩展自定义设置面板(2026-10-24)
|
|
6358
|
-
*
|
|
6359
|
-
* 扩展在文件里声明:
|
|
6360
|
-
* export const qqbotSettingsPage = {
|
|
6361
|
-
* title: '我的设置',
|
|
6362
|
-
* schema: [
|
|
6363
|
-
* { key:'enabled', type:'bool', label:'启用' },
|
|
6364
|
-
* { key:'dirs', type:'list', label:'目录列表' },
|
|
6365
|
-
* { key:'apiKey', type:'string', label:'API Key', secret:true },
|
|
6366
|
-
* { key:'mode', type:'select', label:'模式', options:['a','b'] },
|
|
6367
|
-
* ],
|
|
6368
|
-
* }
|
|
6369
|
-
* 面板自动渲染表单,值存 {dataRoot}/.qqbot-extensions/.state/ext-config/{扩展id}.json
|
|
6370
|
-
*/
|
|
6371
|
-
function ExtSettings(props) {
|
|
6372
|
-
var kind = props.kind, file = props.file, cwd = props.cwd, onClose = props.onClose
|
|
6373
|
-
var _a = useState(null), data = _a[0], setData = _a[1]
|
|
6374
|
-
var _b = useState({}), vals = _b[0], setVals = _b[1]
|
|
6375
|
-
var _c = useState(true), loading = _c[0], setLoading = _c[1]
|
|
6376
|
-
var _d = useState(''), msg = _d[0], setMsg = _d[1]
|
|
6377
|
-
var _e = useState(''), err = _e[0], setErr = _e[1]
|
|
6378
|
-
|
|
6379
|
-
useEffect(function () {
|
|
6380
|
-
var dead = false
|
|
6381
|
-
setLoading(true); setErr('')
|
|
6382
|
-
var qs = '?kind=' + encodeURIComponent(kind) + '&file=' + encodeURIComponent(file)
|
|
6383
|
-
if (cwd) qs += '&cwd=' + encodeURIComponent(cwd)
|
|
6384
|
-
fetch('/api/qqbot-settings/ext-panel' + qs, { credentials: 'same-origin' })
|
|
6385
|
-
.then(function (r) { return r.json() })
|
|
6386
|
-
.then(function (d) {
|
|
6387
|
-
if (dead) return
|
|
6388
|
-
if (!d || d.ok === false) { setErr((d && d.error) || '读取失败'); return }
|
|
6389
|
-
setData(d)
|
|
6390
|
-
setVals(d.values && typeof d.values === 'object' ? d.values : {})
|
|
6391
|
-
})
|
|
6392
|
-
.catch(function (e) { if (!dead) setErr(String((e && e.message) || e)) })
|
|
6393
|
-
.then(function () { if (!dead) setLoading(false) })
|
|
6394
|
-
return function () { dead = true }
|
|
6395
|
-
}, [kind, file, cwd])
|
|
6396
|
-
|
|
6397
|
-
function save() {
|
|
6398
|
-
setMsg(''); setErr('')
|
|
6399
|
-
fetch('/api/qqbot-settings/ext-panel', {
|
|
6400
|
-
method: 'POST',
|
|
6401
|
-
credentials: 'same-origin',
|
|
6402
|
-
headers: { 'Content-Type': 'application/json' },
|
|
6403
|
-
body: JSON.stringify({ kind: kind, file: file, cwd: cwd, values: vals }),
|
|
6404
|
-
})
|
|
6405
|
-
.then(function (r) { return r.json() })
|
|
6406
|
-
.then(function (d) {
|
|
6407
|
-
if (d && d.ok) setMsg('已保存 → ' + (d.configPath || ''))
|
|
6408
|
-
else setErr((d && d.error) || '保存失败')
|
|
6409
|
-
})
|
|
6410
|
-
.catch(function (e) { setErr(String((e && e.message) || e)) })
|
|
6411
|
-
}
|
|
6412
|
-
|
|
6413
|
-
var S = extStyles()
|
|
6414
|
-
var page = (data && data.page) || {}
|
|
6415
|
-
var schema = page.schema || []
|
|
6416
|
-
var title = page.title || (data && data.extId) || file
|
|
6417
|
-
|
|
6418
|
-
return h('div', { style: S.modalMask, onClick: onClose },
|
|
6419
|
-
h('div', { style: S.modalBox, onClick: function (e) { e.stopPropagation() } },
|
|
6420
|
-
h('div', { style: S.modalHead },
|
|
6421
|
-
h('div', null,
|
|
6422
|
-
h('div', { style: S.modalTitle }, title + ' · 设置'),
|
|
6423
|
-
h('div', { style: S.modalSub }, kind + '/' + file)),
|
|
6424
|
-
h('button', { style: S.btn, onClick: onClose }, '关闭')),
|
|
6425
|
-
|
|
6426
|
-
loading ? h('div', { style: S.hint }, '读取中…') :
|
|
6427
|
-
err ? h('div', { style: S.errBox }, err) :
|
|
6428
|
-
(schema.length === 0
|
|
6429
|
-
? h('div', null,
|
|
6430
|
-
h('div', { style: S.empty },
|
|
6431
|
-
page._err ? ('读取失败:' + page._err)
|
|
6432
|
-
: page._miss ? ('文件里没有 qqbotSettingsPage。实际导出:' + (page._keys || []).join(', '))
|
|
6433
|
-
: '这个扩展没有声明配置项。'),
|
|
6434
|
-
h('div', { style: S.emptyHint },
|
|
6435
|
-
page._err || page._miss
|
|
6436
|
-
? '检查扩展文件里的 export const qqbotSettingsPage = { title, schema: [...] } 写法。'
|
|
6437
|
-
: '在它的文件里 export const qqbotSettingsPage = { title, schema: [...] } 即可在这里出现表单。'))
|
|
6438
|
-
: h('div', null,
|
|
6439
|
-
schema.map(function (f, i) { return renderExtField(S, f, i, vals, setVals) }),
|
|
6440
|
-
h('div', { style: S.modalFoot },
|
|
6441
|
-
h('button', { style: S.btnPrimary, onClick: save }, '保存'),
|
|
6442
|
-
msg ? h('span', { style: S.savedMsg }, msg) : null,
|
|
6443
|
-
err ? h('span', { style: S.errMsg }, err) : null))),
|
|
6444
|
-
|
|
6445
|
-
data && data.configPath
|
|
6446
|
-
? h('div', { style: S.cfgPath }, '配置文件:', h('code', { style: S.code }, data.configPath))
|
|
6447
|
-
: null))
|
|
6448
|
-
}
|
|
6449
|
-
|
|
6450
|
-
/** 按 schema 渲染一个字段 */
|
|
6451
|
-
function renderExtField(S, f, i, vals, setVals) {
|
|
6452
|
-
var key = f.key || ('f' + i)
|
|
6453
|
-
var val = vals[key]
|
|
6454
|
-
if (val === undefined && f.default !== undefined) val = f.default
|
|
6455
|
-
function upd(v) { var o = Object.assign({}, vals); o[key] = v; setVals(o) }
|
|
6456
|
-
var label = f.label || key
|
|
6457
|
-
var hint = f.hint || f.description || ''
|
|
6458
|
-
|
|
6459
|
-
var control
|
|
6460
|
-
if (f.type === 'bool') {
|
|
6461
|
-
control = h('input', { type: 'checkbox', checked: !!val, onChange: function (e) { upd(e.target.checked) } })
|
|
6462
|
-
} else if (f.type === 'number') {
|
|
6463
|
-
control = h('input', { type: 'number', style: S.fieldInput, value: val === undefined ? '' : val,
|
|
6464
|
-
onChange: function (e) { var v = e.target.value; upd(v === '' ? undefined : Number(v)) } })
|
|
6465
|
-
} else if (f.type === 'select') {
|
|
6466
|
-
var opts = Array.isArray(f.options) ? f.options : []
|
|
6467
|
-
control = h('select', { style: S.fieldInput, value: val === undefined ? '' : val,
|
|
6468
|
-
onChange: function (e) { upd(e.target.value) } },
|
|
6469
|
-
opts.map(function (o) {
|
|
6470
|
-
var v = (o && typeof o === 'object') ? (o.value !== undefined ? o.value : o.label) : o
|
|
6471
|
-
var t = (o && typeof o === 'object') ? (o.label || o.value) : o
|
|
6472
|
-
return h('option', { key: String(v), value: String(v), style: { background: '#2a2a2e', color: '#e8e8ea' } }, String(t))
|
|
6473
|
-
}))
|
|
6474
|
-
} else if (f.type === 'list') {
|
|
6475
|
-
var arr = Array.isArray(val) ? val : []
|
|
6476
|
-
control = h('div', null,
|
|
6477
|
-
h('textarea', { style: Object.assign({}, S.fieldInput, { minHeight: 62, resize: 'vertical' }),
|
|
6478
|
-
value: arr.join('\n'),
|
|
6479
|
-
placeholder: '一行一项',
|
|
6480
|
-
onChange: function (e) { upd(e.target.value.split('\n').map(function (x) { return x.trim() }).filter(Boolean)) } }))
|
|
6481
|
-
} else {
|
|
6482
|
-
control = h('input', {
|
|
6483
|
-
type: f.secret ? 'password' : 'text',
|
|
6484
|
-
style: S.fieldInput,
|
|
6485
|
-
value: val === undefined ? '' : String(val),
|
|
6486
|
-
placeholder: f.placeholder || '',
|
|
6487
|
-
onChange: function (e) { upd(e.target.value) },
|
|
6488
|
-
})
|
|
6489
|
-
}
|
|
6490
|
-
|
|
6491
|
-
return h('div', { key: key, style: S.field },
|
|
6492
|
-
h('div', { style: S.fieldLabel }, label, f.secret ? h('span', { style: S.secretTag }, ' 密钥') : null),
|
|
6493
|
-
control,
|
|
6494
|
-
hint ? h('div', { style: S.fieldHint }, hint) : null)
|
|
6495
|
-
}
|
|
6496
|
-
|
|
6497
|
-
/**
|
|
6498
|
-
* 「设置中心」(2026-10-24 主人要求)
|
|
6499
|
-
*
|
|
6500
|
-
* 原「设置页」tab 只是把带 ⚙ 的扩展又列一遍、还点不动 —— 没有存在价值。
|
|
6501
|
-
* 改成:列出**全部扩展**(内置 + 各扩展包),每行能
|
|
6502
|
-
* · 点「设置」打开它的设置弹层(有声明才可点)
|
|
6503
|
-
* · 开关启停(启停状态存 .state/ext-disabled.json,扫描器会跳过停用的)
|
|
6504
|
-
* 顶部带搜索框。
|
|
6505
|
-
*/
|
|
6506
|
-
function renderSettingsCenter(S, data, filter, setFilter, onOpen, onToggle, toggling) {
|
|
6507
|
-
// 汇总所有扩展
|
|
6508
|
-
var rows = []
|
|
6509
|
-
var push = function (list, from, isPack) {
|
|
6510
|
-
for (var i = 0; i < list.length; i++) {
|
|
6511
|
-
var it = list[i]
|
|
6512
|
-
rows.push({
|
|
6513
|
-
key: from + '/' + it.name,
|
|
6514
|
-
id: it.id,
|
|
6515
|
-
name: it.name,
|
|
6516
|
-
kind: '',
|
|
6517
|
-
from: from,
|
|
6518
|
-
isPack: isPack,
|
|
6519
|
-
hasSettings: !!it.hasSettings,
|
|
6520
|
-
settingsTitle: it.settingsTitle || '',
|
|
6521
|
-
disabled: !!it.disabled,
|
|
6522
|
-
size: it.size || 0,
|
|
6523
|
-
})
|
|
6524
|
-
}
|
|
6525
|
-
}
|
|
6526
|
-
var groupsForList = []
|
|
6527
|
-
// 内置(按六类)
|
|
6528
|
-
var bk = (data.builtin && data.builtin.kinds) ? data.builtin.kinds : {}
|
|
6529
|
-
for (var ki = 0; ki < EXT_KIND_META.length; ki++) {
|
|
6530
|
-
var km = EXT_KIND_META[ki]
|
|
6531
|
-
var list = bk[km.key] || []
|
|
6532
|
-
for (var j = 0; j < list.length; j++) {
|
|
6533
|
-
var it = list[j]
|
|
6534
|
-
rows.push({ key: 'builtin/' + km.key + '/' + it.name, id: it.id, name: it.name, kind: km.key, kindLabel: km.label,
|
|
6535
|
-
from: '内置', isPack: false, hasSettings: !!it.hasSettings, settingsTitle: it.settingsTitle || '',
|
|
6536
|
-
disabled: !!it.disabled, size: it.size || 0 })
|
|
6537
|
-
}
|
|
6538
|
-
}
|
|
6539
|
-
// 扩展包
|
|
6540
|
-
var packs = data.packs || []
|
|
6541
|
-
for (var pi = 0; pi < packs.length; pi++) {
|
|
6542
|
-
var p = packs[pi]
|
|
6543
|
-
var pn = (p.meta && p.meta.name) || p.dirName
|
|
6544
|
-
for (var k2 = 0; k2 < EXT_KIND_META.length; k2++) {
|
|
6545
|
-
var km2 = EXT_KIND_META[k2]
|
|
6546
|
-
var l2 = (p.kinds && p.kinds[km2.key]) || []
|
|
6547
|
-
for (var m = 0; m < l2.length; m++) {
|
|
6548
|
-
var it2 = l2[m]
|
|
6549
|
-
rows.push({ key: 'pack/' + p.dirName + '/' + km2.key + '/' + it2.name, id: it2.id, name: it2.name,
|
|
6550
|
-
kind: km2.key, kindLabel: km2.label, from: pn, isPack: true,
|
|
6551
|
-
hasSettings: !!it2.hasSettings, settingsTitle: it2.settingsTitle || '',
|
|
6552
|
-
disabled: !!it2.disabled, size: it2.size || 0 })
|
|
6553
|
-
}
|
|
6554
|
-
}
|
|
6555
|
-
}
|
|
6556
|
-
|
|
6557
|
-
var q = String(filter || '').trim().toLowerCase()
|
|
6558
|
-
var shown = q ? rows.filter(function (r) { return (r.name + ' ' + r.from + ' ' + r.kindLabel).toLowerCase().indexOf(q) >= 0 }) : rows
|
|
6559
|
-
|
|
6560
|
-
return h('div', null,
|
|
6561
|
-
// 搜索框
|
|
6562
|
-
h('div', { style: { display: 'flex', alignItems: 'center', gap: 10, marginBottom: 12 } },
|
|
6563
|
-
h('input', {
|
|
6564
|
-
style: Object.assign({}, S.fieldInput, { maxWidth: 280, marginBottom: 0 }),
|
|
6565
|
-
placeholder: '搜索扩展名 / 来源 / 类型…',
|
|
6566
|
-
value: filter,
|
|
6567
|
-
onChange: function (e) { setFilter(e.target.value) },
|
|
6568
|
-
}),
|
|
6569
|
-
h('span', { style: { fontSize: 11.5, color: 'var(--dsw-alias-text-secondary, #9a9aa0)' } },
|
|
6570
|
-
shown.length + ' / ' + rows.length + ' 个扩展')),
|
|
6571
|
-
|
|
6572
|
-
shown.length === 0
|
|
6573
|
-
? h('div', { style: S.empty }, q ? ('没有匹配「' + filter + '」的扩展') : '没有扩展')
|
|
6574
|
-
: h('div', { style: { display: 'flex', flexDirection: 'column', gap: 6 } },
|
|
6575
|
-
shown.map(function (r) {
|
|
6576
|
-
return h('div', { key: r.key, style: S.settingRow },
|
|
6577
|
-
// 名字
|
|
6578
|
-
h('span', { style: { fontWeight: 500, minWidth: 150, opacity: r.disabled ? 0.5 : 1 } }, r.id),
|
|
6579
|
-
// 类型
|
|
6580
|
-
h('span', { style: { fontSize: 11, color: 'var(--dsw-alias-text-secondary, #9a9aa0)', minWidth: 68 } }, r.kindLabel || ''),
|
|
6581
|
-
// 来源
|
|
6582
|
-
h('span', { style: Object.assign({}, S.settingFrom, r.isPack ? { border: '1px solid ' + 'var(--dsw-alias-accent, #5b8def)' } : {}) }, r.from),
|
|
6583
|
-
// 已配置标记
|
|
6584
|
-
r.hasSettings ? h('span', { style: { fontSize: 10.5, color: 'var(--dsw-alias-accent, #5b8def)' } }, '⚙ ' + (r.settingsTitle || '可配置')) : null,
|
|
6585
|
-
// 右侧操作
|
|
6586
|
-
h('span', { style: { marginLeft: 'auto', display: 'flex', alignItems: 'center', gap: 8 } },
|
|
6587
|
-
r.hasSettings
|
|
6588
|
-
? h('button', {
|
|
6589
|
-
style: { padding: '3px 12px', fontSize: 12, borderRadius: 5, border: '1px solid var(--dsw-alias-accent, #5b8def)', background: 'transparent', color: 'inherit', cursor: 'pointer' },
|
|
6590
|
-
onClick: function () { onOpen({ kind: r.kind, file: r.name }) },
|
|
6591
|
-
}, '设置')
|
|
6592
|
-
: h('span', { style: { fontSize: 11, color: 'var(--dsw-alias-text-secondary, #9a9aa0)' } }, '—'),
|
|
6593
|
-
// 启停开关
|
|
6594
|
-
h('label', { style: { display: 'flex', alignItems: 'center', gap: 5, fontSize: 11.5, cursor: toggling ? 'wait' : 'pointer', opacity: toggling ? 0.6 : 1 } },
|
|
6595
|
-
h('input', {
|
|
6596
|
-
type: 'checkbox',
|
|
6597
|
-
checked: !r.disabled,
|
|
6598
|
-
disabled: !!toggling,
|
|
6599
|
-
onChange: function (e) { onToggle({ kind: r.kind, file: r.name, enabled: e.target.checked }) },
|
|
6600
|
-
}),
|
|
6601
|
-
r.disabled ? h('span', { style: { color: 'var(--dsw-alias-text-secondary, #9a9aa0)' } }, '已停用') : h('span', null, '启用'))))
|
|
6602
|
-
})))
|
|
6603
|
-
}
|
|
6604
|
-
|
|
6605
|
-
/**
|
|
6606
|
-
* 插件市场(2026-10-24)
|
|
6607
|
-
* 索引来自 GitHub 仓库 zaofan-make/dsh-qqbot-plugins 的 index.json
|
|
6608
|
-
*/
|
|
6609
|
-
/** 市场专用样式(2026-10-24):内联 style 管不了 ::placeholder 和主题变量,只能插 CSS */
|
|
6610
|
-
/**
|
|
6611
|
-
* 判断当前是深色还是浅色主题。
|
|
6612
|
-
*
|
|
6613
|
-
* 为什么不用 CSS 变量:DSH 的 --dsw-alias-* 变量名没法确认,
|
|
6614
|
-
* 猜错时 fallback 会在深色模式下变成黑字(2026-10-24 主人实测踩到)。
|
|
6615
|
-
* 直接量一下背景亮度最可靠。
|
|
6616
|
-
*/
|
|
6617
|
-
function qqsIsDarkTheme() {
|
|
6618
|
-
try {
|
|
6619
|
-
var el = document.body || document.documentElement
|
|
6620
|
-
var bg = ''
|
|
6621
|
-
// 往上找一个有不透明背景的祖先,body 有时是透明的
|
|
6622
|
-
var node = el
|
|
6623
|
-
for (var i = 0; i < 6 && node; i++) {
|
|
6624
|
-
var cs = window.getComputedStyle(node)
|
|
6625
|
-
var c2 = cs.backgroundColor || ''
|
|
6626
|
-
var m0 = c2.match(/rgba?\(\s*(\d+)[,\s]+(\d+)[,\s]+(\d+)(?:[,\s/]+([\d.]+))?/)
|
|
6627
|
-
if (m0 && (m0[4] === undefined || parseFloat(m0[4]) > 0.5)) { bg = c2; break }
|
|
6628
|
-
node = node.parentElement
|
|
6629
|
-
}
|
|
6630
|
-
var m = String(bg).match(/(\d+)[,\s]+(\d+)[,\s]+(\d+)/)
|
|
6631
|
-
if (m) {
|
|
6632
|
-
var r = +m[1], g = +m[2], b = +m[3]
|
|
6633
|
-
// 相对亮度(sRGB 近似)
|
|
6634
|
-
var lum = (0.299 * r + 0.587 * g + 0.114 * b) / 255
|
|
6635
|
-
if (r + g + b > 24) return lum < 0.5
|
|
6636
|
-
}
|
|
6637
|
-
} catch (e) {}
|
|
6638
|
-
try {
|
|
6639
|
-
return !!(window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches)
|
|
6640
|
-
} catch (e) {}
|
|
6641
|
-
return false
|
|
6642
|
-
}
|
|
6643
|
-
|
|
6644
|
-
/**
|
|
6645
|
-
* 市场样式(2026-10-24 整体重写,不再叠补丁)
|
|
6646
|
-
*
|
|
6647
|
-
* 配色原则:
|
|
6648
|
-
* · 全部走面板自己的主题变量 —— 面板在 body{} 里定义了亮色 token,
|
|
6649
|
-
* 在 body[data-ds-dark-theme]{} 里定义了暗色 token(宿主 ThemePresenter 切类)。
|
|
6650
|
-
* 所以用 var(--qqs-text) / var(--qqs-border) / var(--qqs-input) 就自动跟随主题,
|
|
6651
|
-
* 不需要自己判断深浅(之前猜 --dsw-alias-* 变量名,猜错导致深色下黑字)。
|
|
6652
|
-
* · 主人要求:**除标题外的字也不要灰色**,所以描述/版本/标签全都用主文字色 var(--qqs-text)。
|
|
6653
|
-
* · 卡片不填色块,只有一圈边框;图片框 4:3 固定,图片 contain 完整显示、四周留白。
|
|
6654
|
-
*/
|
|
6655
|
-
/**
|
|
6656
|
-
* 市场配色:直接量页面真实颜色,把 rgb 写死进样式。
|
|
6657
|
-
*
|
|
6658
|
-
* 为什么不再用 CSS 变量:
|
|
6659
|
-
* ① 猜 --dsw-alias-text-primary → 不存在,深色下 fallback 变黑字;
|
|
6660
|
-
* ② 用面板的 --qqs-text → 实测取到的值和当前主题不符(浅色下拿到浅灰)。
|
|
6661
|
-
* 量出来的值一定对,代价是切主题后要刷新一次。
|
|
6662
|
-
*/
|
|
6663
|
-
/**
|
|
6664
|
-
* 市场样式(2026-10-24 第四次,最终版)
|
|
6665
|
-
*
|
|
6666
|
-
* 三次失败的教训:
|
|
6667
|
-
* ① 猜 --dsw-alias-text-primary → 变量不存在,深色下 fallback 黑字;
|
|
6668
|
-
* ② 用 --qqs-text → 拿到的值和自己预期的主题不符;
|
|
6669
|
-
* ③ 量背景亮度写死 rgb → 又引入额外不确定性。
|
|
6670
|
-
*
|
|
6671
|
-
* 根本问题其实一直是**我自己写的硬编码颜色**(inline style 的 color + CSS 里的 !important 色值),
|
|
6672
|
-
* 面板本身(ensureCss 注入的 qqs-css)是靠继承 DSH 主题的。
|
|
6673
|
-
* 所以这一版:**一个颜色都不写**,全部 inherit,让文字跟着面板走。
|
|
6674
|
-
* 只有边框给个中性灰(深浅主题下都看得见)。
|
|
6675
|
-
*/
|
|
6676
|
-
/**
|
|
6677
|
-
* 市场样式(2026-10-24 定稿)
|
|
6678
|
-
*
|
|
6679
|
-
* 主人拍板:**不再跟随主题,统一白底黑字**。
|
|
6680
|
-
* 之前跟着主题走折腾了 5 版(猜变量 / 量亮度 / 继承 / token),
|
|
6681
|
-
* 每次都在「浅色下深底、深色下黑字」之间反复,索性写死最稳。
|
|
6682
|
-
*
|
|
6683
|
-
* 配色(全部硬编码):
|
|
6684
|
-
* 卡片底 #fff / 文字 #1f2329 / 边框 #e2e5ea
|
|
6685
|
-
* 次要文字 #5a6270 / 弱化 #8a919c
|
|
6686
|
-
* 主色按钮 #7c6cf0
|
|
6687
|
-
*/
|
|
6688
|
-
function ensureMarketCss() {
|
|
6689
|
-
if (typeof document === 'undefined') return
|
|
6690
|
-
if (document.getElementById('__qqsMarketCss')) return
|
|
6691
|
-
var st = document.createElement('style')
|
|
6692
|
-
st.id = '__qqsMarketCss'
|
|
6693
|
-
st.textContent = [
|
|
6694
|
-
/* ── 网格:两列等宽铺满 ── */
|
|
6695
|
-
'.qqs-mk-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:20px;align-items:stretch;width:100%;}',
|
|
6696
|
-
'@media (max-width:760px){.qqs-mk-grid{grid-template-columns:1fr;}}',
|
|
6697
|
-
|
|
6698
|
-
/* ── 卡片:白底黑字 + 浅灰边框 ── */
|
|
6699
|
-
'.qqs-mk-card{background:#ffffff!important;color:#1f2329!important;'
|
|
6700
|
-
+ 'border:1px solid #e2e5ea!important;border-radius:10px!important;'
|
|
6701
|
-
+ 'height:100%;display:flex;flex-direction:column;box-sizing:border-box;'
|
|
6702
|
-
+ 'padding:0 14px 14px 14px;overflow:hidden;}',
|
|
6703
|
-
'.qqs-mk-card:hover{border-color:#7c6cf0!important;}',
|
|
6704
|
-
'.qqs-mk-card *{color:inherit;}',
|
|
6705
|
-
|
|
6706
|
-
/* ── 封面:4:3 固定框,contain 完整显示,四周留白 ── */
|
|
6707
|
-
'.qqs-mk-cover{width:100%;aspect-ratio:4/3;object-fit:contain;display:block;'
|
|
6708
|
-
+ 'box-sizing:border-box;padding:10px;background:#f4f5f8;border-radius:6px;}',
|
|
6709
|
-
'.qqs-mk-cover-ph{width:100%;aspect-ratio:4/3;display:flex;align-items:center;justify-content:center;'
|
|
6710
|
-
+ 'box-sizing:border-box;padding:10px;font-size:12px;color:#8a919c;background:#f4f5f8;border-radius:6px;}',
|
|
6711
|
-
|
|
6712
|
-
/* ── 标题行 ── */
|
|
6713
|
-
'.qqs-mk-head{display:flex;align-items:baseline;gap:8px;flex-wrap:wrap;padding:10px 0 8px;}',
|
|
6714
|
-
'.qqs-mk-name{font-size:14.5px;font-weight:600;color:#1f2329;}',
|
|
6715
|
-
'.qqs-mk-meta{font-size:11.5px;color:#8a919c;}',
|
|
6716
|
-
|
|
6717
|
-
/* ── 描述 ── */
|
|
6718
|
-
'.qqs-mk-desc{font-size:12.5px;line-height:1.65;color:#5a6270;flex:1 1 auto;'
|
|
6719
|
-
+ 'display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;margin:0 0 8px 0;}',
|
|
6720
|
-
|
|
6721
|
-
/* ── 标签 ── */
|
|
6722
|
-
'.qqs-mk-tag{font-size:10.5px;padding:1px 7px;border-radius:4px;border:1px solid #e2e5ea;color:#5a6270;}',
|
|
6723
|
-
|
|
6724
|
-
/* ── 按钮 ── */
|
|
6725
|
-
'.qqs-mk-btn{padding:4px 14px;font-size:12px;border-radius:5px;border:1px solid #7c6cf0;'
|
|
6726
|
-
+ 'background:#7c6cf0;color:#fff!important;cursor:pointer;flex:0 0 auto;}',
|
|
6727
|
-
'.qqs-mk-btn:hover{background:#6b5ae6;}',
|
|
6728
|
-
'.qqs-mk-btn:disabled{opacity:.55;cursor:default;}',
|
|
6729
|
-
'.qqs-mk-ghost{padding:4px 14px;font-size:12px;border-radius:5px;border:1px solid #d0d5dd;'
|
|
6730
|
-
+ 'background:#ffffff;color:#1f2329!important;cursor:pointer;flex:0 0 auto;}',
|
|
6731
|
-
'.qqs-mk-ghost:hover{border-color:#7c6cf0;color:#6b5ae6!important;}',
|
|
6732
|
-
|
|
6733
|
-
/* ── 搜索框 / 代理框:白底黑字,看得见也能输入 ── */
|
|
6734
|
-
'.qqs-mk-search{background:#ffffff!important;color:#1f2329!important;'
|
|
6735
|
-
+ 'border:1px solid #d0d5dd!important;box-sizing:border-box;}',
|
|
6736
|
-
'.qqs-mk-search::placeholder{color:#8a919c!important;opacity:1;}',
|
|
6737
|
-
'.qqs-mk-search:focus{outline:none!important;border-color:#7c6cf0!important;box-shadow:0 0 0 2px #7c6cf033!important;}',
|
|
6738
|
-
].join('\n')
|
|
6739
|
-
document.head.appendChild(st)
|
|
6740
|
-
}
|
|
6741
|
-
|
|
6742
|
-
function renderMarket(S, market, loading, search, setSearch, onInstall, onRemove, busyId, proxyInput, setProxyInput, onSaveProxy, savingProxy) {
|
|
6743
|
-
ensureMarketCss()
|
|
6744
|
-
var S2 = {
|
|
6745
|
-
card: { border: '1px solid var(--dsw-alias-border, #3a3a40)', borderRadius: 10, overflow: 'hidden', padding: '0 0 14px 0', display: 'flex', flexDirection: 'column', gap: 8, height: '100%', boxSizing: 'border-box', background: 'var(--dsw-alias-bg-primary, #252528)' },
|
|
6746
|
-
cardHead: { display: 'flex', alignItems: 'baseline', gap: 9, flexWrap: 'wrap', padding: '12px 16px 8px', borderBottom: '1px solid var(--dsw-alias-border, #3a3a40)' },
|
|
6747
|
-
cardName: { fontSize: 13.5, fontWeight: 600 },
|
|
6748
|
-
cardVer: { fontSize: 11 },
|
|
6749
|
-
cardDesc: { fontSize: 12, lineHeight: 1.6, margin: '0 16px', display: '-webkit-box', WebkitLineClamp: 3, WebkitBoxOrient: 'vertical', overflow: 'hidden' },
|
|
6750
|
-
tag: { fontSize: 10.5, padding: '1px 7px', borderRadius: 4, border: '1px solid var(--dsw-alias-border, #3a3a40)', color: 'var(--dsw-alias-text-secondary, #9a9aa0)' },
|
|
6751
|
-
btnInstall: { padding: '4px 14px', fontSize: 12, borderRadius: 5, border: '1px solid var(--dsw-alias-accent, #5b8def)', background: 'var(--dsw-alias-accent, #5b8def)', color: '#fff', cursor: 'pointer' },
|
|
6752
|
-
btnGhost: { padding: '4px 14px', fontSize: 12, borderRadius: 5, border: '1px solid var(--dsw-alias-border, #3a3a40)', background: 'transparent', color: 'inherit', cursor: 'pointer' },
|
|
6753
|
-
}
|
|
6754
|
-
if (loading) return h('div', { style: S.hint }, '正在读取市场索引…')
|
|
6755
|
-
var err = market && market.indexErr
|
|
6756
|
-
var list = (market && market.index && market.index.plugins) || []
|
|
6757
|
-
var installed = (market && market.installed) || []
|
|
6758
|
-
// 兼容旧格式(字符串数组)和新格式({id,version})
|
|
6759
|
-
var installedIds = installed.map(function (x) { return typeof x === 'string' ? x : (x && x.id) })
|
|
6760
|
-
var installedVer = {}
|
|
6761
|
-
installed.forEach(function (x) { if (x && typeof x === 'object' && x.id) installedVer[x.id] = String(x.version || '') })
|
|
6762
|
-
/** 版本比较:a 比 b 新返回 1,相同 0,旧 -1。非语义版本按字符串比 */
|
|
6763
|
-
function cmpVer(a, b) {
|
|
6764
|
-
var pa = String(a || '').split(/[.\-+]/).map(function (s) { return parseInt(s, 10) || 0 })
|
|
6765
|
-
var pb = String(b || '').split(/[.\-+]/).map(function (s) { return parseInt(s, 10) || 0 })
|
|
6766
|
-
var n = Math.max(pa.length, pb.length)
|
|
6767
|
-
for (var i = 0; i < n; i++) {
|
|
6768
|
-
var x = pa[i] || 0, y = pb[i] || 0
|
|
6769
|
-
if (x > y) return 1
|
|
6770
|
-
if (x < y) return -1
|
|
6771
|
-
}
|
|
6772
|
-
return 0
|
|
6773
|
-
}
|
|
6774
|
-
var q = String(search || '').trim().toLowerCase()
|
|
6775
|
-
var shown = q ? list.filter(function (p) {
|
|
6776
|
-
return ((p.name || '') + ' ' + (p.id || '') + ' ' + (p.author || '') + ' ' + (p.description || '') + ' ' + ((p.tags || []).join(' '))).toLowerCase().indexOf(q) >= 0
|
|
6777
|
-
}) : list
|
|
6778
|
-
|
|
6779
|
-
return h('div', null,
|
|
6780
|
-
h('div', { style: { display: 'flex', alignItems: 'center', gap: 10, marginBottom: 12 } },
|
|
6781
|
-
h('input', {
|
|
6782
|
-
style: {
|
|
6783
|
-
width: 320, maxWidth: '50%', boxSizing: 'border-box',
|
|
6784
|
-
padding: '7px 12px', fontSize: 12.5, borderRadius: 16,
|
|
6785
|
-
border: '1px solid var(--dsw-alias-border, #45454c)',
|
|
6786
|
-
background: 'var(--dsw-alias-bg-primary, #252528)',
|
|
6787
|
-
color: 'var(--dsw-alias-text-primary, #e8e8ea)',
|
|
6788
|
-
colorScheme: 'dark', outline: 'none',
|
|
6789
|
-
},
|
|
6790
|
-
className: 'qqs-mk-search',
|
|
6791
|
-
placeholder: '搜索插件名 / 作者 / 标签…',
|
|
6792
|
-
value: search,
|
|
6793
|
-
onChange: function (e) { setSearch(e.target.value) },
|
|
6794
|
-
}),
|
|
6795
|
-
h('span', { style: { fontSize: 11.5, color: '#a8a8b2' } },
|
|
6796
|
-
shown.length + ' / ' + list.length + ' 个插件'),
|
|
6797
|
-
h('span', { style: { marginLeft: 'auto', fontSize: 11, color: '#a8a8b2' } },
|
|
6798
|
-
'已知 ' + installed.length + ' 个已安装')),
|
|
6799
|
-
|
|
6800
|
-
// 代理配置(墙内访问 GitHub 需要)
|
|
6801
|
-
h('div', { style: { display: 'flex', alignItems: 'center', gap: 8, marginBottom: 12, flexWrap: 'wrap' } },
|
|
6802
|
-
h('span', { style: { fontSize: 11.5, color: '#a8a8b2' } }, '代理'),
|
|
6803
|
-
h('input', {
|
|
6804
|
-
className: 'qqs-mk-search qqs-mk-proxy',
|
|
6805
|
-
style: { width: 280, maxWidth: '50%', padding: '6px 11px', fontSize: 12, borderRadius: 14 },
|
|
6806
|
-
placeholder: 'http://127.0.0.1:29290(留空=直连)',
|
|
6807
|
-
value: proxyInput,
|
|
6808
|
-
onChange: function (e) { setProxyInput(e.target.value) },
|
|
6809
|
-
onKeyDown: function (e) { if (e.key === 'Enter') onSaveProxy() },
|
|
6810
|
-
}),
|
|
6811
|
-
h('button', {
|
|
6812
|
-
className: 'qqs-mk-ghost',
|
|
6813
|
-
disabled: savingProxy,
|
|
6814
|
-
onClick: onSaveProxy,
|
|
6815
|
-
}, savingProxy ? '保存中…' : '保存并重试'),
|
|
6816
|
-
market && market.proxy
|
|
6817
|
-
? h('span', { style: { fontSize: 11, color: '#7ec87e' } }, '当前:' + market.proxy)
|
|
6818
|
-
: h('span', { style: { fontSize: 11, color: '#a8a8b2' } }, '当前:直连'),
|
|
6819
|
-
err ? h('span', { style: { fontSize: 11, color: '#e07a7a', marginLeft: 'auto' } }, '索引读取失败') : null),
|
|
6820
|
-
|
|
6821
|
-
err ? h('div', { style: S.errBox }, '索引读取失败:' + err) : null,
|
|
6822
|
-
|
|
6823
|
-
shown.length === 0
|
|
6824
|
-
? h('div', { style: S.empty }, err ? '索引不可用' : (q ? ('没有匹配「' + search + '」的插件') : '市场里还没有插件'))
|
|
6825
|
-
: h('div', { className: 'qqs-mk-grid' },
|
|
6826
|
-
shown.map(function (p) {
|
|
6827
|
-
var id = String(p.id || '')
|
|
6828
|
-
var isIn = installedIds.indexOf(id) >= 0
|
|
6829
|
-
var localVer = installedVer[id] || ''
|
|
6830
|
-
var remoteVer = String(p.version || '')
|
|
6831
|
-
var canUpdate = isIn && localVer && remoteVer && cmpVer(remoteVer, localVer) > 0
|
|
6832
|
-
var busy = busyId === id
|
|
6833
|
-
return h('div', { key: id, className: 'qqs-mk-card', style: S2.card },
|
|
6834
|
-
// 封面(可选)
|
|
6835
|
-
p.cover ? h('img', {
|
|
6836
|
-
src: String(p.cover).replace(
|
|
6837
|
-
/^https?:\/\/raw\.githubusercontent\.com\//,
|
|
6838
|
-
'https://cdn.jsdelivr.net/gh/'
|
|
6839
|
-
),
|
|
6840
|
-
alt: String(p.coverAlt || p.name || id),
|
|
6841
|
-
loading: 'lazy',
|
|
6842
|
-
className: 'qqs-mk-cover',
|
|
6843
|
-
onError: function (e) {
|
|
6844
|
-
try {
|
|
6845
|
-
var el = e.target;
|
|
6846
|
-
var cur = String(el.src || '');
|
|
6847
|
-
if (cur.indexOf('cdn.jsdelivr.net') >= 0) {
|
|
6848
|
-
// 换 gh-proxy 再试一次
|
|
6849
|
-
el.src = cur.replace('https://cdn.jsdelivr.net/gh/', 'https://gh-proxy.com/https://raw.githubusercontent.com/');
|
|
6850
|
-
} else {
|
|
6851
|
-
el.style.display = 'none'; // 都失败就藏掉,别破版
|
|
6852
|
-
}
|
|
6853
|
-
} catch (x) {}
|
|
6854
|
-
},
|
|
6855
|
-
}) : h('div', { className: 'qqs-mk-cover-ph' }, '暂无封面'),
|
|
6856
|
-
h('div', { className: 'qqs-mk-head', style: S2.cardHead },
|
|
6857
|
-
h('span', { className: 'qqs-mk-name', style: S2.cardName }, String(p.name || id)),
|
|
6858
|
-
p.version ? h('span', { className: 'qqs-mk-meta', style: S2.cardVer }, 'v' + p.version) : null,
|
|
6859
|
-
p.author ? h('span', { className: 'qqs-mk-meta', style: S2.cardVer }, 'by ' + p.author) : null,
|
|
6860
|
-
h('span', { style: { marginLeft: 'auto', display: 'flex', gap: 8, alignItems: 'center' } },
|
|
6861
|
-
isIn
|
|
6862
|
-
? h('span', { className: 'qqs-mk-tag' }, canUpdate ? ('已装 v' + localVer) : '已安装')
|
|
6863
|
-
: null,
|
|
6864
|
-
canUpdate
|
|
6865
|
-
? h('button', { className: 'qqs-mk-btn', disabled: busy, onClick: function () { onInstall({ id: id }) } },
|
|
6866
|
-
busy ? '更新中…' : ('更新到 v' + remoteVer))
|
|
6867
|
-
: null,
|
|
6868
|
-
isIn
|
|
6869
|
-
? h('button', { className: 'qqs-mk-ghost', disabled: busy, onClick: function () { onRemove({ id: id }) } }, busy ? '处理中…' : '卸载')
|
|
6870
|
-
: (canUpdate ? null : h('button', { className: 'qqs-mk-btn', disabled: busy, onClick: function () { onInstall({ id: id }) } }, busy ? '安装中…' : '安装')))),
|
|
6871
|
-
p.description ? h('div', { className: 'qqs-mk-desc', style: S2.cardDesc }, String(p.description)) : null,
|
|
6872
|
-
(p.tags && p.tags.length)
|
|
6873
|
-
? h('div', { style: { display: 'flex', gap: 6, flexWrap: 'wrap' } },
|
|
6874
|
-
p.tags.map(function (t, i) { return h('span', { key: i, className: 'qqs-mk-tag' }, String(t)) }))
|
|
6875
|
-
: null)
|
|
6876
|
-
})))
|
|
6877
|
-
}
|
|
6878
|
-
|
|
6879
|
-
function statCard(S, label, num, hint) {
|
|
6880
|
-
return h('div', { style: S.statCard },
|
|
6881
|
-
h('div', { style: S.statNum }, String(num)),
|
|
6882
|
-
h('div', { style: S.statLabel }, label),
|
|
6883
|
-
h('div', { style: S.statHint }, hint))
|
|
6884
|
-
}
|
|
6885
|
-
|
|
6886
|
-
function tabBtn(S, label, key, cur, setTab, count) {
|
|
6887
|
-
var on = cur === key
|
|
6888
|
-
return h('button', {
|
|
6889
|
-
key: key,
|
|
6890
|
-
style: on ? S.tabOn : S.tabOff,
|
|
6891
|
-
onClick: function () { setTab(key) },
|
|
6892
|
-
}, label, count !== undefined ? h('span', { style: S.tabCount }, ' ' + count) : null)
|
|
6893
|
-
}
|
|
6894
|
-
|
|
6895
|
-
function extStyles() {
|
|
6896
|
-
var C = {
|
|
6897
|
-
border: 'var(--dsw-alias-border, #8884)',
|
|
6898
|
-
sub: 'var(--dsw-alias-text-secondary, #888)',
|
|
6899
|
-
bg2: 'var(--dsw-alias-bg-secondary, rgba(127,127,127,.06))',
|
|
6900
|
-
accent: 'var(--dsw-alias-accent, #5b8def)',
|
|
6901
|
-
text: 'inherit',
|
|
6902
|
-
}
|
|
6903
|
-
return {
|
|
6904
|
-
wrap: { padding: '18px 22px', height: '100%', overflow: 'auto', boxSizing: 'border-box', color: C.text },
|
|
6905
|
-
head: { display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', marginBottom: 16 },
|
|
6906
|
-
title: { fontSize: 19, fontWeight: 600, marginBottom: 4 },
|
|
6907
|
-
sub: { fontSize: 12.5, color: C.sub },
|
|
6908
|
-
btn: { padding: '4px 12px', fontSize: 12.5, borderRadius: 6, border: '1px solid ' + C.border, background: 'transparent', color: 'inherit', cursor: 'pointer' },
|
|
6909
|
-
stats: { display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 12, marginBottom: 14 },
|
|
6910
|
-
statCard: { border: '1px solid ' + C.border, borderRadius: 10, padding: '12px 14px', background: C.bg2 },
|
|
6911
|
-
statNum: { fontSize: 24, fontWeight: 600, lineHeight: 1.2 },
|
|
6912
|
-
statLabel: { fontSize: 12.5, marginTop: 2 },
|
|
6913
|
-
statHint: { fontSize: 11.5, color: C.sub, marginTop: 2 },
|
|
6914
|
-
pathBox: { border: '1px solid ' + C.border, borderRadius: 10, padding: '10px 14px', marginBottom: 14, fontSize: 12 },
|
|
6915
|
-
pathRow: { display: 'flex', gap: 10, alignItems: 'baseline', padding: '3px 0' },
|
|
6916
|
-
pathKey: { color: C.sub, minWidth: 68, fontSize: 12 },
|
|
6917
|
-
code: { fontFamily: 'ui-monospace, Consolas, monospace', fontSize: 11.5, wordBreak: 'break-all', opacity: .85 },
|
|
6918
|
-
tabs: { display: 'flex', gap: 6, marginBottom: 14, borderBottom: '1px solid ' + C.border, paddingBottom: 8 },
|
|
6919
|
-
tabOn: { padding: '5px 14px', fontSize: 13, borderRadius: 6, border: '1px solid ' + C.accent, background: C.accent, color: '#fff', cursor: 'pointer' },
|
|
6920
|
-
tabOff: { padding: '5px 14px', fontSize: 13, borderRadius: 6, border: '1px solid ' + C.border, background: 'transparent', color: 'inherit', cursor: 'pointer' },
|
|
6921
|
-
tabCount: { opacity: .75, fontSize: 11.5 },
|
|
6922
|
-
group: { border: '1px solid ' + C.border, borderRadius: 10, padding: '14px 16px', marginBottom: 12 },
|
|
6923
|
-
groupHead: { marginBottom: 10, display: 'flex', alignItems: 'baseline', gap: 10, flexWrap: 'wrap' },
|
|
6924
|
-
groupTitle: { fontSize: 14.5, fontWeight: 600 },
|
|
6925
|
-
groupSub: { fontSize: 11.5, color: C.sub, wordBreak: 'break-all' },
|
|
6926
|
-
kindBlock: { marginBottom: 10 },
|
|
6927
|
-
kindHead: { display: 'flex', alignItems: 'center', gap: 6, marginBottom: 5 },
|
|
6928
|
-
kindIcon: { fontSize: 12.5 },
|
|
6929
|
-
kindLabel: { fontSize: 12.5, fontWeight: 500 },
|
|
6930
|
-
kindCount: { fontSize: 11, color: C.sub, background: C.bg2, borderRadius: 9, padding: '0 6px' },
|
|
6931
|
-
kindHint: { fontSize: 11, color: C.sub, marginLeft: 2 },
|
|
6932
|
-
chipWrap: { display: 'flex', flexWrap: 'wrap', gap: 5 },
|
|
6933
|
-
chip: { fontSize: 11.5, padding: '2px 8px', borderRadius: 5, border: '1px solid ' + C.border, background: C.bg2 },
|
|
6934
|
-
chipWithGear: { fontSize: 11.5, padding: '2px 8px', borderRadius: 5, border: '1px solid ' + C.accent, background: 'transparent' },
|
|
6935
|
-
gear: { opacity: .8, fontSize: 10.5 },
|
|
6936
|
-
settingsList: { display: 'flex', flexDirection: 'column', gap: 6 },
|
|
6937
|
-
settingRow: { display: 'flex', alignItems: 'baseline', gap: 10, border: '1px solid ' + C.border, borderRadius: 8, padding: '8px 12px', fontSize: 12.5 },
|
|
6938
|
-
settingTitle: { fontWeight: 500 },
|
|
6939
|
-
settingFrom: { fontSize: 11, color: C.sub, background: C.bg2, borderRadius: 4, padding: '1px 6px' },
|
|
6940
|
-
settingId: { fontSize: 11, color: C.sub, fontFamily: 'ui-monospace, Consolas, monospace', marginLeft: 'auto' },
|
|
6941
|
-
empty: { fontSize: 12.5, color: 'var(--dsw-alias-text-secondary, #9a9aa0)', padding: '18px 4px', lineHeight: 1.7 },
|
|
6942
|
-
emptyHint: { fontSize: 11.5, opacity: .8, marginTop: 4 },
|
|
6943
|
-
hint: { fontSize: 12.5, color: C.sub, padding: 20 },
|
|
6944
|
-
errBox: { fontSize: 12.5, color: '#c33', padding: '10px 12px', border: '1px solid #c335', borderRadius: 8, marginBottom: 10 },
|
|
6945
|
-
// ── 扩展设置弹层(2026-10-24)──
|
|
6946
|
-
chipClickable: { fontSize: 11.5, padding: '2px 8px', borderRadius: 5, border: '1px solid ' + C.accent, background: 'transparent', cursor: 'pointer' },
|
|
6947
|
-
|
|
6948
|
-
|
|
6949
|
-
|
|
6950
|
-
|
|
6951
|
-
|
|
6952
|
-
|
|
6953
|
-
|
|
6954
|
-
|
|
6955
|
-
|
|
6956
|
-
|
|
6957
|
-
|
|
6958
|
-
|
|
6959
|
-
|
|
6960
|
-
|
|
6961
|
-
|
|
6962
|
-
|
|
6963
|
-
|
|
6964
|
-
|
|
6022
|
+
|
|
6023
|
+
// ═════════════════════════════════════════════════════════════════════════
|
|
6024
|
+
// 「QQ机器人扩展」会话页签(2026-10-24)
|
|
6025
|
+
//
|
|
6026
|
+
// 会话页顶部与「对话 / 轨迹 / 记忆 / 白板看板」并列,进会话就能看到当前工作区
|
|
6027
|
+
// 装了哪些扩展,不用切到设置页。数据来源 GET /api/qqbot-settings/extensions。
|
|
6028
|
+
//
|
|
6029
|
+
// 两块来源:
|
|
6030
|
+
// · 内置扩展 {dataRoot}/.qqbot-extensions/{tools,commands,...}/
|
|
6031
|
+
// · 扩展包 {dataRoot}/extension-packs/{包名}/{同样的子目录}/
|
|
6032
|
+
// 包目录里放 qqbot-pack.json 可声明 name/author/desc/version
|
|
6033
|
+
// ═════════════════════════════════════════════════════════════════════════
|
|
6034
|
+
var _lastCwd = ''
|
|
6035
|
+
var _lastSid = ''
|
|
6036
|
+
// 当前会话工作区(跟随 GUI 切换工作区):与 dsh-auto-memory 的 currentWs() 同一套取法
|
|
6037
|
+
// sessions.list.getSnapshot().byId[current].cwd 是官方 buildListSnapshot 投影出来的权威字段
|
|
6038
|
+
/** 取当前会话 id(cwd 拿不到时后端靠它反查工作区) */
|
|
6039
|
+
function curSessionId() {
|
|
6040
|
+
try {
|
|
6041
|
+
var svc = _extSessionsSvc
|
|
6042
|
+
if (svc && svc.list && typeof svc.list.getSnapshot === 'function') {
|
|
6043
|
+
var snap = svc.list.getSnapshot()
|
|
6044
|
+
if (snap && snap.current) return String(snap.current)
|
|
6045
|
+
}
|
|
6046
|
+
} catch (e) {}
|
|
6047
|
+
try {
|
|
6048
|
+
var raw = localStorage.getItem('dsh.sessions.current')
|
|
6049
|
+
if (raw) { var o = JSON.parse(raw); if (o && o.sessionId) return String(o.sessionId) }
|
|
6050
|
+
} catch (e) {}
|
|
6051
|
+
return ''
|
|
6052
|
+
}
|
|
6053
|
+
|
|
6054
|
+
function curWorkspaceCwd() {
|
|
6055
|
+
try {
|
|
6056
|
+
var svc = _extSessionsSvc
|
|
6057
|
+
if (svc && svc.list && typeof svc.list.getSnapshot === 'function') {
|
|
6058
|
+
var snap = svc.list.getSnapshot()
|
|
6059
|
+
if (!snap) return ''
|
|
6060
|
+
var id = snap.current
|
|
6061
|
+
if (id === undefined || id === null) return ''
|
|
6062
|
+
var row = snap.byId && snap.byId[id]
|
|
6063
|
+
if (row && typeof row.cwd === 'string') return row.cwd
|
|
6064
|
+
}
|
|
6065
|
+
} catch (e) {}
|
|
6066
|
+
// 兜底:宿主持久化镜像(dock 球也在用这个键)
|
|
6067
|
+
try {
|
|
6068
|
+
var raw = localStorage.getItem('dsh.sessions.current')
|
|
6069
|
+
if (raw) { var o = JSON.parse(raw); if (o && o.cwd) return String(o.cwd) }
|
|
6070
|
+
} catch (e) {}
|
|
6071
|
+
return ''
|
|
6072
|
+
}
|
|
6073
|
+
|
|
6074
|
+
var EXT_KIND_META = [
|
|
6075
|
+
{ key: 'tools', label: '扩展工具', icon: '🔧', hint: '模型可调用的工具' },
|
|
6076
|
+
{ key: 'commands', label: '斜杠命令', icon: '⌘', hint: '/ 开头的命令' },
|
|
6077
|
+
{ key: 'botplay', label: '游戏裁判', icon: '🎲', hint: '群内小游戏' },
|
|
6078
|
+
{ key: 'hooks', label: '事件钩子', icon: '🪝', hint: '生命周期回调' },
|
|
6079
|
+
{ key: 'inbound', label: '入站处理', icon: '📥', hint: '消息进模型前' },
|
|
6080
|
+
{ key: 'outbound', label: '出站处理', icon: '📤', hint: '模型输出后' },
|
|
6081
|
+
]
|
|
6082
|
+
|
|
6083
|
+
function ExtPanel() {
|
|
6084
|
+
var _a = useState(null), data = _a[0], setData = _a[1]
|
|
6085
|
+
var _b = useState(true), loading = _b[0], setLoading = _b[1]
|
|
6086
|
+
var _c = useState(''), err = _c[0], setErr = _c[1]
|
|
6087
|
+
var _d = useState('builtin'), tab = _d[0], setTab = _d[1]
|
|
6088
|
+
var _e = useState(0), tick = _e[0], setTick = _e[1]
|
|
6089
|
+
var _sel = useState(null), sel = _sel[0], setSel = _sel[1] // 正在编辑哪个扩展的设置
|
|
6090
|
+
function openSettings(info) { setSel(info) }
|
|
6091
|
+
// 首次进市场页签 -> 拉一次索引
|
|
6092
|
+
useEffect(function () { if (tab === 'market' && !market && !loadingMarket) loadMarket() }, [tab])
|
|
6093
|
+
var _filter = useState(''), filter = _filter[0], setFilter = _filter[1]
|
|
6094
|
+
var _tog = useState(false), toggling = _tog[0], setToggling = _tog[1]
|
|
6095
|
+
function toggleExt(info) {
|
|
6096
|
+
setToggling(true)
|
|
6097
|
+
var body = { kind: info.kind, file: info.file, enabled: info.enabled }
|
|
6098
|
+
var cw = curWorkspaceCwd()
|
|
6099
|
+
if (cw) body.cwd = cw
|
|
6100
|
+
fetch('/api/qqbot-settings/ext-toggle', {
|
|
6101
|
+
method: 'POST',
|
|
6102
|
+
credentials: 'same-origin',
|
|
6103
|
+
headers: { 'Content-Type': 'application/json' },
|
|
6104
|
+
body: JSON.stringify(body),
|
|
6105
|
+
})
|
|
6106
|
+
.then(function (r) { return r.json() })
|
|
6107
|
+
.then(function (d) {
|
|
6108
|
+
if (!d || d.ok === false) { console.warn('[ext-toggle] 失败', d); }
|
|
6109
|
+
else if (d.reloaded) {
|
|
6110
|
+
var r = d.reloaded
|
|
6111
|
+
var bits = []
|
|
6112
|
+
if (r.commands) bits.push('命令 ' + r.commands)
|
|
6113
|
+
if (r.inbound) bits.push('入站 ' + r.inbound)
|
|
6114
|
+
if (r.outbound) bits.push('出站 ' + r.outbound)
|
|
6115
|
+
var tail = r.tools ? ';工具' + r.tools : ''
|
|
6116
|
+
console.log('[ext-toggle] 热重载:', bits.join(' / ') + tail)
|
|
6117
|
+
if (typeof window !== 'undefined' && window.__qqsExtToast) window.__qqsExtToast(bits.join(' / ') + tail)
|
|
6118
|
+
}
|
|
6119
|
+
setTick(function (n) { return n + 1 })
|
|
6120
|
+
})
|
|
6121
|
+
.catch(function (e) { console.warn('[ext-toggle] 异常', e) })
|
|
6122
|
+
.then(function () { setToggling(false) })
|
|
6123
|
+
}
|
|
6124
|
+
// ── 插件市场(2026-10-24)──
|
|
6125
|
+
var _market = useState(null), market = _market[0], setMarket = _market[1]
|
|
6126
|
+
var _loadingMarket = useState(false), loadingMarket = _loadingMarket[0], setLoadingMarket = _loadingMarket[1]
|
|
6127
|
+
var _searchMarket = useState(''), searchMarket = _searchMarket[0], setSearchMarket = _searchMarket[1]
|
|
6128
|
+
var _busyId = useState(''), busyId = _busyId[0], setBusyId = _busyId[1]
|
|
6129
|
+
var marketCount = (market && market.index && market.index.plugins) ? market.index.plugins.length : 0
|
|
6130
|
+
|
|
6131
|
+
function loadMarket() {
|
|
6132
|
+
setLoadingMarket(true)
|
|
6133
|
+
var cw = curWorkspaceCwd()
|
|
6134
|
+
var qs = cw ? ('?cwd=' + encodeURIComponent(cw)) : ''
|
|
6135
|
+
fetch('/api/qqbot-settings/market' + qs, { credentials: 'same-origin' })
|
|
6136
|
+
.then(function (r) { return r.json() })
|
|
6137
|
+
.then(function (d) { if (d && d.ok) setMarket(d); else console.warn('[market] 读取失败', d) })
|
|
6138
|
+
.catch(function (e) { console.warn('[market] 异常', e) })
|
|
6139
|
+
.then(function () { setLoadingMarket(false) })
|
|
6140
|
+
}
|
|
6141
|
+
function marketPost(path, body) {
|
|
6142
|
+
var cw = curWorkspaceCwd()
|
|
6143
|
+
if (cw) body.cwd = cw
|
|
6144
|
+
return fetch(path, {
|
|
6145
|
+
method: 'POST',
|
|
6146
|
+
credentials: 'same-origin',
|
|
6147
|
+
headers: { 'Content-Type': 'application/json' },
|
|
6148
|
+
body: JSON.stringify(body),
|
|
6149
|
+
}) // 返回原始 Response:调用方要看 r.ok 判断 HTTP 状态,并自己 r.text()
|
|
6150
|
+
}
|
|
6151
|
+
function onInstall(info) {
|
|
6152
|
+
setBusyId(info.id)
|
|
6153
|
+
marketPost('/api/qqbot-settings/market/install', { id: info.id })
|
|
6154
|
+
.then(function (r) {
|
|
6155
|
+
return r.text().then(function (txt) {
|
|
6156
|
+
var d = null
|
|
6157
|
+
try { d = JSON.parse(txt) } catch (e) { d = { error: String(txt).slice(0, 200) } }
|
|
6158
|
+
if (!r.ok || !d || d.ok === false) {
|
|
6159
|
+
console.warn('[market] 安装失败 HTTP', r.status, d)
|
|
6160
|
+
var why = (d && (d.error || '')) || ('HTTP ' + r.status)
|
|
6161
|
+
var tag = (d && d.stage) ? ('[' + d.stage + ']') : ''
|
|
6162
|
+
if (window.__qqsExtToast) window.__qqsExtToast('安装失败' + tag + ':' + why)
|
|
6163
|
+
return
|
|
6164
|
+
}
|
|
6165
|
+
if (window.__qqsExtToast) window.__qqsExtToast('已安装 ' + info.id + '(' + d.written + '/' + d.total + ' 个文件)')
|
|
6166
|
+
})
|
|
6167
|
+
})
|
|
6168
|
+
.catch(function (e) { console.warn('[market] 安装异常', e); if (window.__qqsExtToast) window.__qqsExtToast('安装异常:' + e) })
|
|
6169
|
+
.then(function () { setBusyId(''); loadMarket(); setTick(function (n) { return n + 1 }) })
|
|
6170
|
+
}
|
|
6171
|
+
// ── 市场代理配置(2026-10-24 主人要求:墙内要能填代理)──
|
|
6172
|
+
var _proxyInput = useState(''), proxyInput = _proxyInput[0], setProxyInput = _proxyInput[1]
|
|
6173
|
+
var _savingProxy = useState(false), savingProxy = _savingProxy[0], setSavingProxy = _savingProxy[1]
|
|
6174
|
+
var _proxyInited = useState(false), proxyInited = _proxyInited[0], setProxyInited = _proxyInited[1]
|
|
6175
|
+
useEffect(function () {
|
|
6176
|
+
if (market && !proxyInited) { setProxyInput(market.proxy || ''); setProxyInited(true) }
|
|
6177
|
+
}, [market])
|
|
6178
|
+
function onSaveProxy() {
|
|
6179
|
+
setSavingProxy(true)
|
|
6180
|
+
var cw = curWorkspaceCwd()
|
|
6181
|
+
var body = { proxy: String(proxyInput || '').trim() }
|
|
6182
|
+
if (cw) body.cwd = cw
|
|
6183
|
+
fetch('/api/qqbot-settings/market/config', {
|
|
6184
|
+
method: 'POST',
|
|
6185
|
+
credentials: 'same-origin',
|
|
6186
|
+
headers: { 'Content-Type': 'application/json' },
|
|
6187
|
+
body: JSON.stringify(body),
|
|
6188
|
+
})
|
|
6189
|
+
.then(function (r) { return r.json() })
|
|
6190
|
+
.then(function (d) {
|
|
6191
|
+
if (!d || d.ok === false) {
|
|
6192
|
+
if (window.__qqsExtToast) window.__qqsExtToast('代理保存失败:' + ((d && d.error) || '未知'))
|
|
6193
|
+
} else {
|
|
6194
|
+
if (window.__qqsExtToast) window.__qqsExtToast(body.proxy ? ('代理已设为 ' + body.proxy + ',重新拉索引…') : '已切回直连,重新拉索引…')
|
|
6195
|
+
setProxyInited(false)
|
|
6196
|
+
setMarket(null)
|
|
6197
|
+
loadMarket()
|
|
6198
|
+
}
|
|
6199
|
+
})
|
|
6200
|
+
.catch(function (e) { if (window.__qqsExtToast) window.__qqsExtToast('保存异常:' + e) })
|
|
6201
|
+
.then(function () { setSavingProxy(false) })
|
|
6202
|
+
}
|
|
6203
|
+
|
|
6204
|
+
function onRemove(info) {
|
|
6205
|
+
setBusyId(info.id)
|
|
6206
|
+
marketPost('/api/qqbot-settings/market/remove', { id: info.id })
|
|
6207
|
+
.then(function (r) {
|
|
6208
|
+
return r.text().then(function (txt) {
|
|
6209
|
+
var d = null
|
|
6210
|
+
try { d = JSON.parse(txt) } catch (e) { d = { error: String(txt).slice(0, 200) } }
|
|
6211
|
+
if (!r.ok || !d || d.ok === false) {
|
|
6212
|
+
var why = (d && (d.error || '')) || ('HTTP ' + r.status)
|
|
6213
|
+
if (window.__qqsExtToast) window.__qqsExtToast('卸载失败:' + why)
|
|
6214
|
+
return
|
|
6215
|
+
}
|
|
6216
|
+
if (window.__qqsExtToast) window.__qqsExtToast('已卸载 ' + info.id)
|
|
6217
|
+
})
|
|
6218
|
+
})
|
|
6219
|
+
.catch(function (e) { console.warn('[market] 卸载异常', e) })
|
|
6220
|
+
.then(function () { setBusyId(''); loadMarket(); setTick(function (n) { return n + 1 }) })
|
|
6221
|
+
}
|
|
6222
|
+
|
|
6223
|
+
// 工作区切换后自动重载:cwd 一变就刷新
|
|
6224
|
+
useEffect(function () {
|
|
6225
|
+
var nowCwd = curWorkspaceCwd()
|
|
6226
|
+
var nowSid = curSessionId()
|
|
6227
|
+
if ((nowCwd && nowCwd !== _lastCwd) || (nowSid && nowSid !== _lastSid)) {
|
|
6228
|
+
_lastCwd = nowCwd; _lastSid = nowSid
|
|
6229
|
+
setTick(function (n) { return n + 1 })
|
|
6230
|
+
}
|
|
6231
|
+
})
|
|
6232
|
+
|
|
6233
|
+
useEffect(function () {
|
|
6234
|
+
var dead = false
|
|
6235
|
+
setLoading(true)
|
|
6236
|
+
var _cwd = curWorkspaceCwd()
|
|
6237
|
+
var _sid = curSessionId()
|
|
6238
|
+
var _qs = []
|
|
6239
|
+
if (_cwd) _qs.push('cwd=' + encodeURIComponent(_cwd))
|
|
6240
|
+
if (_sid) _qs.push('sid=' + encodeURIComponent(_sid))
|
|
6241
|
+
var _url = '/api/qqbot-settings/extensions' + (_qs.length ? ('?' + _qs.join('&')) : '')
|
|
6242
|
+
fetch(_url, { credentials: 'same-origin' })
|
|
6243
|
+
.then(function (r) { return r.json() })
|
|
6244
|
+
.then(function (d) { if (!dead) { setData(d); setErr(d && d.ok === false ? (d.error || '未知错误') : ''); } })
|
|
6245
|
+
.catch(function (e) { if (!dead) setErr(String((e && e.message) || e)) })
|
|
6246
|
+
.then(function () { if (!dead) setLoading(false) })
|
|
6247
|
+
return function () { dead = true }
|
|
6248
|
+
}, [tick])
|
|
6249
|
+
|
|
6250
|
+
var S = extStyles()
|
|
6251
|
+
|
|
6252
|
+
if (loading && !data) return h('div', { style: S.wrap }, h('div', { style: S.hint }, '正在读取扩展…'))
|
|
6253
|
+
if (err) return h('div', { style: S.wrap },
|
|
6254
|
+
h('div', { style: S.errBox }, '读取失败:' + err),
|
|
6255
|
+
h('button', { style: S.btn, onClick: function () { setTick(tick + 1) } }, '重试'))
|
|
6256
|
+
if (!data) return h('div', { style: S.wrap }, h('div', { style: S.hint }, '暂无数据'))
|
|
6257
|
+
|
|
6258
|
+
// 汇总计数
|
|
6259
|
+
var builtinTotal = data.builtin ? data.builtin.total : 0
|
|
6260
|
+
var packList = data.packs || []
|
|
6261
|
+
var packTotal = packList.reduce(function (n, p) { return n + (p.total || 0) }, 0)
|
|
6262
|
+
var settingsCount = (data.settingsPages || []).length
|
|
6263
|
+
|
|
6264
|
+
// 当前 tab 的内容块列表
|
|
6265
|
+
var groups = []
|
|
6266
|
+
if (tab === 'builtin') {
|
|
6267
|
+
groups = [{ title: '内置扩展', subtitle: data.builtinRoot || '', kinds: (data.builtin || {}).kinds || {}, isPack: false }]
|
|
6268
|
+
} else if (tab === 'packs') {
|
|
6269
|
+
groups = packList.map(function (p) {
|
|
6270
|
+
var meta = p.meta || {}
|
|
6271
|
+
return {
|
|
6272
|
+
title: meta.name || p.dirName,
|
|
6273
|
+
subtitle: [meta.author ? ('作者 ' + meta.author) : '', meta.version ? ('v' + meta.version) : '', meta.desc || ''].filter(Boolean).join(' · ') || p.dir,
|
|
6274
|
+
kinds: p.kinds || {},
|
|
6275
|
+
isPack: true,
|
|
6276
|
+
raw: p,
|
|
6277
|
+
}
|
|
6278
|
+
})
|
|
6279
|
+
} else {
|
|
6280
|
+
// 设置页 tab
|
|
6281
|
+
groups = []
|
|
6282
|
+
}
|
|
6283
|
+
|
|
6284
|
+
function kindRows(kinds, kindMeta, onOpen) {
|
|
6285
|
+
var list = (kinds && kinds[kindMeta.key]) || []
|
|
6286
|
+
if (!list.length) return null
|
|
6287
|
+
return h('div', { key: kindMeta.key, style: S.kindBlock },
|
|
6288
|
+
h('div', { style: S.kindHead },
|
|
6289
|
+
h('span', { style: S.kindIcon }, kindMeta.icon),
|
|
6290
|
+
h('span', { style: S.kindLabel }, kindMeta.label),
|
|
6291
|
+
h('span', { style: S.kindCount }, String(list.length)),
|
|
6292
|
+
h('span', { style: S.kindHint }, kindMeta.hint)),
|
|
6293
|
+
h('div', { style: S.chipWrap },
|
|
6294
|
+
list.map(function (it) {
|
|
6295
|
+
return h('span', {
|
|
6296
|
+
key: it.id,
|
|
6297
|
+
style: it.hasSettings ? S.chipClickable : S.chip,
|
|
6298
|
+
title: it.name + ' · ' + Math.round(it.size / 102.4) / 10 + ' KB' + (it.hasSettings ? '(点击打开设置)' : ''),
|
|
6299
|
+
onClick: it.hasSettings ? function () { onOpen({ kind: kindMeta.key, file: it.name }) } : undefined,
|
|
6300
|
+
}, it.id, it.hasSettings ? h('span', { style: S.gear }, ' ⚙') : null)
|
|
6301
|
+
})))
|
|
6302
|
+
}
|
|
6303
|
+
|
|
6304
|
+
return h('div', { style: S.wrap },
|
|
6305
|
+
// 头部
|
|
6306
|
+
h('div', { style: S.head },
|
|
6307
|
+
h('div', null,
|
|
6308
|
+
h('div', { style: S.title }, 'QQ 机器人扩展'),
|
|
6309
|
+
h('div', { style: S.sub }, '当前工作区已装的扩展;放独立目录的扩展包也能在这里看到')),
|
|
6310
|
+
h('button', { style: S.btn, onClick: function () { setTick(tick + 1) } }, loading ? '刷新中…' : '刷新')),
|
|
6311
|
+
|
|
6312
|
+
// 统计卡
|
|
6313
|
+
h('div', { style: S.stats },
|
|
6314
|
+
statCard(S, '内置扩展', builtinTotal, '个文件'),
|
|
6315
|
+
statCard(S, '扩展包', packList.length, '个包 · 共 ' + packTotal + ' 个扩展'),
|
|
6316
|
+
statCard(S, '带设置页', settingsCount, '个扩展可配置')),
|
|
6317
|
+
|
|
6318
|
+
// 路径提示
|
|
6319
|
+
h('div', { style: S.pathBox },
|
|
6320
|
+
h('div', { style: S.pathRow }, h('span', { style: S.pathKey }, '当前工作区'), h('code', { style: S.code }, (data.workspaceCwd || curWorkspaceCwd() || '(未识别,回退到默认数据根)'),
|
|
6321
|
+
data.workspaceFrom && data.workspaceFrom !== 'default'
|
|
6322
|
+
? h('span', { style: { fontSize: 10.5, opacity: .6, marginLeft: 6 } }, 'via ' + data.workspaceFrom)
|
|
6323
|
+
: null)),
|
|
6324
|
+
h('div', { style: S.pathRow }, h('span', { style: S.pathKey }, '内置目录'), h('code', { style: S.code }, data.builtinRoot || '—')),
|
|
6325
|
+
h('div', { style: S.pathRow }, h('span', { style: S.pathKey }, '扩展包目录'), h('code', { style: S.code }, data.packsRoot || '—'))),
|
|
6326
|
+
|
|
6327
|
+
// 页签
|
|
6328
|
+
h('div', { style: S.tabs },
|
|
6329
|
+
tabBtn(S, '内置扩展', 'builtin', tab, setTab, builtinTotal),
|
|
6330
|
+
tabBtn(S, '扩展包', 'packs', tab, setTab, packList.length),
|
|
6331
|
+
tabBtn(S, '设置中心', 'settings', tab, setTab, settingsCount),
|
|
6332
|
+
tabBtn(S, '市场', 'market', tab, setTab, marketCount)),
|
|
6333
|
+
|
|
6334
|
+
// 内容
|
|
6335
|
+
tab === 'market'
|
|
6336
|
+
? renderMarket(S, market, loadingMarket, searchMarket, setSearchMarket, onInstall, onRemove, busyId, proxyInput, setProxyInput, onSaveProxy, savingProxy)
|
|
6337
|
+
: tab === 'settings'
|
|
6338
|
+
? renderSettingsCenter(S, data, filter, setFilter, openSettings, toggleExt, toggling)
|
|
6339
|
+
: (groups.length
|
|
6340
|
+
? groups.map(function (g, gi) {
|
|
6341
|
+
var hasAny = EXT_KIND_META.some(function (k) { return ((g.kinds || {})[k.key] || []).length > 0 })
|
|
6342
|
+
return h('div', { key: gi, style: S.group },
|
|
6343
|
+
h('div', { style: S.groupHead },
|
|
6344
|
+
h('span', { style: S.groupTitle }, g.title),
|
|
6345
|
+
h('span', { style: S.groupSub }, g.subtitle)),
|
|
6346
|
+
hasAny
|
|
6347
|
+
? EXT_KIND_META.map(function (k) { return kindRows(g.kinds, k, openSettings) })
|
|
6348
|
+
: h('div', { style: S.empty }, '这个' + (g.isPack ? '包' : '目录') + '里还没放扩展'))
|
|
6349
|
+
})
|
|
6350
|
+
: h('div', { style: S.empty }, tab === 'packs' ? '还没有扩展包。在 ' + (data.packsRoot || '') + ' 下建一个子文件夹,里面按 tools/commands/… 放扩展即可。' : '没有扩展'))
|
|
6351
|
+
,
|
|
6352
|
+
sel ? h(ExtSettings, { kind: sel.kind, file: sel.file, cwd: (data && data.workspaceCwd) || '', onClose: function () { setSel(null) } }) : null
|
|
6353
|
+
)
|
|
6354
|
+
}
|
|
6355
|
+
|
|
6356
|
+
/**
|
|
6357
|
+
* 扩展自定义设置面板(2026-10-24)
|
|
6358
|
+
*
|
|
6359
|
+
* 扩展在文件里声明:
|
|
6360
|
+
* export const qqbotSettingsPage = {
|
|
6361
|
+
* title: '我的设置',
|
|
6362
|
+
* schema: [
|
|
6363
|
+
* { key:'enabled', type:'bool', label:'启用' },
|
|
6364
|
+
* { key:'dirs', type:'list', label:'目录列表' },
|
|
6365
|
+
* { key:'apiKey', type:'string', label:'API Key', secret:true },
|
|
6366
|
+
* { key:'mode', type:'select', label:'模式', options:['a','b'] },
|
|
6367
|
+
* ],
|
|
6368
|
+
* }
|
|
6369
|
+
* 面板自动渲染表单,值存 {dataRoot}/.qqbot-extensions/.state/ext-config/{扩展id}.json
|
|
6370
|
+
*/
|
|
6371
|
+
function ExtSettings(props) {
|
|
6372
|
+
var kind = props.kind, file = props.file, cwd = props.cwd, onClose = props.onClose
|
|
6373
|
+
var _a = useState(null), data = _a[0], setData = _a[1]
|
|
6374
|
+
var _b = useState({}), vals = _b[0], setVals = _b[1]
|
|
6375
|
+
var _c = useState(true), loading = _c[0], setLoading = _c[1]
|
|
6376
|
+
var _d = useState(''), msg = _d[0], setMsg = _d[1]
|
|
6377
|
+
var _e = useState(''), err = _e[0], setErr = _e[1]
|
|
6378
|
+
|
|
6379
|
+
useEffect(function () {
|
|
6380
|
+
var dead = false
|
|
6381
|
+
setLoading(true); setErr('')
|
|
6382
|
+
var qs = '?kind=' + encodeURIComponent(kind) + '&file=' + encodeURIComponent(file)
|
|
6383
|
+
if (cwd) qs += '&cwd=' + encodeURIComponent(cwd)
|
|
6384
|
+
fetch('/api/qqbot-settings/ext-panel' + qs, { credentials: 'same-origin' })
|
|
6385
|
+
.then(function (r) { return r.json() })
|
|
6386
|
+
.then(function (d) {
|
|
6387
|
+
if (dead) return
|
|
6388
|
+
if (!d || d.ok === false) { setErr((d && d.error) || '读取失败'); return }
|
|
6389
|
+
setData(d)
|
|
6390
|
+
setVals(d.values && typeof d.values === 'object' ? d.values : {})
|
|
6391
|
+
})
|
|
6392
|
+
.catch(function (e) { if (!dead) setErr(String((e && e.message) || e)) })
|
|
6393
|
+
.then(function () { if (!dead) setLoading(false) })
|
|
6394
|
+
return function () { dead = true }
|
|
6395
|
+
}, [kind, file, cwd])
|
|
6396
|
+
|
|
6397
|
+
function save() {
|
|
6398
|
+
setMsg(''); setErr('')
|
|
6399
|
+
fetch('/api/qqbot-settings/ext-panel', {
|
|
6400
|
+
method: 'POST',
|
|
6401
|
+
credentials: 'same-origin',
|
|
6402
|
+
headers: { 'Content-Type': 'application/json' },
|
|
6403
|
+
body: JSON.stringify({ kind: kind, file: file, cwd: cwd, values: vals }),
|
|
6404
|
+
})
|
|
6405
|
+
.then(function (r) { return r.json() })
|
|
6406
|
+
.then(function (d) {
|
|
6407
|
+
if (d && d.ok) setMsg('已保存 → ' + (d.configPath || ''))
|
|
6408
|
+
else setErr((d && d.error) || '保存失败')
|
|
6409
|
+
})
|
|
6410
|
+
.catch(function (e) { setErr(String((e && e.message) || e)) })
|
|
6411
|
+
}
|
|
6412
|
+
|
|
6413
|
+
var S = extStyles()
|
|
6414
|
+
var page = (data && data.page) || {}
|
|
6415
|
+
var schema = page.schema || []
|
|
6416
|
+
var title = page.title || (data && data.extId) || file
|
|
6417
|
+
|
|
6418
|
+
return h('div', { style: S.modalMask, onClick: onClose },
|
|
6419
|
+
h('div', { style: S.modalBox, onClick: function (e) { e.stopPropagation() } },
|
|
6420
|
+
h('div', { style: S.modalHead },
|
|
6421
|
+
h('div', null,
|
|
6422
|
+
h('div', { style: S.modalTitle }, title + ' · 设置'),
|
|
6423
|
+
h('div', { style: S.modalSub }, kind + '/' + file)),
|
|
6424
|
+
h('button', { style: S.btn, onClick: onClose }, '关闭')),
|
|
6425
|
+
|
|
6426
|
+
loading ? h('div', { style: S.hint }, '读取中…') :
|
|
6427
|
+
err ? h('div', { style: S.errBox }, err) :
|
|
6428
|
+
(schema.length === 0
|
|
6429
|
+
? h('div', null,
|
|
6430
|
+
h('div', { style: S.empty },
|
|
6431
|
+
page._err ? ('读取失败:' + page._err)
|
|
6432
|
+
: page._miss ? ('文件里没有 qqbotSettingsPage。实际导出:' + (page._keys || []).join(', '))
|
|
6433
|
+
: '这个扩展没有声明配置项。'),
|
|
6434
|
+
h('div', { style: S.emptyHint },
|
|
6435
|
+
page._err || page._miss
|
|
6436
|
+
? '检查扩展文件里的 export const qqbotSettingsPage = { title, schema: [...] } 写法。'
|
|
6437
|
+
: '在它的文件里 export const qqbotSettingsPage = { title, schema: [...] } 即可在这里出现表单。'))
|
|
6438
|
+
: h('div', null,
|
|
6439
|
+
schema.map(function (f, i) { return renderExtField(S, f, i, vals, setVals) }),
|
|
6440
|
+
h('div', { style: S.modalFoot },
|
|
6441
|
+
h('button', { style: S.btnPrimary, onClick: save }, '保存'),
|
|
6442
|
+
msg ? h('span', { style: S.savedMsg }, msg) : null,
|
|
6443
|
+
err ? h('span', { style: S.errMsg }, err) : null))),
|
|
6444
|
+
|
|
6445
|
+
data && data.configPath
|
|
6446
|
+
? h('div', { style: S.cfgPath }, '配置文件:', h('code', { style: S.code }, data.configPath))
|
|
6447
|
+
: null))
|
|
6448
|
+
}
|
|
6449
|
+
|
|
6450
|
+
/** 按 schema 渲染一个字段 */
|
|
6451
|
+
function renderExtField(S, f, i, vals, setVals) {
|
|
6452
|
+
var key = f.key || ('f' + i)
|
|
6453
|
+
var val = vals[key]
|
|
6454
|
+
if (val === undefined && f.default !== undefined) val = f.default
|
|
6455
|
+
function upd(v) { var o = Object.assign({}, vals); o[key] = v; setVals(o) }
|
|
6456
|
+
var label = f.label || key
|
|
6457
|
+
var hint = f.hint || f.description || ''
|
|
6458
|
+
|
|
6459
|
+
var control
|
|
6460
|
+
if (f.type === 'bool') {
|
|
6461
|
+
control = h('input', { type: 'checkbox', checked: !!val, onChange: function (e) { upd(e.target.checked) } })
|
|
6462
|
+
} else if (f.type === 'number') {
|
|
6463
|
+
control = h('input', { type: 'number', style: S.fieldInput, value: val === undefined ? '' : val,
|
|
6464
|
+
onChange: function (e) { var v = e.target.value; upd(v === '' ? undefined : Number(v)) } })
|
|
6465
|
+
} else if (f.type === 'select') {
|
|
6466
|
+
var opts = Array.isArray(f.options) ? f.options : []
|
|
6467
|
+
control = h('select', { style: S.fieldInput, value: val === undefined ? '' : val,
|
|
6468
|
+
onChange: function (e) { upd(e.target.value) } },
|
|
6469
|
+
opts.map(function (o) {
|
|
6470
|
+
var v = (o && typeof o === 'object') ? (o.value !== undefined ? o.value : o.label) : o
|
|
6471
|
+
var t = (o && typeof o === 'object') ? (o.label || o.value) : o
|
|
6472
|
+
return h('option', { key: String(v), value: String(v), style: { background: '#2a2a2e', color: '#e8e8ea' } }, String(t))
|
|
6473
|
+
}))
|
|
6474
|
+
} else if (f.type === 'list') {
|
|
6475
|
+
var arr = Array.isArray(val) ? val : []
|
|
6476
|
+
control = h('div', null,
|
|
6477
|
+
h('textarea', { style: Object.assign({}, S.fieldInput, { minHeight: 62, resize: 'vertical' }),
|
|
6478
|
+
value: arr.join('\n'),
|
|
6479
|
+
placeholder: '一行一项',
|
|
6480
|
+
onChange: function (e) { upd(e.target.value.split('\n').map(function (x) { return x.trim() }).filter(Boolean)) } }))
|
|
6481
|
+
} else {
|
|
6482
|
+
control = h('input', {
|
|
6483
|
+
type: f.secret ? 'password' : 'text',
|
|
6484
|
+
style: S.fieldInput,
|
|
6485
|
+
value: val === undefined ? '' : String(val),
|
|
6486
|
+
placeholder: f.placeholder || '',
|
|
6487
|
+
onChange: function (e) { upd(e.target.value) },
|
|
6488
|
+
})
|
|
6489
|
+
}
|
|
6490
|
+
|
|
6491
|
+
return h('div', { key: key, style: S.field },
|
|
6492
|
+
h('div', { style: S.fieldLabel }, label, f.secret ? h('span', { style: S.secretTag }, ' 密钥') : null),
|
|
6493
|
+
control,
|
|
6494
|
+
hint ? h('div', { style: S.fieldHint }, hint) : null)
|
|
6495
|
+
}
|
|
6496
|
+
|
|
6497
|
+
/**
|
|
6498
|
+
* 「设置中心」(2026-10-24 主人要求)
|
|
6499
|
+
*
|
|
6500
|
+
* 原「设置页」tab 只是把带 ⚙ 的扩展又列一遍、还点不动 —— 没有存在价值。
|
|
6501
|
+
* 改成:列出**全部扩展**(内置 + 各扩展包),每行能
|
|
6502
|
+
* · 点「设置」打开它的设置弹层(有声明才可点)
|
|
6503
|
+
* · 开关启停(启停状态存 .state/ext-disabled.json,扫描器会跳过停用的)
|
|
6504
|
+
* 顶部带搜索框。
|
|
6505
|
+
*/
|
|
6506
|
+
function renderSettingsCenter(S, data, filter, setFilter, onOpen, onToggle, toggling) {
|
|
6507
|
+
// 汇总所有扩展
|
|
6508
|
+
var rows = []
|
|
6509
|
+
var push = function (list, from, isPack) {
|
|
6510
|
+
for (var i = 0; i < list.length; i++) {
|
|
6511
|
+
var it = list[i]
|
|
6512
|
+
rows.push({
|
|
6513
|
+
key: from + '/' + it.name,
|
|
6514
|
+
id: it.id,
|
|
6515
|
+
name: it.name,
|
|
6516
|
+
kind: '',
|
|
6517
|
+
from: from,
|
|
6518
|
+
isPack: isPack,
|
|
6519
|
+
hasSettings: !!it.hasSettings,
|
|
6520
|
+
settingsTitle: it.settingsTitle || '',
|
|
6521
|
+
disabled: !!it.disabled,
|
|
6522
|
+
size: it.size || 0,
|
|
6523
|
+
})
|
|
6524
|
+
}
|
|
6525
|
+
}
|
|
6526
|
+
var groupsForList = []
|
|
6527
|
+
// 内置(按六类)
|
|
6528
|
+
var bk = (data.builtin && data.builtin.kinds) ? data.builtin.kinds : {}
|
|
6529
|
+
for (var ki = 0; ki < EXT_KIND_META.length; ki++) {
|
|
6530
|
+
var km = EXT_KIND_META[ki]
|
|
6531
|
+
var list = bk[km.key] || []
|
|
6532
|
+
for (var j = 0; j < list.length; j++) {
|
|
6533
|
+
var it = list[j]
|
|
6534
|
+
rows.push({ key: 'builtin/' + km.key + '/' + it.name, id: it.id, name: it.name, kind: km.key, kindLabel: km.label,
|
|
6535
|
+
from: '内置', isPack: false, hasSettings: !!it.hasSettings, settingsTitle: it.settingsTitle || '',
|
|
6536
|
+
disabled: !!it.disabled, size: it.size || 0 })
|
|
6537
|
+
}
|
|
6538
|
+
}
|
|
6539
|
+
// 扩展包
|
|
6540
|
+
var packs = data.packs || []
|
|
6541
|
+
for (var pi = 0; pi < packs.length; pi++) {
|
|
6542
|
+
var p = packs[pi]
|
|
6543
|
+
var pn = (p.meta && p.meta.name) || p.dirName
|
|
6544
|
+
for (var k2 = 0; k2 < EXT_KIND_META.length; k2++) {
|
|
6545
|
+
var km2 = EXT_KIND_META[k2]
|
|
6546
|
+
var l2 = (p.kinds && p.kinds[km2.key]) || []
|
|
6547
|
+
for (var m = 0; m < l2.length; m++) {
|
|
6548
|
+
var it2 = l2[m]
|
|
6549
|
+
rows.push({ key: 'pack/' + p.dirName + '/' + km2.key + '/' + it2.name, id: it2.id, name: it2.name,
|
|
6550
|
+
kind: km2.key, kindLabel: km2.label, from: pn, isPack: true,
|
|
6551
|
+
hasSettings: !!it2.hasSettings, settingsTitle: it2.settingsTitle || '',
|
|
6552
|
+
disabled: !!it2.disabled, size: it2.size || 0 })
|
|
6553
|
+
}
|
|
6554
|
+
}
|
|
6555
|
+
}
|
|
6556
|
+
|
|
6557
|
+
var q = String(filter || '').trim().toLowerCase()
|
|
6558
|
+
var shown = q ? rows.filter(function (r) { return (r.name + ' ' + r.from + ' ' + r.kindLabel).toLowerCase().indexOf(q) >= 0 }) : rows
|
|
6559
|
+
|
|
6560
|
+
return h('div', null,
|
|
6561
|
+
// 搜索框
|
|
6562
|
+
h('div', { style: { display: 'flex', alignItems: 'center', gap: 10, marginBottom: 12 } },
|
|
6563
|
+
h('input', {
|
|
6564
|
+
style: Object.assign({}, S.fieldInput, { maxWidth: 280, marginBottom: 0 }),
|
|
6565
|
+
placeholder: '搜索扩展名 / 来源 / 类型…',
|
|
6566
|
+
value: filter,
|
|
6567
|
+
onChange: function (e) { setFilter(e.target.value) },
|
|
6568
|
+
}),
|
|
6569
|
+
h('span', { style: { fontSize: 11.5, color: 'var(--dsw-alias-text-secondary, #9a9aa0)' } },
|
|
6570
|
+
shown.length + ' / ' + rows.length + ' 个扩展')),
|
|
6571
|
+
|
|
6572
|
+
shown.length === 0
|
|
6573
|
+
? h('div', { style: S.empty }, q ? ('没有匹配「' + filter + '」的扩展') : '没有扩展')
|
|
6574
|
+
: h('div', { style: { display: 'flex', flexDirection: 'column', gap: 6 } },
|
|
6575
|
+
shown.map(function (r) {
|
|
6576
|
+
return h('div', { key: r.key, style: S.settingRow },
|
|
6577
|
+
// 名字
|
|
6578
|
+
h('span', { style: { fontWeight: 500, minWidth: 150, opacity: r.disabled ? 0.5 : 1 } }, r.id),
|
|
6579
|
+
// 类型
|
|
6580
|
+
h('span', { style: { fontSize: 11, color: 'var(--dsw-alias-text-secondary, #9a9aa0)', minWidth: 68 } }, r.kindLabel || ''),
|
|
6581
|
+
// 来源
|
|
6582
|
+
h('span', { style: Object.assign({}, S.settingFrom, r.isPack ? { border: '1px solid ' + 'var(--dsw-alias-accent, #5b8def)' } : {}) }, r.from),
|
|
6583
|
+
// 已配置标记
|
|
6584
|
+
r.hasSettings ? h('span', { style: { fontSize: 10.5, color: 'var(--dsw-alias-accent, #5b8def)' } }, '⚙ ' + (r.settingsTitle || '可配置')) : null,
|
|
6585
|
+
// 右侧操作
|
|
6586
|
+
h('span', { style: { marginLeft: 'auto', display: 'flex', alignItems: 'center', gap: 8 } },
|
|
6587
|
+
r.hasSettings
|
|
6588
|
+
? h('button', {
|
|
6589
|
+
style: { padding: '3px 12px', fontSize: 12, borderRadius: 5, border: '1px solid var(--dsw-alias-accent, #5b8def)', background: 'transparent', color: 'inherit', cursor: 'pointer' },
|
|
6590
|
+
onClick: function () { onOpen({ kind: r.kind, file: r.name }) },
|
|
6591
|
+
}, '设置')
|
|
6592
|
+
: h('span', { style: { fontSize: 11, color: 'var(--dsw-alias-text-secondary, #9a9aa0)' } }, '—'),
|
|
6593
|
+
// 启停开关
|
|
6594
|
+
h('label', { style: { display: 'flex', alignItems: 'center', gap: 5, fontSize: 11.5, cursor: toggling ? 'wait' : 'pointer', opacity: toggling ? 0.6 : 1 } },
|
|
6595
|
+
h('input', {
|
|
6596
|
+
type: 'checkbox',
|
|
6597
|
+
checked: !r.disabled,
|
|
6598
|
+
disabled: !!toggling,
|
|
6599
|
+
onChange: function (e) { onToggle({ kind: r.kind, file: r.name, enabled: e.target.checked }) },
|
|
6600
|
+
}),
|
|
6601
|
+
r.disabled ? h('span', { style: { color: 'var(--dsw-alias-text-secondary, #9a9aa0)' } }, '已停用') : h('span', null, '启用'))))
|
|
6602
|
+
})))
|
|
6603
|
+
}
|
|
6604
|
+
|
|
6605
|
+
/**
|
|
6606
|
+
* 插件市场(2026-10-24)
|
|
6607
|
+
* 索引来自 GitHub 仓库 zaofan-make/dsh-qqbot-plugins 的 index.json
|
|
6608
|
+
*/
|
|
6609
|
+
/** 市场专用样式(2026-10-24):内联 style 管不了 ::placeholder 和主题变量,只能插 CSS */
|
|
6610
|
+
/**
|
|
6611
|
+
* 判断当前是深色还是浅色主题。
|
|
6612
|
+
*
|
|
6613
|
+
* 为什么不用 CSS 变量:DSH 的 --dsw-alias-* 变量名没法确认,
|
|
6614
|
+
* 猜错时 fallback 会在深色模式下变成黑字(2026-10-24 主人实测踩到)。
|
|
6615
|
+
* 直接量一下背景亮度最可靠。
|
|
6616
|
+
*/
|
|
6617
|
+
function qqsIsDarkTheme() {
|
|
6618
|
+
try {
|
|
6619
|
+
var el = document.body || document.documentElement
|
|
6620
|
+
var bg = ''
|
|
6621
|
+
// 往上找一个有不透明背景的祖先,body 有时是透明的
|
|
6622
|
+
var node = el
|
|
6623
|
+
for (var i = 0; i < 6 && node; i++) {
|
|
6624
|
+
var cs = window.getComputedStyle(node)
|
|
6625
|
+
var c2 = cs.backgroundColor || ''
|
|
6626
|
+
var m0 = c2.match(/rgba?\(\s*(\d+)[,\s]+(\d+)[,\s]+(\d+)(?:[,\s/]+([\d.]+))?/)
|
|
6627
|
+
if (m0 && (m0[4] === undefined || parseFloat(m0[4]) > 0.5)) { bg = c2; break }
|
|
6628
|
+
node = node.parentElement
|
|
6629
|
+
}
|
|
6630
|
+
var m = String(bg).match(/(\d+)[,\s]+(\d+)[,\s]+(\d+)/)
|
|
6631
|
+
if (m) {
|
|
6632
|
+
var r = +m[1], g = +m[2], b = +m[3]
|
|
6633
|
+
// 相对亮度(sRGB 近似)
|
|
6634
|
+
var lum = (0.299 * r + 0.587 * g + 0.114 * b) / 255
|
|
6635
|
+
if (r + g + b > 24) return lum < 0.5
|
|
6636
|
+
}
|
|
6637
|
+
} catch (e) {}
|
|
6638
|
+
try {
|
|
6639
|
+
return !!(window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches)
|
|
6640
|
+
} catch (e) {}
|
|
6641
|
+
return false
|
|
6642
|
+
}
|
|
6643
|
+
|
|
6644
|
+
/**
|
|
6645
|
+
* 市场样式(2026-10-24 整体重写,不再叠补丁)
|
|
6646
|
+
*
|
|
6647
|
+
* 配色原则:
|
|
6648
|
+
* · 全部走面板自己的主题变量 —— 面板在 body{} 里定义了亮色 token,
|
|
6649
|
+
* 在 body[data-ds-dark-theme]{} 里定义了暗色 token(宿主 ThemePresenter 切类)。
|
|
6650
|
+
* 所以用 var(--qqs-text) / var(--qqs-border) / var(--qqs-input) 就自动跟随主题,
|
|
6651
|
+
* 不需要自己判断深浅(之前猜 --dsw-alias-* 变量名,猜错导致深色下黑字)。
|
|
6652
|
+
* · 主人要求:**除标题外的字也不要灰色**,所以描述/版本/标签全都用主文字色 var(--qqs-text)。
|
|
6653
|
+
* · 卡片不填色块,只有一圈边框;图片框 4:3 固定,图片 contain 完整显示、四周留白。
|
|
6654
|
+
*/
|
|
6655
|
+
/**
|
|
6656
|
+
* 市场配色:直接量页面真实颜色,把 rgb 写死进样式。
|
|
6657
|
+
*
|
|
6658
|
+
* 为什么不再用 CSS 变量:
|
|
6659
|
+
* ① 猜 --dsw-alias-text-primary → 不存在,深色下 fallback 变黑字;
|
|
6660
|
+
* ② 用面板的 --qqs-text → 实测取到的值和当前主题不符(浅色下拿到浅灰)。
|
|
6661
|
+
* 量出来的值一定对,代价是切主题后要刷新一次。
|
|
6662
|
+
*/
|
|
6663
|
+
/**
|
|
6664
|
+
* 市场样式(2026-10-24 第四次,最终版)
|
|
6665
|
+
*
|
|
6666
|
+
* 三次失败的教训:
|
|
6667
|
+
* ① 猜 --dsw-alias-text-primary → 变量不存在,深色下 fallback 黑字;
|
|
6668
|
+
* ② 用 --qqs-text → 拿到的值和自己预期的主题不符;
|
|
6669
|
+
* ③ 量背景亮度写死 rgb → 又引入额外不确定性。
|
|
6670
|
+
*
|
|
6671
|
+
* 根本问题其实一直是**我自己写的硬编码颜色**(inline style 的 color + CSS 里的 !important 色值),
|
|
6672
|
+
* 面板本身(ensureCss 注入的 qqs-css)是靠继承 DSH 主题的。
|
|
6673
|
+
* 所以这一版:**一个颜色都不写**,全部 inherit,让文字跟着面板走。
|
|
6674
|
+
* 只有边框给个中性灰(深浅主题下都看得见)。
|
|
6675
|
+
*/
|
|
6676
|
+
/**
|
|
6677
|
+
* 市场样式(2026-10-24 定稿)
|
|
6678
|
+
*
|
|
6679
|
+
* 主人拍板:**不再跟随主题,统一白底黑字**。
|
|
6680
|
+
* 之前跟着主题走折腾了 5 版(猜变量 / 量亮度 / 继承 / token),
|
|
6681
|
+
* 每次都在「浅色下深底、深色下黑字」之间反复,索性写死最稳。
|
|
6682
|
+
*
|
|
6683
|
+
* 配色(全部硬编码):
|
|
6684
|
+
* 卡片底 #fff / 文字 #1f2329 / 边框 #e2e5ea
|
|
6685
|
+
* 次要文字 #5a6270 / 弱化 #8a919c
|
|
6686
|
+
* 主色按钮 #7c6cf0
|
|
6687
|
+
*/
|
|
6688
|
+
function ensureMarketCss() {
|
|
6689
|
+
if (typeof document === 'undefined') return
|
|
6690
|
+
if (document.getElementById('__qqsMarketCss')) return
|
|
6691
|
+
var st = document.createElement('style')
|
|
6692
|
+
st.id = '__qqsMarketCss'
|
|
6693
|
+
st.textContent = [
|
|
6694
|
+
/* ── 网格:两列等宽铺满 ── */
|
|
6695
|
+
'.qqs-mk-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:20px;align-items:stretch;width:100%;}',
|
|
6696
|
+
'@media (max-width:760px){.qqs-mk-grid{grid-template-columns:1fr;}}',
|
|
6697
|
+
|
|
6698
|
+
/* ── 卡片:白底黑字 + 浅灰边框 ── */
|
|
6699
|
+
'.qqs-mk-card{background:#ffffff!important;color:#1f2329!important;'
|
|
6700
|
+
+ 'border:1px solid #e2e5ea!important;border-radius:10px!important;'
|
|
6701
|
+
+ 'height:100%;display:flex;flex-direction:column;box-sizing:border-box;'
|
|
6702
|
+
+ 'padding:0 14px 14px 14px;overflow:hidden;}',
|
|
6703
|
+
'.qqs-mk-card:hover{border-color:#7c6cf0!important;}',
|
|
6704
|
+
'.qqs-mk-card *{color:inherit;}',
|
|
6705
|
+
|
|
6706
|
+
/* ── 封面:4:3 固定框,contain 完整显示,四周留白 ── */
|
|
6707
|
+
'.qqs-mk-cover{width:100%;aspect-ratio:4/3;object-fit:contain;display:block;'
|
|
6708
|
+
+ 'box-sizing:border-box;padding:10px;background:#f4f5f8;border-radius:6px;}',
|
|
6709
|
+
'.qqs-mk-cover-ph{width:100%;aspect-ratio:4/3;display:flex;align-items:center;justify-content:center;'
|
|
6710
|
+
+ 'box-sizing:border-box;padding:10px;font-size:12px;color:#8a919c;background:#f4f5f8;border-radius:6px;}',
|
|
6711
|
+
|
|
6712
|
+
/* ── 标题行 ── */
|
|
6713
|
+
'.qqs-mk-head{display:flex;align-items:baseline;gap:8px;flex-wrap:wrap;padding:10px 0 8px;}',
|
|
6714
|
+
'.qqs-mk-name{font-size:14.5px;font-weight:600;color:#1f2329;}',
|
|
6715
|
+
'.qqs-mk-meta{font-size:11.5px;color:#8a919c;}',
|
|
6716
|
+
|
|
6717
|
+
/* ── 描述 ── */
|
|
6718
|
+
'.qqs-mk-desc{font-size:12.5px;line-height:1.65;color:#5a6270;flex:1 1 auto;'
|
|
6719
|
+
+ 'display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;margin:0 0 8px 0;}',
|
|
6720
|
+
|
|
6721
|
+
/* ── 标签 ── */
|
|
6722
|
+
'.qqs-mk-tag{font-size:10.5px;padding:1px 7px;border-radius:4px;border:1px solid #e2e5ea;color:#5a6270;}',
|
|
6723
|
+
|
|
6724
|
+
/* ── 按钮 ── */
|
|
6725
|
+
'.qqs-mk-btn{padding:4px 14px;font-size:12px;border-radius:5px;border:1px solid #7c6cf0;'
|
|
6726
|
+
+ 'background:#7c6cf0;color:#fff!important;cursor:pointer;flex:0 0 auto;}',
|
|
6727
|
+
'.qqs-mk-btn:hover{background:#6b5ae6;}',
|
|
6728
|
+
'.qqs-mk-btn:disabled{opacity:.55;cursor:default;}',
|
|
6729
|
+
'.qqs-mk-ghost{padding:4px 14px;font-size:12px;border-radius:5px;border:1px solid #d0d5dd;'
|
|
6730
|
+
+ 'background:#ffffff;color:#1f2329!important;cursor:pointer;flex:0 0 auto;}',
|
|
6731
|
+
'.qqs-mk-ghost:hover{border-color:#7c6cf0;color:#6b5ae6!important;}',
|
|
6732
|
+
|
|
6733
|
+
/* ── 搜索框 / 代理框:白底黑字,看得见也能输入 ── */
|
|
6734
|
+
'.qqs-mk-search{background:#ffffff!important;color:#1f2329!important;'
|
|
6735
|
+
+ 'border:1px solid #d0d5dd!important;box-sizing:border-box;}',
|
|
6736
|
+
'.qqs-mk-search::placeholder{color:#8a919c!important;opacity:1;}',
|
|
6737
|
+
'.qqs-mk-search:focus{outline:none!important;border-color:#7c6cf0!important;box-shadow:0 0 0 2px #7c6cf033!important;}',
|
|
6738
|
+
].join('\n')
|
|
6739
|
+
document.head.appendChild(st)
|
|
6740
|
+
}
|
|
6741
|
+
|
|
6742
|
+
function renderMarket(S, market, loading, search, setSearch, onInstall, onRemove, busyId, proxyInput, setProxyInput, onSaveProxy, savingProxy) {
|
|
6743
|
+
ensureMarketCss()
|
|
6744
|
+
var S2 = {
|
|
6745
|
+
card: { border: '1px solid var(--dsw-alias-border, #3a3a40)', borderRadius: 10, overflow: 'hidden', padding: '0 0 14px 0', display: 'flex', flexDirection: 'column', gap: 8, height: '100%', boxSizing: 'border-box', background: 'var(--dsw-alias-bg-primary, #252528)' },
|
|
6746
|
+
cardHead: { display: 'flex', alignItems: 'baseline', gap: 9, flexWrap: 'wrap', padding: '12px 16px 8px', borderBottom: '1px solid var(--dsw-alias-border, #3a3a40)' },
|
|
6747
|
+
cardName: { fontSize: 13.5, fontWeight: 600 },
|
|
6748
|
+
cardVer: { fontSize: 11 },
|
|
6749
|
+
cardDesc: { fontSize: 12, lineHeight: 1.6, margin: '0 16px', display: '-webkit-box', WebkitLineClamp: 3, WebkitBoxOrient: 'vertical', overflow: 'hidden' },
|
|
6750
|
+
tag: { fontSize: 10.5, padding: '1px 7px', borderRadius: 4, border: '1px solid var(--dsw-alias-border, #3a3a40)', color: 'var(--dsw-alias-text-secondary, #9a9aa0)' },
|
|
6751
|
+
btnInstall: { padding: '4px 14px', fontSize: 12, borderRadius: 5, border: '1px solid var(--dsw-alias-accent, #5b8def)', background: 'var(--dsw-alias-accent, #5b8def)', color: '#fff', cursor: 'pointer' },
|
|
6752
|
+
btnGhost: { padding: '4px 14px', fontSize: 12, borderRadius: 5, border: '1px solid var(--dsw-alias-border, #3a3a40)', background: 'transparent', color: 'inherit', cursor: 'pointer' },
|
|
6753
|
+
}
|
|
6754
|
+
if (loading) return h('div', { style: S.hint }, '正在读取市场索引…')
|
|
6755
|
+
var err = market && market.indexErr
|
|
6756
|
+
var list = (market && market.index && market.index.plugins) || []
|
|
6757
|
+
var installed = (market && market.installed) || []
|
|
6758
|
+
// 兼容旧格式(字符串数组)和新格式({id,version})
|
|
6759
|
+
var installedIds = installed.map(function (x) { return typeof x === 'string' ? x : (x && x.id) })
|
|
6760
|
+
var installedVer = {}
|
|
6761
|
+
installed.forEach(function (x) { if (x && typeof x === 'object' && x.id) installedVer[x.id] = String(x.version || '') })
|
|
6762
|
+
/** 版本比较:a 比 b 新返回 1,相同 0,旧 -1。非语义版本按字符串比 */
|
|
6763
|
+
function cmpVer(a, b) {
|
|
6764
|
+
var pa = String(a || '').split(/[.\-+]/).map(function (s) { return parseInt(s, 10) || 0 })
|
|
6765
|
+
var pb = String(b || '').split(/[.\-+]/).map(function (s) { return parseInt(s, 10) || 0 })
|
|
6766
|
+
var n = Math.max(pa.length, pb.length)
|
|
6767
|
+
for (var i = 0; i < n; i++) {
|
|
6768
|
+
var x = pa[i] || 0, y = pb[i] || 0
|
|
6769
|
+
if (x > y) return 1
|
|
6770
|
+
if (x < y) return -1
|
|
6771
|
+
}
|
|
6772
|
+
return 0
|
|
6773
|
+
}
|
|
6774
|
+
var q = String(search || '').trim().toLowerCase()
|
|
6775
|
+
var shown = q ? list.filter(function (p) {
|
|
6776
|
+
return ((p.name || '') + ' ' + (p.id || '') + ' ' + (p.author || '') + ' ' + (p.description || '') + ' ' + ((p.tags || []).join(' '))).toLowerCase().indexOf(q) >= 0
|
|
6777
|
+
}) : list
|
|
6778
|
+
|
|
6779
|
+
return h('div', null,
|
|
6780
|
+
h('div', { style: { display: 'flex', alignItems: 'center', gap: 10, marginBottom: 12 } },
|
|
6781
|
+
h('input', {
|
|
6782
|
+
style: {
|
|
6783
|
+
width: 320, maxWidth: '50%', boxSizing: 'border-box',
|
|
6784
|
+
padding: '7px 12px', fontSize: 12.5, borderRadius: 16,
|
|
6785
|
+
border: '1px solid var(--dsw-alias-border, #45454c)',
|
|
6786
|
+
background: 'var(--dsw-alias-bg-primary, #252528)',
|
|
6787
|
+
color: 'var(--dsw-alias-text-primary, #e8e8ea)',
|
|
6788
|
+
colorScheme: 'dark', outline: 'none',
|
|
6789
|
+
},
|
|
6790
|
+
className: 'qqs-mk-search',
|
|
6791
|
+
placeholder: '搜索插件名 / 作者 / 标签…',
|
|
6792
|
+
value: search,
|
|
6793
|
+
onChange: function (e) { setSearch(e.target.value) },
|
|
6794
|
+
}),
|
|
6795
|
+
h('span', { style: { fontSize: 11.5, color: '#a8a8b2' } },
|
|
6796
|
+
shown.length + ' / ' + list.length + ' 个插件'),
|
|
6797
|
+
h('span', { style: { marginLeft: 'auto', fontSize: 11, color: '#a8a8b2' } },
|
|
6798
|
+
'已知 ' + installed.length + ' 个已安装')),
|
|
6799
|
+
|
|
6800
|
+
// 代理配置(墙内访问 GitHub 需要)
|
|
6801
|
+
h('div', { style: { display: 'flex', alignItems: 'center', gap: 8, marginBottom: 12, flexWrap: 'wrap' } },
|
|
6802
|
+
h('span', { style: { fontSize: 11.5, color: '#a8a8b2' } }, '代理'),
|
|
6803
|
+
h('input', {
|
|
6804
|
+
className: 'qqs-mk-search qqs-mk-proxy',
|
|
6805
|
+
style: { width: 280, maxWidth: '50%', padding: '6px 11px', fontSize: 12, borderRadius: 14 },
|
|
6806
|
+
placeholder: 'http://127.0.0.1:29290(留空=直连)',
|
|
6807
|
+
value: proxyInput,
|
|
6808
|
+
onChange: function (e) { setProxyInput(e.target.value) },
|
|
6809
|
+
onKeyDown: function (e) { if (e.key === 'Enter') onSaveProxy() },
|
|
6810
|
+
}),
|
|
6811
|
+
h('button', {
|
|
6812
|
+
className: 'qqs-mk-ghost',
|
|
6813
|
+
disabled: savingProxy,
|
|
6814
|
+
onClick: onSaveProxy,
|
|
6815
|
+
}, savingProxy ? '保存中…' : '保存并重试'),
|
|
6816
|
+
market && market.proxy
|
|
6817
|
+
? h('span', { style: { fontSize: 11, color: '#7ec87e' } }, '当前:' + market.proxy)
|
|
6818
|
+
: h('span', { style: { fontSize: 11, color: '#a8a8b2' } }, '当前:直连'),
|
|
6819
|
+
err ? h('span', { style: { fontSize: 11, color: '#e07a7a', marginLeft: 'auto' } }, '索引读取失败') : null),
|
|
6820
|
+
|
|
6821
|
+
err ? h('div', { style: S.errBox }, '索引读取失败:' + err) : null,
|
|
6822
|
+
|
|
6823
|
+
shown.length === 0
|
|
6824
|
+
? h('div', { style: S.empty }, err ? '索引不可用' : (q ? ('没有匹配「' + search + '」的插件') : '市场里还没有插件'))
|
|
6825
|
+
: h('div', { className: 'qqs-mk-grid' },
|
|
6826
|
+
shown.map(function (p) {
|
|
6827
|
+
var id = String(p.id || '')
|
|
6828
|
+
var isIn = installedIds.indexOf(id) >= 0
|
|
6829
|
+
var localVer = installedVer[id] || ''
|
|
6830
|
+
var remoteVer = String(p.version || '')
|
|
6831
|
+
var canUpdate = isIn && localVer && remoteVer && cmpVer(remoteVer, localVer) > 0
|
|
6832
|
+
var busy = busyId === id
|
|
6833
|
+
return h('div', { key: id, className: 'qqs-mk-card', style: S2.card },
|
|
6834
|
+
// 封面(可选)
|
|
6835
|
+
p.cover ? h('img', {
|
|
6836
|
+
src: String(p.cover).replace(
|
|
6837
|
+
/^https?:\/\/raw\.githubusercontent\.com\//,
|
|
6838
|
+
'https://cdn.jsdelivr.net/gh/'
|
|
6839
|
+
),
|
|
6840
|
+
alt: String(p.coverAlt || p.name || id),
|
|
6841
|
+
loading: 'lazy',
|
|
6842
|
+
className: 'qqs-mk-cover',
|
|
6843
|
+
onError: function (e) {
|
|
6844
|
+
try {
|
|
6845
|
+
var el = e.target;
|
|
6846
|
+
var cur = String(el.src || '');
|
|
6847
|
+
if (cur.indexOf('cdn.jsdelivr.net') >= 0) {
|
|
6848
|
+
// 换 gh-proxy 再试一次
|
|
6849
|
+
el.src = cur.replace('https://cdn.jsdelivr.net/gh/', 'https://gh-proxy.com/https://raw.githubusercontent.com/');
|
|
6850
|
+
} else {
|
|
6851
|
+
el.style.display = 'none'; // 都失败就藏掉,别破版
|
|
6852
|
+
}
|
|
6853
|
+
} catch (x) {}
|
|
6854
|
+
},
|
|
6855
|
+
}) : h('div', { className: 'qqs-mk-cover-ph' }, '暂无封面'),
|
|
6856
|
+
h('div', { className: 'qqs-mk-head', style: S2.cardHead },
|
|
6857
|
+
h('span', { className: 'qqs-mk-name', style: S2.cardName }, String(p.name || id)),
|
|
6858
|
+
p.version ? h('span', { className: 'qqs-mk-meta', style: S2.cardVer }, 'v' + p.version) : null,
|
|
6859
|
+
p.author ? h('span', { className: 'qqs-mk-meta', style: S2.cardVer }, 'by ' + p.author) : null,
|
|
6860
|
+
h('span', { style: { marginLeft: 'auto', display: 'flex', gap: 8, alignItems: 'center' } },
|
|
6861
|
+
isIn
|
|
6862
|
+
? h('span', { className: 'qqs-mk-tag' }, canUpdate ? ('已装 v' + localVer) : '已安装')
|
|
6863
|
+
: null,
|
|
6864
|
+
canUpdate
|
|
6865
|
+
? h('button', { className: 'qqs-mk-btn', disabled: busy, onClick: function () { onInstall({ id: id }) } },
|
|
6866
|
+
busy ? '更新中…' : ('更新到 v' + remoteVer))
|
|
6867
|
+
: null,
|
|
6868
|
+
isIn
|
|
6869
|
+
? h('button', { className: 'qqs-mk-ghost', disabled: busy, onClick: function () { onRemove({ id: id }) } }, busy ? '处理中…' : '卸载')
|
|
6870
|
+
: (canUpdate ? null : h('button', { className: 'qqs-mk-btn', disabled: busy, onClick: function () { onInstall({ id: id }) } }, busy ? '安装中…' : '安装')))),
|
|
6871
|
+
p.description ? h('div', { className: 'qqs-mk-desc', style: S2.cardDesc }, String(p.description)) : null,
|
|
6872
|
+
(p.tags && p.tags.length)
|
|
6873
|
+
? h('div', { style: { display: 'flex', gap: 6, flexWrap: 'wrap' } },
|
|
6874
|
+
p.tags.map(function (t, i) { return h('span', { key: i, className: 'qqs-mk-tag' }, String(t)) }))
|
|
6875
|
+
: null)
|
|
6876
|
+
})))
|
|
6877
|
+
}
|
|
6878
|
+
|
|
6879
|
+
function statCard(S, label, num, hint) {
|
|
6880
|
+
return h('div', { style: S.statCard },
|
|
6881
|
+
h('div', { style: S.statNum }, String(num)),
|
|
6882
|
+
h('div', { style: S.statLabel }, label),
|
|
6883
|
+
h('div', { style: S.statHint }, hint))
|
|
6884
|
+
}
|
|
6885
|
+
|
|
6886
|
+
function tabBtn(S, label, key, cur, setTab, count) {
|
|
6887
|
+
var on = cur === key
|
|
6888
|
+
return h('button', {
|
|
6889
|
+
key: key,
|
|
6890
|
+
style: on ? S.tabOn : S.tabOff,
|
|
6891
|
+
onClick: function () { setTab(key) },
|
|
6892
|
+
}, label, count !== undefined ? h('span', { style: S.tabCount }, ' ' + count) : null)
|
|
6893
|
+
}
|
|
6894
|
+
|
|
6895
|
+
function extStyles() {
|
|
6896
|
+
var C = {
|
|
6897
|
+
border: 'var(--dsw-alias-border, #8884)',
|
|
6898
|
+
sub: 'var(--dsw-alias-text-secondary, #888)',
|
|
6899
|
+
bg2: 'var(--dsw-alias-bg-secondary, rgba(127,127,127,.06))',
|
|
6900
|
+
accent: 'var(--dsw-alias-accent, #5b8def)',
|
|
6901
|
+
text: 'inherit',
|
|
6902
|
+
}
|
|
6903
|
+
return {
|
|
6904
|
+
wrap: { padding: '18px 22px', height: '100%', overflow: 'auto', boxSizing: 'border-box', color: C.text },
|
|
6905
|
+
head: { display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', marginBottom: 16 },
|
|
6906
|
+
title: { fontSize: 19, fontWeight: 600, marginBottom: 4 },
|
|
6907
|
+
sub: { fontSize: 12.5, color: C.sub },
|
|
6908
|
+
btn: { padding: '4px 12px', fontSize: 12.5, borderRadius: 6, border: '1px solid ' + C.border, background: 'transparent', color: 'inherit', cursor: 'pointer' },
|
|
6909
|
+
stats: { display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 12, marginBottom: 14 },
|
|
6910
|
+
statCard: { border: '1px solid ' + C.border, borderRadius: 10, padding: '12px 14px', background: C.bg2 },
|
|
6911
|
+
statNum: { fontSize: 24, fontWeight: 600, lineHeight: 1.2 },
|
|
6912
|
+
statLabel: { fontSize: 12.5, marginTop: 2 },
|
|
6913
|
+
statHint: { fontSize: 11.5, color: C.sub, marginTop: 2 },
|
|
6914
|
+
pathBox: { border: '1px solid ' + C.border, borderRadius: 10, padding: '10px 14px', marginBottom: 14, fontSize: 12 },
|
|
6915
|
+
pathRow: { display: 'flex', gap: 10, alignItems: 'baseline', padding: '3px 0' },
|
|
6916
|
+
pathKey: { color: C.sub, minWidth: 68, fontSize: 12 },
|
|
6917
|
+
code: { fontFamily: 'ui-monospace, Consolas, monospace', fontSize: 11.5, wordBreak: 'break-all', opacity: .85 },
|
|
6918
|
+
tabs: { display: 'flex', gap: 6, marginBottom: 14, borderBottom: '1px solid ' + C.border, paddingBottom: 8 },
|
|
6919
|
+
tabOn: { padding: '5px 14px', fontSize: 13, borderRadius: 6, border: '1px solid ' + C.accent, background: C.accent, color: '#fff', cursor: 'pointer' },
|
|
6920
|
+
tabOff: { padding: '5px 14px', fontSize: 13, borderRadius: 6, border: '1px solid ' + C.border, background: 'transparent', color: 'inherit', cursor: 'pointer' },
|
|
6921
|
+
tabCount: { opacity: .75, fontSize: 11.5 },
|
|
6922
|
+
group: { border: '1px solid ' + C.border, borderRadius: 10, padding: '14px 16px', marginBottom: 12 },
|
|
6923
|
+
groupHead: { marginBottom: 10, display: 'flex', alignItems: 'baseline', gap: 10, flexWrap: 'wrap' },
|
|
6924
|
+
groupTitle: { fontSize: 14.5, fontWeight: 600 },
|
|
6925
|
+
groupSub: { fontSize: 11.5, color: C.sub, wordBreak: 'break-all' },
|
|
6926
|
+
kindBlock: { marginBottom: 10 },
|
|
6927
|
+
kindHead: { display: 'flex', alignItems: 'center', gap: 6, marginBottom: 5 },
|
|
6928
|
+
kindIcon: { fontSize: 12.5 },
|
|
6929
|
+
kindLabel: { fontSize: 12.5, fontWeight: 500 },
|
|
6930
|
+
kindCount: { fontSize: 11, color: C.sub, background: C.bg2, borderRadius: 9, padding: '0 6px' },
|
|
6931
|
+
kindHint: { fontSize: 11, color: C.sub, marginLeft: 2 },
|
|
6932
|
+
chipWrap: { display: 'flex', flexWrap: 'wrap', gap: 5 },
|
|
6933
|
+
chip: { fontSize: 11.5, padding: '2px 8px', borderRadius: 5, border: '1px solid ' + C.border, background: C.bg2 },
|
|
6934
|
+
chipWithGear: { fontSize: 11.5, padding: '2px 8px', borderRadius: 5, border: '1px solid ' + C.accent, background: 'transparent' },
|
|
6935
|
+
gear: { opacity: .8, fontSize: 10.5 },
|
|
6936
|
+
settingsList: { display: 'flex', flexDirection: 'column', gap: 6 },
|
|
6937
|
+
settingRow: { display: 'flex', alignItems: 'baseline', gap: 10, border: '1px solid ' + C.border, borderRadius: 8, padding: '8px 12px', fontSize: 12.5 },
|
|
6938
|
+
settingTitle: { fontWeight: 500 },
|
|
6939
|
+
settingFrom: { fontSize: 11, color: C.sub, background: C.bg2, borderRadius: 4, padding: '1px 6px' },
|
|
6940
|
+
settingId: { fontSize: 11, color: C.sub, fontFamily: 'ui-monospace, Consolas, monospace', marginLeft: 'auto' },
|
|
6941
|
+
empty: { fontSize: 12.5, color: 'var(--dsw-alias-text-secondary, #9a9aa0)', padding: '18px 4px', lineHeight: 1.7 },
|
|
6942
|
+
emptyHint: { fontSize: 11.5, opacity: .8, marginTop: 4 },
|
|
6943
|
+
hint: { fontSize: 12.5, color: C.sub, padding: 20 },
|
|
6944
|
+
errBox: { fontSize: 12.5, color: '#c33', padding: '10px 12px', border: '1px solid #c335', borderRadius: 8, marginBottom: 10 },
|
|
6945
|
+
// ── 扩展设置弹层(2026-10-24)──
|
|
6946
|
+
chipClickable: { fontSize: 11.5, padding: '2px 8px', borderRadius: 5, border: '1px solid ' + C.accent, background: 'transparent', cursor: 'pointer' },
|
|
6947
|
+
// 2026-10-24 修「安卓上扩展自定义设置界面被截断、填不了」:
|
|
6948
|
+
// 原来是 minWidth:460 —— 手机视口只有 360~400px,盒子硬撑到 460 就横向溢出,
|
|
6949
|
+
// 右半边(输入框 / 保存按钮)直接被切掉。
|
|
6950
|
+
// 改成 minWidth:0 + width:100%(由 maxWidth 兜桌面宽度)+ 遮罩加 padding,
|
|
6951
|
+
// maxHeight 用 calc(100vh - 24px) 对齐 padding,上下也不再各切一截。
|
|
6952
|
+
modalMask: { position: 'fixed', top: 0, left: 0, right: 0, bottom: 0, width: '100vw', height: '100vh', background: 'rgba(0,0,0,.45)', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 12, boxSizing: 'border-box', zIndex: 2147483000 },
|
|
6953
|
+
modalBox: { background: 'var(--dsw-alias-bg-primary, #252528)', color: 'var(--dsw-alias-text-primary, #e8e8ea)', colorScheme: 'dark', borderRadius: 12, padding: '18px 20px', minWidth: 0, width: '100%', maxWidth: 640, maxHeight: 'calc(100vh - 24px)', overflow: 'auto', boxSizing: 'border-box', boxShadow: '0 16px 56px rgba(0,0,0,.5)', border: '1px solid ' + C.border },
|
|
6954
|
+
modalHead: { display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', marginBottom: 14, gap: 12 },
|
|
6955
|
+
modalTitle: { fontSize: 15.5, fontWeight: 600, color: 'var(--dsw-alias-text-primary, #e8e8ea)' },
|
|
6956
|
+
modalSub: { fontSize: 11.5, color: C.sub, marginTop: 3, fontFamily: 'ui-monospace, Consolas, monospace', wordBreak: 'break-all' },
|
|
6957
|
+
modalFoot: { display: 'flex', alignItems: 'center', gap: 12, marginTop: 18, paddingTop: 14, borderTop: '1px solid ' + C.border },
|
|
6958
|
+
field: { marginBottom: 14 },
|
|
6959
|
+
fieldLabel: { fontSize: 12.5, marginBottom: 6, display: 'flex', alignItems: 'center', gap: 6 },
|
|
6960
|
+
fieldHint: { fontSize: 11, color: C.sub, marginTop: 4 },
|
|
6961
|
+
fieldInput: { width: '100%', boxSizing: 'border-box', padding: '7px 10px', fontSize: 12.5, borderRadius: 6, border: '1px solid ' + C.border, background: 'var(--dsw-alias-bg-primary, #252528)', color: 'var(--dsw-alias-text-primary, #e8e8ea)', fontFamily: 'inherit', colorScheme: 'dark' },
|
|
6962
|
+
secretTag: { fontSize: 10, color: C.sub, background: C.bg2, borderRadius: 4, padding: '1px 5px', fontWeight: 400 },
|
|
6963
|
+
btnPrimary: { padding: '7px 18px', fontSize: 13, borderRadius: 6, border: '1px solid ' + C.accent, background: C.accent, color: '#fff', cursor: 'pointer' },
|
|
6964
|
+
savedMsg: { fontSize: 12, color: '#4a4' },
|
|
6965
|
+
errMsg: { fontSize: 12, color: '#e55' },
|
|
6966
|
+
cfgPath: { fontSize: 11, color: C.sub, marginTop: 16, paddingTop: 12, borderTop: '1px solid ' + C.border, wordBreak: 'break-all' },
|
|
6967
|
+
}
|
|
6968
|
+
}
|
|
6969
|
+
|
|
6965
6970
|
function apply(ctx) {
|
|
6966
6971
|
// 防御(与 dsh-undo-savepoint 的 safe() 同款): client 半边任一环节抛异常, 这条 entry 就会
|
|
6967
6972
|
// 被判 FAILED, 而 dsh 0.1.7 的客户端启动审计是**致命**的 —— 整个 Web UI 只剩余下
|
|
@@ -6981,19 +6986,19 @@ var QQS_THEME_CSS = [
|
|
|
6981
6986
|
function (props) { return h(QqbotHome, { close: props && props.close }) })
|
|
6982
6987
|
})
|
|
6983
6988
|
})
|
|
6984
|
-
// 「QQ机器人扩展」会话页签(2026-10-24):与「对话/轨迹/记忆/白板看板」并列
|
|
6985
|
-
safe('conversation view', function () {
|
|
6986
|
-
slots.inject('conversation.view', function () {
|
|
6987
|
-
return slots.register(
|
|
6988
|
-
{ name: 'conversation.view', id: 'qqbot-ext', order: 81, label: 'QQ机器人扩展' },
|
|
6989
|
-
ExtPanel)
|
|
6990
|
-
})
|
|
6991
|
-
})
|
|
6989
|
+
// 「QQ机器人扩展」会话页签(2026-10-24):与「对话/轨迹/记忆/白板看板」并列
|
|
6990
|
+
safe('conversation view', function () {
|
|
6991
|
+
slots.inject('conversation.view', function () {
|
|
6992
|
+
return slots.register(
|
|
6993
|
+
{ name: 'conversation.view', id: 'qqbot-ext', order: 81, label: 'QQ机器人扩展' },
|
|
6994
|
+
ExtPanel)
|
|
6995
|
+
})
|
|
6996
|
+
})
|
|
6992
6997
|
safe('approval float', startApprovalFloat)
|
|
6993
6998
|
// sessions 服务(宿主根服务): 权威读 web 当前会话(current)与 cwd(agent id === session id)
|
|
6994
6999
|
var sessionsSvc = null
|
|
6995
7000
|
try { sessionsSvc = (ctx && (ctx.sessions || (ctx.get && ctx.get('sessions')))) || null } catch (e) { sessionsSvc = null }
|
|
6996
|
-
if (!sessionsSvc) { try { sessionsSvc = ctx.get && ctx.get('sessions') } catch (e) {} }
|
|
7001
|
+
if (!sessionsSvc) { try { sessionsSvc = ctx.get && ctx.get('sessions') } catch (e) {} }
|
|
6997
7002
|
_extSessionsSvc = sessionsSvc // 供 ExtPanel 读当前工作区 cwd
|
|
6998
7003
|
safe('dock', function () { startQqDock(sessionsSvc) })
|
|
6999
7004
|
}
|