@fish-under-sea/magpie-neartoken 0.2.0 → 0.2.2

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
@@ -12,7 +12,7 @@
12
12
  // 站点自己的 /v1/models 按令牌分组过滤,很多账号拿到的是空列表,所以模型目录
13
13
  // 直接读 /api/pricing(公开、全量),不靠那个端点。
14
14
 
15
- import { readFileSync } from 'node:fs'
15
+ import { readFileSync, writeFileSync } from 'node:fs'
16
16
  import { dirname, join } from 'node:path'
17
17
  import { fileURLToPath } from 'node:url'
18
18
 
@@ -118,6 +118,9 @@ const hasWord = (id, words) => words.some((w) => id.toLowerCase().includes(w))
118
118
  * 按那一行走那一组——这就是"模型后面跟着它的分组"。
119
119
  */
120
120
  async function catalog(disabled = new Set()) {
121
+ // 同时把「全部分组」抄一份给页面:页面在沙箱里发不出网络请求,但能加载自己
122
+ // 目录下的脚本,所以这里写 page/groups.js,模型广场按它重绘。
123
+ const groupsOut = new Map()
121
124
  const env = await call('/api/pricing')
122
125
  const rows = Array.isArray(env?.data) ? env.data : []
123
126
  const refs = env?.group_refs && typeof env.group_refs === 'object' ? env.group_refs : {}
@@ -132,6 +135,13 @@ async function catalog(disabled = new Set()) {
132
135
  const film = hasWord(model, FILMS)
133
136
  if (!chat && !drawer && !film) continue
134
137
  const all = Array.isArray(row?.enable_groups) && row.enable_groups.length ? row.enable_groups : ['']
138
+ // 先按「全部分组」计数(关掉的也算),模型广场要显示它有多少个模型
139
+ for (const raw of all) {
140
+ const g = String(raw ?? '').trim()
141
+ if (!g) continue
142
+ const had = groupsOut.get(g)
143
+ groupsOut.set(g, { name: g, ratio: 0, models: (had?.models || 0) + 1, ref: '' })
144
+ }
135
145
  const groups = all.filter((g) => !disabled.has(String(g ?? '').trim()))
136
146
  if (!groups.length) continue
137
147
  for (const raw of groups) {
@@ -156,9 +166,30 @@ async function catalog(disabled = new Set()) {
156
166
  }
157
167
  }
158
168
  }
169
+ writeGroups([...groupsOut.values()].map((g) => ({
170
+ ...g,
171
+ ratio: Number(ratios[g.name]) > 0 ? Number(ratios[g.name]) : 0,
172
+ ref: refs[g.name] ? String(refs[g.name]) : '',
173
+ })).sort((a, b) => a.name.localeCompare(b.name, 'zh')))
159
174
  return out
160
175
  }
161
176
 
177
+ /**
178
+ * writeGroups 把分组表写成 page/groups.js,给沙箱里的页面用。
179
+ *
180
+ * 页面发不出网络请求(CSP connect-src 'none'),但能加载自己目录下的脚本,
181
+ * 所以这里不推数据、只落一个文件:`<script src="groups.js?t=…">` 一引就有。
182
+ * 写不进去(插件目录只读)就跳过,页面退回它自己的说明。
183
+ */
184
+ function writeGroups(groups) {
185
+ try {
186
+ const js = 'self.NEARTOKEN_GROUPS = ' + JSON.stringify(groups) + '\nself.NEARTOKEN_GROUPS_AT = ' + Date.now() + '\n'
187
+ writeFileSync(join(here, 'page', 'groups.js'), js, 'utf8')
188
+ } catch {
189
+ /* 只读安装:页面自己会说没拿到明细 */
190
+ }
191
+ }
192
+
162
193
  export default async function neartoken(input, options) {
163
194
  // 页面写进来的开关:关掉的分组,它们的模型不再出现在 magpie 的列表里。
164
195
  // 走的是 magpie 的 plugin options(页面 magpie.options.set),不碰宿主代码。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fish-under-sea/magpie-neartoken",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
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",
@@ -8,6 +8,9 @@
8
8
  ".": "./index.mjs",
9
9
  "./page": "./page/index.html"
10
10
  },
11
+ "scripts": {
12
+ "prepack": "node scripts/stamp.mjs"
13
+ },
11
14
  "license": "MIT",
