koishi-plugin-chatluna-usage 1.0.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/lib/index.d.ts ADDED
@@ -0,0 +1,311 @@
1
+ import { Context, Schema } from 'koishi';
2
+ import { DataService } from '@koishijs/plugin-console';
3
+ import type { UsageMetadata } from '@langchain/core/messages';
4
+ import type { ModelUsageCallType } from 'koishi-plugin-chatluna/llm-core/platform/usage';
5
+ declare class ChatLunaUsage extends DataService<ChatLunaUsage.Payload> {
6
+ config: ChatLunaUsage.Config;
7
+ constructor(ctx: Context, config: ChatLunaUsage.Config);
8
+ get(): Promise<{
9
+ query: {
10
+ period: ChatLunaUsage.Period;
11
+ groupBy: ChatLunaUsage.GroupBy;
12
+ sortBy: ChatLunaUsage.SortBy;
13
+ desc: boolean;
14
+ page: number;
15
+ pageSize: number;
16
+ listSortBy: ChatLunaUsage.ListSortBy;
17
+ listDesc: boolean;
18
+ start: Date;
19
+ end: Date;
20
+ source?: string;
21
+ model?: string;
22
+ platform?: string;
23
+ chatPlatform?: string;
24
+ callType?: ModelUsageCallType;
25
+ guildId?: string;
26
+ userId?: string;
27
+ success?: boolean;
28
+ estimated?: boolean;
29
+ keyword?: string;
30
+ };
31
+ totals: ChatLunaUsage.Summary;
32
+ groups: ChatLunaUsage.Summary[];
33
+ models: ChatLunaUsage.Summary[];
34
+ sources: ChatLunaUsage.Summary[];
35
+ timeline: ChatLunaUsage.Timeline[];
36
+ modelTimeline: {
37
+ model: string;
38
+ points: {
39
+ date: string;
40
+ calls: number;
41
+ }[];
42
+ }[];
43
+ list: {
44
+ total: number;
45
+ page: number;
46
+ pageSize: number;
47
+ rows: {
48
+ inputTokens: number;
49
+ outputTokens: number;
50
+ totalTokens: number;
51
+ estimated: boolean;
52
+ cachedTokens: number;
53
+ reasoningTokens: number;
54
+ id?: number;
55
+ source: string;
56
+ callType: ModelUsageCallType;
57
+ platform: string;
58
+ chatPlatform?: string | null;
59
+ model: string;
60
+ usageMetadata: UsageMetadata;
61
+ success: boolean;
62
+ createdAt: Date;
63
+ conversationId?: string | null;
64
+ requestId?: string | null;
65
+ userId?: string | null;
66
+ guildId?: string | null;
67
+ }[];
68
+ };
69
+ }>;
70
+ query(input?: ChatLunaUsage.Query): Promise<{
71
+ query: {
72
+ period: ChatLunaUsage.Period;
73
+ groupBy: ChatLunaUsage.GroupBy;
74
+ sortBy: ChatLunaUsage.SortBy;
75
+ desc: boolean;
76
+ page: number;
77
+ pageSize: number;
78
+ listSortBy: ChatLunaUsage.ListSortBy;
79
+ listDesc: boolean;
80
+ start: Date;
81
+ end: Date;
82
+ source?: string;
83
+ model?: string;
84
+ platform?: string;
85
+ chatPlatform?: string;
86
+ callType?: ModelUsageCallType;
87
+ guildId?: string;
88
+ userId?: string;
89
+ success?: boolean;
90
+ estimated?: boolean;
91
+ keyword?: string;
92
+ };
93
+ totals: ChatLunaUsage.Summary;
94
+ groups: ChatLunaUsage.Summary[];
95
+ models: ChatLunaUsage.Summary[];
96
+ sources: ChatLunaUsage.Summary[];
97
+ timeline: ChatLunaUsage.Timeline[];
98
+ modelTimeline: {
99
+ model: string;
100
+ points: {
101
+ date: string;
102
+ calls: number;
103
+ }[];
104
+ }[];
105
+ list: {
106
+ total: number;
107
+ page: number;
108
+ pageSize: number;
109
+ rows: {
110
+ inputTokens: number;
111
+ outputTokens: number;
112
+ totalTokens: number;
113
+ estimated: boolean;
114
+ cachedTokens: number;
115
+ reasoningTokens: number;
116
+ id?: number;
117
+ source: string;
118
+ callType: ModelUsageCallType;
119
+ platform: string;
120
+ chatPlatform?: string | null;
121
+ model: string;
122
+ usageMetadata: UsageMetadata;
123
+ success: boolean;
124
+ createdAt: Date;
125
+ conversationId?: string | null;
126
+ requestId?: string | null;
127
+ userId?: string | null;
128
+ guildId?: string | null;
129
+ }[];
130
+ };
131
+ }>;
132
+ list(input?: ChatLunaUsage.Query): Promise<{
133
+ total: number;
134
+ page: number;
135
+ pageSize: number;
136
+ rows: {
137
+ inputTokens: number;
138
+ outputTokens: number;
139
+ totalTokens: number;
140
+ estimated: boolean;
141
+ cachedTokens: number;
142
+ reasoningTokens: number;
143
+ id?: number;
144
+ source: string;
145
+ callType: ModelUsageCallType;
146
+ platform: string;
147
+ chatPlatform?: string | null;
148
+ model: string;
149
+ usageMetadata: UsageMetadata;
150
+ success: boolean;
151
+ createdAt: Date;
152
+ conversationId?: string | null;
153
+ requestId?: string | null;
154
+ userId?: string | null;
155
+ guildId?: string | null;
156
+ }[];
157
+ }>;
158
+ cleanup(before?: Date): Promise<void>;
159
+ private search;
160
+ private withDefaults;
161
+ private pageRows;
162
+ private add;
163
+ private finish;
164
+ private groupKey;
165
+ private groupLabel;
166
+ private dateKey;
167
+ }
168
+ declare namespace ChatLunaUsage {
169
+ interface Record {
170
+ id?: number;
171
+ source: string;
172
+ callType: ModelUsageCallType;
173
+ platform: string;
174
+ chatPlatform?: string | null;
175
+ model: string;
176
+ usageMetadata: UsageMetadata;
177
+ estimated: boolean;
178
+ success: boolean;
179
+ createdAt: Date;
180
+ conversationId?: string | null;
181
+ requestId?: string | null;
182
+ userId?: string | null;
183
+ guildId?: string | null;
184
+ }
185
+ interface ListRow extends Record {
186
+ inputTokens: number;
187
+ outputTokens: number;
188
+ totalTokens: number;
189
+ estimated: boolean;
190
+ cachedTokens: number;
191
+ reasoningTokens: number;
192
+ }
193
+ type Period = 'day' | 'month' | 'year';
194
+ type GroupBy = 'source' | 'model' | 'guild' | 'platform' | 'chatPlatform' | 'callType';
195
+ type SortBy = 'calls' | 'successfulCalls' | 'failedCalls' | 'inputTokens' | 'outputTokens' | 'totalTokens' | 'estimatedTokens' | 'cachedTokens' | 'reasoningTokens' | 'successRate';
196
+ type ListSortBy = 'createdAt' | 'inputTokens' | 'outputTokens' | 'totalTokens' | 'cachedTokens' | 'reasoningTokens';
197
+ interface Query {
198
+ period?: Period;
199
+ start?: string | Date;
200
+ end?: string | Date;
201
+ groupBy?: GroupBy;
202
+ sortBy?: SortBy;
203
+ desc?: boolean;
204
+ page?: number;
205
+ pageSize?: number;
206
+ listSortBy?: ListSortBy;
207
+ listDesc?: boolean;
208
+ source?: string;
209
+ model?: string;
210
+ platform?: string;
211
+ chatPlatform?: string;
212
+ callType?: ModelUsageCallType;
213
+ guildId?: string;
214
+ userId?: string;
215
+ success?: boolean;
216
+ estimated?: boolean;
217
+ keyword?: string;
218
+ }
219
+ interface Summary {
220
+ key: string;
221
+ label: string;
222
+ platform?: string;
223
+ calls: number;
224
+ successfulCalls: number;
225
+ failedCalls: number;
226
+ inputTokens: number;
227
+ outputTokens: number;
228
+ totalTokens: number;
229
+ estimatedTokens: number;
230
+ cachedTokens: number;
231
+ reasoningTokens: number;
232
+ successRate: number;
233
+ lastSeen?: Date;
234
+ }
235
+ interface Timeline {
236
+ date: string;
237
+ calls: number;
238
+ inputTokens: number;
239
+ outputTokens: number;
240
+ totalTokens: number;
241
+ cachedTokens: number;
242
+ reasoningTokens: number;
243
+ }
244
+ interface ModelTimeline {
245
+ model: string;
246
+ points: {
247
+ date: string;
248
+ calls: number;
249
+ }[];
250
+ }
251
+ interface List {
252
+ total: number;
253
+ page: number;
254
+ pageSize: number;
255
+ rows: ListRow[];
256
+ }
257
+ interface Payload {
258
+ query: Required<Pick<Query, 'period' | 'groupBy' | 'sortBy' | 'desc' | 'page' | 'pageSize' | 'listSortBy' | 'listDesc'>> & {
259
+ start: Date;
260
+ end: Date;
261
+ } & Query;
262
+ totals: Summary;
263
+ groups: Summary[];
264
+ models: Summary[];
265
+ sources: Summary[];
266
+ timeline: Timeline[];
267
+ modelTimeline: ModelTimeline[];
268
+ list: List;
269
+ }
270
+ interface Config {
271
+ recentDays: number;
272
+ pageSize: number;
273
+ webui: boolean;
274
+ }
275
+ interface ActionResult {
276
+ success: boolean;
277
+ }
278
+ const Config: Schema<Config>;
279
+ const inject: string[];
280
+ }
281
+ export default ChatLunaUsage;
282
+ export { ChatLunaUsage };
283
+ export declare function queryUsage(ctx: Context, source?: string): Promise<ChatLunaUsage.Summary[]>;
284
+ export declare function cleanupUsage(ctx: Context, before?: Date): Promise<void>;
285
+ export declare function apply(ctx: Context, config: ChatLunaUsage.Config): void;
286
+ export declare const Config: Schema<ChatLunaUsage.Config>;
287
+ export declare const inject: {
288
+ required: string[];
289
+ optional: string[];
290
+ };
291
+ export declare const name = "chatluna-usage";
292
+ declare module 'koishi' {
293
+ interface Context {
294
+ chatluna_usage: ChatLunaUsage;
295
+ }
296
+ interface Tables {
297
+ chatluna_usage: ChatLunaUsage.Record;
298
+ }
299
+ }
300
+ declare module '@koishijs/plugin-console' {
301
+ namespace Console {
302
+ interface Services {
303
+ chatluna_usage: ChatLunaUsage;
304
+ }
305
+ }
306
+ interface Events {
307
+ 'chatluna-usage/query': (input?: ChatLunaUsage.Query) => Promise<ChatLunaUsage.Payload>;
308
+ 'chatluna-usage/list': (input?: ChatLunaUsage.Query) => Promise<ChatLunaUsage.List>;
309
+ 'chatluna-usage/cleanup': (before?: string) => Promise<ChatLunaUsage.ActionResult>;
310
+ }
311
+ }
package/lib/index.mjs ADDED
@@ -0,0 +1,396 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
+
4
+ // src/index.ts
5
+ import { Logger, Schema, Time } from "koishi";
6
+ import { DataService } from "@koishijs/plugin-console";
7
+ import { resolve } from "path";
8
+ var logger = new Logger("chatluna-usage");
9
+ var ChatLunaUsage = class extends DataService {
10
+ constructor(ctx, config) {
11
+ super(ctx, "chatluna_usage", {
12
+ immediate: true
13
+ });
14
+ this.config = config;
15
+ ctx.database.extend(
16
+ "chatluna_usage",
17
+ {
18
+ id: "unsigned",
19
+ source: { type: "char", length: 128 },
20
+ callType: { type: "char", length: 20 },
21
+ platform: { type: "char", length: 128 },
22
+ chatPlatform: { type: "char", length: 128, nullable: true },
23
+ model: { type: "char", length: 255 },
24
+ usageMetadata: {
25
+ type: "json",
26
+ nullable: false,
27
+ initial: {
28
+ input_tokens: 0,
29
+ output_tokens: 0,
30
+ total_tokens: 0
31
+ }
32
+ },
33
+ estimated: "boolean",
34
+ success: "boolean",
35
+ createdAt: { type: "timestamp", nullable: false },
36
+ conversationId: { type: "char", length: 255, nullable: true },
37
+ requestId: { type: "char", length: 255, nullable: true },
38
+ userId: { type: "char", length: 255, nullable: true },
39
+ guildId: { type: "char", length: 255, nullable: true }
40
+ },
41
+ {
42
+ autoInc: true,
43
+ primary: "id",
44
+ indexes: ["createdAt", "source", "model", "guildId"]
45
+ }
46
+ );
47
+ ctx.on("chatluna/model-usage", async (usage) => {
48
+ try {
49
+ await ctx.database.create("chatluna_usage", {
50
+ source: usage.source,
51
+ callType: usage.callType,
52
+ platform: usage.platform,
53
+ chatPlatform: usage.context?.chatPlatform ?? null,
54
+ model: usage.model,
55
+ usageMetadata: usage.usageMetadata,
56
+ estimated: usage.estimated,
57
+ success: usage.success,
58
+ createdAt: usage.createdAt,
59
+ conversationId: usage.context?.conversationId ?? null,
60
+ requestId: usage.context?.requestId ?? null,
61
+ userId: usage.context?.userId ?? null,
62
+ guildId: usage.context?.guildId ?? null
63
+ });
64
+ if (config.webui) await this.refresh();
65
+ } catch (e) {
66
+ logger.error(e);
67
+ }
68
+ });
69
+ if (!config.webui) return;
70
+ ctx.inject(["console"], (ctx2) => {
71
+ ctx2.console.addListener(
72
+ "chatluna-usage/query",
73
+ async (input) => this.query(input)
74
+ );
75
+ ctx2.console.addListener(
76
+ "chatluna-usage/list",
77
+ async (input) => this.list(input)
78
+ );
79
+ ctx2.console.addListener(
80
+ "chatluna-usage/cleanup",
81
+ async (before) => {
82
+ await this.cleanup(before ? new Date(before) : void 0);
83
+ await this.refresh();
84
+ return { success: true };
85
+ }
86
+ );
87
+ ctx2.console.addEntry({
88
+ dev: resolve(__dirname, "../client/index.ts"),
89
+ prod: resolve(__dirname, "../dist")
90
+ });
91
+ });
92
+ }
93
+ static {
94
+ __name(this, "ChatLunaUsage");
95
+ }
96
+ async get() {
97
+ return await this.query();
98
+ }
99
+ async query(input = {}) {
100
+ const rows = await this.search(input);
101
+ const groupBy = input.groupBy ?? "model";
102
+ const sortBy = input.sortBy ?? "totalTokens";
103
+ const desc = input.desc ?? true;
104
+ const groups = /* @__PURE__ */ new Map();
105
+ const models = /* @__PURE__ */ new Map();
106
+ const sources = /* @__PURE__ */ new Map();
107
+ const timeline = /* @__PURE__ */ new Map();
108
+ const modelTimeline = /* @__PURE__ */ new Map();
109
+ const totals = {
110
+ key: "total",
111
+ label: "全部用量",
112
+ calls: 0,
113
+ successfulCalls: 0,
114
+ failedCalls: 0,
115
+ inputTokens: 0,
116
+ outputTokens: 0,
117
+ totalTokens: 0,
118
+ estimatedTokens: 0,
119
+ cachedTokens: 0,
120
+ reasoningTokens: 0,
121
+ successRate: 0
122
+ };
123
+ for (const row of rows) {
124
+ const key = this.groupKey(row, groupBy);
125
+ const item = groups.get(key) ?? {
126
+ key,
127
+ label: this.groupLabel(key, groupBy),
128
+ platform: groupBy === "model" ? row.platform : void 0,
129
+ calls: 0,
130
+ successfulCalls: 0,
131
+ failedCalls: 0,
132
+ inputTokens: 0,
133
+ outputTokens: 0,
134
+ totalTokens: 0,
135
+ estimatedTokens: 0,
136
+ cachedTokens: 0,
137
+ reasoningTokens: 0,
138
+ successRate: 0
139
+ };
140
+ const model = models.get(row.model) ?? {
141
+ key: row.model,
142
+ label: row.model,
143
+ platform: row.platform,
144
+ calls: 0,
145
+ successfulCalls: 0,
146
+ failedCalls: 0,
147
+ inputTokens: 0,
148
+ outputTokens: 0,
149
+ totalTokens: 0,
150
+ estimatedTokens: 0,
151
+ cachedTokens: 0,
152
+ reasoningTokens: 0,
153
+ successRate: 0
154
+ };
155
+ const source = sources.get(row.source) ?? {
156
+ key: row.source,
157
+ label: row.source,
158
+ calls: 0,
159
+ successfulCalls: 0,
160
+ failedCalls: 0,
161
+ inputTokens: 0,
162
+ outputTokens: 0,
163
+ totalTokens: 0,
164
+ estimatedTokens: 0,
165
+ cachedTokens: 0,
166
+ reasoningTokens: 0,
167
+ successRate: 0
168
+ };
169
+ const date = this.dateKey(row.createdAt, input.period ?? "day");
170
+ const point = timeline.get(date) ?? {
171
+ date,
172
+ calls: 0,
173
+ inputTokens: 0,
174
+ outputTokens: 0,
175
+ totalTokens: 0,
176
+ cachedTokens: 0,
177
+ reasoningTokens: 0
178
+ };
179
+ this.add(row, item);
180
+ this.add(row, model);
181
+ this.add(row, source);
182
+ this.add(row, totals);
183
+ point.calls += 1;
184
+ point.inputTokens += row.usageMetadata.input_tokens;
185
+ point.outputTokens += row.usageMetadata.output_tokens;
186
+ point.totalTokens += row.usageMetadata.total_tokens;
187
+ point.cachedTokens += (row.usageMetadata.input_token_details?.cache_read ?? 0) + (row.usageMetadata.input_token_details?.cache_creation ?? 0);
188
+ point.reasoningTokens += row.usageMetadata.output_token_details?.reasoning ?? 0;
189
+ if (!modelTimeline.has(row.model))
190
+ modelTimeline.set(row.model, /* @__PURE__ */ new Map());
191
+ modelTimeline.get(row.model).set(date, (modelTimeline.get(row.model).get(date) ?? 0) + 1);
192
+ groups.set(key, item);
193
+ models.set(row.model, model);
194
+ sources.set(row.source, source);
195
+ timeline.set(date, point);
196
+ }
197
+ this.finish(totals);
198
+ return {
199
+ query: this.withDefaults(input),
200
+ totals,
201
+ groups: [...groups.values()].map((row) => this.finish(row)).sort((a, b) => {
202
+ const diff = a[sortBy] - b[sortBy];
203
+ return desc ? -diff : diff;
204
+ }),
205
+ models: [...models.values()].map((row) => this.finish(row)).sort((a, b) => b.calls - a.calls),
206
+ sources: [...sources.values()].map((row) => this.finish(row)).sort((a, b) => b.calls - a.calls),
207
+ timeline: [...timeline.values()].sort(
208
+ (a, b) => a.date.localeCompare(b.date)
209
+ ),
210
+ modelTimeline: [...modelTimeline.entries()].map(
211
+ ([model, dates]) => ({
212
+ model,
213
+ points: [...dates.entries()].map(([date, calls]) => ({ date, calls })).sort((a, b) => a.date.localeCompare(b.date))
214
+ })
215
+ ),
216
+ list: this.pageRows(rows, input)
217
+ };
218
+ }
219
+ async list(input = {}) {
220
+ const rows = await this.search(input);
221
+ return this.pageRows(rows, input);
222
+ }
223
+ async cleanup(before) {
224
+ await this.ctx.database.remove(
225
+ "chatluna_usage",
226
+ before ? { createdAt: { $lt: before } } : {}
227
+ );
228
+ }
229
+ async search(input) {
230
+ const query = this.withDefaults(input);
231
+ const where = {
232
+ createdAt: { $gte: query.start, $lt: query.end }
233
+ };
234
+ if (query.source) where.source = query.source;
235
+ if (query.model) where.model = query.model;
236
+ if (query.platform) where.platform = query.platform;
237
+ if (query.callType) where.callType = query.callType;
238
+ if (query.success != null) where.success = query.success;
239
+ if (query.estimated != null) where.estimated = query.estimated;
240
+ const rows = await this.ctx.database.get(
241
+ "chatluna_usage",
242
+ where
243
+ );
244
+ if (!query.chatPlatform && !query.guildId && !query.userId && !query.keyword) {
245
+ return rows;
246
+ }
247
+ return rows.filter((row) => {
248
+ if (query.chatPlatform && !(row.chatPlatform ?? "").includes(query.chatPlatform)) {
249
+ return false;
250
+ }
251
+ if (query.guildId && !(row.guildId ?? "").includes(query.guildId)) {
252
+ return false;
253
+ }
254
+ if (query.userId && !(row.userId ?? "").includes(query.userId)) {
255
+ return false;
256
+ }
257
+ if (!query.keyword) return true;
258
+ return [
259
+ row.source,
260
+ row.callType,
261
+ row.platform,
262
+ row.chatPlatform,
263
+ row.model,
264
+ row.conversationId,
265
+ row.requestId,
266
+ row.userId,
267
+ row.guildId
268
+ ].filter(Boolean).some((value) => value.includes(query.keyword));
269
+ });
270
+ }
271
+ withDefaults(input) {
272
+ const period = input.period ?? "day";
273
+ const end = input.end ? new Date(input.end) : /* @__PURE__ */ new Date();
274
+ const start = input.start ? new Date(input.start) : period === "year" ? new Date(end.getFullYear() - 1, end.getMonth(), end.getDate()) : period === "month" ? new Date(end.getFullYear(), end.getMonth() - 11, 1) : new Date(+end - this.config.recentDays * Time.day);
275
+ return {
276
+ ...input,
277
+ period,
278
+ groupBy: input.groupBy ?? "model",
279
+ sortBy: input.sortBy ?? "totalTokens",
280
+ desc: input.desc ?? true,
281
+ page: input.page ?? 1,
282
+ pageSize: input.pageSize ?? this.config.pageSize,
283
+ listSortBy: input.listSortBy ?? "createdAt",
284
+ listDesc: input.listDesc ?? true,
285
+ start,
286
+ end
287
+ };
288
+ }
289
+ pageRows(rows, input) {
290
+ const query = this.withDefaults(input);
291
+ const sorted = rows.map((row) => ({
292
+ ...row,
293
+ inputTokens: row.usageMetadata.input_tokens,
294
+ outputTokens: row.usageMetadata.output_tokens,
295
+ totalTokens: row.usageMetadata.total_tokens,
296
+ estimated: row.estimated,
297
+ cachedTokens: (row.usageMetadata.input_token_details?.cache_read ?? 0) + (row.usageMetadata.input_token_details?.cache_creation ?? 0),
298
+ reasoningTokens: row.usageMetadata.output_token_details?.reasoning ?? 0
299
+ })).sort((a, b) => {
300
+ const left = a[query.listSortBy];
301
+ const right = b[query.listSortBy];
302
+ let diff;
303
+ if (left instanceof Date && right instanceof Date) {
304
+ diff = +left - +right;
305
+ } else if (typeof left === "string" && typeof right === "string") {
306
+ diff = left.localeCompare(right);
307
+ } else {
308
+ diff = Number(left) - Number(right);
309
+ }
310
+ return query.listDesc ? -diff : diff;
311
+ });
312
+ const start = (query.page - 1) * query.pageSize;
313
+ return {
314
+ total: sorted.length,
315
+ page: query.page,
316
+ pageSize: query.pageSize,
317
+ rows: sorted.slice(start, start + query.pageSize)
318
+ };
319
+ }
320
+ add(row, item) {
321
+ item.calls += 1;
322
+ if (row.success) item.successfulCalls += 1;
323
+ else item.failedCalls += 1;
324
+ item.inputTokens += row.usageMetadata.input_tokens;
325
+ item.outputTokens += row.usageMetadata.output_tokens;
326
+ item.totalTokens += row.usageMetadata.total_tokens;
327
+ item.cachedTokens += (row.usageMetadata.input_token_details?.cache_read ?? 0) + (row.usageMetadata.input_token_details?.cache_creation ?? 0);
328
+ item.reasoningTokens += row.usageMetadata.output_token_details?.reasoning ?? 0;
329
+ if (row.estimated)
330
+ item.estimatedTokens += row.usageMetadata.total_tokens;
331
+ if (!item.lastSeen || row.createdAt > item.lastSeen)
332
+ item.lastSeen = row.createdAt;
333
+ }
334
+ finish(item) {
335
+ item.successRate = item.calls ? item.successfulCalls / item.calls : 0;
336
+ return item;
337
+ }
338
+ groupKey(row, groupBy) {
339
+ if (groupBy === "guild") return row.guildId ?? "private";
340
+ if (groupBy === "chatPlatform") return row.chatPlatform ?? "unknown";
341
+ return row[groupBy];
342
+ }
343
+ groupLabel(key, groupBy) {
344
+ if (groupBy === "guild" && key === "private") return "私聊/未知群";
345
+ if (groupBy === "chatPlatform" && key === "unknown")
346
+ return "未知聊天平台";
347
+ return key;
348
+ }
349
+ dateKey(date, period) {
350
+ const y = date.getFullYear();
351
+ const m = String(date.getMonth() + 1).padStart(2, "0");
352
+ const d = String(date.getDate()).padStart(2, "0");
353
+ if (period === "year") return String(y);
354
+ if (period === "month") return `${y}-${m}`;
355
+ return `${y}-${m}-${d}`;
356
+ }
357
+ };
358
+ ((ChatLunaUsage2) => {
359
+ ChatLunaUsage2.Config = Schema.object({
360
+ recentDays: Schema.natural().description("默认统计最近几天的数据。").default(30),
361
+ pageSize: Schema.natural().description("调用明细分页大小。").default(50),
362
+ webui: Schema.boolean().description("启用 Web UI 控制台用量面板。").default(true)
363
+ });
364
+ ChatLunaUsage2.inject = ["chatluna", "database"];
365
+ })(ChatLunaUsage || (ChatLunaUsage = {}));
366
+ var index_default = ChatLunaUsage;
367
+ async function queryUsage(ctx, source) {
368
+ const result = await ctx.chatluna_usage.query({ groupBy: "source" });
369
+ if (!source) return result.groups;
370
+ return result.groups.filter((row) => row.key === source);
371
+ }
372
+ __name(queryUsage, "queryUsage");
373
+ async function cleanupUsage(ctx, before) {
374
+ await ctx.chatluna_usage.cleanup(before);
375
+ }
376
+ __name(cleanupUsage, "cleanupUsage");
377
+ function apply(ctx, config) {
378
+ ctx.plugin(ChatLunaUsage, config);
379
+ }
380
+ __name(apply, "apply");
381
+ var Config = ChatLunaUsage.Config;
382
+ var inject = {
383
+ required: ["chatluna", "database"],
384
+ optional: ["console"]
385
+ };
386
+ var name = "chatluna-usage";
387
+ export {
388
+ ChatLunaUsage,
389
+ Config,
390
+ apply,
391
+ cleanupUsage,
392
+ index_default as default,
393
+ inject,
394
+ name,
395
+ queryUsage
396
+ };