@te-river/opencode-alibabatokenplan 0.1.0

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/dist/tui.js ADDED
@@ -0,0 +1,1142 @@
1
+ // src/tui/index.tsx
2
+ import { createElement, setProp, effect, insert, createComponent } from "@opentui/solid";
3
+ import { createSignal, onCleanup } from "solid-js";
4
+
5
+ // src/core/bailian-snapshot.ts
6
+ var CREDITS_PER_YUAN = 250;
7
+ var BAILIAN_SNAPSHOT_AT = "2026-08-30";
8
+ var TOKEN_PLAN_TEXT_MODELS = [
9
+ "qwen3.8-max",
10
+ "qwen3.8-flash",
11
+ "qwen3.7-max",
12
+ "qwen3.7-plus",
13
+ "qwen3.6-plus",
14
+ "qwen3.6-flash",
15
+ "glm-5.2",
16
+ "deepseek-v4-pro-0813"
17
+ ];
18
+ var BAILIAN_WINDOW_RULES = [
19
+ {
20
+ id: "night-qwen38-max",
21
+ label: "Token Plan \u591C\u95F4\u4E94\u6298",
22
+ models: ["qwen3.8-max"],
23
+ factor: 0.5,
24
+ start: "22:00",
25
+ end: "08:00",
26
+ source: "\u5B98\u65B9\u6587\u6863 \xB7 Token Plan\uFF08\u4E2A\u4EBA\u7248\uFF09\u6982\u8FF0"
27
+ },
28
+ {
29
+ id: "night-deepseek-v4-pro",
30
+ label: "Token Plan \u591C\u95F4\u4E94\u6298",
31
+ models: ["deepseek-v4-pro-0813"],
32
+ factor: 0.5,
33
+ start: "22:00",
34
+ end: "08:00",
35
+ source: "\u5B98\u65B9\u6587\u6863 \xB7 Token Plan\uFF08\u4E2A\u4EBA\u7248\uFF09\u6982\u8FF0"
36
+ }
37
+ ];
38
+
39
+ // src/core/qoder-snapshot.ts
40
+ var QODER_SNAPSHOT_DATE = "2026-08-31";
41
+ var W_OFFPEAK = { start: "22:00", end: "08:00", tz: "Asia/Shanghai" };
42
+ var L_QWENMAX = "https://docs.qoder.cn/events/qwen-max";
43
+ var L_QWEN37 = "https://docs.qoder.cn/product-overview/qwen-3-7-series-model-staggering-discount";
44
+ var L_OFFPEAK_RULES = "https://help.aliyun.com/document_detail/3042139.html";
45
+ var L_ULTIMATE = "https://docs.qoder.com/zh/events/ultimatediscount";
46
+ var L_CANTUS = "https://docs.qoder.com/zh/events/cantuslaunch";
47
+ var L_FREECALLS = "https://docs.qoder.com/zh/events/ultimatefreecalls";
48
+ var L_REFERRAL = "https://qoder.cn/referral";
49
+ function qoderSnapshotOffers() {
50
+ const S = QODER_SNAPSHOT_DATE;
51
+ const o = (r) => ({
52
+ ...r,
53
+ origins: ["qoder-snapshot"],
54
+ snapshotDate: S
55
+ });
56
+ return [
57
+ o({
58
+ id: "atp-offpeak-qwen38",
59
+ kind: "time-window",
60
+ label: "\u9519\u5CF0 5 \u6298\uFF08Off-Peak 50% off\uFF09",
61
+ models: ["qwen3.8-max"],
62
+ unit: "credits-factor",
63
+ factor: 0.25,
64
+ baseFactor: 0.5,
65
+ window: { ...W_OFFPEAK },
66
+ note: "\u5E38\u89C4 0.5x \u2192 \u9519\u5CF0\u65F6\u6BB5 0.25x\uFF085 \u6298\uFF09",
67
+ links: [L_QWENMAX]
68
+ }),
69
+ o({
70
+ id: "atp-offpeak-qwen37max",
71
+ kind: "time-window",
72
+ label: "\u9519\u5CF0 2 \u6298\uFF08Off-Peak 80% off\uFF09",
73
+ models: ["qwen3.7-max"],
74
+ unit: "credits-factor",
75
+ factor: 0.1,
76
+ baseFactor: 0.5,
77
+ window: { ...W_OFFPEAK },
78
+ note: "\u5E38\u89C4 0.5x \u2192 \u9519\u5CF0\u65F6\u6BB5 0.1x\uFF082 \u6298\uFF09",
79
+ links: [L_QWEN37]
80
+ }),
81
+ o({
82
+ id: "atp-offpeak-qwen37plus",
83
+ kind: "time-window",
84
+ label: "\u9519\u5CF0 4 \u6298\uFF08Off-Peak 60% off\uFF09",
85
+ models: ["qwen3.7-plus"],
86
+ unit: "credits-factor",
87
+ factor: 0.04,
88
+ baseFactor: 0.1,
89
+ window: { ...W_OFFPEAK },
90
+ note: "\u5E38\u89C4 0.1x \u2192 \u9519\u5CF0\u65F6\u6BB5 0.04x\uFF084 \u6298\uFF09",
91
+ links: [L_QWEN37]
92
+ }),
93
+ o({
94
+ id: "atp-flat-preview",
95
+ kind: "flat-cut",
96
+ label: "\u9650\u65F6 1 \u6298",
97
+ models: ["qwen3.8-max-preview"],
98
+ unit: "credits-factor",
99
+ factor: 0.1,
100
+ note: "\u6A21\u578B\u63CF\u8FF0\u539F\u6587\uFF1A\u5E38\u89C4\u65F6\u6BB5 1 \u6298",
101
+ links: [L_OFFPEAK_RULES]
102
+ }),
103
+ o({
104
+ id: "atp-flat-glm53f",
105
+ kind: "flat-cut",
106
+ label: "\u5212\u7EBF\u534A\u4EF7\uFF080.1 \u2192 0.05\uFF09",
107
+ models: ["glm-5.3-flash"],
108
+ unit: "credits-factor",
109
+ factor: 0.05,
110
+ baseFactor: 0.1,
111
+ note: "originalPriceFactor \u7ED3\u6784\uFF0C\u65E0 promotion \u6807\u8BB0\uFF08\u5FEB\u7167\uFF09",
112
+ links: [L_OFFPEAK_RULES]
113
+ }),
114
+ o({
115
+ id: "atp-flat-ultimate",
116
+ kind: "flat-cut",
117
+ label: "\u9650\u65F6\u6298\u6263\u8FDB\u884C\u4E2D",
118
+ models: ["ultimate", "cantus"],
119
+ unit: "credits-factor",
120
+ note: "\u6570\u503C\u672A\u516C\u5F00\uFF0C\u4EC5\u6D3B\u52A8\u6587\u6848\uFF08\u5FEB\u7167\uFF09",
121
+ links: [L_ULTIMATE, L_CANTUS]
122
+ }),
123
+ o({
124
+ id: "atp-free-200",
125
+ kind: "free-calls",
126
+ label: "\u6781\u81F4\u6A21\u578B 200 \u6B21\u514D\u8D39",
127
+ models: [],
128
+ unit: "credits-factor",
129
+ note: "\u514D\u8D39\u989D\u5EA6\u6D3B\u52A8\uFF1B\u5DF2\u8FC7\u671F\uFF08expired:true\uFF09",
130
+ expiry: "2026-07-30T23:59:59+08:00",
131
+ links: [L_FREECALLS]
132
+ }),
133
+ o({
134
+ id: "atp-free-800-2000",
135
+ kind: "free-calls",
136
+ label: "qwen3.8-max 800/2000 \u6B21\u514D\u8D39\uFF08claim\uFF09",
137
+ models: ["qwen3.8-max"],
138
+ unit: "credits-factor",
139
+ note: "activityId: qwen38_800_invoke / qwen38_2000_invoke\uFF08\u5FEB\u7167\uFF09",
140
+ links: []
141
+ }),
142
+ o({
143
+ id: "atp-referral",
144
+ kind: "policy",
145
+ label: "\u63A8\u8350\u8FD4\u79EF\u5206 \u226440000+",
146
+ models: [],
147
+ unit: "credits-factor",
148
+ note: "\u63A8\u8350\u6D3B\u52A8\u81F3 2026-09-03",
149
+ expiry: "2026-09-03T23:59:59+08:00",
150
+ links: [L_REFERRAL]
151
+ }),
152
+ o({
153
+ id: "atp-lite",
154
+ kind: "policy",
155
+ label: "lite \u57FA\u7840\u6A21\u578B\u964D\u7EA7\u653F\u7B56",
156
+ models: ["lite"],
157
+ unit: "credits-factor",
158
+ note: "Credits \u8017\u5C3D\u540E\u964D\u7EA7\u4E3A lite \u57FA\u7840\u6A21\u578B\uFF08\u514D\u8D39/\u6BCF\u65E5\u9650\u989D/\u9AD8\u5CF0\u65F6\u6BB5\u53D8\u6162\uFF09\uFF1B\u4F59\u989D <200 \u4F4E\u989D\u63D0\u793A\uFF1B\u9996\u6708 prorated\uFF08\u5FEB\u7167\uFF09",
159
+ links: []
160
+ })
161
+ ];
162
+ }
163
+ var QODER_INSALE_FACTORS = {
164
+ auto: { factor: 0.5 },
165
+ "qwen3.8-flash": { factor: 0.1 },
166
+ "qwen3.7-flash": { factor: 0.1 },
167
+ "deepseek-v4-pro": { factor: 0.8 },
168
+ "deepseek-v4-flash": { factor: 0.3 },
169
+ "glm-5.3": { factor: 0.6 },
170
+ "glm-5.2": { factor: 0.6 },
171
+ "kimi-k2.7-code": { factor: 0.3, note: "highspeed 0.6" },
172
+ "minimax-m2.7": { factor: 0.2 }
173
+ };
174
+
175
+ // src/core/merge.ts
176
+ var MODEL_ALIASES = {
177
+ qmodel_38max: "qwen3.8-max",
178
+ qmodel_latest: "qwen3.7-max",
179
+ qmodel: "qwen3.7-plus",
180
+ qfmodel: "qwen3.8-flash",
181
+ q37fmodel: "qwen3.7-flash",
182
+ dmodel: "deepseek-v4-pro",
183
+ gmodel: "glm-5.3",
184
+ gfmodel: "glm-5.3-flash",
185
+ gm51model: "glm-5.2",
186
+ kmodel: "kimi-k2.7-code",
187
+ mmodel: "minimax-m2.7"
188
+ };
189
+ function canonicalize(model) {
190
+ const k = String(model || "").toLowerCase();
191
+ return MODEL_ALIASES[k] ?? k;
192
+ }
193
+ function originRank(origin) {
194
+ return { "bailian-live": 0, "bailian-cli": 1, "qoder-snapshot": 2, "bailian-snapshot": 3 }[origin];
195
+ }
196
+ var OFFPEAK_WINDOW = { start: "22:00", end: "08:00", tz: "Asia/Shanghai" };
197
+ function catalogToOffers(models, origin, snapshotDate) {
198
+ const out = [];
199
+ const offpeakModels = models.filter((m) => m.prices.some((p) => p.band === "offpeak")).map((m) => m.model.toLowerCase());
200
+ if (offpeakModels.length) {
201
+ out.push({
202
+ id: "catalog-offpeak",
203
+ kind: "time-window",
204
+ label: "\u76EE\u5F55\u591C\u95F4\u4F18\u60E0\uFF08\u9519\u5CF0 5 \u6298\uFF09",
205
+ models: offpeakModels,
206
+ unit: "cny-price",
207
+ factor: 0.5,
208
+ window: { ...OFFPEAK_WINDOW },
209
+ note: "\u76EE\u5F55\u542B offpeak \u6863\u7684\u6A21\u578B\uFF0C\u8C37\u65F6\u4EF7=\u5CF0\u65F6 5 \u6298",
210
+ links: [],
211
+ origins: [origin],
212
+ ...snapshotDate ? { snapshotDate } : {}
213
+ });
214
+ }
215
+ const windowedModels = /* @__PURE__ */ new Set([
216
+ ...out.filter((o) => o.kind === "time-window").flatMap((o) => o.models.map((m) => m.toLowerCase())),
217
+ ...BAILIAN_WINDOW_RULES.flatMap((r) => r.models.map((m) => m.toLowerCase()))
218
+ ]);
219
+ for (const m of models) {
220
+ const model = m.model.toLowerCase();
221
+ if (windowedModels.has(model)) continue;
222
+ const std = m.prices.find((p) => p.type === "input_token" && p.discount !== void 0 && p.discount < 1);
223
+ const modelFactor = std?.discount;
224
+ const batchEntry = m.prices.find((p) => /batch/i.test(p.type) && p.discount !== void 0 && p.discount < 1);
225
+ const batchCombined = batchEntry ? Math.round((batchEntry.discount ?? 1) * (modelFactor ?? 1) * 1e3) / 1e3 : void 0;
226
+ if (modelFactor === void 0 && batchCombined === void 0) continue;
227
+ const factors = [];
228
+ if (modelFactor !== void 0) factors.push(modelFactor);
229
+ else if (batchCombined !== void 0) factors.push(batchCombined);
230
+ out.push({
231
+ id: "limited-" + model,
232
+ kind: "flat-cut",
233
+ label: "\u9650\u65F6\u4F18\u60E0",
234
+ models: [model],
235
+ unit: "cny-price",
236
+ factor: factors[0],
237
+ ...batchCombined !== void 0 ? { batchStack: batchCombined } : {},
238
+ note: modelFactor !== void 0 ? `\u6A21\u578B\u9650\u65F6 \xD7${modelFactor}` + (batchCombined !== void 0 ? `\uFF1BBatch \u53E0\u52A0 \xD7${batchCombined}` : "") : `\u4EC5 Batch \u7EF4\u5EA6 \xD7${batchCombined}`,
239
+ links: [],
240
+ origins: [origin],
241
+ ...snapshotDate ? { snapshotDate } : {}
242
+ });
243
+ }
244
+ return out;
245
+ }
246
+ function mergeKey(r) {
247
+ return [r.kind, r.unit, r.window ? `${r.window.start}-${r.window.end}@${r.window.tz}` : "-", r.factor ?? "-", r.baseFactor ?? "-", [...r.models].sort().join(",")].join("|");
248
+ }
249
+ function explodePerModel(rules) {
250
+ const out = [];
251
+ for (const r of rules) {
252
+ if (r.models.length <= 1) {
253
+ out.push(r);
254
+ continue;
255
+ }
256
+ for (const m of new Set(r.models)) out.push({ ...r, id: `${r.id}#${m}`, models: [m] });
257
+ }
258
+ return out;
259
+ }
260
+ function mergeOffers(all) {
261
+ const byKey = /* @__PURE__ */ new Map();
262
+ for (const r of explodePerModel(all)) {
263
+ const k = mergeKey(r);
264
+ const prev = byKey.get(k);
265
+ if (!prev) {
266
+ byKey.set(k, { ...r, models: [...r.models], origins: [...r.origins], links: [...r.links] });
267
+ continue;
268
+ }
269
+ for (const m of r.models) if (!prev.models.includes(m)) prev.models.push(m);
270
+ for (const o of r.origins) if (!prev.origins.includes(o)) prev.origins.push(o);
271
+ for (const l of r.links) if (l && !prev.links.includes(l)) prev.links.push(l);
272
+ if (r.snapshotDate && (!prev.snapshotDate || r.snapshotDate > prev.snapshotDate)) prev.snapshotDate = r.snapshotDate;
273
+ }
274
+ const list = [...byKey.values()];
275
+ const rank = (r) => Math.min(...r.origins.map(originRank));
276
+ const kindOrder = { "time-window": 0, "flat-cut": 1, "free-calls": 2, policy: 3 };
277
+ list.sort((a, b) => {
278
+ const rd = rank(a) - rank(b);
279
+ if (rd) return rd;
280
+ const kd = kindOrder[a.kind] - kindOrder[b.kind];
281
+ if (kd) return kd;
282
+ const fa = a.factor ?? 1, fb = b.factor ?? 1;
283
+ if (fa !== fb) return fa - fb;
284
+ return a.id.localeCompare(b.id);
285
+ });
286
+ return list;
287
+ }
288
+ function isCrossVerified(r) {
289
+ const hasQ = r.origins.includes("qoder-snapshot");
290
+ const hasB = r.origins.some((o) => o.startsWith("bailian-"));
291
+ return hasQ && hasB;
292
+ }
293
+
294
+ // src/core/rules.ts
295
+ var SH_OFFSET_MIN = 8 * 60;
296
+ var DAY_MIN = 1440;
297
+ function shanghaiMinutes(now = /* @__PURE__ */ new Date()) {
298
+ const shifted = new Date(now.getTime() + (now.getTimezoneOffset() + SH_OFFSET_MIN) * 6e4);
299
+ return shifted.getHours() * 60 + shifted.getMinutes() + shifted.getSeconds() / 60;
300
+ }
301
+ function parseHm(s) {
302
+ const [h2, min] = s.split(":").map(Number);
303
+ return h2 * 60 + (min || 0);
304
+ }
305
+ function windowState(rule, now = /* @__PURE__ */ new Date()) {
306
+ const start = parseHm(rule.start);
307
+ const end = parseHm(rule.end);
308
+ const m = shanghaiMinutes(now);
309
+ const wraps = start > end;
310
+ const active = wraps ? m >= start || m < end : m >= start && m < end;
311
+ let toNext;
312
+ let kind;
313
+ if (active) {
314
+ toNext = wraps ? m >= start ? end + DAY_MIN - m : end - m : end - m;
315
+ kind = "end";
316
+ } else {
317
+ toNext = m < start ? start - m : start + DAY_MIN - m;
318
+ kind = "start";
319
+ }
320
+ return {
321
+ active,
322
+ minutesToNextBoundary: Math.max(0, Math.round(toNext)),
323
+ boundaryKind: kind,
324
+ nextBoundaryAtMs: now.getTime() + toNext * 6e4,
325
+ nextBoundaryAt: shanghaiISO(now.getTime() + toNext * 6e4)
326
+ };
327
+ }
328
+ function shanghaiISO(ms) {
329
+ const d = new Date(ms + SH_OFFSET_MIN * 6e4);
330
+ const p = (n, w = 2) => String(n).padStart(w, "0");
331
+ return d.getUTCFullYear() + "-" + p(d.getUTCMonth() + 1) + "-" + p(d.getUTCDate()) + "T" + p(d.getUTCHours()) + ":" + p(d.getUTCMinutes()) + ":" + p(d.getUTCSeconds()) + "." + p(d.getUTCMilliseconds(), 3) + "+08:00";
332
+ }
333
+ function fmtCountdown(mins) {
334
+ if (mins <= 0) return "\u4E0D\u8DB3 1 \u5206\u949F";
335
+ const d = Math.floor(mins / DAY_MIN);
336
+ const h2 = Math.floor(mins % DAY_MIN / 60);
337
+ const m = mins % 60;
338
+ const parts = [];
339
+ if (d) parts.push(d + " \u5929");
340
+ if (h2) parts.push(h2 + " \u5C0F\u65F6");
341
+ parts.push(m + " \u5206\u949F");
342
+ return parts.join(" ");
343
+ }
344
+ function fmtHMS(msRemaining) {
345
+ const total = Math.max(0, Math.floor(msRemaining / 1e3));
346
+ const h2 = Math.floor(total / 3600);
347
+ const m = Math.floor(total % 3600 / 60);
348
+ const s = total % 60;
349
+ const p = (n) => String(n).padStart(2, "0");
350
+ return `${p(h2)}:${p(m)}:${p(s)}`;
351
+ }
352
+ function fmtShortDur(msRemaining) {
353
+ const total = Math.max(0, Math.floor(msRemaining / 6e4));
354
+ const d = Math.floor(total / DAY_MIN);
355
+ const h2 = Math.floor(total % DAY_MIN / 60);
356
+ const m = total % 60;
357
+ if (d) return `${d}d${h2}h`;
358
+ if (h2) return `${h2}h${m}m`;
359
+ return `${m}m`;
360
+ }
361
+
362
+ // src/core/text.ts
363
+ function fmtFactor(f) {
364
+ if (f === void 0 || !Number.isFinite(f)) return void 0;
365
+ let s = f.toFixed(2);
366
+ s = s.replace(/0+$/, "").replace(/\.$/, ".0");
367
+ return s + "x";
368
+ }
369
+ function sourceBadgeLine(st) {
370
+ const cat = st.sources.catalog;
371
+ const catLabel = cat === "qoder-snapshot" ? "Qoder\u5FEB\u7167" : cat === "bailian-live" ? "\u5B9E\u65F6" : cat === "bailian-cli" ? "CLI" : "\u5FEB\u7167";
372
+ const t = st.updatedAt ? new Date(st.updatedAt) : void 0;
373
+ const hhmm = t && !Number.isNaN(t.getTime()) ? t.toLocaleTimeString("zh-CN", { hour: "2-digit", minute: "2-digit", hour12: false, timeZone: "Asia/Shanghai" }) : "";
374
+ return `\u6E90:${catLabel}${hhmm ? " " + hhmm : ""} \xB7 Qoder\u5FEB\u7167${st.sources.qoderSnapshot.slice(5)}`;
375
+ }
376
+ function homeSummaryLine(st, now = /* @__PURE__ */ new Date()) {
377
+ const w = st.windows.find((x) => x.id.startsWith("offpeak")) ?? st.windows[0];
378
+ if (!w) return "Token Plan \u6682\u65E0\u5DF2\u77E5\u4F18\u60E0";
379
+ const starModel = w.models.find((m) => m.model === "qwen3.8-max") ?? w.models.find((m) => m.model.startsWith("qwen")) ?? w.models[0];
380
+ const parts = [`\u9519\u5CF0 ${w.start}\u2013${w.end}`];
381
+ if (starModel) {
382
+ const seg = starModel.regular && starModel.effective && starModel.regular !== starModel.effective ? `${starModel.model}\xD7${starModel.regular.replace("x", "")}/\xD7${starModel.effective.replace("x", "")}` : `${starModel.model}\xD7${(starModel.effective ?? starModel.regular ?? "").replace("x", "")}`;
383
+ parts.push(seg);
384
+ }
385
+ const ws = windowState(w, now);
386
+ parts.push(`${ws.active ? "\u8DDD\u7ED3\u675F" : "\u8DDD\u5F00\u59CB"} ${fmtShortDur(ws.nextBoundaryAtMs - now.getTime())}`);
387
+ parts.push(sourceBadgeLine(st));
388
+ return parts.join(" \xB7 ");
389
+ }
390
+
391
+ // src/core/status.ts
392
+ import * as fs from "node:fs";
393
+ import * as path from "node:path";
394
+ import * as os from "node:os";
395
+ var WRITTEN_BY = "@te-river/opencode-alibabatokenplan/server@0.1.0";
396
+ var STALE_AFTER_SEC = 600;
397
+ var CATALOG_ORIGIN = {
398
+ live: "bailian-live",
399
+ cli: "bailian-cli",
400
+ snapshot: "bailian-snapshot"
401
+ };
402
+ function windowId(start, end) {
403
+ if (start === "22:00" && end === "08:00") return "offpeak-22-08";
404
+ return `win-${start.replace(":", "")}-${end.replace(":", "")}`;
405
+ }
406
+ function buildOffers(catalog) {
407
+ const origin = CATALOG_ORIGIN[catalog.source] ?? "bailian-snapshot";
408
+ const legacy = BAILIAN_WINDOW_RULES.map((r) => ({
409
+ id: r.id,
410
+ kind: "time-window",
411
+ label: r.label,
412
+ models: [...r.models],
413
+ unit: "cny-price",
414
+ factor: r.factor,
415
+ window: { start: r.start, end: r.end, tz: "Asia/Shanghai" },
416
+ note: r.source,
417
+ links: [],
418
+ origins: ["bailian-snapshot"],
419
+ snapshotDate: BAILIAN_SNAPSHOT_AT
420
+ }));
421
+ return mergeOffers([...qoderSnapshotOffers(), ...legacy, ...catalogToOffers(catalog.models, origin, catalog.fetchedAt?.slice(0, 10))]);
422
+ }
423
+ function buildWindows(offers, now) {
424
+ const byWindow = /* @__PURE__ */ new Map();
425
+ for (const r of offers) {
426
+ if (r.kind !== "time-window" || !r.window) continue;
427
+ const key = `${r.window.start}-${r.window.end}@${r.window.tz}`;
428
+ let g = byWindow.get(key);
429
+ if (!g) {
430
+ g = { ...r.window, rules: [] };
431
+ byWindow.set(key, g);
432
+ }
433
+ g.rules.push(r);
434
+ }
435
+ const out = [];
436
+ for (const g of byWindow.values()) {
437
+ const w = windowState(g, now);
438
+ const models = [];
439
+ for (const r of g.rules) {
440
+ const cross = isCrossVerified(r);
441
+ for (const m of r.models) {
442
+ models.push({
443
+ model: m,
444
+ regular: fmtFactor(r.baseFactor),
445
+ effective: fmtFactor(r.factor),
446
+ badge: r.label + (cross ? " \xB7 \u53CC\u6E90\u5370\u8BC1" : ""),
447
+ origins: r.origins,
448
+ links: r.links
449
+ });
450
+ }
451
+ }
452
+ models.sort((a, b) => {
453
+ const fa = parseFloat(a.effective ?? "1"), fb = parseFloat(b.effective ?? "1");
454
+ if (fa !== fb) return fa - fb;
455
+ return a.model.localeCompare(b.model);
456
+ });
457
+ out.push({
458
+ id: windowId(g.start, g.end),
459
+ label: g.rules[0].label,
460
+ start: g.start,
461
+ end: g.end,
462
+ tz: g.tz,
463
+ activeNow: w.active,
464
+ boundaryKind: w.boundaryKind,
465
+ nextBoundaryAt: w.nextBoundaryAt,
466
+ models
467
+ });
468
+ }
469
+ out.sort((a, b) => Number(b.id.startsWith("offpeak")) - Number(a.id.startsWith("offpeak")) || a.start.localeCompare(b.start));
470
+ return out;
471
+ }
472
+ function buildDiscounts(offers) {
473
+ const byModel = /* @__PURE__ */ new Map();
474
+ for (const r of offers) {
475
+ if (r.kind === "free-calls" || r.kind === "policy") continue;
476
+ for (const m of r.models) {
477
+ const arr = byModel.get(m) ?? [];
478
+ arr.push(r);
479
+ byModel.set(m, arr);
480
+ }
481
+ }
482
+ const out = [];
483
+ for (const [model, applies] of byModel) {
484
+ const bestFactors = {};
485
+ for (const r of applies) {
486
+ if (r.factor === void 0) continue;
487
+ const cur = bestFactors[r.unit];
488
+ if (cur === void 0 || r.factor < cur) bestFactors[r.unit] = r.factor;
489
+ }
490
+ out.push({
491
+ model,
492
+ applies,
493
+ ...Object.keys(bestFactors).length ? { bestFactors } : {}
494
+ });
495
+ }
496
+ const lowest = (d) => {
497
+ const vs = Object.values(d.bestFactors ?? {});
498
+ return vs.length ? Math.min(...vs) : 1;
499
+ };
500
+ const seriesOrder = (n) => /^qwen/.test(n) ? 0 : /^(deepseek|glm|kimi|minimax)/.test(n) ? 1 : 2;
501
+ out.sort((a, b) => {
502
+ const fa = lowest(a), fb = lowest(b);
503
+ if (fa !== fb) return fa - fb;
504
+ const sd = seriesOrder(a.model) - seriesOrder(b.model);
505
+ if (sd) return sd;
506
+ return a.model.localeCompare(b.model);
507
+ });
508
+ return out;
509
+ }
510
+ function pickPrice(prices, type, band) {
511
+ return prices.find((p) => p.type === type && (band ? p.band === band : !p.band || p.band === "standard"));
512
+ }
513
+ function creditsPerM(price) {
514
+ const v = price * CREDITS_PER_YUAN;
515
+ return Number.isInteger(v) ? v.toLocaleString("en-US") : String(Number(v.toFixed(1)));
516
+ }
517
+ function buildTextModels(models, windows, now) {
518
+ return TOKEN_PLAN_TEXT_MODELS.map((name) => {
519
+ const m = models.find((x) => x.model === name) || models.find((x) => x.family === name);
520
+ let badge;
521
+ for (const w of windows) {
522
+ if (w.models.some((x) => x.model === name)) {
523
+ const ws = windowState(w, now);
524
+ badge = {
525
+ label: w.label + (w.models.find((x) => x.model === name)?.badge?.includes("\u53CC\u6E90") ? " \xB7 \u53CC\u6E90\u5370\u8BC1" : ""),
526
+ countdown: ws.active ? `${fmtCountdown(ws.minutesToNextBoundary)}\u540E\u7ED3\u675F\uFF08${w.end}\uFF09` : `${fmtCountdown(ws.minutesToNextBoundary)}\u540E\u5F00\u59CB\uFF08${w.start}\uFF09`
527
+ };
528
+ break;
529
+ }
530
+ }
531
+ const insale = QODER_INSALE_FACTORS[name];
532
+ if (!m) {
533
+ return { model: name, input: "\u2014", cache: "\u2014", output: "\u2014", ...insale ? { creditsFactor: fmtFactor(insale.factor) } : {}, ...badge ? { badge } : {} };
534
+ }
535
+ const peakIn = pickPrice(m.prices, "input_token", "peak");
536
+ const offIn = pickPrice(m.prices, "input_token", "offpeak");
537
+ const stdIn = pickPrice(m.prices, "input_token");
538
+ const stdOut = pickPrice(m.prices, "output_token", "peak") || pickPrice(m.prices, "output_token");
539
+ const offOut = pickPrice(m.prices, "output_token", "offpeak");
540
+ const stdCache = pickPrice(m.prices, "input_token_cache", "peak") || pickPrice(m.prices, "input_token_cache") || pickPrice(m.prices, "input_token_cache_read");
541
+ const offCache = pickPrice(m.prices, "input_token_cache", "offpeak") || pickPrice(m.prices, "input_token_cache_read", "offpeak");
542
+ const dual = !!(peakIn && offIn);
543
+ const pair = (a, b) => a ? b ? `${creditsPerM(a.price)} / ${creditsPerM(b.price)}` : creditsPerM(a.price) : "\u2014";
544
+ return {
545
+ model: name,
546
+ input: dual ? pair(peakIn, offIn) : pair(stdIn),
547
+ cache: dual ? pair(stdCache, offCache) : pair(stdCache),
548
+ output: dual ? pair(stdOut, offOut) : pair(stdOut),
549
+ ...dual ? { bands: "\u5CF0/\u8C37" } : {},
550
+ ...insale ? { creditsFactor: fmtFactor(insale.factor) } : {},
551
+ ...badge ? { badge } : {}
552
+ };
553
+ });
554
+ }
555
+ function buildNotices(offers, now) {
556
+ return offers.filter((r) => r.kind === "free-calls" || r.kind === "policy").map((r) => ({
557
+ kind: r.kind,
558
+ text: `${r.label}${r.note ? " \u2014 " + r.note : ""}`,
559
+ source: r.origins.includes("qoder-snapshot") ? `Qoder \u5FEB\u7167 ${r.snapshotDate ?? QODER_SNAPSHOT_DATE}` : r.origins[0] ?? "unknown",
560
+ expired: r.expiry ? new Date(r.expiry).getTime() < now.getTime() : void 0
561
+ }));
562
+ }
563
+ function buildStatus(catalog, now = /* @__PURE__ */ new Date(), quota) {
564
+ const offers = buildOffers(catalog);
565
+ const windows = buildWindows(offers, now);
566
+ return {
567
+ schemaVersion: 1,
568
+ writtenBy: WRITTEN_BY,
569
+ updatedAt: now.toISOString(),
570
+ staleAfterSec: STALE_AFTER_SEC,
571
+ timezone: "Asia/Shanghai",
572
+ creditsPerYuan: CREDITS_PER_YUAN,
573
+ creditsRateNote: "\u636E\u767E\u70BC\u8BA1\u8D39\u793A\u4F8B\u6362\u7B97\uFF0C\u975E\u5B98\u65B9\u6C47\u7387",
574
+ sources: {
575
+ catalog: CATALOG_ORIGIN[catalog.source] ?? "bailian-snapshot",
576
+ qoderSnapshot: QODER_SNAPSHOT_DATE,
577
+ ...catalog.error ? { error: catalog.error } : {}
578
+ },
579
+ windows,
580
+ offers,
581
+ discounts: buildDiscounts(offers),
582
+ textModels: buildTextModels(catalog.models, windows, now),
583
+ quota: quota ?? { available: false, hint: "\u5B9E\u65F6\u989D\u5EA6\u9700: bl auth login --console --console-site domestic" },
584
+ notices: buildNotices(offers, now)
585
+ };
586
+ }
587
+ function isStale(st, now = /* @__PURE__ */ new Date()) {
588
+ const t = new Date(st.updatedAt).getTime();
589
+ if (Number.isNaN(t)) return true;
590
+ return now.getTime() - t > st.staleAfterSec * 1e3;
591
+ }
592
+ var STATUS_SIZE_CAP = 256 * 1024;
593
+ function stateDir() {
594
+ return process.env.ATP_STATE_DIR || path.join(os.homedir(), ".cache", "opencode", "alibabatokenplan");
595
+ }
596
+ function statusFilePath() {
597
+ return path.join(stateDir(), "status.json");
598
+ }
599
+ function configFilePath() {
600
+ return process.env.ATP_CONFIG_DIR ? path.join(process.env.ATP_CONFIG_DIR, "config.jsonc") : path.join(os.homedir(), ".config", "opencode", "alibabatokenplan", "config.jsonc");
601
+ }
602
+ function parseJsonc(text) {
603
+ let out = "";
604
+ let i = 0;
605
+ let inStr = false;
606
+ let esc = false;
607
+ while (i < text.length) {
608
+ const c = text[i];
609
+ const n = text[i + 1];
610
+ if (inStr) {
611
+ out += c;
612
+ if (esc) esc = false;
613
+ else if (c === "\\") esc = true;
614
+ else if (c === '"') inStr = false;
615
+ i++;
616
+ continue;
617
+ }
618
+ if (c === '"') {
619
+ inStr = true;
620
+ out += c;
621
+ i++;
622
+ continue;
623
+ }
624
+ if (c === "/" && n === "/") {
625
+ while (i < text.length && text[i] !== "\n") i++;
626
+ continue;
627
+ }
628
+ if (c === "/" && n === "*") {
629
+ i += 2;
630
+ while (i < text.length && !(text[i] === "*" && text[i + 1] === "/")) i++;
631
+ i += 2;
632
+ continue;
633
+ }
634
+ out += c;
635
+ i++;
636
+ }
637
+ let res = "";
638
+ inStr = false;
639
+ esc = false;
640
+ i = 0;
641
+ while (i < out.length) {
642
+ const c = out[i];
643
+ if (inStr) {
644
+ res += c;
645
+ if (esc) esc = false;
646
+ else if (c === "\\") esc = true;
647
+ else if (c === '"') inStr = false;
648
+ i++;
649
+ continue;
650
+ }
651
+ if (c === '"') {
652
+ inStr = true;
653
+ res += c;
654
+ i++;
655
+ continue;
656
+ }
657
+ if (c === ",") {
658
+ let j = i + 1;
659
+ while (j < out.length && /\s/.test(out[j])) j++;
660
+ if (out[j] === "}" || out[j] === "]") {
661
+ i++;
662
+ continue;
663
+ }
664
+ }
665
+ res += c;
666
+ i++;
667
+ }
668
+ return JSON.parse(res);
669
+ }
670
+ var DEFAULT_CONFIG = {
671
+ webpanel: { enabled: false, port: 7777 },
672
+ sidebar: { order: 160, showNonTokenPlan: false },
673
+ toast: true,
674
+ refresh: { catalogMin: 10, quotaMin: 5 }
675
+ };
676
+ function loadConfig() {
677
+ const d = structuredClone(DEFAULT_CONFIG);
678
+ try {
679
+ const raw = parseJsonc(fs.readFileSync(configFilePath(), "utf8"));
680
+ if (raw && typeof raw === "object") {
681
+ if (raw.webpanel?.enabled !== void 0) d.webpanel.enabled = !!raw.webpanel.enabled;
682
+ if (Number.isInteger(raw.webpanel?.port) && raw.webpanel.port >= 1024 && raw.webpanel.port <= 65535) d.webpanel.port = raw.webpanel.port;
683
+ if (Number.isInteger(raw.sidebar?.order)) d.sidebar.order = raw.sidebar.order;
684
+ if (raw.sidebar?.showNonTokenPlan !== void 0) d.sidebar.showNonTokenPlan = !!raw.sidebar.showNonTokenPlan;
685
+ if (raw.toast !== void 0) d.toast = !!raw.toast;
686
+ if (Number.isInteger(raw.refresh?.catalogMin) && raw.refresh.catalogMin > 0) d.refresh.catalogMin = raw.refresh.catalogMin;
687
+ if (Number.isInteger(raw.refresh?.quotaMin) && raw.refresh.quotaMin > 0) d.refresh.quotaMin = raw.refresh.quotaMin;
688
+ }
689
+ } catch {
690
+ }
691
+ return d;
692
+ }
693
+ function readStatusFile(now = /* @__PURE__ */ new Date()) {
694
+ let text;
695
+ try {
696
+ text = fs.readFileSync(statusFilePath(), "utf8");
697
+ } catch {
698
+ return { kind: "missing" };
699
+ }
700
+ try {
701
+ const j = JSON.parse(text);
702
+ if (!j || j.schemaVersion !== 1 || !Array.isArray(j.windows)) return { kind: "corrupt", error: "schema" };
703
+ return isStale(j, now) ? { kind: "stale", status: j } : { kind: "live", status: j };
704
+ } catch (e) {
705
+ return { kind: "corrupt", error: String(e?.message || e) };
706
+ }
707
+ }
708
+
709
+ // src/tui/read-status.ts
710
+ function recomputeWindows(status, now) {
711
+ return {
712
+ ...status,
713
+ windows: status.windows.map((w) => {
714
+ const ws = windowState(w, now);
715
+ return { ...w, activeNow: ws.active, boundaryKind: ws.boundaryKind, nextBoundaryAt: ws.nextBoundaryAt };
716
+ })
717
+ };
718
+ }
719
+ function embeddedSnapshotView(now = /* @__PURE__ */ new Date()) {
720
+ const status = buildStatus({ source: "snapshot", models: [], fetchedAt: "embedded" }, now);
721
+ return { status, degraded: true };
722
+ }
723
+ function loadStatusView(now = /* @__PURE__ */ new Date()) {
724
+ const r = readStatusFile(now);
725
+ switch (r.kind) {
726
+ case "live":
727
+ return { status: r.status, degraded: false };
728
+ case "stale":
729
+ return { status: recomputeWindows(r.status, now), degraded: true };
730
+ default:
731
+ return { status: recomputeWindows(embeddedSnapshotView(now).status, now), degraded: true };
732
+ }
733
+ }
734
+
735
+ // src/tui/index.tsx
736
+ var PKG_ID = "@te-river/opencode-alibabatokenplan";
737
+ var POLL_MS = 3e4;
738
+ var EVENT_DEBOUNCE_MS = [150, 600];
739
+ var getNow = () => Date.now();
740
+ var stopSharedTicker = null;
741
+ function h(type, props, ...children) {
742
+ const el = createElement(type);
743
+ for (const [k, v] of Object.entries(props ?? {})) {
744
+ if (k === "ref" && typeof v === "function") {
745
+ v(el);
746
+ continue;
747
+ }
748
+ if (typeof v === "function" && !k.startsWith("on")) {
749
+ effect((p) => {
750
+ setProp(el, k, v(), p);
751
+ });
752
+ } else {
753
+ setProp(el, k, v);
754
+ }
755
+ }
756
+ for (const c of children) insertChild(el, c);
757
+ return el;
758
+ }
759
+ function insertChild(parent, c) {
760
+ if (c === null || c === void 0 || c === false || c === "") return;
761
+ if (Array.isArray(c)) {
762
+ for (const x of c) insertChild(parent, x);
763
+ return;
764
+ }
765
+ insert(parent, c);
766
+ }
767
+ function line(text, style = {}) {
768
+ const props = { wrapMode: "word", ...style, content: text };
769
+ return h("text", props);
770
+ }
771
+ var TP_PROVIDER_RE = /^alibaba-token-plan(-cn)?$/i;
772
+ var TP_HOST_RE = /token-plan\.[a-z0-9-]+\.maas\.aliyuncs\.com/i;
773
+ function isTPProviderID(id) {
774
+ return typeof id === "string" && TP_PROVIDER_RE.test(id);
775
+ }
776
+ function sessionProviderID(api, sessionID) {
777
+ try {
778
+ const sess = api.state.session.get(sessionID);
779
+ const direct = sess?.data?.model?.providerID ?? sess?.model?.providerID ?? sess?.providerID;
780
+ if (typeof direct === "string" && direct) return direct;
781
+ } catch {
782
+ }
783
+ try {
784
+ const msgs = api.state.session.messages(sessionID) ?? [];
785
+ for (let i = msgs.length - 1; i >= 0; i--) {
786
+ const mi = msgs[i]?.info ?? msgs[i];
787
+ if (typeof mi?.providerID === "string" && mi.providerID) return mi.providerID;
788
+ }
789
+ } catch {
790
+ }
791
+ return void 0;
792
+ }
793
+ function sessionIsTokenPlan(api, sessionID) {
794
+ const pid = sessionProviderID(api, sessionID);
795
+ if (isTPProviderID(pid)) return true;
796
+ if (!pid) return false;
797
+ try {
798
+ const p = api.state.provider?.find((x) => x?.id === pid || x?.provider?.id === pid);
799
+ const base = p?.options?.baseURL ?? p?.provider?.options?.baseURL ?? "";
800
+ return TP_HOST_RE.test(String(base));
801
+ } catch {
802
+ return false;
803
+ }
804
+ }
805
+ function anyTokenPlanProvider(api) {
806
+ try {
807
+ for (const p of api.state.provider ?? []) {
808
+ if (isTPProviderID(p?.id) || isTPProviderID(p?.provider?.id)) return true;
809
+ const base = p?.options?.baseURL ?? p?.provider?.options?.baseURL ?? "";
810
+ if (TP_HOST_RE.test(String(base))) return true;
811
+ }
812
+ } catch {
813
+ }
814
+ return false;
815
+ }
816
+ function createViewState(api) {
817
+ const cfg = loadConfig();
818
+ const [view, setView] = createSignal(loadStatusView());
819
+ const refresh = () => {
820
+ try {
821
+ setView(loadStatusView());
822
+ } catch {
823
+ }
824
+ };
825
+ const timer = setInterval(refresh, POLL_MS);
826
+ timer.unref?.();
827
+ const offs = [];
828
+ let t1, t2;
829
+ const debounced = () => {
830
+ clearTimeout(t1);
831
+ clearTimeout(t2);
832
+ t1 = setTimeout(refresh, EVENT_DEBOUNCE_MS[0]);
833
+ t2 = setTimeout(refresh, EVENT_DEBOUNCE_MS[1]);
834
+ };
835
+ for (const ev of ["message.updated", "session.idle", "tui.session.select"]) {
836
+ try {
837
+ offs.push(api.event.on(ev, debounced));
838
+ } catch {
839
+ }
840
+ }
841
+ onCleanup(() => {
842
+ clearInterval(timer);
843
+ clearTimeout(t1);
844
+ clearTimeout(t2);
845
+ for (const off of offs) {
846
+ try {
847
+ off();
848
+ } catch {
849
+ }
850
+ }
851
+ });
852
+ return { get: view, refresh, cfg };
853
+ }
854
+ function wState(w, now) {
855
+ return windowState(w, new Date(now));
856
+ }
857
+ function currentWindow(st, now) {
858
+ return [...st.windows].sort((a, b) => {
859
+ const aa = wState(a, now).active ? 0 : 1, bb = wState(b, now).active ? 0 : 1;
860
+ if (aa !== bb) return aa - bb;
861
+ return new Date(a.nextBoundaryAt).getTime() - new Date(b.nextBoundaryAt).getTime();
862
+ })[0];
863
+ }
864
+ function sessionModelName(api, sessionID) {
865
+ try {
866
+ const sess = api.state.session.get(sessionID);
867
+ const m = sess?.data?.model?.modelID ?? sess?.model?.modelID ?? sess?.model?.id;
868
+ if (m) return canonicalize(String(m));
869
+ const msgs = api.state.session.messages(sessionID) ?? [];
870
+ for (let i = msgs.length - 1; i >= 0; i--) {
871
+ const mi = msgs[i]?.info ?? msgs[i];
872
+ if (mi?.modelID) return canonicalize(String(mi.modelID));
873
+ }
874
+ } catch {
875
+ }
876
+ return void 0;
877
+ }
878
+ function strikethroughText(s) {
879
+ return s.replace(/[^\u0300-\u036f]/g, (c) => c + "\u0336");
880
+ }
881
+ function buildSidebar(api, vs, sessionID, collapsed, toggle) {
882
+ const theme = () => api.theme.current;
883
+ const now = getNow;
884
+ const header = h(
885
+ "box",
886
+ { style: { flexDirection: "row", gap: 1 }, onMouseUp: toggle },
887
+ line("Token Plan \u4F18\u60E0", { fg: () => theme().text }),
888
+ line(
889
+ () => vs.get().degraded ? `\u9759\u6001\u5FEB\u7167(\u622A\u81F3${QODER_SNAPSHOT_DATE})` : sourceBadgeLine(vs.get().status),
890
+ { fg: () => vs.get().degraded ? theme().textMuted : theme().success }
891
+ )
892
+ );
893
+ const body = h("box", { style: { flexDirection: "column" } });
894
+ insert(body, () => {
895
+ const sv = vs.get();
896
+ if (collapsed()) {
897
+ return line(() => "\u5DF2\u6298\u53E0 \xB7 /tokenplan \u67E5\u770B", { fg: theme().textMuted });
898
+ }
899
+ const s = sv.status;
900
+ const w = currentWindow(s, now());
901
+ if (!w) return line("\u6682\u65E0\u5DF2\u77E5\u4F18\u60E0\u7A97\u53E3", { fg: theme().textMuted });
902
+ const star = w.models[0];
903
+ const rows = [
904
+ line(() => `${wState(w, now()).active ? "\u25B8" : "\u25B9"} ${w.label} ${w.start}\u2013${w.end}${star?.effective ? " " + star.effective : ""}`, {
905
+ fg: () => wState(w, now()).active ? theme().success : theme().text
906
+ }),
907
+ line(() => `${fmtHMS(new Date(w.nextBoundaryAt).getTime() - now())} ${wState(w, now()).active ? "\u540E\u7ED3\u675F\xB7\u8FDB\u884C\u4E2D" : "\u540E\u5F00\u59CB"}`, {
908
+ fg: () => wState(w, now()).active ? theme().success : theme().warning
909
+ })
910
+ ];
911
+ const model = sessionModelName(api, sessionID);
912
+ if (model) {
913
+ const entry = s.discounts.find((d) => d.model === model);
914
+ const qf = QODER_INSALE_FACTORS[model];
915
+ const factorRules = (entry?.applies ?? []).filter(
916
+ (r) => r.unit === "credits-factor" && r.factor !== void 0 && (r.kind === "time-window" && r.window ? wState(r.window, now()).active : true)
917
+ );
918
+ const cur = factorRules[factorRules.length - 1];
919
+ const fStr = fmtFactor(cur?.factor ?? qf?.factor) ?? "\u2014";
920
+ const bStr = fmtFactor(cur?.baseFactor ?? qf?.factor);
921
+ const struck = bStr && bStr !== fStr ? strikethroughText(bStr) + " " : "";
922
+ rows.push(
923
+ line(
924
+ () => `\u5F53\u524D ${model} ${struck}${fmtFactor(cur?.factor ?? QODER_INSALE_FACTORS[model]?.factor)}${cur ? ` ${cur.label}${isCrossVerified(cur) ? " [\u53CC\u6E90\u5370\u8BC1]" : ""}` : " Credits\u500D\u7387"}`,
925
+ { fg: () => theme().accent }
926
+ )
927
+ );
928
+ }
929
+ for (const o of s.windows.filter((x) => x.id !== w.id).slice(0, 2)) {
930
+ rows.push(
931
+ line(() => {
932
+ const ws = wState(o, now());
933
+ return `${o.label} \xB7 ${ws.active ? "\u5269" : "\u8DDD"} ${fmtShortDur(new Date(o.nextBoundaryAt).getTime() - now())}${ws.active ? "\u7ED3\u675F" : "\u5F00\u59CB"}`;
934
+ }, { fg: () => theme().textMuted })
935
+ );
936
+ }
937
+ if (sv.degraded) rows.push(line(`\u5FEB\u7167\u53EF\u80FD\u5931\u6548 \xB7 \u6570\u636E\u622A\u81F3 ${QODER_SNAPSHOT_DATE}`, { fg: () => theme().warning }));
938
+ return h("box", { style: { flexDirection: "column" } }, ...rows.slice(0, 6));
939
+ });
940
+ return h("box", { style: { flexDirection: "column" } }, header, body);
941
+ }
942
+ var TABS = [
943
+ { key: "windows", title: "\u7A97\u53E3\u5012\u8BA1\u65F6", desc: "\u5404\u4F18\u60E0\u7A97\u53E3\u4E0E\u4E0B\u4E00\u8FB9\u754C\uFF08\u6BCF\u79D2\u8D70\u5B57\uFF09" },
944
+ { key: "models", title: "\u6A21\u578B\u500D\u7387", desc: "\u767E\u70BC\u76EE\u5F55\u4EF7 + Qoder \u5728\u5356\u500D\u7387" },
945
+ { key: "limited", title: "\u9650\u65F6\u4F18\u60E0", desc: "flat-cut \u660E\u7EC6\uFF08\u4E24\u5355\u4F4D\u4E0D\u4E92\u7B97\uFF09" },
946
+ { key: "free", title: "\u514D\u8D39\u6D3B\u52A8", desc: "free-calls / policy \u4E0E\u65F6\u6548\u58F0\u660E" },
947
+ { key: "about", title: "\u53E3\u5F84\u8BF4\u660E", desc: "\u6570\u636E\u6E90\u3001\u6C47\u7387\u3001\u5FEB\u7167\u7EA2\u7EBF" }
948
+ ];
949
+ function openMainDialog(api, vs) {
950
+ try {
951
+ api.ui.dialog.replace(
952
+ () => createComponent(api.ui.DialogSelect, {
953
+ title: `Token Plan \u4F18\u60E0\uFF08Qoder \u5FEB\u7167\u622A\u81F3 ${QODER_SNAPSHOT_DATE}\uFF09`,
954
+ options: TABS.map((t) => ({ title: t.title, value: t.key, description: t.desc })),
955
+ current: (() => {
956
+ try {
957
+ return api.kv.get("atp.lastDialogTab", "windows");
958
+ } catch {
959
+ return "windows";
960
+ }
961
+ })(),
962
+ onSelect: (opt) => {
963
+ try {
964
+ api.kv.set("atp.lastDialogTab", opt.value);
965
+ } catch {
966
+ }
967
+ openTabDialog(api, vs, String(opt.value));
968
+ }
969
+ })
970
+ );
971
+ api.ui.dialog.setSize("medium");
972
+ } catch {
973
+ try {
974
+ api.ui.toast({ variant: "error", message: "Token Plan \u9762\u677F\u6253\u5F00\u5931\u8D25" });
975
+ } catch {
976
+ }
977
+ }
978
+ }
979
+ function openTabDialog(api, vs, tab) {
980
+ const theme = () => api.theme.current;
981
+ const now = getNow;
982
+ const rowsFor = () => {
983
+ const s = vs.get().status;
984
+ const tt = now();
985
+ switch (tab) {
986
+ case "windows":
987
+ return s.windows.flatMap((w) => {
988
+ const ws = wState(w, tt);
989
+ const head = `${w.label} [${w.start}\u2013${w.end}] ${ws.active ? "\u8FDB\u884C\u4E2D" : "\u672A\u5F00\u59CB"} \xB7 ${ws.active ? "\u8DDD\u7ED3\u675F" : "\u8DDD\u5F00\u59CB"} ${fmtHMS(new Date(w.nextBoundaryAt).getTime() - tt)}`;
990
+ const rows = w.models.map(
991
+ (m) => ` ${m.model} ${m.regular && m.regular !== m.effective ? strikethroughText(m.regular) + " " : ""}${m.effective ?? ""} ${m.badge ?? ""}${isCrossVerified(m) ? " [\u53CC\u6E90\u5370\u8BC1]" : ""}`
992
+ );
993
+ return [head, ...rows];
994
+ });
995
+ case "models":
996
+ return [
997
+ "\u2014 \u767E\u70BC\u76EE\u5F55\uFF08Credits/\u767E\u4E07tok\uFF09\u2014",
998
+ ...s.textModels.map((m) => `${m.model} \u8F93\u5165 ${m.input} \u7F13\u5B58 ${m.cache} \u8F93\u51FA ${m.output}${m.bands ? `\uFF08${m.bands}\uFF09` : ""}${m.creditsFactor ? ` \xB7 Qoder ${m.creditsFactor}` : ""}${m.badge ? ` \xB7 ${m.badge.label}` : ""}`),
999
+ "\u2014 Qoder \u5728\u5356\u500D\u7387\uFF08\u5FEB\u7167\uFF0C\u975E\u4F18\u60E0\uFF09\u2014",
1000
+ ...Object.entries(QODER_INSALE_FACTORS).map(([k, v]) => `${k} ${fmtFactor(v.factor)}${v.note ? ` (${v.note})` : ""}`)
1001
+ ];
1002
+ case "limited":
1003
+ return s.offers.filter((r) => r.kind === "flat-cut").flatMap((r) => [
1004
+ `${r.models.join(", ")} \xD7${r.factor ?? "-"}\uFF08${r.unit === "credits-factor" ? "Qoder Credits" : "\u767E\u70BC \xA5"}\uFF09${r.batchStack !== void 0 ? ` Batch\u53E0\u52A0\xD7${r.batchStack}` : ""}`,
1005
+ ...r.note ? [" " + r.note] : []
1006
+ ]);
1007
+ case "free":
1008
+ return s.notices.map((n) => `[${n.kind}] ${n.text}${n.expired ? "\uFF08\u5DF2\u8FC7\u671F\uFF0C\u5FEB\u7167\u53EF\u80FD\u5931\u6548\uFF09" : ""} \xB7 ${n.source}`);
1009
+ default:
1010
+ return [
1011
+ `\u767E\u70BC\u76EE\u5F55\u6E90: ${s.sources.catalog}\uFF08live\u2192cli\u2192snapshot \u4E09\u7EA7\u56DE\u9000\uFF09`,
1012
+ `Qoder \u5FEB\u7167: ${s.sources.qoderSnapshot}\uFF08\u9759\u6001\uFF0C\u4E0D\u8C03\u7528\u4EFB\u4F55 Qoder \u79C1\u6709\u7AEF\u70B9\uFF09`,
1013
+ `\u6362\u7B97\u6CE8\u8BB0: 1 \u5143 = ${s.creditsPerYuan} Credits\uFF08${s.creditsRateNote}\uFF09`,
1014
+ "\u4E24\u5957\u5355\u4F4D\u6C38\u4E0D\u4E92\u7B97\uFF1ACredits \u500D\u7387\u4E0E \xA5 \u4EF7\u683C\u500D\u7387\u5404\u5C45\u5176 facet",
1015
+ "\u6298\u540E\u6D88\u8017\u663E\u793A\u300C\u6D88\u8017/Cost\uFF0C\u5355\u4F4D Credit\u300D\uFF1BruntimeConfig \u4F1A\u53D8\u5B9E\u9645\u6D88\u8017",
1016
+ s.quota.available ? `\u989D\u5EA6: \u5B9E\u65F6\uFF08${s.quota.checkedAt}\uFF09` : `\u989D\u5EA6: ${s.quota.hint ?? "\u4E0D\u53EF\u7528"}`
1017
+ ];
1018
+ }
1019
+ };
1020
+ try {
1021
+ api.ui.dialog.replace(() => {
1022
+ const scroll = h("scrollbox", { style: { flexGrow: 1, flexDirection: "column" } });
1023
+ insert(scroll, () => {
1024
+ const container = h("box", { style: { flexDirection: "column" } });
1025
+ for (const r of rowsFor()) insertChild(container, line(r));
1026
+ return container;
1027
+ });
1028
+ return h(
1029
+ "box",
1030
+ { style: { flexDirection: "column", height: "100%" } },
1031
+ line(`${TABS.find((x) => x.key === tab)?.title ?? tab} \xB7 \u6570\u636E\u622A\u81F3 ${QODER_SNAPSHOT_DATE} \xB7 esc \u5173\u95ED`, { fg: () => theme().textMuted }),
1032
+ scroll
1033
+ );
1034
+ });
1035
+ api.ui.dialog.setSize("large");
1036
+ } catch {
1037
+ try {
1038
+ api.ui.toast({ variant: "error", message: "Token Plan \u660E\u7EC6\u6253\u5F00\u5931\u8D25" });
1039
+ } catch {
1040
+ }
1041
+ }
1042
+ }
1043
+ var tui = async (api, options, _meta) => {
1044
+ const g = globalThis;
1045
+ if (g.__atpTuiLoaded) return;
1046
+ g.__atpTuiLoaded = true;
1047
+ const [nowSig, setNowSig] = createSignal(Date.now());
1048
+ getNow = nowSig;
1049
+ const sharedTick = setInterval(() => setNowSig(Date.now()), 1e3);
1050
+ sharedTick.unref?.();
1051
+ let stopped = false;
1052
+ stopSharedTicker = () => {
1053
+ if (stopped) return;
1054
+ stopped = true;
1055
+ clearInterval(sharedTick);
1056
+ };
1057
+ try {
1058
+ onCleanup(stopSharedTicker);
1059
+ } catch {
1060
+ }
1061
+ const stopTicker = stopSharedTicker;
1062
+ const vs = createViewState(api);
1063
+ let cfg = vs.cfg;
1064
+ try {
1065
+ const extra = typeof options === "string" ? void 0 : options;
1066
+ if (extra && typeof extra === "object") {
1067
+ for (const k of ["sidebar", "webpanel", "toast", "refresh"]) {
1068
+ const v = extra[k];
1069
+ if (v === void 0) continue;
1070
+ if (typeof v !== "object" || v === null) cfg[k] = v;
1071
+ else Object.assign(cfg[k] ?? {}, v);
1072
+ }
1073
+ }
1074
+ } catch {
1075
+ }
1076
+ const collapsed = () => {
1077
+ try {
1078
+ return !!api.kv.get("atp.collapsed", false);
1079
+ } catch {
1080
+ return false;
1081
+ }
1082
+ };
1083
+ const toggleCollapsed = () => {
1084
+ try {
1085
+ api.kv.set("atp.collapsed", !collapsed());
1086
+ } catch {
1087
+ }
1088
+ };
1089
+ api.slots.register({
1090
+ order: cfg.sidebar.order,
1091
+ slots: {
1092
+ sidebar_content(_ctx, props) {
1093
+ const sid = String(props?.session_id ?? "");
1094
+ if (!sid) return null;
1095
+ const tp = sessionIsTokenPlan(api, sid);
1096
+ if (!tp && !cfg.sidebar.showNonTokenPlan) return null;
1097
+ if (!tp) {
1098
+ return line(() => {
1099
+ const w = currentWindow(vs.get().status, Date.now());
1100
+ return w ? `Token Plan: ${w.label} ${wState(w, Date.now()).active ? "\u8FDB\u884C\u4E2D" : w.start + " \u5F00\u59CB"}` : "Token Plan: \u65E0\u5DF2\u77E5\u7A97\u53E3";
1101
+ }, { fg: () => api.theme.current.textMuted });
1102
+ }
1103
+ return buildSidebar(api, vs, sid, collapsed, toggleCollapsed);
1104
+ },
1105
+ home_bottom() {
1106
+ if (!anyTokenPlanProvider(api)) return null;
1107
+ return line(() => homeSummaryLine(vs.get().status, new Date(getNow())), { fg: () => api.theme.current.textMuted });
1108
+ }
1109
+ }
1110
+ });
1111
+ const dispose = api.keymap.registerLayer({
1112
+ commands: [{
1113
+ namespace: "palette",
1114
+ name: "tokenplan.main",
1115
+ title: "Token Plan \u4F18\u60E0\u603B\u89C8",
1116
+ desc: `\u767E\u70BC+Qoder \u5FEB\u7167\u4F18\u60E0\u7A97\u53E3/\u500D\u7387/\u5012\u8BA1\u65F6\uFF08\u6570\u636E\u622A\u81F3 ${QODER_SNAPSHOT_DATE}\uFF09`,
1117
+ category: "Token Plan",
1118
+ slashName: "tokenplan",
1119
+ run: () => {
1120
+ vs.refresh();
1121
+ openMainDialog(api, vs);
1122
+ }
1123
+ }],
1124
+ bindings: []
1125
+ });
1126
+ api.lifecycle.onDispose(() => {
1127
+ try {
1128
+ stopTicker();
1129
+ } catch {
1130
+ }
1131
+ try {
1132
+ dispose();
1133
+ } catch {
1134
+ }
1135
+ });
1136
+ };
1137
+ var index_default = { id: PKG_ID, tui };
1138
+ var AliBabaTokenPlanTui = tui;
1139
+ export {
1140
+ AliBabaTokenPlanTui,
1141
+ index_default as default
1142
+ };