aicodeswitch 1.8.0 → 1.10.1

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.
@@ -0,0 +1,34 @@
1
+ name: Publish To NPM
2
+ on:
3
+ pull_request:
4
+ types:
5
+ - closed
6
+ branches:
7
+ - main
8
+ jobs:
9
+ publish:
10
+ runs-on: ubuntu-latest
11
+ permissions:
12
+ contents: write
13
+ id-token: write
14
+ if: (github.event_name == 'push' && github.ref == 'refs/heads/main') || (github.event_name == 'pull_request' && github.event.pull_request.merged == true)
15
+ steps:
16
+ - uses: actions/checkout@v5
17
+ - uses: actions/setup-node@v4
18
+ with:
19
+ node-version: '24.x'
20
+ registry-url: 'https://registry.npmjs.org'
21
+ - run: npm install --verbose
22
+ - run: |
23
+ git config --global user.name 'github-actions[bot]'
24
+ git config --global user.email 'github-actions[bot]@users.noreply.github.com'
25
+ - run: npm run release
26
+ - run: npx can-npm-publish
27
+ - run: npm publish --provenance
28
+ env:
29
+ NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
30
+ - uses: ad-m/github-push-action@master
31
+ with:
32
+ github_token: ${{ secrets.GITHUB_TOKEN }}
33
+ branch: main
34
+ tags: true
package/CHANGELOG.md ADDED
@@ -0,0 +1,15 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
+
5
+ ### 1.10.1 (2026-01-25)
6
+
7
+ ## [1.10.0](https://github.com/tangshuang/aicodeswitch/compare/v1.9.0...v1.10.0) (2026-01-25)
8
+
9
+
10
+ ### Features
11
+
12
+ * 日志分页 ([0e68786](https://github.com/tangshuang/aicodeswitch/commit/0e68786b4e5bdce9bf7fc5ce85a1d4bdaf5a710c))
13
+ * 新增了代理能力 ([d6254ae](https://github.com/tangshuang/aicodeswitch/commit/d6254ae463f01f583601ed71f64f346b63718853))
14
+ * 优化细节 ([e4bdaef](https://github.com/tangshuang/aicodeswitch/commit/e4bdaef14bb0088e7feb5371490124c8dd169fdd))
15
+ * github workflows ([37dd371](https://github.com/tangshuang/aicodeswitch/commit/37dd3717e79dab1c4aec99b762774cc549f1efc8))
@@ -57,6 +57,31 @@ class DatabaseManager {
57
57
  writable: true,
58
58
  value: void 0
59
59
  });
60
+ // 缓存机制:总数查询缓存
61
+ Object.defineProperty(this, "logsCountCache", {
62
+ enumerable: true,
63
+ configurable: true,
64
+ writable: true,
65
+ value: null
66
+ });
67
+ Object.defineProperty(this, "accessLogsCountCache", {
68
+ enumerable: true,
69
+ configurable: true,
70
+ writable: true,
71
+ value: null
72
+ });
73
+ Object.defineProperty(this, "errorLogsCountCache", {
74
+ enumerable: true,
75
+ configurable: true,
76
+ writable: true,
77
+ value: null
78
+ });
79
+ Object.defineProperty(this, "CACHE_TTL", {
80
+ enumerable: true,
81
+ configurable: true,
82
+ writable: true,
83
+ value: 1000
84
+ }); // 1秒缓存TTL
60
85
  this.db = new better_sqlite3_1.default(path_1.default.join(dataPath, 'app.db'));
61
86
  // 启用外键约束(SQLite 默认禁用,必须手动启用才能使 ON DELETE CASCADE 生效)
62
87
  this.db.pragma('foreign_keys = ON');
@@ -100,6 +125,61 @@ class DatabaseManager {
100
125
  console.log('[DB] Migration completed: model_limits column added');
101
126
  }
102
127
  }
128
+ // 检查vendors表是否有sort_order字段
129
+ const vendorsColumns = this.db.pragma('table_info(vendors)');
130
+ const hasSortOrder = vendorsColumns.some((col) => col.name === 'sort_order');
131
+ if (!hasSortOrder) {
132
+ console.log('[DB] Running migration: Adding sort_order column to vendors table');
133
+ this.db.exec('ALTER TABLE vendors ADD COLUMN sort_order INTEGER DEFAULT 0;');
134
+ console.log('[DB] Migration completed: sort_order column added to vendors');
135
+ }
136
+ // 检查rules表是否有token相关字段
137
+ const rulesColumns = this.db.pragma('table_info(rules)');
138
+ const hasTokenLimit = rulesColumns.some((col) => col.name === 'token_limit');
139
+ const hasTotalTokensUsed = rulesColumns.some((col) => col.name === 'total_tokens_used');
140
+ const hasResetInterval = rulesColumns.some((col) => col.name === 'reset_interval');
141
+ const hasLastResetAt = rulesColumns.some((col) => col.name === 'last_reset_at');
142
+ if (!hasTokenLimit) {
143
+ console.log('[DB] Running migration: Adding token_limit column to rules table');
144
+ this.db.exec('ALTER TABLE rules ADD COLUMN token_limit INTEGER;');
145
+ console.log('[DB] Migration completed: token_limit column added');
146
+ }
147
+ if (!hasTotalTokensUsed) {
148
+ console.log('[DB] Running migration: Adding total_tokens_used column to rules table');
149
+ this.db.exec('ALTER TABLE rules ADD COLUMN total_tokens_used INTEGER DEFAULT 0;');
150
+ console.log('[DB] Migration completed: total_tokens_used column added');
151
+ }
152
+ if (!hasResetInterval) {
153
+ console.log('[DB] Running migration: Adding reset_interval column to rules table');
154
+ this.db.exec('ALTER TABLE rules ADD COLUMN reset_interval INTEGER;');
155
+ console.log('[DB] Migration completed: reset_interval column added');
156
+ }
157
+ if (!hasLastResetAt) {
158
+ console.log('[DB] Running migration: Adding last_reset_at column to rules table');
159
+ this.db.exec('ALTER TABLE rules ADD COLUMN last_reset_at INTEGER;');
160
+ console.log('[DB] Migration completed: last_reset_at column added');
161
+ }
162
+ // 检查rules表是否有timeout字段
163
+ const hasRuleTimeout = rulesColumns.some((col) => col.name === 'timeout');
164
+ if (!hasRuleTimeout) {
165
+ console.log('[DB] Running migration: Adding timeout column to rules table');
166
+ this.db.exec('ALTER TABLE rules ADD COLUMN timeout INTEGER;');
167
+ console.log('[DB] Migration completed: timeout column added to rules');
168
+ }
169
+ // 检查api_services表是否有timeout字段,如果有则移除
170
+ const hasServiceTimeout = columns.some((col) => col.name === 'timeout');
171
+ if (hasServiceTimeout) {
172
+ console.log('[DB] Running migration: Removing timeout column from api_services table');
173
+ yield this.migrateRemoveServiceTimeout();
174
+ console.log('[DB] Migration completed: timeout column removed from api_services');
175
+ }
176
+ // 检查api_services表是否有enable_proxy字段
177
+ const hasEnableProxy = columns.some((col) => col.name === 'enable_proxy');
178
+ if (!hasEnableProxy) {
179
+ console.log('[DB] Running migration: Adding enable_proxy column to api_services table');
180
+ this.db.exec('ALTER TABLE api_services ADD COLUMN enable_proxy INTEGER DEFAULT 0;');
181
+ console.log('[DB] Migration completed: enable_proxy column added');
182
+ }
103
183
  });
104
184
  }
105
185
  migrateMaxOutputTokensToModelLimits() {
@@ -138,12 +218,42 @@ class DatabaseManager {
138
218
  console.log('[DB] Migration completed: Replaced max_output_tokens with model_limits');
139
219
  });
140
220
  }
221
+ migrateRemoveServiceTimeout() {
222
+ return __awaiter(this, void 0, void 0, function* () {
223
+ // SQLite 不支持直接删除列,需要重建表
224
+ this.db.pragma('foreign_keys = OFF');
225
+ this.db.exec(`
226
+ CREATE TABLE api_services_new (
227
+ id TEXT PRIMARY KEY,
228
+ vendor_id TEXT NOT NULL,
229
+ name TEXT NOT NULL,
230
+ api_url TEXT NOT NULL,
231
+ api_key TEXT NOT NULL,
232
+ source_type TEXT,
233
+ supported_models TEXT,
234
+ model_limits TEXT,
235
+ created_at INTEGER NOT NULL,
236
+ updated_at INTEGER NOT NULL,
237
+ FOREIGN KEY (vendor_id) REFERENCES vendors(id) ON DELETE CASCADE
238
+ );
239
+
240
+ INSERT INTO api_services_new (id, vendor_id, name, api_url, api_key, source_type, supported_models, model_limits, created_at, updated_at)
241
+ SELECT id, vendor_id, name, api_url, api_key, source_type, supported_models, model_limits, created_at, updated_at
242
+ FROM api_services;
243
+
244
+ DROP TABLE api_services;
245
+ ALTER TABLE api_services_new RENAME TO api_services;
246
+ `);
247
+ this.db.pragma('foreign_keys = ON');
248
+ });
249
+ }
141
250
  createTables() {
142
251
  this.db.exec(`
143
252
  CREATE TABLE IF NOT EXISTS vendors (
144
253
  id TEXT PRIMARY KEY,
145
254
  name TEXT NOT NULL,
146
255
  description TEXT,
256
+ sort_order INTEGER DEFAULT 0,
147
257
  created_at INTEGER NOT NULL,
148
258
  updated_at INTEGER NOT NULL
149
259
  );
@@ -154,7 +264,6 @@ class DatabaseManager {
154
264
  name TEXT NOT NULL,
155
265
  api_url TEXT NOT NULL,
156
266
  api_key TEXT NOT NULL,
157
- timeout INTEGER,
158
267
  source_type TEXT,
159
268
  supported_models TEXT,
160
269
  created_at INTEGER NOT NULL,
@@ -180,6 +289,11 @@ class DatabaseManager {
180
289
  target_model TEXT,
181
290
  replaced_model TEXT,
182
291
  sort_order INTEGER DEFAULT 0,
292
+ timeout INTEGER,
293
+ token_limit INTEGER,
294
+ total_tokens_used INTEGER DEFAULT 0,
295
+ reset_interval INTEGER,
296
+ last_reset_at INTEGER,
183
297
  created_at INTEGER NOT NULL,
184
298
  updated_at INTEGER NOT NULL,
185
299
  FOREIGN KEY (route_id) REFERENCES routes(id) ON DELETE CASCADE,
@@ -199,9 +313,13 @@ class DatabaseManager {
199
313
  const defaultConfig = {
200
314
  enableLogging: true,
201
315
  logRetentionDays: 30,
202
- maxLogSize: 1000,
316
+ maxLogSize: 100000,
203
317
  apiKey: '',
204
318
  enableFailover: true, // 默认启用智能故障切换
319
+ proxyEnabled: false, // 默认不启用代理
320
+ proxyUrl: '',
321
+ proxyUsername: '',
322
+ proxyPassword: '',
205
323
  };
206
324
  this.db.prepare('INSERT INTO config (key, value) VALUES (?, ?)').run('app_config', JSON.stringify(defaultConfig));
207
325
  }
@@ -209,11 +327,12 @@ class DatabaseManager {
209
327
  }
210
328
  // Vendor operations
211
329
  getVendors() {
212
- const rows = this.db.prepare('SELECT * FROM vendors ORDER BY created_at DESC').all();
330
+ const rows = this.db.prepare('SELECT * FROM vendors ORDER BY sort_order DESC, created_at DESC').all();
213
331
  return rows.map((row) => ({
214
332
  id: row.id,
215
333
  name: row.name,
216
334
  description: row.description,
335
+ sortOrder: row.sort_order,
217
336
  createdAt: row.created_at,
218
337
  updatedAt: row.updated_at,
219
338
  }));
@@ -222,15 +341,15 @@ class DatabaseManager {
222
341
  const id = crypto_1.default.randomUUID();
223
342
  const now = Date.now();
224
343
  this.db
225
- .prepare('INSERT INTO vendors (id, name, description, created_at, updated_at) VALUES (?, ?, ?, ?, ?)')
226
- .run(id, vendor.name, vendor.description || null, now, now);
344
+ .prepare('INSERT INTO vendors (id, name, description, sort_order, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)')
345
+ .run(id, vendor.name, vendor.description || null, vendor.sortOrder || 0, now, now);
227
346
  return Object.assign(Object.assign({}, vendor), { id, createdAt: now, updatedAt: now });
228
347
  }
229
348
  updateVendor(id, vendor) {
230
349
  const now = Date.now();
231
350
  const result = this.db
232
- .prepare('UPDATE vendors SET name = ?, description = ?, updated_at = ? WHERE id = ?')
233
- .run(vendor.name, vendor.description || null, now, id);
351
+ .prepare('UPDATE vendors SET name = ?, description = ?, sort_order = ?, updated_at = ? WHERE id = ?')
352
+ .run(vendor.name, vendor.description || null, vendor.sortOrder !== undefined ? vendor.sortOrder : 0, now, id);
234
353
  return result.changes > 0;
235
354
  }
236
355
  deleteVendor(id) {
@@ -252,10 +371,10 @@ class DatabaseManager {
252
371
  name: row.name,
253
372
  apiUrl: row.api_url,
254
373
  apiKey: row.api_key,
255
- timeout: row.timeout,
256
374
  sourceType: row.source_type,
257
375
  supportedModels: row.supported_models ? row.supported_models.split(',').map((model) => model.trim()).filter((model) => model.length > 0) : undefined,
258
376
  modelLimits: row.model_limits ? JSON.parse(row.model_limits) : undefined,
377
+ enableProxy: row.enable_proxy === 1,
259
378
  createdAt: row.created_at,
260
379
  updatedAt: row.updated_at,
261
380
  }));
@@ -269,18 +388,18 @@ class DatabaseManager {
269
388
  const id = crypto_1.default.randomUUID();
270
389
  const now = Date.now();
271
390
  this.db
272
- .prepare('INSERT INTO api_services (id, vendor_id, name, api_url, api_key, timeout, source_type, supported_models, model_limits, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)')
273
- .run(id, service.vendorId, service.name, service.apiUrl, service.apiKey, service.timeout || null, service.sourceType || null, service.supportedModels ? service.supportedModels.join(',') : null, service.modelLimits ? JSON.stringify(service.modelLimits) : null, now, now);
391
+ .prepare('INSERT INTO api_services (id, vendor_id, name, api_url, api_key, source_type, supported_models, model_limits, enable_proxy, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)')
392
+ .run(id, service.vendorId, service.name, service.apiUrl, service.apiKey, service.sourceType || null, service.supportedModels ? service.supportedModels.join(',') : null, service.modelLimits ? JSON.stringify(service.modelLimits) : null, service.enableProxy ? 1 : 0, now, now);
274
393
  return Object.assign(Object.assign({}, service), { id, createdAt: now, updatedAt: now });
275
394
  }
276
395
  updateAPIService(id, service) {
277
396
  const now = Date.now();
278
397
  const result = this.db
279
- .prepare('UPDATE api_services SET name = ?, api_url = ?, api_key = ?, timeout = ?, source_type = ?, supported_models = ?, model_limits = ?, updated_at = ? WHERE id = ?')
280
- .run(service.name, service.apiUrl, service.apiKey, service.timeout || null, service.sourceType || null, service.supportedModels ? service.supportedModels.join(',') : null, service.modelLimits ? JSON.stringify(service.modelLimits) : null, now, id);
398
+ .prepare('UPDATE api_services SET name = ?, api_url = ?, api_key = ?, source_type = ?, supported_models = ?, model_limits = ?, enable_proxy = ?, updated_at = ? WHERE id = ?')
399
+ .run(service.name, service.apiUrl, service.apiKey, service.sourceType || null, service.supportedModels ? service.supportedModels.join(',') : null, service.modelLimits ? JSON.stringify(service.modelLimits) : null, service.enableProxy !== undefined ? (service.enableProxy ? 1 : 0) : null, now, id);
281
400
  // 调试日志: 记录更新操作
282
401
  if (result.changes > 0 && process.env.NODE_ENV === 'development') {
283
- console.log(`[DB] Updated service ${id}: ${service.name} -> ${service.apiUrl} (timeout: ${service.timeout})`);
402
+ console.log(`[DB] Updated service ${id}: ${service.name} -> ${service.apiUrl}`);
284
403
  }
285
404
  return result.changes > 0;
286
405
  }
@@ -348,6 +467,11 @@ class DatabaseManager {
348
467
  targetModel: row.target_model,
349
468
  replacedModel: row.replaced_model,
350
469
  sortOrder: row.sort_order,
470
+ timeout: row.timeout,
471
+ tokenLimit: row.token_limit,
472
+ totalTokensUsed: row.total_tokens_used,
473
+ resetInterval: row.reset_interval,
474
+ lastResetAt: row.last_reset_at,
351
475
  createdAt: row.created_at,
352
476
  updatedAt: row.updated_at,
353
477
  }));
@@ -356,26 +480,79 @@ class DatabaseManager {
356
480
  const id = crypto_1.default.randomUUID();
357
481
  const now = Date.now();
358
482
  this.db
359
- .prepare('INSERT INTO rules (id, route_id, content_type, target_service_id, target_model, replaced_model, sort_order, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)')
360
- .run(id, route.routeId, route.contentType, route.targetServiceId, route.targetModel || null, route.replacedModel || null, route.sortOrder || 0, now, now);
483
+ .prepare('INSERT INTO rules (id, route_id, content_type, target_service_id, target_model, replaced_model, sort_order, timeout, token_limit, total_tokens_used, reset_interval, last_reset_at, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)')
484
+ .run(id, route.routeId, route.contentType, route.targetServiceId, route.targetModel || null, route.replacedModel || null, route.sortOrder || 0, route.timeout || null, route.tokenLimit || null, route.totalTokensUsed || 0, route.resetInterval || null, route.lastResetAt || null, now, now);
361
485
  return Object.assign(Object.assign({}, route), { id, createdAt: now, updatedAt: now });
362
486
  }
363
487
  updateRule(id, route) {
364
488
  const now = Date.now();
365
489
  const result = this.db
366
- .prepare('UPDATE rules SET content_type = ?, target_service_id = ?, target_model = ?, replaced_model = ?, sort_order = ?, updated_at = ? WHERE id = ?')
367
- .run(route.contentType, route.targetServiceId, route.targetModel || null, route.replacedModel || null, route.sortOrder || 0, now, id);
490
+ .prepare('UPDATE rules SET content_type = ?, target_service_id = ?, target_model = ?, replaced_model = ?, sort_order = ?, timeout = ?, token_limit = ?, reset_interval = ?, updated_at = ? WHERE id = ?')
491
+ .run(route.contentType, route.targetServiceId, route.targetModel || null, route.replacedModel || null, route.sortOrder || 0, route.timeout !== undefined ? route.timeout : null, route.tokenLimit !== undefined ? route.tokenLimit : null, route.resetInterval !== undefined ? route.resetInterval : null, now, id);
368
492
  return result.changes > 0;
369
493
  }
370
494
  deleteRule(id) {
371
495
  const result = this.db.prepare('DELETE FROM rules WHERE id = ?').run(id);
372
496
  return result.changes > 0;
373
497
  }
498
+ /**
499
+ * 增加规则的token使用量
500
+ * @param ruleId 规则ID
501
+ * @param tokensUsed 使用的token数量
502
+ * @returns 是否成功
503
+ */
504
+ incrementRuleTokenUsage(ruleId, tokensUsed) {
505
+ const result = this.db
506
+ .prepare('UPDATE rules SET total_tokens_used = total_tokens_used + ? WHERE id = ?')
507
+ .run(tokensUsed, ruleId);
508
+ return result.changes > 0;
509
+ }
510
+ /**
511
+ * 重置规则的token使用量
512
+ * @param ruleId 规则ID
513
+ * @returns 是否成功
514
+ */
515
+ resetRuleTokenUsage(ruleId) {
516
+ const now = Date.now();
517
+ const result = this.db
518
+ .prepare('UPDATE rules SET total_tokens_used = 0, last_reset_at = ? WHERE id = ?')
519
+ .run(now, ruleId);
520
+ return result.changes > 0;
521
+ }
522
+ /**
523
+ * 检查并重置到期的规则
524
+ * 如果规则设置了reset_interval且已经到了重置时间,则自动重置token使用量
525
+ * @param ruleId 规则ID
526
+ * @returns 是否进行了重置
527
+ */
528
+ checkAndResetRuleIfNeeded(ruleId) {
529
+ const rule = this.db
530
+ .prepare('SELECT reset_interval, last_reset_at FROM rules WHERE id = ?')
531
+ .get(ruleId);
532
+ if (!rule || !rule.reset_interval) {
533
+ return false; // 没有设置重置间隔
534
+ }
535
+ const now = Date.now();
536
+ const resetIntervalMs = rule.reset_interval * 60 * 60 * 1000; // 小时转毫秒
537
+ const lastResetAt = rule.last_reset_at || 0;
538
+ // 检查是否已经到了重置时间
539
+ if (now - lastResetAt >= resetIntervalMs) {
540
+ this.resetRuleTokenUsage(ruleId);
541
+ return true;
542
+ }
543
+ return false;
544
+ }
374
545
  // Log operations
375
546
  addLog(log) {
376
547
  return __awaiter(this, void 0, void 0, function* () {
548
+ const { path } = log;
549
+ if (!path.startsWith('/v1/')) {
550
+ return;
551
+ }
377
552
  const id = crypto_1.default.randomUUID();
378
553
  yield this.logDb.put(id, JSON.stringify(Object.assign(Object.assign({}, log), { id })));
554
+ // 清除缓存
555
+ this.logsCountCache = null;
379
556
  });
380
557
  }
381
558
  getLogs() {
@@ -406,6 +583,8 @@ class DatabaseManager {
406
583
  clearLogs() {
407
584
  return __awaiter(this, void 0, void 0, function* () {
408
585
  yield this.logDb.clear();
586
+ // 清除缓存
587
+ this.logsCountCache = null;
409
588
  });
410
589
  }
411
590
  // Access log operations
@@ -413,6 +592,8 @@ class DatabaseManager {
413
592
  return __awaiter(this, void 0, void 0, function* () {
414
593
  const id = crypto_1.default.randomUUID();
415
594
  yield this.accessLogDb.put(id, JSON.stringify(Object.assign(Object.assign({}, log), { id })));
595
+ // 清除缓存
596
+ this.accessLogsCountCache = null;
416
597
  return id;
417
598
  });
418
599
  }
@@ -451,6 +632,8 @@ class DatabaseManager {
451
632
  clearAccessLogs() {
452
633
  return __awaiter(this, void 0, void 0, function* () {
453
634
  yield this.accessLogDb.clear();
635
+ // 清除缓存
636
+ this.accessLogsCountCache = null;
454
637
  });
455
638
  }
456
639
  // Error log operations
@@ -458,6 +641,8 @@ class DatabaseManager {
458
641
  return __awaiter(this, void 0, void 0, function* () {
459
642
  const id = crypto_1.default.randomUUID();
460
643
  yield this.errorLogDb.put(id, JSON.stringify(Object.assign(Object.assign({}, log), { id })));
644
+ // 清除缓存
645
+ this.errorLogsCountCache = null;
461
646
  });
462
647
  }
463
648
  getErrorLogs() {
@@ -488,6 +673,98 @@ class DatabaseManager {
488
673
  clearErrorLogs() {
489
674
  return __awaiter(this, void 0, void 0, function* () {
490
675
  yield this.errorLogDb.clear();
676
+ // 清除缓存
677
+ this.errorLogsCountCache = null;
678
+ });
679
+ }
680
+ /**
681
+ * 获取请求日志总数(带缓存)
682
+ */
683
+ getLogsCount() {
684
+ return __awaiter(this, void 0, void 0, function* () {
685
+ var _a, e_4, _b, _c;
686
+ const now = Date.now();
687
+ if (this.logsCountCache && now - this.logsCountCache.timestamp < this.CACHE_TTL) {
688
+ return this.logsCountCache.count;
689
+ }
690
+ let count = 0;
691
+ try {
692
+ for (var _d = true, _e = __asyncValues(this.logDb.iterator()), _f; _f = yield _e.next(), _a = _f.done, !_a; _d = true) {
693
+ _c = _f.value;
694
+ _d = false;
695
+ const _ = _c;
696
+ count++;
697
+ }
698
+ }
699
+ catch (e_4_1) { e_4 = { error: e_4_1 }; }
700
+ finally {
701
+ try {
702
+ if (!_d && !_a && (_b = _e.return)) yield _b.call(_e);
703
+ }
704
+ finally { if (e_4) throw e_4.error; }
705
+ }
706
+ this.logsCountCache = { count, timestamp: now };
707
+ return count;
708
+ });
709
+ }
710
+ /**
711
+ * 获取访问日志总数(带缓存)
712
+ */
713
+ getAccessLogsCount() {
714
+ return __awaiter(this, void 0, void 0, function* () {
715
+ var _a, e_5, _b, _c;
716
+ const now = Date.now();
717
+ if (this.accessLogsCountCache && now - this.accessLogsCountCache.timestamp < this.CACHE_TTL) {
718
+ return this.accessLogsCountCache.count;
719
+ }
720
+ let count = 0;
721
+ try {
722
+ for (var _d = true, _e = __asyncValues(this.accessLogDb.iterator()), _f; _f = yield _e.next(), _a = _f.done, !_a; _d = true) {
723
+ _c = _f.value;
724
+ _d = false;
725
+ const _ = _c;
726
+ count++;
727
+ }
728
+ }
729
+ catch (e_5_1) { e_5 = { error: e_5_1 }; }
730
+ finally {
731
+ try {
732
+ if (!_d && !_a && (_b = _e.return)) yield _b.call(_e);
733
+ }
734
+ finally { if (e_5) throw e_5.error; }
735
+ }
736
+ this.accessLogsCountCache = { count, timestamp: now };
737
+ return count;
738
+ });
739
+ }
740
+ /**
741
+ * 获取错误日志总数(带缓存)
742
+ */
743
+ getErrorLogsCount() {
744
+ return __awaiter(this, void 0, void 0, function* () {
745
+ var _a, e_6, _b, _c;
746
+ const now = Date.now();
747
+ if (this.errorLogsCountCache && now - this.errorLogsCountCache.timestamp < this.CACHE_TTL) {
748
+ return this.errorLogsCountCache.count;
749
+ }
750
+ let count = 0;
751
+ try {
752
+ for (var _d = true, _e = __asyncValues(this.errorLogDb.iterator()), _f; _f = yield _e.next(), _a = _f.done, !_a; _d = true) {
753
+ _c = _f.value;
754
+ _d = false;
755
+ const _ = _c;
756
+ count++;
757
+ }
758
+ }
759
+ catch (e_6_1) { e_6 = { error: e_6_1 }; }
760
+ finally {
761
+ try {
762
+ if (!_d && !_a && (_b = _e.return)) yield _b.call(_e);
763
+ }
764
+ finally { if (e_6) throw e_6.error; }
765
+ }
766
+ this.errorLogsCountCache = { count, timestamp: now };
767
+ return count;
491
768
  });
492
769
  }
493
770
  // Service blacklist operations
@@ -552,7 +829,7 @@ class DatabaseManager {
552
829
  }
553
830
  cleanupExpiredBlacklist() {
554
831
  return __awaiter(this, void 0, void 0, function* () {
555
- var _a, e_4, _b, _c;
832
+ var _a, e_7, _b, _c;
556
833
  const now = Date.now();
557
834
  let count = 0;
558
835
  try {
@@ -567,12 +844,12 @@ class DatabaseManager {
567
844
  }
568
845
  }
569
846
  }
570
- catch (e_4_1) { e_4 = { error: e_4_1 }; }
847
+ catch (e_7_1) { e_7 = { error: e_7_1 }; }
571
848
  finally {
572
849
  try {
573
850
  if (!_d && !_a && (_b = _e.return)) yield _b.call(_e);
574
851
  }
575
- finally { if (e_4) throw e_4.error; }
852
+ finally { if (e_7) throw e_7.error; }
576
853
  }
577
854
  return count;
578
855
  });
@@ -619,14 +896,14 @@ class DatabaseManager {
619
896
  // Import vendors
620
897
  for (const vendor of importData.vendors) {
621
898
  this.db
622
- .prepare('INSERT INTO vendors (id, name, description, created_at, updated_at) VALUES (?, ?, ?, ?, ?)')
623
- .run(vendor.id, vendor.name, vendor.description || null, vendor.createdAt, vendor.updatedAt);
899
+ .prepare('INSERT INTO vendors (id, name, description, sort_order, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)')
900
+ .run(vendor.id, vendor.name, vendor.description || null, vendor.sortOrder || 0, vendor.createdAt, vendor.updatedAt);
624
901
  }
625
902
  // Import API services
626
903
  for (const service of importData.apiServices) {
627
904
  this.db
628
- .prepare('INSERT INTO api_services (id, vendor_id, name, api_url, api_key, timeout, source_type, supported_models, model_limits, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)')
629
- .run(service.id, service.vendorId, service.name, service.apiUrl, service.apiKey, service.timeout || null, service.sourceType || null, service.supportedModels ? service.supportedModels.join(',') : null, service.modelLimits ? JSON.stringify(service.modelLimits) : null, service.createdAt, service.updatedAt);
905
+ .prepare('INSERT INTO api_services (id, vendor_id, name, api_url, api_key, source_type, supported_models, model_limits, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)')
906
+ .run(service.id, service.vendorId, service.name, service.apiUrl, service.apiKey, service.sourceType || null, service.supportedModels ? service.supportedModels.join(',') : null, service.modelLimits ? JSON.stringify(service.modelLimits) : null, service.createdAt, service.updatedAt);
630
907
  }
631
908
  // Import routes
632
909
  for (const route of importData.routes) {
@@ -637,8 +914,8 @@ class DatabaseManager {
637
914
  // Import rules
638
915
  for (const rule of importData.rules) {
639
916
  this.db
640
- .prepare('INSERT INTO rules (id, route_id, content_type, target_service_id, target_model, replaced_model, sort_order, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)')
641
- .run(rule.id, rule.routeId, rule.contentType || 'default', rule.targetServiceId, rule.targetModel || null, rule.replacedModel || null, rule.sortOrder || 0, rule.createdAt, rule.updatedAt);
917
+ .prepare('INSERT INTO rules (id, route_id, content_type, target_service_id, target_model, replaced_model, sort_order, timeout, token_limit, total_tokens_used, reset_interval, last_reset_at, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)')
918
+ .run(rule.id, rule.routeId, rule.contentType || 'default', rule.targetServiceId, rule.targetModel || null, rule.replacedModel || null, rule.sortOrder || 0, rule.timeout || null, rule.tokenLimit || null, rule.totalTokensUsed || 0, rule.resetInterval || null, rule.lastResetAt || null, rule.createdAt, rule.updatedAt);
642
919
  }
643
920
  // Update config
644
921
  this.updateConfig(importData.config);
@@ -653,7 +930,7 @@ class DatabaseManager {
653
930
  // Statistics operations
654
931
  getStatistics() {
655
932
  return __awaiter(this, arguments, void 0, function* (days = 30) {
656
- var _a, e_5, _b, _c, _d, e_6, _e, _f;
933
+ var _a, e_8, _b, _c, _d, e_9, _e, _f;
657
934
  var _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y;
658
935
  const now = Date.now();
659
936
  const startTime = now - days * 24 * 60 * 60 * 1000;
@@ -670,12 +947,12 @@ class DatabaseManager {
670
947
  }
671
948
  }
672
949
  }
673
- catch (e_5_1) { e_5 = { error: e_5_1 }; }
950
+ catch (e_8_1) { e_8 = { error: e_8_1 }; }
674
951
  finally {
675
952
  try {
676
953
  if (!_z && !_a && (_b = _0.return)) yield _b.call(_0);
677
954
  }
678
- finally { if (e_5) throw e_5.error; }
955
+ finally { if (e_8) throw e_8.error; }
679
956
  }
680
957
  // Get all error logs
681
958
  const errorLogs = [];
@@ -693,12 +970,12 @@ class DatabaseManager {
693
970
  }
694
971
  }
695
972
  }
696
- catch (e_6_1) { e_6 = { error: e_6_1 }; }
973
+ catch (e_9_1) { e_9 = { error: e_9_1 }; }
697
974
  finally {
698
975
  try {
699
976
  if (!_2 && !_d && (_e = _3.return)) yield _e.call(_3);
700
977
  }
701
- finally { if (e_6) throw e_6.error; }
978
+ finally { if (e_9) throw e_9.error; }
702
979
  }
703
980
  // Get vendors and services for mapping
704
981
  const vendors = this.getVendors();
@@ -282,6 +282,7 @@ const registerRoutes = (dbManager, proxyServer) => {
282
282
  app.post('/api/rules', (req, res) => res.json(dbManager.createRule(req.body)));
283
283
  app.put('/api/rules/:id', (req, res) => res.json(dbManager.updateRule(req.params.id, req.body)));
284
284
  app.delete('/api/rules/:id', (req, res) => res.json(dbManager.deleteRule(req.params.id)));
285
+ app.put('/api/rules/:id/reset-tokens', (req, res) => res.json(dbManager.resetRuleTokenUsage(req.params.id)));
285
286
  app.get('/api/logs', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
286
287
  const rawLimit = typeof req.query.limit === 'string' ? parseInt(req.query.limit, 10) : NaN;
287
288
  const rawOffset = typeof req.query.offset === 'string' ? parseInt(req.query.offset, 10) : NaN;
@@ -318,6 +319,18 @@ const registerRoutes = (dbManager, proxyServer) => {
318
319
  yield dbManager.clearErrorLogs();
319
320
  res.json(true);
320
321
  })));
322
+ app.get('/api/logs/count', asyncHandler((_req, res) => __awaiter(void 0, void 0, void 0, function* () {
323
+ const count = yield dbManager.getLogsCount();
324
+ res.json({ count });
325
+ })));
326
+ app.get('/api/access-logs/count', asyncHandler((_req, res) => __awaiter(void 0, void 0, void 0, function* () {
327
+ const count = yield dbManager.getAccessLogsCount();
328
+ res.json({ count });
329
+ })));
330
+ app.get('/api/error-logs/count', asyncHandler((_req, res) => __awaiter(void 0, void 0, void 0, function* () {
331
+ const count = yield dbManager.getErrorLogsCount();
332
+ res.json({ count });
333
+ })));
321
334
  app.get('/api/config', (_req, res) => res.json(dbManager.getConfig()));
322
335
  app.put('/api/config', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
323
336
  const config = req.body;