@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.
@@ -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
- 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', zIndex: 2147483000 },
6948
- modalBox: { background: 'var(--dsw-alias-bg-primary, #252528)', color: 'var(--dsw-alias-text-primary, #e8e8ea)', colorScheme: 'dark', borderRadius: 12, padding: '18px 20px', minWidth: 460, maxWidth: 640, maxHeight: '82vh', overflow: 'auto', boxShadow: '0 16px 56px rgba(0,0,0,.5)', border: '1px solid ' + C.border },
6949
- modalHead: { display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', marginBottom: 14, gap: 12 },
6950
- modalTitle: { fontSize: 15.5, fontWeight: 600, color: 'var(--dsw-alias-text-primary, #e8e8ea)' },
6951
- modalSub: { fontSize: 11.5, color: C.sub, marginTop: 3, fontFamily: 'ui-monospace, Consolas, monospace', wordBreak: 'break-all' },
6952
- modalFoot: { display: 'flex', alignItems: 'center', gap: 12, marginTop: 18, paddingTop: 14, borderTop: '1px solid ' + C.border },
6953
- field: { marginBottom: 14 },
6954
- fieldLabel: { fontSize: 12.5, marginBottom: 6, display: 'flex', alignItems: 'center', gap: 6 },
6955
- fieldHint: { fontSize: 11, color: C.sub, marginTop: 4 },
6956
- 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' },
6957
- secretTag: { fontSize: 10, color: C.sub, background: C.bg2, borderRadius: 4, padding: '1px 5px', fontWeight: 400 },
6958
- btnPrimary: { padding: '7px 18px', fontSize: 13, borderRadius: 6, border: '1px solid ' + C.accent, background: C.accent, color: '#fff', cursor: 'pointer' },
6959
- savedMsg: { fontSize: 12, color: '#4a4' },
6960
- errMsg: { fontSize: 12, color: '#e55' },
6961
- cfgPath: { fontSize: 11, color: C.sub, marginTop: 16, paddingTop: 12, borderTop: '1px solid ' + C.border, wordBreak: 'break-all' },
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
  }