@fish-under-sea/magpie-neartoken 0.1.8 → 0.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.mjs +21 -4
- package/package.json +1 -1
- package/page/app.js +64 -1
- package/page/style.css +35 -1
package/index.mjs
CHANGED
|
@@ -117,7 +117,7 @@ const hasWord = (id, words) => words.some((w) => id.toLowerCase().includes(w))
|
|
|
117
117
|
* 「站点原文组名 + 倍率 + 模型」。每行带自己的 x-neartoken-group 头,请求
|
|
118
118
|
* 按那一行走那一组——这就是"模型后面跟着它的分组"。
|
|
119
119
|
*/
|
|
120
|
-
async function catalog() {
|
|
120
|
+
async function catalog(disabled = new Set()) {
|
|
121
121
|
const env = await call('/api/pricing')
|
|
122
122
|
const rows = Array.isArray(env?.data) ? env.data : []
|
|
123
123
|
const refs = env?.group_refs && typeof env.group_refs === 'object' ? env.group_refs : {}
|
|
@@ -131,7 +131,9 @@ async function catalog() {
|
|
|
131
131
|
const drawer = hasWord(model, DRAWS) || ends.includes('image-generation')
|
|
132
132
|
const film = hasWord(model, FILMS)
|
|
133
133
|
if (!chat && !drawer && !film) continue
|
|
134
|
-
const
|
|
134
|
+
const all = Array.isArray(row?.enable_groups) && row.enable_groups.length ? row.enable_groups : ['']
|
|
135
|
+
const groups = all.filter((g) => !disabled.has(String(g ?? '').trim()))
|
|
136
|
+
if (!groups.length) continue
|
|
135
137
|
for (const raw of groups) {
|
|
136
138
|
const group = String(raw ?? '').trim()
|
|
137
139
|
const ref = group && refs[group] ? String(refs[group]) : ''
|
|
@@ -157,7 +159,22 @@ async function catalog() {
|
|
|
157
159
|
return out
|
|
158
160
|
}
|
|
159
161
|
|
|
160
|
-
export default async function neartoken() {
|
|
162
|
+
export default async function neartoken(input, options) {
|
|
163
|
+
// 页面写进来的开关:关掉的分组,它们的模型不再出现在 magpie 的列表里。
|
|
164
|
+
// 走的是 magpie 的 plugin options(页面 magpie.options.set),不碰宿主代码。
|
|
165
|
+
const disabled = (() => {
|
|
166
|
+
const raw = options?.disabledGroups
|
|
167
|
+
if (Array.isArray(raw)) return new Set(raw.filter((g) => typeof g === 'string'))
|
|
168
|
+
if (typeof raw === 'string' && raw.trim()) {
|
|
169
|
+
try {
|
|
170
|
+
const parsed = JSON.parse(raw)
|
|
171
|
+
return new Set(Array.isArray(parsed) ? parsed.filter((g) => typeof g === 'string') : [])
|
|
172
|
+
} catch {
|
|
173
|
+
return new Set()
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return new Set()
|
|
177
|
+
})()
|
|
161
178
|
return {
|
|
162
179
|
auth: {
|
|
163
180
|
provider: PROVIDER_ID,
|
|
@@ -274,7 +291,7 @@ export default async function neartoken() {
|
|
|
274
291
|
* @returns {Promise<Record<string, object>>}
|
|
275
292
|
*/
|
|
276
293
|
async models() {
|
|
277
|
-
return catalog()
|
|
294
|
+
return catalog(disabled)
|
|
278
295
|
},
|
|
279
296
|
},
|
|
280
297
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fish-under-sea/magpie-neartoken",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"description": "NearToken for magpie: a provider whose sign-in is the site's own device authorization, models listed per billing group with their multiplier, balance and vouchers on magpie's usage card, plus a page for spending and top-ups.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.mjs",
|
package/page/app.js
CHANGED
|
@@ -18,6 +18,9 @@
|
|
|
18
18
|
const windows = new Map()
|
|
19
19
|
/** 充值面:档位、渠道、当前选择。 */
|
|
20
20
|
let topupInfo = null
|
|
21
|
+
/** 插件自己的开关:关掉的分组(写进 magpie 的 plugin options,provider 插件按它过滤)。 */
|
|
22
|
+
let options = {}
|
|
23
|
+
let disabledGroups = new Set()
|
|
21
24
|
const pick = { amount: null, method: null, custom: '' }
|
|
22
25
|
/** 正在等宿主的哪个动作。 */
|
|
23
26
|
let pendingAct = ''
|
|
@@ -98,6 +101,13 @@
|
|
|
98
101
|
}
|
|
99
102
|
try {
|
|
100
103
|
await bridge.ready
|
|
104
|
+
// 插件自己的开关(关掉的分组):写在这里,provider 插件按它过滤模型
|
|
105
|
+
try {
|
|
106
|
+
options = (await bridge.options.get()) || {}
|
|
107
|
+
} catch {
|
|
108
|
+
options = {}
|
|
109
|
+
}
|
|
110
|
+
disabledGroups = new Set(Array.isArray(options.disabledGroups) ? options.disabledGroups : [])
|
|
101
111
|
await load()
|
|
102
112
|
clock = setInterval(tick, 1000)
|
|
103
113
|
bridge.onVisibility((visible) => {
|
|
@@ -161,7 +171,7 @@
|
|
|
161
171
|
frag.append(pendingCard())
|
|
162
172
|
} else if (view.configured) {
|
|
163
173
|
setStatus('已连接', 'on')
|
|
164
|
-
frag.append(accountCard(), boardCard(), topupCard(), providerCard())
|
|
174
|
+
frag.append(accountCard(), boardCard(), groupsCard(), topupCard(), providerCard())
|
|
165
175
|
} else {
|
|
166
176
|
setStatus('未连接')
|
|
167
177
|
frag.append(connectCard())
|
|
@@ -330,6 +340,59 @@
|
|
|
330
340
|
return card('充值', ...parts)
|
|
331
341
|
}
|
|
332
342
|
|
|
343
|
+
/** 分组墙:站点原文组名 + 倍率 + 模型数,点一下启停(写进插件 options)。 */
|
|
344
|
+
function groupsCard() {
|
|
345
|
+
const list = Array.isArray(view.groupList) ? view.groupList : null
|
|
346
|
+
const head = el('div', { class: 'row' },
|
|
347
|
+
el('h2', { class: 'grow', text: '分组', style: 'margin:0' }),
|
|
348
|
+
el('a', { class: 'link', href: 'https://neartoken.cc/pricing', target: '_blank', rel: 'noreferrer' }, '站点广场 ↗'))
|
|
349
|
+
if (!list || !list.length) {
|
|
350
|
+
return card(null, head,
|
|
351
|
+
el('p', { class: 'note' },
|
|
352
|
+
'宿主还没把分组明细交给页面。开关已经接好(写进 magpie 的 plugin options,provider 插件按它滤模型),',
|
|
353
|
+
'宿主一回 groupList,这里就是一墙可点的分组。先去 ',
|
|
354
|
+
el('a', { class: 'link', href: 'https://neartoken.cc/pricing', target: '_blank', rel: 'noreferrer' }, '站点广场'),
|
|
355
|
+
' 看分组与倍率。'))
|
|
356
|
+
}
|
|
357
|
+
const chips = list.map((g) => {
|
|
358
|
+
const name = String(g?.name ?? '')
|
|
359
|
+
const on = !disabledGroups.has(name)
|
|
360
|
+
const ratio = Number(g?.ratio) || 0
|
|
361
|
+
const label = ratio > 0 ? name + ' ' + ratioText(ratio) + 'x' : name
|
|
362
|
+
return el('button', {
|
|
363
|
+
class: 'group-chip',
|
|
364
|
+
'aria-pressed': String(on),
|
|
365
|
+
title: g?.models ? g.models + ' 个模型' : '',
|
|
366
|
+
onclick: () => toggleGroup(name),
|
|
367
|
+
}, label, el('span', { class: 'n', text: String(g?.models || 0) }))
|
|
368
|
+
})
|
|
369
|
+
const off = disabledGroups.size
|
|
370
|
+
return card(null, head,
|
|
371
|
+
el('div', { class: 'groups' }, chips),
|
|
372
|
+
el('p', { class: 'note', text: off
|
|
373
|
+
? '已关掉 ' + off + ' 个分组:它们的模型不再出现在 magpie 的模型列表里。'
|
|
374
|
+
: '全部开着。点一下关掉一组,它的模型就不再出现在 magpie 的模型列表里。' }))
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/** toggleGroup 翻转一个分组,并写回插件 options(provider 插件按它过滤)。 */
|
|
378
|
+
async function toggleGroup(name) {
|
|
379
|
+
if (!name) return
|
|
380
|
+
if (disabledGroups.has(name)) disabledGroups.delete(name)
|
|
381
|
+
else disabledGroups.add(name)
|
|
382
|
+
render()
|
|
383
|
+
try {
|
|
384
|
+
const next = await bridge.options.set({ ...options, disabledGroups: [...disabledGroups] })
|
|
385
|
+
if (next && typeof next === 'object') options = next
|
|
386
|
+
} catch (error) {
|
|
387
|
+
showError(error)
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/** ratioText 是倍率的写法:0.1 → 0.1,1 → 1。 */
|
|
392
|
+
function ratioText(v) {
|
|
393
|
+
return String(Math.round(v * 10000) / 10000)
|
|
394
|
+
}
|
|
395
|
+
|
|
333
396
|
/** 供应商卡:说明模型已经交给 magpie 了。 */
|
|
334
397
|
function providerCard() {
|
|
335
398
|
const models = view.models || 0
|
package/page/style.css
CHANGED
|
@@ -382,4 +382,38 @@ select {
|
|
|
382
382
|
clip: rect(0 0 0 0);
|
|
383
383
|
white-space: nowrap;
|
|
384
384
|
border: 0;
|
|
385
|
-
}
|
|
385
|
+
}
|
|
386
|
+
/* 分组墙:像模型广场那样成片的组名与倍率 */
|
|
387
|
+
.groups { display: flex; flex-wrap: wrap; gap: 8px; margin: 10px 0 4px; }
|
|
388
|
+
|
|
389
|
+
.group-chip {
|
|
390
|
+
display: inline-flex;
|
|
391
|
+
align-items: baseline;
|
|
392
|
+
gap: 7px;
|
|
393
|
+
height: 30px;
|
|
394
|
+
padding: 0 11px;
|
|
395
|
+
border-radius: 999px;
|
|
396
|
+
border: 1px solid var(--nt-line);
|
|
397
|
+
background: transparent;
|
|
398
|
+
color: var(--nt-muted);
|
|
399
|
+
font-size: 12px;
|
|
400
|
+
white-space: nowrap;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
.group-chip[aria-pressed="true"] {
|
|
404
|
+
color: var(--nt-ink);
|
|
405
|
+
border-color: var(--nt-accent);
|
|
406
|
+
background: var(--nt-accent-soft);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
.group-chip .n {
|
|
410
|
+
font-family: var(--nt-code);
|
|
411
|
+
font-size: 10.5px;
|
|
412
|
+
color: var(--nt-faint);
|
|
413
|
+
font-variant-numeric: tabular-nums;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
.group-chip[aria-pressed="true"] .n { color: var(--nt-muted); }
|
|
417
|
+
|
|
418
|
+
.link { color: var(--nt-accent); text-decoration: none; font-size: 11.5px; }
|
|
419
|
+
.link:hover { text-decoration: underline; }
|