@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 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 groups = Array.isArray(row?.enable_groups) && row.enable_groups.length ? row.enable_groups : ['']
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.8",
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; }