aicodeswitch 1.10.1 → 1.10.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +2 -0
- package/dist/server/database.js +290 -19
- package/dist/server/main.js +48 -0
- package/dist/server/proxy-server.js +158 -37
- package/dist/ui/assets/index-D6RrKKB5.js +391 -0
- package/dist/ui/assets/index-DU8EG0kT.css +1 -0
- package/dist/ui/index.html +2 -2
- package/package.json +1 -1
- package/dist/ui/assets/index-BdKga7KO.js +0 -391
- package/dist/ui/assets/index-Dat4drax.css +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
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
4
|
|
|
5
|
+
### 1.10.2 (2026-01-26)
|
|
6
|
+
|
|
5
7
|
### 1.10.1 (2026-01-25)
|
|
6
8
|
|
|
7
9
|
## [1.10.0](https://github.com/tangshuang/aicodeswitch/compare/v1.9.0...v1.10.0) (2026-01-25)
|
package/dist/server/database.js
CHANGED
|
@@ -180,6 +180,96 @@ class DatabaseManager {
|
|
|
180
180
|
this.db.exec('ALTER TABLE api_services ADD COLUMN enable_proxy INTEGER DEFAULT 0;');
|
|
181
181
|
console.log('[DB] Migration completed: enable_proxy column added');
|
|
182
182
|
}
|
|
183
|
+
// 检查rules表是否有请求次数相关字段
|
|
184
|
+
const hasRequestCountLimit = rulesColumns.some((col) => col.name === 'request_count_limit');
|
|
185
|
+
const hasTotalRequestsUsed = rulesColumns.some((col) => col.name === 'total_requests_used');
|
|
186
|
+
const hasRequestResetInterval = rulesColumns.some((col) => col.name === 'request_reset_interval');
|
|
187
|
+
const hasRequestLastResetAt = rulesColumns.some((col) => col.name === 'request_last_reset_at');
|
|
188
|
+
if (!hasRequestCountLimit) {
|
|
189
|
+
console.log('[DB] Running migration: Adding request_count_limit column to rules table');
|
|
190
|
+
this.db.exec('ALTER TABLE rules ADD COLUMN request_count_limit INTEGER;');
|
|
191
|
+
console.log('[DB] Migration completed: request_count_limit column added');
|
|
192
|
+
}
|
|
193
|
+
if (!hasTotalRequestsUsed) {
|
|
194
|
+
console.log('[DB] Running migration: Adding total_requests_used column to rules table');
|
|
195
|
+
this.db.exec('ALTER TABLE rules ADD COLUMN total_requests_used INTEGER DEFAULT 0;');
|
|
196
|
+
console.log('[DB] Migration completed: total_requests_used column added');
|
|
197
|
+
}
|
|
198
|
+
if (!hasRequestResetInterval) {
|
|
199
|
+
console.log('[DB] Running migration: Adding request_reset_interval column to rules table');
|
|
200
|
+
this.db.exec('ALTER TABLE rules ADD COLUMN request_reset_interval INTEGER;');
|
|
201
|
+
console.log('[DB] Migration completed: request_reset_interval column added');
|
|
202
|
+
}
|
|
203
|
+
if (!hasRequestLastResetAt) {
|
|
204
|
+
console.log('[DB] Running migration: Adding request_last_reset_at column to rules table');
|
|
205
|
+
this.db.exec('ALTER TABLE rules ADD COLUMN request_last_reset_at INTEGER;');
|
|
206
|
+
console.log('[DB] Migration completed: request_last_reset_at column added');
|
|
207
|
+
}
|
|
208
|
+
// 检查rules表是否有request_reset_base_time字段
|
|
209
|
+
const hasRequestResetBaseTime = rulesColumns.some((col) => col.name === 'request_reset_base_time');
|
|
210
|
+
if (!hasRequestResetBaseTime) {
|
|
211
|
+
console.log('[DB] Running migration: Adding request_reset_base_time column to rules table');
|
|
212
|
+
this.db.exec('ALTER TABLE rules ADD COLUMN request_reset_base_time INTEGER;');
|
|
213
|
+
console.log('[DB] Migration completed: request_reset_base_time column added');
|
|
214
|
+
}
|
|
215
|
+
// 检查rules表是否有token_reset_base_time字段
|
|
216
|
+
const hasTokenResetBaseTime = rulesColumns.some((col) => col.name === 'token_reset_base_time');
|
|
217
|
+
if (!hasTokenResetBaseTime) {
|
|
218
|
+
console.log('[DB] Running migration: Adding token_reset_base_time column to rules table');
|
|
219
|
+
this.db.exec('ALTER TABLE rules ADD COLUMN token_reset_base_time INTEGER;');
|
|
220
|
+
console.log('[DB] Migration completed: token_reset_base_time column added');
|
|
221
|
+
}
|
|
222
|
+
// 检查api_services表是否有超量配置相关字段
|
|
223
|
+
// Token超量配置
|
|
224
|
+
const hasEnableTokenLimit = columns.some((col) => col.name === 'enable_token_limit');
|
|
225
|
+
if (!hasEnableTokenLimit) {
|
|
226
|
+
console.log('[DB] Running migration: Adding enable_token_limit column to api_services table');
|
|
227
|
+
this.db.exec('ALTER TABLE api_services ADD COLUMN enable_token_limit INTEGER DEFAULT 0;');
|
|
228
|
+
console.log('[DB] Migration completed: enable_token_limit column added');
|
|
229
|
+
}
|
|
230
|
+
const hasServiceTokenLimit = columns.some((col) => col.name === 'token_limit');
|
|
231
|
+
if (!hasServiceTokenLimit) {
|
|
232
|
+
console.log('[DB] Running migration: Adding token_limit column to api_services table');
|
|
233
|
+
this.db.exec('ALTER TABLE api_services ADD COLUMN token_limit INTEGER;');
|
|
234
|
+
console.log('[DB] Migration completed: token_limit column added');
|
|
235
|
+
}
|
|
236
|
+
const hasTokenResetInterval = columns.some((col) => col.name === 'token_reset_interval');
|
|
237
|
+
if (!hasTokenResetInterval) {
|
|
238
|
+
console.log('[DB] Running migration: Adding token_reset_interval column to api_services table');
|
|
239
|
+
this.db.exec('ALTER TABLE api_services ADD COLUMN token_reset_interval INTEGER;');
|
|
240
|
+
console.log('[DB] Migration completed: token_reset_interval column added');
|
|
241
|
+
}
|
|
242
|
+
const hasServiceTokenResetBaseTime = columns.some((col) => col.name === 'token_reset_base_time');
|
|
243
|
+
if (!hasServiceTokenResetBaseTime) {
|
|
244
|
+
console.log('[DB] Running migration: Adding token_reset_base_time column to api_services table');
|
|
245
|
+
this.db.exec('ALTER TABLE api_services ADD COLUMN token_reset_base_time INTEGER;');
|
|
246
|
+
console.log('[DB] Migration completed: token_reset_base_time column added');
|
|
247
|
+
}
|
|
248
|
+
// 请求次数超量配置
|
|
249
|
+
const hasEnableRequestLimit = columns.some((col) => col.name === 'enable_request_limit');
|
|
250
|
+
if (!hasEnableRequestLimit) {
|
|
251
|
+
console.log('[DB] Running migration: Adding enable_request_limit column to api_services table');
|
|
252
|
+
this.db.exec('ALTER TABLE api_services ADD COLUMN enable_request_limit INTEGER DEFAULT 0;');
|
|
253
|
+
console.log('[DB] Migration completed: enable_request_limit column added');
|
|
254
|
+
}
|
|
255
|
+
const hasServiceRequestCountLimit = columns.some((col) => col.name === 'request_count_limit');
|
|
256
|
+
if (!hasServiceRequestCountLimit) {
|
|
257
|
+
console.log('[DB] Running migration: Adding request_count_limit column to api_services table');
|
|
258
|
+
this.db.exec('ALTER TABLE api_services ADD COLUMN request_count_limit INTEGER;');
|
|
259
|
+
console.log('[DB] Migration completed: request_count_limit column added');
|
|
260
|
+
}
|
|
261
|
+
const hasServiceRequestResetInterval = columns.some((col) => col.name === 'request_reset_interval');
|
|
262
|
+
if (!hasServiceRequestResetInterval) {
|
|
263
|
+
console.log('[DB] Running migration: Adding request_reset_interval column to api_services table');
|
|
264
|
+
this.db.exec('ALTER TABLE api_services ADD COLUMN request_reset_interval INTEGER;');
|
|
265
|
+
console.log('[DB] Migration completed: request_reset_interval column added');
|
|
266
|
+
}
|
|
267
|
+
const hasServiceRequestResetBaseTime = columns.some((col) => col.name === 'request_reset_base_time');
|
|
268
|
+
if (!hasServiceRequestResetBaseTime) {
|
|
269
|
+
console.log('[DB] Running migration: Adding request_reset_base_time column to api_services table');
|
|
270
|
+
this.db.exec('ALTER TABLE api_services ADD COLUMN request_reset_base_time INTEGER;');
|
|
271
|
+
console.log('[DB] Migration completed: request_reset_base_time column added');
|
|
272
|
+
}
|
|
183
273
|
});
|
|
184
274
|
}
|
|
185
275
|
migrateMaxOutputTokensToModelLimits() {
|
|
@@ -294,6 +384,10 @@ class DatabaseManager {
|
|
|
294
384
|
total_tokens_used INTEGER DEFAULT 0,
|
|
295
385
|
reset_interval INTEGER,
|
|
296
386
|
last_reset_at INTEGER,
|
|
387
|
+
request_count_limit INTEGER,
|
|
388
|
+
total_requests_used INTEGER DEFAULT 0,
|
|
389
|
+
request_reset_interval INTEGER,
|
|
390
|
+
request_last_reset_at INTEGER,
|
|
297
391
|
created_at INTEGER NOT NULL,
|
|
298
392
|
updated_at INTEGER NOT NULL,
|
|
299
393
|
FOREIGN KEY (route_id) REFERENCES routes(id) ON DELETE CASCADE,
|
|
@@ -375,6 +469,16 @@ class DatabaseManager {
|
|
|
375
469
|
supportedModels: row.supported_models ? row.supported_models.split(',').map((model) => model.trim()).filter((model) => model.length > 0) : undefined,
|
|
376
470
|
modelLimits: row.model_limits ? JSON.parse(row.model_limits) : undefined,
|
|
377
471
|
enableProxy: row.enable_proxy === 1,
|
|
472
|
+
// Token超量配置
|
|
473
|
+
enableTokenLimit: row.enable_token_limit === 1,
|
|
474
|
+
tokenLimit: row.token_limit,
|
|
475
|
+
tokenResetInterval: row.token_reset_interval,
|
|
476
|
+
tokenResetBaseTime: row.token_reset_base_time,
|
|
477
|
+
// 请求次数超量配置
|
|
478
|
+
enableRequestLimit: row.enable_request_limit === 1,
|
|
479
|
+
requestCountLimit: row.request_count_limit,
|
|
480
|
+
requestResetInterval: row.request_reset_interval,
|
|
481
|
+
requestResetBaseTime: row.request_reset_base_time,
|
|
378
482
|
createdAt: row.created_at,
|
|
379
483
|
updatedAt: row.updated_at,
|
|
380
484
|
}));
|
|
@@ -388,15 +492,15 @@ class DatabaseManager {
|
|
|
388
492
|
const id = crypto_1.default.randomUUID();
|
|
389
493
|
const now = Date.now();
|
|
390
494
|
this.db
|
|
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);
|
|
495
|
+
.prepare('INSERT INTO api_services (id, vendor_id, name, api_url, api_key, source_type, supported_models, model_limits, enable_proxy, enable_token_limit, token_limit, token_reset_interval, token_reset_base_time, enable_request_limit, request_count_limit, request_reset_interval, request_reset_base_time, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)')
|
|
496
|
+
.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, service.enableTokenLimit ? 1 : 0, service.tokenLimit || null, service.tokenResetInterval || null, service.tokenResetBaseTime || null, service.enableRequestLimit ? 1 : 0, service.requestCountLimit || null, service.requestResetInterval || null, service.requestResetBaseTime || null, now, now);
|
|
393
497
|
return Object.assign(Object.assign({}, service), { id, createdAt: now, updatedAt: now });
|
|
394
498
|
}
|
|
395
499
|
updateAPIService(id, service) {
|
|
396
500
|
const now = Date.now();
|
|
397
501
|
const result = this.db
|
|
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);
|
|
502
|
+
.prepare('UPDATE api_services SET vendor_id = ?, name = ?, api_url = ?, api_key = ?, source_type = ?, supported_models = ?, model_limits = ?, enable_proxy = ?, enable_token_limit = ?, token_limit = ?, token_reset_interval = ?, token_reset_base_time = ?, enable_request_limit = ?, request_count_limit = ?, request_reset_interval = ?, request_reset_base_time = ?, updated_at = ? WHERE id = ?')
|
|
503
|
+
.run(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 !== undefined ? (service.enableProxy ? 1 : 0) : null, service.enableTokenLimit !== undefined ? (service.enableTokenLimit ? 1 : 0) : null, service.tokenLimit !== undefined ? service.tokenLimit : null, service.tokenResetInterval !== undefined ? service.tokenResetInterval : null, service.tokenResetBaseTime !== undefined ? service.tokenResetBaseTime : null, service.enableRequestLimit !== undefined ? (service.enableRequestLimit ? 1 : 0) : null, service.requestCountLimit !== undefined ? service.requestCountLimit : null, service.requestResetInterval !== undefined ? service.requestResetInterval : null, service.requestResetBaseTime !== undefined ? service.requestResetBaseTime : null, now, id);
|
|
400
504
|
// 调试日志: 记录更新操作
|
|
401
505
|
if (result.changes > 0 && process.env.NODE_ENV === 'development') {
|
|
402
506
|
console.log(`[DB] Updated service ${id}: ${service.name} -> ${service.apiUrl}`);
|
|
@@ -472,23 +576,56 @@ class DatabaseManager {
|
|
|
472
576
|
totalTokensUsed: row.total_tokens_used,
|
|
473
577
|
resetInterval: row.reset_interval,
|
|
474
578
|
lastResetAt: row.last_reset_at,
|
|
579
|
+
tokenResetBaseTime: row.token_reset_base_time,
|
|
580
|
+
requestCountLimit: row.request_count_limit,
|
|
581
|
+
totalRequestsUsed: row.total_requests_used,
|
|
582
|
+
requestResetInterval: row.request_reset_interval,
|
|
583
|
+
requestLastResetAt: row.request_last_reset_at,
|
|
584
|
+
requestResetBaseTime: row.request_reset_base_time,
|
|
475
585
|
createdAt: row.created_at,
|
|
476
586
|
updatedAt: row.updated_at,
|
|
477
587
|
}));
|
|
478
588
|
}
|
|
589
|
+
getRule(id) {
|
|
590
|
+
const row = this.db.prepare('SELECT * FROM rules WHERE id = ?').get(id);
|
|
591
|
+
if (!row)
|
|
592
|
+
return undefined;
|
|
593
|
+
return {
|
|
594
|
+
id: row.id,
|
|
595
|
+
routeId: row.route_id,
|
|
596
|
+
contentType: row.content_type,
|
|
597
|
+
targetServiceId: row.target_service_id,
|
|
598
|
+
targetModel: row.target_model,
|
|
599
|
+
replacedModel: row.replaced_model,
|
|
600
|
+
sortOrder: row.sort_order,
|
|
601
|
+
timeout: row.timeout,
|
|
602
|
+
tokenLimit: row.token_limit,
|
|
603
|
+
totalTokensUsed: row.total_tokens_used,
|
|
604
|
+
resetInterval: row.reset_interval,
|
|
605
|
+
lastResetAt: row.last_reset_at,
|
|
606
|
+
tokenResetBaseTime: row.token_reset_base_time,
|
|
607
|
+
requestCountLimit: row.request_count_limit,
|
|
608
|
+
totalRequestsUsed: row.total_requests_used,
|
|
609
|
+
requestResetInterval: row.request_reset_interval,
|
|
610
|
+
requestLastResetAt: row.request_last_reset_at,
|
|
611
|
+
requestResetBaseTime: row.request_reset_base_time,
|
|
612
|
+
createdAt: row.created_at,
|
|
613
|
+
updatedAt: row.updated_at,
|
|
614
|
+
};
|
|
615
|
+
}
|
|
479
616
|
createRule(route) {
|
|
480
617
|
const id = crypto_1.default.randomUUID();
|
|
481
618
|
const now = Date.now();
|
|
482
619
|
this.db
|
|
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);
|
|
620
|
+
.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, token_reset_base_time, request_count_limit, total_requests_used, request_reset_interval, request_last_reset_at, request_reset_base_time, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)')
|
|
621
|
+
.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, route.tokenResetBaseTime || null, route.requestCountLimit || null, route.totalRequestsUsed || 0, route.requestResetInterval || null, route.requestLastResetAt || null, route.requestResetBaseTime || null, now, now);
|
|
485
622
|
return Object.assign(Object.assign({}, route), { id, createdAt: now, updatedAt: now });
|
|
486
623
|
}
|
|
487
624
|
updateRule(id, route) {
|
|
488
625
|
const now = Date.now();
|
|
489
626
|
const result = this.db
|
|
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);
|
|
627
|
+
.prepare('UPDATE rules SET content_type = ?, target_service_id = ?, target_model = ?, replaced_model = ?, sort_order = ?, timeout = ?, token_limit = ?, reset_interval = ?, token_reset_base_time = ?, request_count_limit = ?, request_reset_interval = ?, request_reset_base_time = ?, updated_at = ? WHERE id = ?')
|
|
628
|
+
.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, route.tokenResetBaseTime !== undefined ? route.tokenResetBaseTime : null, route.requestCountLimit !== undefined ? route.requestCountLimit : null, route.requestResetInterval !== undefined ? route.requestResetInterval : null, route.requestResetBaseTime !== undefined ? route.requestResetBaseTime : null, now, id);
|
|
492
629
|
return result.changes > 0;
|
|
493
630
|
}
|
|
494
631
|
deleteRule(id) {
|
|
@@ -527,28 +664,133 @@ class DatabaseManager {
|
|
|
527
664
|
*/
|
|
528
665
|
checkAndResetRuleIfNeeded(ruleId) {
|
|
529
666
|
const rule = this.db
|
|
530
|
-
.prepare('SELECT reset_interval, last_reset_at FROM rules WHERE id = ?')
|
|
667
|
+
.prepare('SELECT reset_interval, last_reset_at, token_reset_base_time FROM rules WHERE id = ?')
|
|
531
668
|
.get(ruleId);
|
|
532
669
|
if (!rule || !rule.reset_interval) {
|
|
533
670
|
return false; // 没有设置重置间隔
|
|
534
671
|
}
|
|
535
672
|
const now = Date.now();
|
|
536
673
|
const resetIntervalMs = rule.reset_interval * 60 * 60 * 1000; // 小时转毫秒
|
|
674
|
+
const baseTime = rule.token_reset_base_time;
|
|
537
675
|
const lastResetAt = rule.last_reset_at || 0;
|
|
538
|
-
//
|
|
676
|
+
// 场景1: 设置了时间基点
|
|
677
|
+
if (baseTime) {
|
|
678
|
+
if (now >= baseTime) {
|
|
679
|
+
this.resetRuleTokenUsageWithBaseTime(ruleId, baseTime);
|
|
680
|
+
return true;
|
|
681
|
+
}
|
|
682
|
+
return false;
|
|
683
|
+
}
|
|
684
|
+
// 场景2: 未设置时间基点,使用原始逻辑(向后兼容)
|
|
539
685
|
if (now - lastResetAt >= resetIntervalMs) {
|
|
540
686
|
this.resetRuleTokenUsage(ruleId);
|
|
541
687
|
return true;
|
|
542
688
|
}
|
|
543
689
|
return false;
|
|
544
690
|
}
|
|
691
|
+
/**
|
|
692
|
+
* 重置规则的Token使用量(带时间基点更新)
|
|
693
|
+
*/
|
|
694
|
+
resetRuleTokenUsageWithBaseTime(ruleId, currentBaseTime) {
|
|
695
|
+
const now = Date.now();
|
|
696
|
+
const rule = this.db
|
|
697
|
+
.prepare('SELECT reset_interval FROM rules WHERE id = ?')
|
|
698
|
+
.get(ruleId);
|
|
699
|
+
if (!rule || !rule.reset_interval) {
|
|
700
|
+
return false;
|
|
701
|
+
}
|
|
702
|
+
const resetIntervalMs = rule.reset_interval * 60 * 60 * 1000;
|
|
703
|
+
// 计算下一个时间基点
|
|
704
|
+
let nextBaseTime = currentBaseTime;
|
|
705
|
+
while (nextBaseTime <= now) {
|
|
706
|
+
nextBaseTime += resetIntervalMs;
|
|
707
|
+
}
|
|
708
|
+
const result = this.db
|
|
709
|
+
.prepare('UPDATE rules SET total_tokens_used = 0, last_reset_at = ?, token_reset_base_time = ? WHERE id = ?')
|
|
710
|
+
.run(now, nextBaseTime, ruleId);
|
|
711
|
+
return result.changes > 0;
|
|
712
|
+
}
|
|
713
|
+
/**
|
|
714
|
+
* 增加规则的请求次数
|
|
715
|
+
* @param ruleId 规则ID
|
|
716
|
+
* @param count 增加的次数
|
|
717
|
+
* @returns 是否成功
|
|
718
|
+
*/
|
|
719
|
+
incrementRuleRequestCount(ruleId, count) {
|
|
720
|
+
const result = this.db
|
|
721
|
+
.prepare('UPDATE rules SET total_requests_used = total_requests_used + ? WHERE id = ?')
|
|
722
|
+
.run(count, ruleId);
|
|
723
|
+
return result.changes > 0;
|
|
724
|
+
}
|
|
725
|
+
/**
|
|
726
|
+
* 重置规则的请求次数
|
|
727
|
+
* @param ruleId 规则ID
|
|
728
|
+
* @returns 是否成功
|
|
729
|
+
*/
|
|
730
|
+
resetRuleRequestCount(ruleId) {
|
|
731
|
+
const now = Date.now();
|
|
732
|
+
const result = this.db
|
|
733
|
+
.prepare('UPDATE rules SET total_requests_used = 0, request_last_reset_at = ? WHERE id = ?')
|
|
734
|
+
.run(now, ruleId);
|
|
735
|
+
return result.changes > 0;
|
|
736
|
+
}
|
|
737
|
+
/**
|
|
738
|
+
* 检查并重置到期的规则(请求次数)
|
|
739
|
+
* 如果规则设置了request_reset_interval且已经到了重置时间,则自动重置请求次数
|
|
740
|
+
* @param ruleId 规则ID
|
|
741
|
+
* @returns 是否进行了重置
|
|
742
|
+
*/
|
|
743
|
+
checkAndResetRequestCountIfNeeded(ruleId) {
|
|
744
|
+
const rule = this.db
|
|
745
|
+
.prepare('SELECT request_reset_interval, request_last_reset_at, request_reset_base_time FROM rules WHERE id = ?')
|
|
746
|
+
.get(ruleId);
|
|
747
|
+
if (!rule || !rule.request_reset_interval) {
|
|
748
|
+
return false; // 没有设置重置间隔
|
|
749
|
+
}
|
|
750
|
+
const now = Date.now();
|
|
751
|
+
const resetIntervalMs = rule.request_reset_interval * 60 * 60 * 1000; // 小时转毫秒
|
|
752
|
+
const baseTime = rule.request_reset_base_time;
|
|
753
|
+
const lastResetAt = rule.request_last_reset_at || 0;
|
|
754
|
+
// 场景1: 设置了时间基点
|
|
755
|
+
if (baseTime) {
|
|
756
|
+
if (now >= baseTime) {
|
|
757
|
+
this.resetRuleRequestCountWithBaseTime(ruleId, baseTime);
|
|
758
|
+
return true;
|
|
759
|
+
}
|
|
760
|
+
return false;
|
|
761
|
+
}
|
|
762
|
+
// 场景2: 未设置时间基点,使用原始逻辑(向后兼容)
|
|
763
|
+
if (now - lastResetAt >= resetIntervalMs) {
|
|
764
|
+
this.resetRuleRequestCount(ruleId);
|
|
765
|
+
return true;
|
|
766
|
+
}
|
|
767
|
+
return false;
|
|
768
|
+
}
|
|
769
|
+
/**
|
|
770
|
+
* 重置规则的请求次数(带时间基点更新)
|
|
771
|
+
*/
|
|
772
|
+
resetRuleRequestCountWithBaseTime(ruleId, currentBaseTime) {
|
|
773
|
+
const now = Date.now();
|
|
774
|
+
const rule = this.db
|
|
775
|
+
.prepare('SELECT request_reset_interval FROM rules WHERE id = ?')
|
|
776
|
+
.get(ruleId);
|
|
777
|
+
if (!rule || !rule.request_reset_interval) {
|
|
778
|
+
return false;
|
|
779
|
+
}
|
|
780
|
+
const resetIntervalMs = rule.request_reset_interval * 60 * 60 * 1000;
|
|
781
|
+
// 计算下一个时间基点
|
|
782
|
+
let nextBaseTime = currentBaseTime;
|
|
783
|
+
while (nextBaseTime <= now) {
|
|
784
|
+
nextBaseTime += resetIntervalMs;
|
|
785
|
+
}
|
|
786
|
+
const result = this.db
|
|
787
|
+
.prepare('UPDATE rules SET total_requests_used = 0, request_last_reset_at = ?, request_reset_base_time = ? WHERE id = ?')
|
|
788
|
+
.run(now, nextBaseTime, ruleId);
|
|
789
|
+
return result.changes > 0;
|
|
790
|
+
}
|
|
545
791
|
// Log operations
|
|
546
792
|
addLog(log) {
|
|
547
793
|
return __awaiter(this, void 0, void 0, function* () {
|
|
548
|
-
const { path } = log;
|
|
549
|
-
if (!path.startsWith('/v1/')) {
|
|
550
|
-
return;
|
|
551
|
-
}
|
|
552
794
|
const id = crypto_1.default.randomUUID();
|
|
553
795
|
yield this.logDb.put(id, JSON.stringify(Object.assign(Object.assign({}, log), { id })));
|
|
554
796
|
// 清除缓存
|
|
@@ -790,7 +1032,7 @@ class DatabaseManager {
|
|
|
790
1032
|
}
|
|
791
1033
|
});
|
|
792
1034
|
}
|
|
793
|
-
addToBlacklist(serviceId, routeId, contentType, errorMessage, statusCode) {
|
|
1035
|
+
addToBlacklist(serviceId, routeId, contentType, errorMessage, statusCode, errorType) {
|
|
794
1036
|
return __awaiter(this, void 0, void 0, function* () {
|
|
795
1037
|
const key = `${routeId}:${contentType}:${serviceId}`;
|
|
796
1038
|
const now = Date.now();
|
|
@@ -804,6 +1046,7 @@ class DatabaseManager {
|
|
|
804
1046
|
entry.errorCount++;
|
|
805
1047
|
entry.lastError = errorMessage;
|
|
806
1048
|
entry.lastStatusCode = statusCode;
|
|
1049
|
+
entry.errorType = errorType;
|
|
807
1050
|
yield this.blacklistDb.put(key, JSON.stringify(entry));
|
|
808
1051
|
}
|
|
809
1052
|
catch (error) {
|
|
@@ -818,6 +1061,7 @@ class DatabaseManager {
|
|
|
818
1061
|
errorCount: 1,
|
|
819
1062
|
lastError: errorMessage,
|
|
820
1063
|
lastStatusCode: statusCode,
|
|
1064
|
+
errorType,
|
|
821
1065
|
};
|
|
822
1066
|
yield this.blacklistDb.put(key, JSON.stringify(entry));
|
|
823
1067
|
}
|
|
@@ -827,6 +1071,12 @@ class DatabaseManager {
|
|
|
827
1071
|
}
|
|
828
1072
|
});
|
|
829
1073
|
}
|
|
1074
|
+
removeFromBlacklist(serviceId, routeId, contentType) {
|
|
1075
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1076
|
+
const key = `${routeId}:${contentType}:${serviceId}`;
|
|
1077
|
+
yield this.blacklistDb.del(key);
|
|
1078
|
+
});
|
|
1079
|
+
}
|
|
830
1080
|
cleanupExpiredBlacklist() {
|
|
831
1081
|
return __awaiter(this, void 0, void 0, function* () {
|
|
832
1082
|
var _a, e_7, _b, _c;
|
|
@@ -902,8 +1152,8 @@ class DatabaseManager {
|
|
|
902
1152
|
// Import API services
|
|
903
1153
|
for (const service of importData.apiServices) {
|
|
904
1154
|
this.db
|
|
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);
|
|
1155
|
+
.prepare('INSERT INTO api_services (id, vendor_id, name, api_url, api_key, source_type, supported_models, model_limits, enable_proxy, enable_token_limit, token_limit, token_reset_interval, token_reset_base_time, enable_request_limit, request_count_limit, request_reset_interval, request_reset_base_time, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)')
|
|
1156
|
+
.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.enableProxy ? 1 : 0, service.enableTokenLimit ? 1 : 0, service.tokenLimit || null, service.tokenResetInterval || null, service.tokenResetBaseTime || null, service.enableRequestLimit ? 1 : 0, service.requestCountLimit || null, service.requestResetInterval || null, service.requestResetBaseTime || null, service.createdAt, service.updatedAt);
|
|
907
1157
|
}
|
|
908
1158
|
// Import routes
|
|
909
1159
|
for (const route of importData.routes) {
|
|
@@ -914,8 +1164,8 @@ class DatabaseManager {
|
|
|
914
1164
|
// Import rules
|
|
915
1165
|
for (const rule of importData.rules) {
|
|
916
1166
|
this.db
|
|
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);
|
|
1167
|
+
.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, token_reset_base_time, request_count_limit, total_requests_used, request_reset_interval, request_last_reset_at, request_reset_base_time, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)')
|
|
1168
|
+
.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.tokenResetBaseTime || null, rule.requestCountLimit || null, rule.totalRequestsUsed || 0, rule.requestResetInterval || null, rule.requestLastResetAt || null, rule.requestResetBaseTime || null, rule.createdAt, rule.updatedAt);
|
|
919
1169
|
}
|
|
920
1170
|
// Update config
|
|
921
1171
|
this.updateConfig(importData.config);
|
|
@@ -1150,6 +1400,27 @@ class DatabaseManager {
|
|
|
1150
1400
|
};
|
|
1151
1401
|
});
|
|
1152
1402
|
}
|
|
1403
|
+
getRuleBlacklistStatus(serviceId, routeId, contentType) {
|
|
1404
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1405
|
+
const key = `${routeId}:${contentType}:${serviceId}`;
|
|
1406
|
+
try {
|
|
1407
|
+
const value = yield this.blacklistDb.get(key);
|
|
1408
|
+
const entry = JSON.parse(value);
|
|
1409
|
+
// 检查是否过期
|
|
1410
|
+
if (Date.now() > entry.expiresAt) {
|
|
1411
|
+
yield this.blacklistDb.del(key);
|
|
1412
|
+
return null;
|
|
1413
|
+
}
|
|
1414
|
+
return entry;
|
|
1415
|
+
}
|
|
1416
|
+
catch (error) {
|
|
1417
|
+
if (error.code === 'LEVEL_NOT_FOUND') {
|
|
1418
|
+
return null;
|
|
1419
|
+
}
|
|
1420
|
+
throw error;
|
|
1421
|
+
}
|
|
1422
|
+
});
|
|
1423
|
+
}
|
|
1153
1424
|
close() {
|
|
1154
1425
|
this.db.close();
|
|
1155
1426
|
this.logDb.close();
|
package/dist/server/main.js
CHANGED
|
@@ -283,6 +283,54 @@ const registerRoutes = (dbManager, proxyServer) => {
|
|
|
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
285
|
app.put('/api/rules/:id/reset-tokens', (req, res) => res.json(dbManager.resetRuleTokenUsage(req.params.id)));
|
|
286
|
+
app.put('/api/rules/:id/reset-requests', (req, res) => res.json(dbManager.resetRuleRequestCount(req.params.id)));
|
|
287
|
+
// 解除规则的黑名单状态
|
|
288
|
+
app.put('/api/rules/:id/clear-blacklist', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
|
289
|
+
const { id } = req.params;
|
|
290
|
+
const rule = dbManager.getRule(id);
|
|
291
|
+
if (!rule) {
|
|
292
|
+
res.status(404).json({ error: 'Rule not found' });
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
// 找到该规则所属的路由
|
|
296
|
+
const routes = dbManager.getRoutes();
|
|
297
|
+
const route = routes.find(r => {
|
|
298
|
+
const rules = dbManager.getRules(r.id);
|
|
299
|
+
return rules.some(r => r.id === id);
|
|
300
|
+
});
|
|
301
|
+
if (!route) {
|
|
302
|
+
res.status(404).json({ error: 'Route not found' });
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
try {
|
|
306
|
+
yield dbManager.removeFromBlacklist(rule.targetServiceId, route.id, rule.contentType);
|
|
307
|
+
res.json({ success: true });
|
|
308
|
+
}
|
|
309
|
+
catch (error) {
|
|
310
|
+
console.error('Error clearing blacklist:', error);
|
|
311
|
+
res.status(500).json({ error: 'Failed to clear blacklist' });
|
|
312
|
+
}
|
|
313
|
+
})));
|
|
314
|
+
// 获取规则的黑名单状态
|
|
315
|
+
app.get('/api/rules/:routeId/blacklist-status', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
|
316
|
+
const { routeId } = req.params;
|
|
317
|
+
const rules = dbManager.getRules(routeId);
|
|
318
|
+
try {
|
|
319
|
+
const results = yield Promise.all(rules.map((rule) => __awaiter(void 0, void 0, void 0, function* () {
|
|
320
|
+
const blacklistStatus = yield dbManager.getRuleBlacklistStatus(rule.targetServiceId, routeId, rule.contentType);
|
|
321
|
+
return {
|
|
322
|
+
ruleId: rule.id,
|
|
323
|
+
isBlacklisted: blacklistStatus !== null,
|
|
324
|
+
blacklistEntry: blacklistStatus,
|
|
325
|
+
};
|
|
326
|
+
})));
|
|
327
|
+
res.json(results);
|
|
328
|
+
}
|
|
329
|
+
catch (error) {
|
|
330
|
+
console.error('Error getting blacklist status:', error);
|
|
331
|
+
res.status(500).json({ error: 'Failed to get blacklist status' });
|
|
332
|
+
}
|
|
333
|
+
})));
|
|
286
334
|
app.get('/api/logs', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
|
287
335
|
const rawLimit = typeof req.query.limit === 'string' ? parseInt(req.query.limit, 10) : NaN;
|
|
288
336
|
const rawOffset = typeof req.query.offset === 'string' ? parseInt(req.query.offset, 10) : NaN;
|