12
15
  "files": [
13
16
  "index.mjs",
package/page/app.js CHANGED
@@ -23,6 +23,9 @@
23
23
  let disabledGroups = new Set()
24
24
  /** 当前视角:home / market / logs。 */
25
25
  let tab = 'home'
26
+ /** 插件写下的分组快照(page/groups.js),页面加载脚本取到。 */
27
+ let groupSnapshot = null
28
+ let groupsAsked = false
26
29
  const pick = { amount: null, method: null, custom: '' }
27
30
  /** 正在等宿主的哪个动作。 */
28
31
  let pendingAct = ''
@@ -97,6 +100,15 @@
97
100
  // ---------- 主流程 ----------
98
101
 
99
102
  async function boot() {
103
+ // 版本号来自 page/version.js(由 scripts/stamp.mjs 按 package.json 生成)
104
+ const ver = self.NEARTOKEN_VERSION
105
+ if (ver) {
106
+ const chip = document.getElementById('version')
107
+ if (chip) {
108
+ chip.textContent = 'v' + ver
109
+ chip.title = '插件版本 ' + ver
110
+ }
111
+ }
100
112
  // 预览用:?tab=market 可直接落在某个视角,生产里没这个参数就是原本界面
101
113
  const want = new URLSearchParams(location.search).get('tab')
102
114
  if (want === 'market' || want === 'logs' || want === 'home') tab = want
@@ -206,6 +218,9 @@
206
218
  if (key !== 'home' && view.configured && !windows.has(span)) {
207
219
  call('neartoken:usage', { range: span }).then((w) => { windows.set(span, w); render() }).catch(showError)
208
220
  }
221
+ if (key === 'market' && !groupSnapshot) {
222
+ loadGroups().then((got) => { if (got) render() })
223
+ }
209
224
  },
210
225
  }, label)))
211
226
  }
@@ -367,6 +382,39 @@
367
382
  return card('充值', ...parts)
368
383
  }
369
384
 
385
+ /**
386
+ * loadGroups 取分组快照。
387
+ *
388
+ * 页面在沙箱里发不出网络请求(CSP connect-src 'none'),但能加载自己目录下的
389
+ * 脚本;插件(index.mjs)每次拉目录都会写一份 page/groups.js,这里引一下就有。
390
+ */
391
+ function loadGroups(force) {
392
+ if (groupsAsked && !force) return Promise.resolve(groupSnapshot)
393
+ groupsAsked = true
394
+ return new Promise((resolve) => {
395
+ const s = document.createElement('script')
396
+ s.src = 'groups.js?t=' + Date.now()
397
+ s.onload = () => {
398
+ groupSnapshot = Array.isArray(self.NEARTOKEN_GROUPS) ? self.NEARTOKEN_GROUPS : null
399
+ resolve(groupSnapshot)
400
+ }
401
+ s.onerror = () => resolve(null)
402
+ document.head.append(s)
403
+ })
404
+ }
405
+
406
+ /** retryGroups 让插件重新拉一次目录(刷新模型表也顺手刷新了这份快照)。 */
407
+ async function retryGroups() {
408
+ groupSnapshot = null
409
+ render()
410
+ try {
411
+ await bridge.call('neartoken:refresh', {})
412
+ } catch {
413
+ /* 刷新失败也让下面的读取照跑一次 */
414
+ }
415
+ await loadGroups(true)
416
+ render()
417
+ }
370
418
  /** 分组落在哪一类:低价 / 满血 / 图片 / 视频 / 语音 / 向量 / 其他。 */
371
419
  function bucketOf(name) {
372
420
  const n = String(name || '')
@@ -382,19 +430,18 @@
382
430
 
383
431
  /** 模型广场:按站点返回的分组重绘,分组即为开关。 */
384
432
  function marketCard() {
385
- const list = Array.isArray(view.groupList) ? view.groupList : null
433
+ const list = Array.isArray(view.groupList) && view.groupList.length ? view.groupList : groupSnapshot
386
434
  const head = el('div', { class: 'row' },
387
435
  el('h2', { class: 'grow', text: '分组', style: 'margin:0' }),
388
436
  el('a', { class: 'link', href: 'https://neartoken.cc/pricing', target: '_blank', rel: 'noreferrer' }, '站点广场 ↗'))
389
437
  if (!list || !list.length) {
390
438
  return card(null, head,
391
439
  el('p', { class: 'note' },
392
- '宿主还没把分组明细交给页面(`neartoken:state` 目前只回分组数)。'),
393
- el('p', { class: 'note' },
394
- '开关已接好:写进 magpie 的 plugin options,provider 插件按它过滤模型表;',
395
- '宿主一回 groupList,这里就按站点分组重绘。先去 ',
396
- el('a', { class: 'link', href: 'https://neartoken.cc/pricing', target: '_blank', rel: 'noreferrer' }, '站点广场'),
397
- ' 看分组与倍率。'))
440
+ '还没拿到分组表。点「重新读取」——插件会去站点拉一次 /api/pricing,',
441
+ '写成 page/groups.js,页面加载它就有数据(不需要宿主改动)。'),
442
+ el('div', { class: 'row', style: 'margin-top:10px' },
443
+ el('button', { class: 'ghost', onclick: () => retryGroups() }, '重新读取'),
444
+ el('a', { class: 'link', href: 'https://neartoken.cc/pricing', target: '_blank', rel: 'noreferrer', style: 'align-self:center' }, '站点广场 ↗')) )
398
445
  }
399
446
  const buckets = new Map()
