aicodeswitch 3.0.1 → 3.0.3
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/README.md +6 -2
- package/bin/restore.js +18 -5
- package/dist/server/config-metadata.js +40 -7
- package/dist/server/database-factory.js +54 -1
- package/dist/server/database.js +31 -9
- package/dist/server/fs-database.js +394 -70
- package/dist/server/main.js +23 -17
- package/dist/server/migrate-to-fs.js +124 -24
- package/dist/server/proxy-server.js +1 -0
- package/dist/ui/assets/index-BFZeqM0H.css +1 -0
- package/dist/ui/assets/index-DgBQpyCC.js +476 -0
- package/dist/ui/index.html +2 -2
- package/package.json +2 -2
- package/schema/claude.schema.md +945 -0
- package/schema/gemini.schema.md +1397 -0
- package/schema/openai.schema.md +2162 -0
- package/dist/ui/assets/index-Bo5rJH01.js +0 -472
- package/dist/ui/assets/index-uPfIRVTr.css +0 -1
|
@@ -8,6 +8,17 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
8
8
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
9
|
});
|
|
10
10
|
};
|
|
11
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
12
|
+
var t = {};
|
|
13
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
14
|
+
t[p] = s[p];
|
|
15
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
16
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
17
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
18
|
+
t[p[i]] = s[p[i]];
|
|
19
|
+
}
|
|
20
|
+
return t;
|
|
21
|
+
};
|
|
11
22
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
23
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
24
|
};
|
|
@@ -26,7 +37,7 @@ class FileSystemDatabaseManager {
|
|
|
26
37
|
get vendorsFile() { return path_1.default.join(this.dataPath, 'vendors.json'); }
|
|
27
38
|
get servicesFile() { return path_1.default.join(this.dataPath, 'services.json'); }
|
|
28
39
|
get routesFile() { return path_1.default.join(this.dataPath, 'routes.json'); }
|
|
29
|
-
get rulesFile() { return path_1.default.join(this.dataPath, 'rules.json'); }
|
|
40
|
+
get rulesFile() { return path_1.default.join(this.dataPath, 'rules.json'); } // legacy
|
|
30
41
|
get configFile() { return path_1.default.join(this.dataPath, 'config.json'); }
|
|
31
42
|
get sessionsFile() { return path_1.default.join(this.dataPath, 'sessions.json'); }
|
|
32
43
|
get logsDir() { return path_1.default.join(this.dataPath, 'logs'); }
|
|
@@ -76,12 +87,7 @@ class FileSystemDatabaseManager {
|
|
|
76
87
|
writable: true,
|
|
77
88
|
value: []
|
|
78
89
|
});
|
|
79
|
-
|
|
80
|
-
enumerable: true,
|
|
81
|
-
configurable: true,
|
|
82
|
-
writable: true,
|
|
83
|
-
value: []
|
|
84
|
-
});
|
|
90
|
+
// 移除独立的 apiServices 存储,现在作为 vendor 的属性
|
|
85
91
|
Object.defineProperty(this, "routes", {
|
|
86
92
|
enumerable: true,
|
|
87
93
|
configurable: true,
|
|
@@ -131,6 +137,18 @@ class FileSystemDatabaseManager {
|
|
|
131
137
|
writable: true,
|
|
132
138
|
value: this.createEmptyStatistics()
|
|
133
139
|
});
|
|
140
|
+
Object.defineProperty(this, "contentTypeDistributionInitialized", {
|
|
141
|
+
enumerable: true,
|
|
142
|
+
configurable: true,
|
|
143
|
+
writable: true,
|
|
144
|
+
value: false
|
|
145
|
+
});
|
|
146
|
+
Object.defineProperty(this, "contentTypeDistributionInitializing", {
|
|
147
|
+
enumerable: true,
|
|
148
|
+
configurable: true,
|
|
149
|
+
writable: true,
|
|
150
|
+
value: false
|
|
151
|
+
});
|
|
134
152
|
// 缓存机制
|
|
135
153
|
Object.defineProperty(this, "logsCountCache", {
|
|
136
154
|
enumerable: true,
|
|
@@ -178,10 +196,9 @@ class FileSystemDatabaseManager {
|
|
|
178
196
|
loadAllData() {
|
|
179
197
|
return __awaiter(this, void 0, void 0, function* () {
|
|
180
198
|
yield Promise.all([
|
|
181
|
-
this.loadVendors(),
|
|
182
|
-
this.loadServices(),
|
|
199
|
+
this.loadVendors(), // loadVendors 内部会处理旧 services.json 的迁移
|
|
200
|
+
// 删除: this.loadServices(),
|
|
183
201
|
this.loadRoutes(),
|
|
184
|
-
this.loadRules(),
|
|
185
202
|
this.loadConfig(),
|
|
186
203
|
this.loadSessions(),
|
|
187
204
|
this.loadLogsIndex(),
|
|
@@ -200,59 +217,172 @@ class FileSystemDatabaseManager {
|
|
|
200
217
|
catch (_a) {
|
|
201
218
|
this.vendors = [];
|
|
202
219
|
}
|
|
220
|
+
// 兼容性检查:如果存在旧的 services.json,自动迁移
|
|
221
|
+
yield this.migrateServicesIfNeeded();
|
|
203
222
|
});
|
|
204
223
|
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
224
|
+
/**
|
|
225
|
+
* 检测并迁移旧的 services.json 到新结构
|
|
226
|
+
* 旧格式:vendors.json 和 services.json 分离
|
|
227
|
+
* 新格式:vendors.json 包含嵌套的 services 数组
|
|
228
|
+
*/
|
|
229
|
+
migrateServicesIfNeeded() {
|
|
211
230
|
return __awaiter(this, void 0, void 0, function* () {
|
|
231
|
+
const oldServicesFile = this.servicesFile;
|
|
212
232
|
try {
|
|
213
|
-
|
|
214
|
-
|
|
233
|
+
yield promises_1.default.access(oldServicesFile);
|
|
234
|
+
console.log('[Database] 发现旧的 services.json 文件,开始迁移到新结构...');
|
|
235
|
+
// 读取旧服务数据
|
|
236
|
+
const servicesData = yield promises_1.default.readFile(oldServicesFile, 'utf-8');
|
|
237
|
+
const oldServices = JSON.parse(servicesData);
|
|
238
|
+
console.log(`[Database] 准备迁移 ${oldServices.length} 个服务...`);
|
|
239
|
+
// 按 vendorId 分组
|
|
240
|
+
const servicesByVendor = new Map();
|
|
241
|
+
for (const service of oldServices) {
|
|
242
|
+
if (!service.vendorId) {
|
|
243
|
+
console.warn(`[Database] 跳过没有 vendorId 的服务: ${service.id}`);
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
if (!servicesByVendor.has(service.vendorId)) {
|
|
247
|
+
servicesByVendor.set(service.vendorId, []);
|
|
248
|
+
}
|
|
249
|
+
// 移除 vendorId 字段,因为现在通过父级关系隐式关联
|
|
250
|
+
const { vendorId } = service, serviceWithoutVendorId = __rest(service, ["vendorId"]);
|
|
251
|
+
servicesByVendor.get(service.vendorId).push(serviceWithoutVendorId);
|
|
252
|
+
}
|
|
253
|
+
// 合并到 vendors 数组
|
|
254
|
+
let migratedCount = 0;
|
|
255
|
+
for (const vendor of this.vendors) {
|
|
256
|
+
const services = servicesByVendor.get(vendor.id);
|
|
257
|
+
if (services) {
|
|
258
|
+
vendor.services = services;
|
|
259
|
+
migratedCount += services.length;
|
|
260
|
+
}
|
|
261
|
+
else {
|
|
262
|
+
vendor.services = [];
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
// 保存新的 vendors.json
|
|
266
|
+
yield this.saveVendors();
|
|
267
|
+
console.log(`[Database] 迁移完成:${migratedCount} 个服务已迁移`);
|
|
215
268
|
}
|
|
216
|
-
catch (
|
|
217
|
-
|
|
269
|
+
catch (err) {
|
|
270
|
+
if (err.code === 'ENOENT') {
|
|
271
|
+
// 旧文件不存在,这是正常的(新安装或已迁移)
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
console.error('[Database] 迁移 services 时出错:', err);
|
|
218
275
|
}
|
|
219
276
|
});
|
|
220
277
|
}
|
|
221
|
-
|
|
278
|
+
saveVendors() {
|
|
222
279
|
return __awaiter(this, void 0, void 0, function* () {
|
|
223
|
-
|
|
280
|
+
// 确保每个供应商都有 services 数组
|
|
281
|
+
const normalizedVendors = this.vendors.map(v => (Object.assign(Object.assign({}, v), { services: v.services || [] })));
|
|
282
|
+
yield promises_1.default.writeFile(this.vendorsFile, JSON.stringify(normalizedVendors, null, 2));
|
|
224
283
|
});
|
|
225
284
|
}
|
|
285
|
+
// loadServices 和 saveServices 已移除
|
|
286
|
+
// 服务现在作为供应商的属性存储在 vendors.json 中
|
|
287
|
+
// 迁移逻辑见 migrateServicesIfNeeded() 方法
|
|
226
288
|
loadRoutes() {
|
|
227
289
|
return __awaiter(this, void 0, void 0, function* () {
|
|
290
|
+
let routesFileFormat = 'missing';
|
|
291
|
+
let routesFromFile = [];
|
|
292
|
+
let rulesFromFile = [];
|
|
293
|
+
let hasRulesInRoutesFile = false;
|
|
228
294
|
try {
|
|
229
295
|
const data = yield promises_1.default.readFile(this.routesFile, 'utf-8');
|
|
230
|
-
|
|
296
|
+
const parsed = JSON.parse(data);
|
|
297
|
+
if (Array.isArray(parsed)) {
|
|
298
|
+
routesFileFormat = 'array';
|
|
299
|
+
routesFromFile = parsed;
|
|
300
|
+
}
|
|
301
|
+
else if (parsed && typeof parsed === 'object') {
|
|
302
|
+
routesFileFormat = 'combined';
|
|
303
|
+
routesFromFile = Array.isArray(parsed.routes) ? parsed.routes : [];
|
|
304
|
+
if (Array.isArray(parsed.rules)) {
|
|
305
|
+
rulesFromFile = parsed.rules;
|
|
306
|
+
hasRulesInRoutesFile = true;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
else {
|
|
310
|
+
routesFileFormat = 'unknown';
|
|
311
|
+
}
|
|
231
312
|
}
|
|
232
313
|
catch (_a) {
|
|
233
|
-
|
|
314
|
+
routesFileFormat = 'missing';
|
|
234
315
|
}
|
|
316
|
+
this.routes = routesFromFile;
|
|
317
|
+
this.rules = rulesFromFile;
|
|
318
|
+
// 兼容旧的 rules.json 文件(迁移到 routes.json 的 rules 属性)
|
|
319
|
+
yield this.migrateRulesIfNeeded(routesFileFormat, hasRulesInRoutesFile);
|
|
235
320
|
});
|
|
236
321
|
}
|
|
237
|
-
|
|
322
|
+
saveRoutesData() {
|
|
238
323
|
return __awaiter(this, void 0, void 0, function* () {
|
|
239
|
-
|
|
324
|
+
const payload = {
|
|
325
|
+
routes: this.routes,
|
|
326
|
+
rules: this.rules,
|
|
327
|
+
};
|
|
328
|
+
yield promises_1.default.writeFile(this.routesFile, JSON.stringify(payload, null, 2));
|
|
240
329
|
});
|
|
241
330
|
}
|
|
242
|
-
|
|
331
|
+
saveRoutes() {
|
|
243
332
|
return __awaiter(this, void 0, void 0, function* () {
|
|
244
|
-
|
|
245
|
-
const data = yield promises_1.default.readFile(this.rulesFile, 'utf-8');
|
|
246
|
-
this.rules = JSON.parse(data);
|
|
247
|
-
}
|
|
248
|
-
catch (_a) {
|
|
249
|
-
this.rules = [];
|
|
250
|
-
}
|
|
333
|
+
yield this.saveRoutesData();
|
|
251
334
|
});
|
|
252
335
|
}
|
|
253
336
|
saveRules() {
|
|
254
337
|
return __awaiter(this, void 0, void 0, function* () {
|
|
255
|
-
yield
|
|
338
|
+
yield this.saveRoutesData();
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* 检测并迁移旧的 rules.json 到 routes.json 的 rules 属性
|
|
343
|
+
* 旧格式:routes.json + rules.json 分离
|
|
344
|
+
* 新格式:routes.json 内包含 { routes, rules }
|
|
345
|
+
*/
|
|
346
|
+
migrateRulesIfNeeded(routesFileFormat, hasRulesInRoutesFile) {
|
|
347
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
348
|
+
const oldRulesFile = this.rulesFile;
|
|
349
|
+
const oldRulesExists = yield promises_1.default.access(oldRulesFile)
|
|
350
|
+
.then(() => true)
|
|
351
|
+
.catch(() => false);
|
|
352
|
+
let merged = false;
|
|
353
|
+
if (oldRulesExists) {
|
|
354
|
+
try {
|
|
355
|
+
const data = yield promises_1.default.readFile(oldRulesFile, 'utf-8');
|
|
356
|
+
const oldRules = JSON.parse(data);
|
|
357
|
+
if (Array.isArray(oldRules)) {
|
|
358
|
+
if (this.rules.length > 0) {
|
|
359
|
+
const mergedMap = new Map();
|
|
360
|
+
oldRules.forEach((rule, index) => {
|
|
361
|
+
const key = (rule === null || rule === void 0 ? void 0 : rule.id) || `legacy-${index}`;
|
|
362
|
+
if (!mergedMap.has(key)) {
|
|
363
|
+
mergedMap.set(key, rule);
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
this.rules.forEach((rule, index) => {
|
|
367
|
+
const key = (rule === null || rule === void 0 ? void 0 : rule.id) || `current-${index}`;
|
|
368
|
+
mergedMap.set(key, rule);
|
|
369
|
+
});
|
|
370
|
+
this.rules = Array.from(mergedMap.values());
|
|
371
|
+
}
|
|
372
|
+
else {
|
|
373
|
+
this.rules = oldRules;
|
|
374
|
+
}
|
|
375
|
+
merged = true;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
catch (error) {
|
|
379
|
+
console.error('[Database] 迁移 rules.json 时出错:', error);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
// 如果 routes.json 还是旧格式/缺失,或从旧 rules.json 合并过数据,或缺少 rules 字段,则写入新格式
|
|
383
|
+
if (routesFileFormat !== 'combined' || merged || (routesFileFormat === 'combined' && !hasRulesInRoutesFile)) {
|
|
384
|
+
yield this.saveRoutesData();
|
|
385
|
+
}
|
|
256
386
|
});
|
|
257
387
|
}
|
|
258
388
|
loadConfig() {
|
|
@@ -387,10 +517,6 @@ class FileSystemDatabaseManager {
|
|
|
387
517
|
// 保存索引
|
|
388
518
|
yield this.saveLogsIndex();
|
|
389
519
|
console.log(`[Database] Successfully migrated ${migratedCount} log entries to ${this.logShardsIndex.length} shard(s)`);
|
|
390
|
-
// 备份旧文件
|
|
391
|
-
const backupFile = path_1.default.join(this.dataPath, 'logs.json.backup');
|
|
392
|
-
yield promises_1.default.rename(oldLogsFile, backupFile);
|
|
393
|
-
console.log(`[Database] Old logs.json backed up to ${backupFile}`);
|
|
394
520
|
}
|
|
395
521
|
catch (err) {
|
|
396
522
|
if (err.code === 'ENOENT') {
|
|
@@ -533,9 +659,11 @@ class FileSystemDatabaseManager {
|
|
|
533
659
|
try {
|
|
534
660
|
const data = yield promises_1.default.readFile(this.statisticsFile, 'utf-8');
|
|
535
661
|
this.statistics = JSON.parse(data);
|
|
662
|
+
this.contentTypeDistributionInitialized = this.statistics.contentTypeDistribution.length > 0;
|
|
536
663
|
}
|
|
537
664
|
catch (_a) {
|
|
538
665
|
this.statistics = this.createEmptyStatistics();
|
|
666
|
+
this.contentTypeDistributionInitialized = false;
|
|
539
667
|
// 创建空文件
|
|
540
668
|
yield this.saveStatistics();
|
|
541
669
|
}
|
|
@@ -573,11 +701,17 @@ class FileSystemDatabaseManager {
|
|
|
573
701
|
return b.createdAt - a.createdAt;
|
|
574
702
|
});
|
|
575
703
|
}
|
|
704
|
+
// 新增:获取单个供应商(带服务)
|
|
705
|
+
getVendor(id) {
|
|
706
|
+
return this.vendors.find(v => v.id === id);
|
|
707
|
+
}
|
|
576
708
|
createVendor(vendor) {
|
|
577
709
|
return __awaiter(this, void 0, void 0, function* () {
|
|
710
|
+
console.log('[数据库] 创建供应商,输入数据:', JSON.stringify(vendor, null, 2));
|
|
578
711
|
const id = crypto_1.default.randomUUID();
|
|
579
712
|
const now = Date.now();
|
|
580
|
-
const newVendor = Object.assign(Object.assign({}, vendor), { id, createdAt: now, updatedAt: now });
|
|
713
|
+
const newVendor = Object.assign(Object.assign({}, vendor), { id, services: vendor.services || [], createdAt: now, updatedAt: now });
|
|
714
|
+
console.log('[数据库] 创建供应商,返回数据:', JSON.stringify(newVendor, null, 2));
|
|
581
715
|
this.vendors.push(newVendor);
|
|
582
716
|
yield this.saveVendors();
|
|
583
717
|
return newVendor;
|
|
@@ -589,7 +723,7 @@ class FileSystemDatabaseManager {
|
|
|
589
723
|
if (index === -1)
|
|
590
724
|
return false;
|
|
591
725
|
const now = Date.now();
|
|
592
|
-
this.vendors[index] = Object.assign(Object.assign(Object.assign({}, this.vendors[index]), vendor), { id, updatedAt: now });
|
|
726
|
+
this.vendors[index] = Object.assign(Object.assign(Object.assign({}, this.vendors[index]), vendor), { id, services: vendor.services !== undefined ? vendor.services : this.vendors[index].services, updatedAt: now });
|
|
593
727
|
yield this.saveVendors();
|
|
594
728
|
return true;
|
|
595
729
|
});
|
|
@@ -599,9 +733,13 @@ class FileSystemDatabaseManager {
|
|
|
599
733
|
const index = this.vendors.findIndex(v => v.id === id);
|
|
600
734
|
if (index === -1)
|
|
601
735
|
return false;
|
|
602
|
-
//
|
|
603
|
-
|
|
604
|
-
|
|
736
|
+
// 检查是否有服务被规则使用
|
|
737
|
+
const vendor = this.vendors[index];
|
|
738
|
+
const serviceIds = (vendor.services || []).map(s => s.id);
|
|
739
|
+
const rulesUsingServices = this.rules.filter(r => serviceIds.includes(r.targetServiceId));
|
|
740
|
+
if (rulesUsingServices.length > 0) {
|
|
741
|
+
throw new Error(`无法删除供应商:有 ${rulesUsingServices.length} 个规则正在使用该供应商的服务`);
|
|
742
|
+
}
|
|
605
743
|
this.vendors.splice(index, 1);
|
|
606
744
|
yield this.saveVendors();
|
|
607
745
|
return true;
|
|
@@ -609,29 +747,89 @@ class FileSystemDatabaseManager {
|
|
|
609
747
|
}
|
|
610
748
|
// API Service operations
|
|
611
749
|
getAPIServices(vendorId) {
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
750
|
+
if (vendorId) {
|
|
751
|
+
const vendor = this.vendors.find(v => v.id === vendorId);
|
|
752
|
+
if (!vendor)
|
|
753
|
+
return [];
|
|
754
|
+
// 返回指定供应商的服务,并添加 vendorId
|
|
755
|
+
return (vendor.services || []).map(service => (Object.assign(Object.assign({}, service), { vendorId: vendor.id // 添加 vendorId 以便前端使用
|
|
756
|
+
})));
|
|
757
|
+
}
|
|
758
|
+
// 返回所有供应商的所有服务(扁平化),并添加 vendorId
|
|
759
|
+
const allServices = [];
|
|
760
|
+
for (const vendor of this.vendors) {
|
|
761
|
+
if (vendor.services) {
|
|
762
|
+
const servicesWithVendorId = vendor.services.map(service => (Object.assign(Object.assign({}, service), { vendorId: vendor.id // 添加 vendorId 以便前端使用
|
|
763
|
+
})));
|
|
764
|
+
allServices.push(...servicesWithVendorId);
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
return allServices.sort((a, b) => b.createdAt - a.createdAt);
|
|
768
|
+
}
|
|
769
|
+
// 新增:通过 ID 获取服务
|
|
770
|
+
getAPIService(id) {
|
|
771
|
+
var _a;
|
|
772
|
+
for (const vendor of this.vendors) {
|
|
773
|
+
const service = (_a = vendor.services) === null || _a === void 0 ? void 0 : _a.find(s => s.id === id);
|
|
774
|
+
if (service) {
|
|
775
|
+
return service;
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
return undefined;
|
|
779
|
+
}
|
|
780
|
+
// 新增:获取服务所属的供应商
|
|
781
|
+
getVendorByServiceId(serviceId) {
|
|
782
|
+
var _a;
|
|
783
|
+
for (const vendor of this.vendors) {
|
|
784
|
+
if ((_a = vendor.services) === null || _a === void 0 ? void 0 : _a.some(s => s.id === serviceId)) {
|
|
785
|
+
return vendor;
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
return undefined;
|
|
616
789
|
}
|
|
617
790
|
createAPIService(service) {
|
|
618
791
|
return __awaiter(this, void 0, void 0, function* () {
|
|
792
|
+
console.log('[数据库] 创建服务,输入数据:', JSON.stringify(service, null, 2));
|
|
793
|
+
// 从 vendorId 找到供应商
|
|
794
|
+
const vendorId = service.vendorId;
|
|
795
|
+
if (!vendorId) {
|
|
796
|
+
throw new Error('创建服务时必须提供 vendorId');
|
|
797
|
+
}
|
|
798
|
+
const vendor = this.vendors.find(v => v.id === vendorId);
|
|
799
|
+
if (!vendor) {
|
|
800
|
+
throw new Error(`供应商不存在: ${vendorId}`);
|
|
801
|
+
}
|
|
802
|
+
// 移除 vendorId 字段(数据存储时不需要)
|
|
803
|
+
const _a = service, { vendorId: _ } = _a, serviceData = __rest(_a, ["vendorId"]);
|
|
619
804
|
const id = crypto_1.default.randomUUID();
|
|
620
805
|
const now = Date.now();
|
|
621
|
-
const newService = Object.assign(Object.assign({},
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
806
|
+
const newService = Object.assign(Object.assign({}, serviceData), { id, createdAt: now, updatedAt: now });
|
|
807
|
+
console.log('[数据库] 创建服务,最终数据:', JSON.stringify(newService, null, 2));
|
|
808
|
+
if (!vendor.services) {
|
|
809
|
+
vendor.services = [];
|
|
810
|
+
}
|
|
811
|
+
vendor.services.push(newService);
|
|
812
|
+
// 更新供应商的 updatedAt 时间
|
|
813
|
+
vendor.updatedAt = now;
|
|
814
|
+
yield this.saveVendors();
|
|
815
|
+
console.log('[数据库] 服务已保存,当前总数:', vendor.services.length);
|
|
816
|
+
return Object.assign(Object.assign({}, newService), { vendorId });
|
|
625
817
|
});
|
|
626
818
|
}
|
|
627
819
|
updateAPIService(id, service) {
|
|
628
820
|
return __awaiter(this, void 0, void 0, function* () {
|
|
629
|
-
|
|
821
|
+
// 查找服务所属的供应商
|
|
822
|
+
const vendor = this.getVendorByServiceId(id);
|
|
823
|
+
if (!vendor)
|
|
824
|
+
return false;
|
|
825
|
+
const index = vendor.services.findIndex(s => s.id === id);
|
|
630
826
|
if (index === -1)
|
|
631
827
|
return false;
|
|
632
828
|
const now = Date.now();
|
|
633
|
-
|
|
634
|
-
|
|
829
|
+
vendor.services[index] = Object.assign(Object.assign(Object.assign({}, vendor.services[index]), service), { id, updatedAt: now });
|
|
830
|
+
// 更新供应商的 updatedAt 时间
|
|
831
|
+
vendor.updatedAt = now;
|
|
832
|
+
yield this.saveVendors();
|
|
635
833
|
// 同步规则的超量限制
|
|
636
834
|
yield this.syncRulesWithServiceLimits(id, service);
|
|
637
835
|
return true;
|
|
@@ -639,14 +837,22 @@ class FileSystemDatabaseManager {
|
|
|
639
837
|
}
|
|
640
838
|
deleteAPIService(id) {
|
|
641
839
|
return __awaiter(this, void 0, void 0, function* () {
|
|
642
|
-
|
|
840
|
+
// 查找服务所属的供应商
|
|
841
|
+
const vendor = this.getVendorByServiceId(id);
|
|
842
|
+
if (!vendor)
|
|
843
|
+
return false;
|
|
844
|
+
const index = vendor.services.findIndex(s => s.id === id);
|
|
643
845
|
if (index === -1)
|
|
644
846
|
return false;
|
|
645
|
-
//
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
847
|
+
// 检查是否有规则正在使用此服务
|
|
848
|
+
const rulesUsingService = this.rules.filter(r => r.targetServiceId === id);
|
|
849
|
+
if (rulesUsingService.length > 0) {
|
|
850
|
+
throw new Error(`无法删除服务:有 ${rulesUsingService.length} 个规则正在使用此服务`);
|
|
851
|
+
}
|
|
852
|
+
vendor.services.splice(index, 1);
|
|
853
|
+
// 更新供应商的 updatedAt 时间
|
|
854
|
+
vendor.updatedAt = Date.now();
|
|
855
|
+
yield this.saveVendors();
|
|
650
856
|
return true;
|
|
651
857
|
});
|
|
652
858
|
}
|
|
@@ -656,7 +862,7 @@ class FileSystemDatabaseManager {
|
|
|
656
862
|
if (relatedRules.length === 0)
|
|
657
863
|
return;
|
|
658
864
|
const now = Date.now();
|
|
659
|
-
const currentService = this.
|
|
865
|
+
const currentService = this.getAPIService(serviceId);
|
|
660
866
|
if (!currentService)
|
|
661
867
|
return;
|
|
662
868
|
let updated = false;
|
|
@@ -952,7 +1158,8 @@ class FileSystemDatabaseManager {
|
|
|
952
1158
|
addLog(log) {
|
|
953
1159
|
return __awaiter(this, void 0, void 0, function* () {
|
|
954
1160
|
const id = crypto_1.default.randomUUID();
|
|
955
|
-
const
|
|
1161
|
+
const contentType = this.resolveLogContentType(log);
|
|
1162
|
+
const logWithId = Object.assign(Object.assign({}, log), { contentType, id });
|
|
956
1163
|
// 获取目标分片文件名
|
|
957
1164
|
const filename = yield this.getLogShardFilename(logWithId.timestamp);
|
|
958
1165
|
// 加载现有分片数据
|
|
@@ -1158,11 +1365,20 @@ class FileSystemDatabaseManager {
|
|
|
1158
1365
|
// Export/Import operations
|
|
1159
1366
|
exportData(password) {
|
|
1160
1367
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1368
|
+
// 扁平化所有服务(兼容旧格式)
|
|
1369
|
+
const allServices = [];
|
|
1370
|
+
for (const vendor of this.vendors) {
|
|
1371
|
+
if (vendor.services) {
|
|
1372
|
+
// 为每个服务添加 vendorId(兼容旧格式)
|
|
1373
|
+
const servicesWithVendorId = vendor.services.map(s => (Object.assign(Object.assign({}, s), { vendorId: vendor.id })));
|
|
1374
|
+
allServices.push(...servicesWithVendorId);
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1161
1377
|
const exportData = {
|
|
1162
|
-
version: '
|
|
1378
|
+
version: '2.0.0', // 更新版本号
|
|
1163
1379
|
exportDate: Date.now(),
|
|
1164
1380
|
vendors: this.vendors,
|
|
1165
|
-
apiServices:
|
|
1381
|
+
apiServices: allServices, // 兼容旧格式
|
|
1166
1382
|
routes: this.routes,
|
|
1167
1383
|
rules: this.rules,
|
|
1168
1384
|
config: this.config,
|
|
@@ -1178,16 +1394,36 @@ class FileSystemDatabaseManager {
|
|
|
1178
1394
|
const decrypted = crypto_js_1.default.AES.decrypt(encryptedData, password);
|
|
1179
1395
|
const jsonData = decrypted.toString(crypto_js_1.default.enc.Utf8);
|
|
1180
1396
|
const importData = JSON.parse(jsonData);
|
|
1181
|
-
|
|
1182
|
-
|
|
1397
|
+
// 检测数据版本
|
|
1398
|
+
const isNewFormat = importData.vendors.some((v) => v.services);
|
|
1399
|
+
if (isNewFormat || importData.version >= '2.0.0') {
|
|
1400
|
+
// 新格式:直接使用 vendors(已包含 services)
|
|
1401
|
+
this.vendors = importData.vendors;
|
|
1402
|
+
}
|
|
1403
|
+
else {
|
|
1404
|
+
// 旧格式:需要重建 services 关系
|
|
1405
|
+
const { vendors, apiServices } = importData;
|
|
1406
|
+
// 构建 vendorId -> services 映射
|
|
1407
|
+
const servicesByVendor = new Map();
|
|
1408
|
+
for (const service of apiServices) {
|
|
1409
|
+
if (!service.vendorId)
|
|
1410
|
+
continue;
|
|
1411
|
+
if (!servicesByVendor.has(service.vendorId)) {
|
|
1412
|
+
servicesByVendor.set(service.vendorId, []);
|
|
1413
|
+
}
|
|
1414
|
+
const { vendorId } = service, serviceWithoutVendorId = __rest(service, ["vendorId"]);
|
|
1415
|
+
servicesByVendor.get(service.vendorId).push(serviceWithoutVendorId);
|
|
1416
|
+
}
|
|
1417
|
+
// 合并到 vendors
|
|
1418
|
+
this.vendors = vendors.map((vendor) => (Object.assign(Object.assign({}, vendor), { services: servicesByVendor.get(vendor.id) || [] })));
|
|
1419
|
+
}
|
|
1183
1420
|
this.routes = importData.routes;
|
|
1184
1421
|
this.rules = importData.rules;
|
|
1185
1422
|
this.config = importData.config;
|
|
1186
1423
|
yield Promise.all([
|
|
1187
1424
|
this.saveVendors(),
|
|
1188
|
-
this.saveServices(),
|
|
1425
|
+
// 删除: this.saveServices(),
|
|
1189
1426
|
this.saveRoutes(),
|
|
1190
|
-
this.saveRules(),
|
|
1191
1427
|
this.saveConfig(),
|
|
1192
1428
|
]);
|
|
1193
1429
|
return true;
|
|
@@ -1198,6 +1434,79 @@ class FileSystemDatabaseManager {
|
|
|
1198
1434
|
}
|
|
1199
1435
|
});
|
|
1200
1436
|
}
|
|
1437
|
+
inferContentTypeFromLog(log) {
|
|
1438
|
+
var _a, _b;
|
|
1439
|
+
const requestModel = ((_a = log.requestModel) === null || _a === void 0 ? void 0 : _a.toLowerCase()) || '';
|
|
1440
|
+
let bodyText = '';
|
|
1441
|
+
if (typeof log.body === 'string') {
|
|
1442
|
+
bodyText = log.body;
|
|
1443
|
+
}
|
|
1444
|
+
else if (log.body !== undefined) {
|
|
1445
|
+
try {
|
|
1446
|
+
bodyText = JSON.stringify(log.body);
|
|
1447
|
+
}
|
|
1448
|
+
catch (_c) {
|
|
1449
|
+
bodyText = '';
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
const lowerBody = bodyText.toLowerCase();
|
|
1453
|
+
if (lowerBody.includes('image') || lowerBody.includes('base64')) {
|
|
1454
|
+
return 'image-understanding';
|
|
1455
|
+
}
|
|
1456
|
+
if (requestModel.includes('think')) {
|
|
1457
|
+
return 'thinking';
|
|
1458
|
+
}
|
|
1459
|
+
if ((((_b = log.usage) === null || _b === void 0 ? void 0 : _b.inputTokens) || 0) > 12000) {
|
|
1460
|
+
return 'long-context';
|
|
1461
|
+
}
|
|
1462
|
+
return 'default';
|
|
1463
|
+
}
|
|
1464
|
+
resolveLogContentType(log) {
|
|
1465
|
+
if (log.contentType) {
|
|
1466
|
+
return log.contentType;
|
|
1467
|
+
}
|
|
1468
|
+
if (log.ruleId) {
|
|
1469
|
+
const rule = this.getRule(log.ruleId);
|
|
1470
|
+
if (rule === null || rule === void 0 ? void 0 : rule.contentType) {
|
|
1471
|
+
return rule.contentType;
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
return this.inferContentTypeFromLog(log);
|
|
1475
|
+
}
|
|
1476
|
+
ensureContentTypeDistribution() {
|
|
1477
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1478
|
+
if (this.contentTypeDistributionInitialized || this.contentTypeDistributionInitializing) {
|
|
1479
|
+
return;
|
|
1480
|
+
}
|
|
1481
|
+
this.contentTypeDistributionInitializing = true;
|
|
1482
|
+
try {
|
|
1483
|
+
if (this.logShardsIndex.length === 0) {
|
|
1484
|
+
this.contentTypeDistributionInitialized = true;
|
|
1485
|
+
return;
|
|
1486
|
+
}
|
|
1487
|
+
const counts = new Map();
|
|
1488
|
+
let totalRequests = 0;
|
|
1489
|
+
for (const shard of this.logShardsIndex) {
|
|
1490
|
+
const shardLogs = yield this.loadLogShard(shard.filename);
|
|
1491
|
+
for (const log of shardLogs) {
|
|
1492
|
+
totalRequests++;
|
|
1493
|
+
const contentType = this.resolveLogContentType(log);
|
|
1494
|
+
counts.set(contentType, (counts.get(contentType) || 0) + 1);
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
this.statistics.contentTypeDistribution = Array.from(counts.entries()).map(([contentType, count]) => ({
|
|
1498
|
+
contentType,
|
|
1499
|
+
count,
|
|
1500
|
+
percentage: totalRequests > 0 ? Math.round((count / totalRequests) * 100) : 0,
|
|
1501
|
+
}));
|
|
1502
|
+
this.contentTypeDistributionInitialized = true;
|
|
1503
|
+
yield this.saveStatistics();
|
|
1504
|
+
}
|
|
1505
|
+
finally {
|
|
1506
|
+
this.contentTypeDistributionInitializing = false;
|
|
1507
|
+
}
|
|
1508
|
+
});
|
|
1509
|
+
}
|
|
1201
1510
|
// Statistics operations
|
|
1202
1511
|
/**
|
|
1203
1512
|
* 更新统计数据 - 在每次添加日志时调用
|
|
@@ -1297,6 +1606,19 @@ class FileSystemDatabaseManager {
|
|
|
1297
1606
|
modelStats.avgResponseTime =
|
|
1298
1607
|
(modelStats.avgResponseTime * (modelStats.totalRequests - 1) + responseTime) / modelStats.totalRequests;
|
|
1299
1608
|
}
|
|
1609
|
+
// 更新 contentTypeDistribution
|
|
1610
|
+
const resolvedContentType = this.resolveLogContentType(log);
|
|
1611
|
+
let contentTypeStats = this.statistics.contentTypeDistribution.find(s => s.contentType === resolvedContentType);
|
|
1612
|
+
if (!contentTypeStats) {
|
|
1613
|
+
contentTypeStats = { contentType: resolvedContentType, count: 0, percentage: 0 };
|
|
1614
|
+
this.statistics.contentTypeDistribution.push(contentTypeStats);
|
|
1615
|
+
}
|
|
1616
|
+
contentTypeStats.count++;
|
|
1617
|
+
const totalRequests = this.statistics.overview.totalRequests;
|
|
1618
|
+
for (const entry of this.statistics.contentTypeDistribution) {
|
|
1619
|
+
entry.percentage = totalRequests > 0 ? Math.round((entry.count / totalRequests) * 100) : 0;
|
|
1620
|
+
}
|
|
1621
|
+
this.contentTypeDistributionInitialized = true;
|
|
1300
1622
|
// 更新 timeline
|
|
1301
1623
|
const date = new Date(log.timestamp).toISOString().split('T')[0];
|
|
1302
1624
|
let timelineStats = this.statistics.timeline.find(t => t.date === date);
|
|
@@ -1324,6 +1646,7 @@ class FileSystemDatabaseManager {
|
|
|
1324
1646
|
*/
|
|
1325
1647
|
getStatistics() {
|
|
1326
1648
|
return __awaiter(this, arguments, void 0, function* (days = 30) {
|
|
1649
|
+
yield this.ensureContentTypeDistribution();
|
|
1327
1650
|
const now = Date.now();
|
|
1328
1651
|
const startTime = now - days * 24 * 60 * 60 * 1000;
|
|
1329
1652
|
// 过滤 timeline 数据
|
|
@@ -1420,7 +1743,8 @@ class FileSystemDatabaseManager {
|
|
|
1420
1743
|
// 检查 body 中的 metadata.user_id(Claude Code)
|
|
1421
1744
|
if (log.body) {
|
|
1422
1745
|
try {
|
|
1423
|
-
|
|
1746
|
+
// body 可能是对象(已解析)或字符串(未解析)
|
|
1747
|
+
const body = typeof log.body === 'string' ? JSON.parse(log.body) : log.body;
|
|
1424
1748
|
if (((_b = body.metadata) === null || _b === void 0 ? void 0 : _b.user_id) === sessionId) {
|
|
1425
1749
|
return true;
|
|
1426
1750
|
}
|