400
447
  for (const g of list) {
package/page/groups.js ADDED
@@ -0,0 +1,2 @@
1
+ // 由插件在拉取目录时覆盖写入;这里只是占位,免得页面加载时报 404。
2
+ self.NEARTOKEN_GROUPS = self.NEARTOKEN_GROUPS || null
package/page/icon.png CHANGED
Binary file
package/page/icon.svg CHANGED
@@ -1,5 +1,28 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round">
2
- <circle cx="10.8" cy="12.4" r="7"/>
3
- <path d="M8.3 15.4V9.4l5 6V9.4"/>
4
- <path d="M19.3 3.2l.62 1.48 1.48.62-1.48.62-.62 1.48-.62-1.48L17.2 5.3l1.48-.62z" fill="currentColor" stroke="none"/>
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" width="256" height="256">
2
+ <defs>
3
+ <radialGradient id="body" cx="38%" cy="30%" r="80%">
4
+ <stop offset="0" stop-color="#FFEFB8"/>
5
+ <stop offset="55%" stop-color="#FFD166"/>
6
+ <stop offset="100%" stop-color="#FFA63A"/>
7
+ </radialGradient>
8
+ <linearGradient id="leaf" x1="0" y1="0" x2="1" y2="1">
9
+ <stop offset="0" stop-color="#A9EBC6"/>
10
+ <stop offset="100%" stop-color="#39A377"/>
11
+ </linearGradient>
12
+ </defs>
13
+ <!-- 叶与梗 -->
14
+ <path d="M128 66c-4-19 6-31 26-35 3 19-7 31-26 35z" fill="url(#leaf)" stroke="#2B7F5C" stroke-width="7" stroke-linejoin="round"/>
15
+ <path d="M125 74c2-9 2-16 1-23" stroke="#2B7F5C" stroke-width="8" stroke-linecap="round" fill="none"/>
16
+ <!-- 果身 -->
17
+ <circle cx="128" cy="152" r="82" fill="url(#body)" stroke="#FFF8E9" stroke-width="10"/>
18
+ <!-- 脸 -->
19
+ <ellipse cx="104" cy="150" rx="7.5" ry="9" fill="#4A3B2A"/>
20
+ <ellipse cx="152" cy="150" rx="7.5" ry="9" fill="#4A3B2A"/>
21
+ <circle cx="101.5" cy="147" r="2.4" fill="#FFFDF4"/>
22
+ <circle cx="149.5" cy="147" r="2.4" fill="#FFFDF4"/>
23
+ <path d="M118 170c6 7 14 7 20 0" stroke="#4A3B2A" stroke-width="7" stroke-linecap="round" fill="none"/>
24
+ <ellipse cx="80" cy="168" rx="13" ry="8" fill="#FF8E7A" opacity="0.55"/>
25
+ <ellipse cx="176" cy="168" rx="13" ry="8" fill="#FF8E7A" opacity="0.55"/>
26
+ <!-- 高光 -->
27
+ <ellipse cx="97" cy="118" rx="24" ry="15" fill="#FFFDF3" opacity="0.5" transform="rotate(-28 97 118)"/>
5
28
  </svg>
package/page/index.html CHANGED
@@ -14,6 +14,8 @@
14
14
  <h1>NearToken</h1>
15
15
  <p class="sub" id="site">neartoken.cc</p>
16
16
  </div>
17
+ <span class="chip ver" id="version" title="插件版本">v—</span>
18
+ <a class="ghost link" id="updates" href="https://github.com/Fish-under-sea/magpie-neartoken/releases" target="_blank" rel="noreferrer">检查更新</a>
17
19
  <span class="chip" id="status"><span class="dot"></span><span id="status-text">正在读取</span></span>
18
20
  <button class="ghost" id="disconnect" hidden>断开连接</button>
19
21
  </header>
@@ -22,6 +24,7 @@
22
24
  </main>
23
25
  </div>
24
26
  <script src="_magpie/bridge.js"></script>
27
+ <script src="version.js"></script>
25
28
  <script src="app.js"></script>
26
29
  </body>
27
30
  </html>
package/page/style.css CHANGED
@@ -447,4 +447,14 @@ select {
447
447
 
448
448
  .log-row.head { color: var(--nt-muted); font-size: 11px; border-bottom-color: transparent; }
449
449
  .log-row .who { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
450
- .log-row .num { text-align: right; font-family: var(--nt-code); font-variant-numeric: tabular-nums; color: var(--nt-muted); }
450
+ .log-row .num { text-align: right; font-family: var(--nt-code); font-variant-numeric: tabular-nums; color: var(--nt-muted); }
451
+ /* 版本号与更新入口 */
452
+ .chip.ver {
453
+ font-family: var(--nt-code);
454
+ font-size: 10.5px;
455
+ letter-spacing: 0.02em;
456
+ color: var(--nt-muted);
457
+ padding: 3px 8px;
458
+ }
459
+
460
+ .ghost.link { text-decoration: none; display: inline-flex; align-items: center; }
@@ -0,0 +1,2 @@
1
+ // 由 scripts/stamp.mjs 生成,别手改:版本号在 package.json。
2
+ self.NEARTOKEN_VERSION = "0.2.2"