aicodeswitch 3.0.2 → 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 -0
- package/dist/server/fs-database.js +105 -23
- 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-B8caYm4n.css +0 -1
- package/dist/ui/assets/index-BHXiQgEv.js +0 -466
package/README.md
CHANGED
|
@@ -141,6 +141,12 @@ aicodeswitch内部,会根据“源类型”来转换数据。例如,你的
|
|
|
141
141
|
|
|
142
142
|
当同一请求类型配置多个规则时,系统会按排序优先使用第一个,如果某个服务报错(4xx/5xx)或请求超时,将自动切换到下一个可用规则,确保你可以正常使用coding工具。
|
|
143
143
|
|
|
144
|
+
### 数据转流
|
|
145
|
+
|
|
146
|
+
支持将openai的流式响应数据转换成Claude Code的流式数据,并支持实时转换,从而可以让你使用支持openAI的API服务来在Claude Code中使用。
|
|
147
|
+
|
|
148
|
+
*不过,需要注意,如果你的API服务商不支持tools,或者所支持的max_tokens太小,是无法在Claude Code中使用的。*
|
|
149
|
+
|
|
144
150
|
## Skills管理
|
|
145
151
|
|
|
146
152
|
你可以在 aicodeswitch 中集中统一管理 skills,把skills分发给claude code和codex,随时启用和停用skills。
|
|
@@ -137,6 +137,18 @@ class FileSystemDatabaseManager {
|
|
|
137
137
|
writable: true,
|
|
138
138
|
value: this.createEmptyStatistics()
|
|
139
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
|
+
});
|
|
140
152
|
// 缓存机制
|
|
141
153
|
Object.defineProperty(this, "logsCountCache", {
|
|
142
154
|
enumerable: true,
|
|
@@ -252,12 +264,7 @@ class FileSystemDatabaseManager {
|
|
|
252
264
|
}
|
|
253
265
|
// 保存新的 vendors.json
|
|
254
266
|
yield this.saveVendors();
|
|
255
|
-
// 备份旧文件
|
|
256
|
-
const timestamp = Date.now();
|
|
257
|
-
const backupFile = path_1.default.join(this.dataPath, `services.json.backup.${timestamp}`);
|
|
258
|
-
yield promises_1.default.rename(oldServicesFile, backupFile);
|
|
259
267
|
console.log(`[Database] 迁移完成:${migratedCount} 个服务已迁移`);
|
|
260
|
-
console.log(`[Database] 旧的 services.json 已备份到 ${backupFile}`);
|
|
261
268
|
}
|
|
262
269
|
catch (err) {
|
|
263
270
|
if (err.code === 'ENOENT') {
|
|
@@ -376,18 +383,6 @@ class FileSystemDatabaseManager {
|
|
|
376
383
|
if (routesFileFormat !== 'combined' || merged || (routesFileFormat === 'combined' && !hasRulesInRoutesFile)) {
|
|
377
384
|
yield this.saveRoutesData();
|
|
378
385
|
}
|
|
379
|
-
// 备份旧的 rules.json 文件
|
|
380
|
-
if (oldRulesExists) {
|
|
381
|
-
try {
|
|
382
|
-
const timestamp = Date.now();
|
|
383
|
-
const backupFile = path_1.default.join(this.dataPath, `rules.json.backup.${timestamp}`);
|
|
384
|
-
yield promises_1.default.rename(oldRulesFile, backupFile);
|
|
385
|
-
console.log(`[Database] 旧的 rules.json 已备份到 ${backupFile}`);
|
|
386
|
-
}
|
|
387
|
-
catch (error) {
|
|
388
|
-
console.error('[Database] 备份 rules.json 失败:', error);
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
386
|
});
|
|
392
387
|
}
|
|
393
388
|
loadConfig() {
|
|
@@ -522,10 +517,6 @@ class FileSystemDatabaseManager {
|
|
|
522
517
|
// 保存索引
|
|
523
518
|
yield this.saveLogsIndex();
|
|
524
519
|
console.log(`[Database] Successfully migrated ${migratedCount} log entries to ${this.logShardsIndex.length} shard(s)`);
|
|
525
|
-
// 备份旧文件
|
|
526
|
-
const backupFile = path_1.default.join(this.dataPath, 'logs.json.backup');
|
|
527
|
-
yield promises_1.default.rename(oldLogsFile, backupFile);
|
|
528
|
-
console.log(`[Database] Old logs.json backed up to ${backupFile}`);
|
|
529
520
|
}
|
|
530
521
|
catch (err) {
|
|
531
522
|
if (err.code === 'ENOENT') {
|
|
@@ -668,9 +659,11 @@ class FileSystemDatabaseManager {
|
|
|
668
659
|
try {
|
|
669
660
|
const data = yield promises_1.default.readFile(this.statisticsFile, 'utf-8');
|
|
670
661
|
this.statistics = JSON.parse(data);
|
|
662
|
+
this.contentTypeDistributionInitialized = this.statistics.contentTypeDistribution.length > 0;
|
|
671
663
|
}
|
|
672
664
|
catch (_a) {
|
|
673
665
|
this.statistics = this.createEmptyStatistics();
|
|
666
|
+
this.contentTypeDistributionInitialized = false;
|
|
674
667
|
// 创建空文件
|
|
675
668
|
yield this.saveStatistics();
|
|
676
669
|
}
|
|
@@ -1165,7 +1158,8 @@ class FileSystemDatabaseManager {
|
|
|
1165
1158
|
addLog(log) {
|
|
1166
1159
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1167
1160
|
const id = crypto_1.default.randomUUID();
|
|
1168
|
-
const
|
|
1161
|
+
const contentType = this.resolveLogContentType(log);
|
|
1162
|
+
const logWithId = Object.assign(Object.assign({}, log), { contentType, id });
|
|
1169
1163
|
// 获取目标分片文件名
|
|
1170
1164
|
const filename = yield this.getLogShardFilename(logWithId.timestamp);
|
|
1171
1165
|
// 加载现有分片数据
|
|
@@ -1440,6 +1434,79 @@ class FileSystemDatabaseManager {
|
|
|
1440
1434
|
}
|
|
1441
1435
|
});
|
|
1442
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
|
+
}
|
|
1443
1510
|
// Statistics operations
|
|
1444
1511
|
/**
|
|
1445
1512
|
* 更新统计数据 - 在每次添加日志时调用
|
|
@@ -1539,6 +1606,19 @@ class FileSystemDatabaseManager {
|
|
|
1539
1606
|
modelStats.avgResponseTime =
|
|
1540
1607
|
(modelStats.avgResponseTime * (modelStats.totalRequests - 1) + responseTime) / modelStats.totalRequests;
|
|
1541
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;
|
|
1542
1622
|
// 更新 timeline
|
|
1543
1623
|
const date = new Date(log.timestamp).toISOString().split('T')[0];
|
|
1544
1624
|
let timelineStats = this.statistics.timeline.find(t => t.date === date);
|
|
@@ -1566,6 +1646,7 @@ class FileSystemDatabaseManager {
|
|
|
1566
1646
|
*/
|
|
1567
1647
|
getStatistics() {
|
|
1568
1648
|
return __awaiter(this, arguments, void 0, function* (days = 30) {
|
|
1649
|
+
yield this.ensureContentTypeDistribution();
|
|
1569
1650
|
const now = Date.now();
|
|
1570
1651
|
const startTime = now - days * 24 * 60 * 60 * 1000;
|
|
1571
1652
|
// 过滤 timeline 数据
|
|
@@ -1662,7 +1743,8 @@ class FileSystemDatabaseManager {
|
|
|
1662
1743
|
// 检查 body 中的 metadata.user_id(Claude Code)
|
|
1663
1744
|
if (log.body) {
|
|
1664
1745
|
try {
|
|
1665
|
-
|
|
1746
|
+
// body 可能是对象(已解析)或字符串(未解析)
|
|
1747
|
+
const body = typeof log.body === 'string' ? JSON.parse(log.body) : log.body;
|
|
1666
1748
|
if (((_b = body.metadata) === null || _b === void 0 ? void 0 : _b.user_id) === sessionId) {
|
|
1667
1749
|
return true;
|
|
1668
1750
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.nav-item-with-tooltip{position:relative;display:block}.nav-tooltip{position:absolute;left:calc(100% + 12px);top:50%;transform:translateY(-50%);background:var(--bg-card);color:var(--text-primary);padding:8px 12px;border-radius:8px;font-size:14px;font-weight:500;white-space:nowrap;z-index:999999;pointer-events:none;box-shadow:0 4px 12px #00000026;border:1px solid var(--border-primary);animation:tooltipFadeIn .2s ease-out}.nav-tooltip:before{content:"";position:absolute;right:100%;top:50%;transform:translateY(-50%);border:6px solid transparent;border-right-color:var(--border-primary)}.nav-tooltip:after{content:"";position:absolute;right:100%;top:50%;transform:translateY(-50%);border:5px solid transparent;border-right-color:var(--bg-card);margin-right:-1px}@keyframes tooltipFadeIn{0%{opacity:0;transform:translateY(-50%) translate(-8px)}to{opacity:1;transform:translateY(-50%) translate(0)}}.app{display:flex;width:100%;height:100%;overflow:hidden}.sidebar{width:260px;background:var(--bg-sidebar);color:var(--text-primary);display:flex;flex-direction:column;padding:0 0 80px;border-radius:12px;box-shadow:0 4px 12px #00000026;margin:8px;position:relative;z-index:999999;border:1px solid rgba(255,255,255,.15);transition:width .1s ease,margin .1s ease}.sidebar.collapsed{width:80px;margin-right:8px;overflow:visible}.sidebar.collapsed .logo h2,.sidebar.collapsed .nav-menu .nav-text,.sidebar.collapsed .github-link,.sidebar.collapsed .version-info{display:none;opacity:0}.sidebar.collapsed .logo{padding:20px 10px}.sidebar.collapsed .nav-menu a{padding:14px;justify-content:center}.sidebar.collapsed .theme-toggle{flex-direction:column;gap:8px;align-items:center}.logo{padding:16px 20px;border-bottom:2px solid rgba(255,255,255,.2);text-align:center;position:relative;overflow:hidden;transition:padding .3s ease;display:flex;flex-direction:row;align-items:center;justify-content:center;gap:12px}.logo h2{font-size:18px;font-weight:800;background:linear-gradient(135deg,#fff,#f8f8ff,#e6e6fa);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;text-shadow:0 2px 4px rgba(0,0,0,.1);position:relative;z-index:1;letter-spacing:1px;text-transform:uppercase;transition:all .3s ease;margin:0}.logo-image{width:36px;height:36px;object-fit:contain;transition:all .3s ease;filter:drop-shadow(0 2px 8px rgba(0,0,0,.2));flex-shrink:0}.sidebar.collapsed .logo{justify-content:center;padding:12px 10px}.sidebar.collapsed .logo-image{width:32px;height:32px}.nav-menu{list-style:none;padding:16px 0;margin:0;overflow:visible}.nav-menu li{margin:8px 12px;overflow:visible}.nav-menu a{display:flex;align-items:center;gap:12px;padding:14px 20px;color:var(--text-sidebar);text-decoration:none;border-radius:12px;transition:all .3s ease-out;font-weight:500;position:relative;overflow:hidden}.nav-menu .nav-icon{font-size:20px;flex-shrink:0;transition:all .3s ease}.nav-menu .nav-text{transition:opacity .3s ease,width .3s ease;white-space:nowrap}.nav-menu a:before{content:"";position:absolute;top:0;left:-100%;width:100%;height:100%;background:linear-gradient(90deg,transparent,rgba(255,255,255,.15),transparent);transition:left .5s ease-out}.nav-menu a:hover:before{left:100%}.nav-menu a:hover{background-color:#ffffff1a}.nav-menu a.active{background:linear-gradient(135deg,var(--accent-primary),var(--accent-secondary))}.main-content{flex:1;padding:24px;overflow:auto;background:var(--bg-primary);position:relative;margin:16px 16px 16px 0;border-radius:20px;transition:margin .3s ease}.main-content:before{content:"";position:absolute;top:0;left:0;right:0;bottom:0;background-image:radial-gradient(circle at 20% 80%,rgba(255,206,92,.1) 0%,transparent 50%),radial-gradient(circle at 80% 20%,rgba(134,204,202,.1) 0%,transparent 50%),radial-gradient(circle at 40% 40%,rgba(255,113,206,.05) 0%,transparent 50%);pointer-events:none}.main-content>.routes-page,.main-content>.vendors-page,.main-content>.logs-page{min-width:1000px}.page-header{margin-bottom:24px;position:relative;z-index:1}.page-header-content{display:flex;justify-content:space-between;align-items:center;gap:20px}.page-header-text h1{font-family:Fredoka,sans-serif;font-size:28px;font-weight:700;color:var(--accent-primary);margin-bottom:8px;text-shadow:0 2px 4px var(--shadow-primary)}.page-header-text p{color:var(--text-muted);font-size:16px;font-weight:400}.card{background:var(--bg-card);border-radius:20px;padding:24px;box-shadow:0 2px 8px #00000014;margin-bottom:24px;border:1px solid var(--border-primary);position:relative;overflow:hidden;transition:all .3s ease-out}.card:before{content:"";position:absolute;top:0;left:0;right:0;height:4px;background:linear-gradient(90deg,var(--accent-primary),var(--accent-secondary));border-radius:20px 20px 0 0}.card:hover{box-shadow:0 4px 12px #0000001f}.btn{padding:10px 20px;border:none;border-radius:12px;cursor:pointer;font-size:14px;font-weight:600;transition:all .3s ease-out;position:relative;overflow:hidden;white-space:nowrap}.btn:before{content:"";position:absolute;top:50%;left:50%;width:0;height:0;background:#ffffff4d;border-radius:50%;transform:translate(-50%,-50%);transition:width .6s,height .6s}.btn:hover:before{width:300px;height:300px}.btn:active{transform:translateY(0)}.btn:disabled{opacity:.4;cursor:not-allowed}.btn-primary{background:linear-gradient(135deg,var(--accent-primary),var(--accent-secondary));color:#fff;position:relative;overflow:hidden}.btn-primary:before{content:"";position:absolute;top:0;left:-100%;width:100%;height:100%;background:linear-gradient(90deg,transparent,rgba(255,255,255,.3),transparent);transition:left .5s ease}.btn-primary:hover:before{left:100%}.btn-danger{background:var(--accent-danger);color:#fff}.btn-danger:hover{background:#ea580c}.btn-success{background:var(--accent-success);color:#fff}.btn-success:hover{background:#059669}.btn-secondary{background:var(--accent-secondary);color:#fff}.btn-secondary:hover{background:var(--accent-primary)}.btn-sm{padding:4px 8px}table{width:100%;border-collapse:collapse;margin-top:16px;border-radius:12px;overflow:hidden}table th,table td{padding:16px;text-align:left;border-bottom:1px solid var(--border-secondary);color:var(--text-primary)}table th{background:var(--bg-table-header);font-weight:700;color:var(--text-secondary);font-family:Fredoka,sans-serif;font-size:14px;white-space:nowrap}@media (max-width: 1200px){.rules-table .col-priority{display:none}.rules-table .action-buttons{flex-wrap:wrap;justify-content:flex-end}}.form-group{margin-bottom:20px}.form-group label{display:block;margin-bottom:8px;color:var(--text-primary);font-size:14px;font-weight:600;font-family:Nunito,sans-serif}.form-group input,.form-group select,.form-group textarea{width:100%;padding:12px 16px;border:2px solid var(--border-primary);border-radius:12px;font-size:14px;font-family:Nunito,sans-serif;background:var(--bg-secondary);color:var(--text-primary);transition:all .3s ease-out}.form-group input:focus,.form-group select:focus,.form-group textarea:focus{outline:none;border-color:var(--accent-primary);box-shadow:0 0 0 3px var(--shadow-secondary);background:var(--bg-secondary)}.form-group input:disabled,.form-group select:disabled,.form-group textarea:disabled{background:var(--bg-input-disabled);cursor:not-allowed;color:var(--text-input-disabled);opacity:.7}.form-group label small{font-size:.8em;color:var(--text-light)}@keyframes modalFadeIn{0%{opacity:0}to{opacity:1}}.modal{background:var(--bg-secondary);border-radius:24px;padding:32px 28px 32px 32px;min-width:500px;width:800px;max-width:90%;max-height:90vh;box-shadow:0 8px 24px #0003;border:1px solid var(--border-secondary);animation:modalSlideIn .4s ease-out;overflow:hidden;display:flex;flex-direction:column}.modal-container{overflow-y:auto;overflow-x:hidden;margin-right:-8px;padding-right:8px;scrollbar-gutter:stable;scrollbar-width:thin;scrollbar-color:var(--border-secondary) transparent}.modal-container::-webkit-scrollbar{width:8px}.modal-container::-webkit-scrollbar-track{background:transparent;border-radius:4px;margin:4px}.modal-container::-webkit-scrollbar-thumb{background:var(--border-secondary);border-radius:4px;border:2px solid transparent}.modal-container::-webkit-scrollbar-thumb:hover{background:var(--text-muted)}.modal-overlay{position:fixed;top:0;left:0;right:0;bottom:0;background:#0009;display:flex;justify-content:center;align-items:center;z-index:1000000;animation:modalFadeIn .3s ease-out}.modal-close-btn{position:absolute;top:20px;right:20px;width:48px;height:48px;border:none;background:var(--accent-light);color:var(--text-primary);font-size:32px;line-height:1;cursor:pointer;border-radius:50%;transition:all .2s ease;display:flex;align-items:center;justify-content:center;padding:0;border:1px solid rgba(255,255,255,.3);z-index:1001}.modal-close-btn:hover{background:var(--accent-danger);color:#fff;transform:scale(1.05)}.modal-close-btn:active{transform:scale(.95)}@keyframes modalSlideIn{0%{opacity:0;transform:translateY(-20px) scale(.95)}to{opacity:1;transform:translateY(0) scale(1)}}.modal-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:20px;position:relative}.modal-header h2{font-size:20px;color:var(--text-primary);flex:1}.modal-footer{display:flex;justify-content:flex-end;gap:10px;margin-top:20px}.action-buttons{display:flex;gap:8px}.badge{display:inline-block;padding:6px 12px;border-radius:20px;font-size:12px;font-weight:700;text-transform:uppercase;letter-spacing:.5px;white-space:nowrap}.badge-success{background:var(--accent-success);color:#fff}.badge-warning{background:var(--accent-warning);color:#fff}.badge-danger{background:var(--accent-danger);color:#fff}.badge-claude-code{background:#ce653c;color:#fff}.badge-codex{background:#1f2937;color:#fff}.empty-state{text-align:center;padding:60px 20px;color:var(--text-muted);font-family:Nunito,sans-serif}.empty-state p{margin-bottom:20px;font-size:16px;font-weight:500}.pagination{display:flex;justify-content:space-between;align-items:center;margin-top:20px;padding:16px;background:var(--bg-secondary);border-radius:12px;border:1px solid var(--border-primary);gap:20px;flex-wrap:wrap}.pagination-info{color:var(--text-muted);font-size:14px;font-weight:500}.pagination-controls{display:flex;align-items:center;gap:10px}.pagination-btn{padding:6px 14px;border:1px solid var(--border-primary);background:var(--bg-card);color:var(--text-primary);border-radius:8px;cursor:pointer;font-size:14px;font-weight:500;transition:all .2s ease}.pagination-btn:hover:not(:disabled){background:var(--accent-primary);color:#fff;border-color:var(--accent-primary)}.pagination-btn:disabled{opacity:.4;cursor:not-allowed;background:var(--bg-secondary)}.pagination-size{display:flex;align-items:center;gap:8px;color:var(--text-primary);font-size:14px;font-weight:500}.pagination-select{padding:6px 10px;border:1px solid var(--border-primary);background:var(--bg-card);color:var(--text-primary);border-radius:8px;cursor:pointer;font-size:14px;outline:none;transition:all .2s ease}.pagination-select:hover{border-color:var(--accent-primary)}.pagination-select:focus{border-color:var(--accent-primary);box-shadow:0 0 0 2px #3498db1a}.toolbar{display:flex;justify-content:space-between;align-items:center;margin-bottom:16px}.theme-toggle{position:absolute;bottom:20px;left:20px;right:20px;display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap}.sidebar-toggle-btn{background:none;border:none;color:var(--text-sidebar);cursor:pointer;padding:8px;border-radius:8px;transition:all .3s ease-out;display:flex;align-items:center;justify-content:center;font-size:18px;font-weight:700}.sidebar-toggle-btn:hover{background:#ffffff26;opacity:1}.github-link{background:none;border:none;color:var(--text-sidebar);cursor:pointer;padding:8px;border-radius:8px;transition:all .3s ease-out;display:flex;align-items:center;justify-content:center;opacity:.8;margin-left:auto}.github-link:hover{background:#ffffff26;opacity:1}.theme-toggle button{background:none;border:none;color:var(--text-sidebar);cursor:pointer;padding:8px;border-radius:8px;transition:all .3s ease-out;display:flex;align-items:center;justify-content:center}.version-info{font-size:12px;color:var(--text-sidebar);opacity:.7;font-family:Monaco,Menlo,monospace;font-weight:500;-webkit-user-select:none;user-select:none;transition:opacity .3s ease}.theme-toggle button:hover{background:#ffffff26}.theme-toggle button.active{background:linear-gradient(135deg,var(--accent-primary),var(--accent-secondary));color:#fff;animation:pulse 2s ease-in-out infinite}@keyframes pulse{0%,to{transform:scale(1)}50%{transform:scale(1.05)}}.version-info-wrapper{position:relative;display:flex;align-items:center}.version-info{font-size:12px;color:var(--text-sidebar);opacity:.7;font-family:Monaco,Menlo,monospace;font-weight:500;-webkit-user-select:none;user-select:none;transition:opacity .3s ease;position:relative}.version-info:hover{opacity:1}.version-badge{position:absolute;top:-2px;right:-6px;width:8px;height:8px;background:linear-gradient(135deg,#ef4444,#dc2626);border-radius:50%;border:2px solid var(--bg-sidebar);animation:pulse-badge 2s ease-in-out infinite;box-shadow:0 0 8px #ef444499}@keyframes pulse-badge{0%,to{transform:scale(1);opacity:1}50%{transform:scale(1.2);opacity:.8}}.version-update-popup{position:absolute;bottom:calc(100% + 12px);left:50%;transform:translate(-50%);background:#10b98126;border:1px solid rgba(16,185,129,.4);border-radius:12px;padding:12px;min-width:220px;box-shadow:0 4px 12px #0003;opacity:0;visibility:hidden;transition:all .3s ease-out;pointer-events:none;z-index:10000;text-decoration:none;-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px)}.version-info-wrapper:hover .version-update-popup,.version-update-popup:hover{opacity:1;visibility:visible;pointer-events:auto;transform:translate(-50%) translateY(12px)}.version-update-popup:after{content:"";position:absolute;top:100%;left:50%;transform:translate(-50%);border:6px solid transparent;border-top-color:#10b98166}.update-popup-content{display:flex;align-items:flex-start;gap:10px}.update-icon{font-size:18px;animation:bounce 1s ease-in-out infinite;flex-shrink:0}@keyframes bounce{0%,to{transform:translateY(0)}50%{transform:translateY(-3px)}}.update-text{flex:1}.update-title{font-size:13px;font-weight:600;color:var(--text-sidebar);margin-bottom:4px}.update-versions{font-size:11px;color:#fffc;font-family:Monaco,Menlo,monospace;margin-bottom:6px}.update-message{font-size:10px;color:var(--text-sidebar);line-height:1.4}.update-message code{background:#ffffff26;padding:2px 4px;border-radius:4px;font-family:Monaco,Menlo,monospace;margin-top:2px;display:inline-block;color:#ffffffe6}@media (prefers-reduced-motion: reduce){.nav-menu a,.card,.btn,.form-group input,.form-group select,.form-group textarea,.modal-overlay,.modal,table tr{transition:none;animation:none}.nav-menu a:hover,.card:hover,.btn:hover,table tr:hover{transform:none}.btn:before{display:none}}.markdown-content{line-height:1.7;color:var(--text-primary);max-width:100%;overflow-x:auto}.markdown-content h1{font-size:2em;font-weight:700;margin-top:0;margin-bottom:.8em;color:var(--text-primary);border-bottom:2px solid var(--border-color);padding-bottom:.3em}.markdown-content h2{font-size:1.6em;font-weight:600;margin-top:1.5em;margin-bottom:.6em;color:var(--text-primary);border-bottom:1px solid var(--border-color);padding-bottom:.25em}.markdown-content h3{font-size:1.3em;font-weight:600;margin-top:1.2em;margin-bottom:.5em;color:var(--text-primary)}.markdown-content h4{font-size:1.1em;font-weight:600;margin-top:1em;margin-bottom:.4em;color:var(--text-primary)}.markdown-content p{margin-bottom:1em;line-height:1.8}.markdown-content ul,.markdown-content ol{margin-bottom:1em;padding-left:2em}.markdown-content li{margin-bottom:.5em;line-height:1.7}.markdown-content code{background:var(--bg-secondary);padding:.2em .4em;border-radius:4px;font-size:.9em;font-family:Monaco,Menlo,Ubuntu Mono,Consolas,monospace;color:var(--accent-secondary);border:1px solid var(--border-color)}.markdown-content pre{background:var(--bg-secondary);padding:1em;border-radius:8px;overflow-x:auto;margin-bottom:1em;border:1px solid var(--border-color)}.markdown-content pre code{background:none;padding:0;border:none;color:var(--text-primary);font-size:.95em}.markdown-content blockquote{border-left:4px solid var(--accent-primary);padding-left:1em;margin-left:0;margin-bottom:1em;color:var(--text-secondary);font-style:italic}.markdown-content a{color:var(--accent-primary);text-decoration:none;border-bottom:1px solid var(--accent-secondary);transition:all .2s ease}.markdown-content a:hover{color:var(--accent-secondary)}.markdown-content table{width:100%;border-collapse:collapse;margin-bottom:1em}.markdown-content table th,.markdown-content table td{padding:.75em;text-align:left;border:1px solid var(--border-color)}.markdown-content table th{background:var(--bg-secondary);font-weight:600}.markdown-content hr{border:none;border-top:2px solid var(--border-color);margin:2em 0}.markdown-content strong{font-weight:600;color:var(--text-primary)}.markdown-content em{font-style:italic}.markdown-content img{max-width:100%;height:auto;border-radius:8px;margin:1em 0}.config-status-indicator{display:inline-block}.status-badge{display:inline-block;padding:4px 10px;border-radius:12px;font-size:11px;font-weight:600}.status-active{background:linear-gradient(135deg,#4caf50,#45a049);color:#fff}.status-backup{background:linear-gradient(135deg,#ff9800,#f57c00);color:#fff}.status-inactive{background:linear-gradient(135deg,#9e9e9e,#757575);color:#fff}.skills-header-row{display:flex;justify-content:space-between;align-items:flex-start;gap:16px;flex-wrap:wrap}.skills-header-actions{margin-top:8px}.skills-discover-btn{padding:18px 36px;font-size:16px;font-weight:700;border-radius:14px;box-shadow:0 4px 12px #00000026;position:relative}.skills-discover-btn:after{content:"";position:absolute;top:-6px;right:-6px;bottom:-6px;left:-6px;border-radius:18px;border:2px solid rgba(16,185,129,.35);opacity:.6}.skills-section-header{display:flex;flex-direction:column;gap:6px;margin-bottom:20px}.skills-section-header h2{margin:0;font-size:22px;color:var(--text-primary)}.skills-section-header span{color:var(--text-muted);font-size:14px}.skills-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(360px,1fr));gap:16px}.skill-card{display:flex;flex-direction:column;gap:16px;min-height:180px}.skill-title{font-size:18px;font-weight:700;color:var(--text-primary);margin-bottom:8px}.skill-description{color:var(--text-muted);font-size:14px;line-height:1.6;flex:1}.skill-tags{display:flex;flex-wrap:wrap;gap:8px}.skills-discover{display:flex;flex-direction:column;gap:20px}.skills-search-card{display:flex;flex-direction:column;gap:16px}.skills-search-header h3{margin:0 0 6px;font-size:18px;color:var(--text-primary)}.skills-search-header p{margin:0;color:var(--text-muted);font-size:14px}.skills-search-input{width:100%;padding:12px 14px;border-radius:12px;border:1px solid var(--border-primary);background:var(--bg-secondary);color:var(--text-primary);resize:vertical;min-height:120px;font-size:14px;line-height:1.6}.skills-search-actions{display:flex;justify-content:flex-end}.skills-results{display:flex;flex-direction:column;gap:16px}.skills-result-actions{display:flex;justify-content:flex-end;gap:8px}.badge-secondary{background:#0f513226;color:var(--text-primary)}.switch{position:relative;width:44px;height:24px;border-radius:12px;border:none;cursor:pointer;background-color:#4b5563;transition:background-color .2s}.switch.active{background-color:var(--primary-color)}.switch:disabled{cursor:not-allowed;opacity:.6}.switch:focus{outline:none;box-shadow:0 0 0 2px var(--primary-color)}.switch::-moz-focus-inner{border:0}.rules-table .vendor-sevices-col>div{white-space:nowrap}*{margin:0;padding:0;box-sizing:border-box}:root{--primary-color: #14532D;--error-color: #DC2626;--bg-primary: linear-gradient(135deg, #F7FEE7 0%, #F0FDF4 100%);--bg-secondary: rgba(255, 255, 255, .95);--bg-sidebar: linear-gradient(135deg, #0F5132 0%, #065F46 100%);--bg-card: rgba(255, 255, 255, .98);--bg-code: #f4fff7;--bg-table-header: linear-gradient(135deg, #A7F3D0 0%, #6EE7B7 100%);--text-primary: #14532D;--text-secondary: #064E3B;--text-muted: #065F46;--text-sidebar: #FFFFFF;--text-on-dark: #FFFFFF;--border-primary: rgba(15, 81, 50, .2);--border-secondary: rgba(6, 95, 70, .2);--accent-primary: #0F5132;--accent-secondary: #047857;--accent-light: #a1e9c7;--accent-success: #047857;--accent-warning: #D97706;--accent-danger: #DC2626;--shadow-primary: rgba(15, 81, 50, .3);--shadow-secondary: rgba(15, 81, 50, .15);--bg-route-item: rgba(248, 249, 250, .9);--bg-route-item-hover: rgba(230, 244, 234, .95);--bg-route-item-selected: rgba(161, 233, 199, .25);--text-route-muted: #6c757d;--text-info: #2faeee;--text-light: #018038;--bg-info-box: #f8f9fa;--border-info-box: #e0e0e0;--text-info-box: #666;--bg-info-blue: #e3f2fd;--border-info-blue: #2196f3;--bg-info-green: #f1f8e9;--border-info-green: #8bc34a;--bg-info-orange: #fff3e0;--border-info-orange: #ff9800;--bg-info-yellow: #fff8e1;--border-info-yellow: #ffc107;--bg-assembled-text: #f8f9fa;--bg-thinking-box: #fff9e6;--border-thinking-box: #e0e0e0;--text-thinking-title: #f39c12;--bg-thinking-content: #fff;--border-thinking-content: #f0e6d3;--text-thinking-content: #555;--text-reply-title: #333;--bg-reply-content: #fff;--border-reply-content: #ddd;--text-reply-content: #333;--bg-input-disabled: #e5e7eb;--text-input-disabled: #9ca3af}[data-theme=dark]{--bg-primary: linear-gradient(135deg, #0A1A0F 0%, #0F2415 100%);--bg-secondary: rgba(15, 36, 21, .9);--bg-sidebar: linear-gradient(135deg, #0F5132 0%, #065F46 100%);--bg-card: rgba(25, 51, 31, .95);--bg-code: #0f172a;--bg-table-header: linear-gradient(135deg, #0F5132 0%, #047857 100%);--text-primary: #ECFEF5;--text-secondary: #A7F3D0;--text-muted: #6EE7B7;--text-sidebar: #FFFFFF;--text-on-dark: #ECFEF5;--border-primary: rgba(15, 81, 50, .5);--border-secondary: rgba(6, 95, 70, .5);--accent-primary: #0F5132;--accent-secondary: #047857;--accent-light: #002d18;--accent-success: #10B981;--accent-warning: #F59E0B;--accent-danger: #EF4444;--shadow-primary: rgba(15, 81, 50, .4);--shadow-secondary: rgba(6, 95, 70, .3);--bg-route-item: rgba(20, 46, 28, .7);--bg-route-item-hover: rgba(30, 60, 38, .85);--bg-route-item-selected: rgba(16, 185, 129, .15);--text-route-muted: #A7F3D0;--text-light: #999999;--bg-info-box: rgba(20, 46, 28, .6);--border-info-box: rgba(161, 233, 199, .2);--text-info-box: #A7F3D0;--bg-info-blue: rgba(13, 71, 161, .25);--border-info-blue: #1976d2;--bg-info-green: rgba(27, 94, 32, .25);--border-info-green: #689f38;--bg-info-orange: rgba(230, 81, 0, .2);--border-info-orange: #f57c00;--bg-info-yellow: rgba(255, 193, 7, .15);--border-info-yellow: #ffb300;--bg-assembled-text: rgba(15, 23, 42, .5);--bg-thinking-box: rgba(230, 81, 0, .15);--border-thinking-box: rgba(245, 158, 11, .3);--text-thinking-title: #fbbf24;--bg-thinking-content: rgba(15, 23, 42, .4);--border-thinking-content: rgba(245, 158, 11, .2);--text-thinking-content: #d1d5db;--text-reply-title: #e5e7eb;--bg-reply-content: rgba(15, 23, 42, .4);--border-reply-content: rgba(161, 233, 199, .2);--text-reply-content: #e5e7eb;--bg-input-disabled: #2d3748;--text-input-disabled: #718096}body{font-family:Nunito,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;background:var(--bg-primary);color:var(--text-primary);transition:all .3s ease;min-height:100vh}code{font-family:source-code-pro,Menlo,Monaco,Consolas,Courier New,monospace}#root{width:100vw;height:100vh;overflow:visible}::-webkit-scrollbar{width:10px;height:10px}::-webkit-scrollbar-track{background:var(--bg-secondary);border-radius:6px}::-webkit-scrollbar-thumb{background:var(--accent-secondary);border-radius:6px;border:2px solid var(--bg-secondary)}::-webkit-scrollbar-thumb:hover{background:var(--accent-primary)}::-webkit-scrollbar-corner{background:var(--bg-secondary)}*{scrollbar-width:thin;scrollbar-color:var(--accent-primary) var(--bg-secondary)}::-webkit-scrollbar-button{display:none}html{scroll-behavior:smooth}.datetime-picker-input{cursor:pointer}.datetime-picker-input::-webkit-calendar-picker-indicator{cursor:pointer;opacity:1;filter:brightness(0);padding:2px 4px}.datetime-picker-input::-webkit-calendar-picker-indicator:hover{filter:brightness(.3)}.datetime-picker-input::-moz-calendar-picker-indicator{cursor:pointer;opacity:1;filter:brightness(0);padding:2px 4px}.datetime-picker-input::-moz-calendar-picker-indicator:hover{filter:brightness(.3)}[data-theme=dark] .datetime-picker-input::-webkit-calendar-picker-indicator{filter:brightness(0) invert(1)}[data-theme=dark] .datetime-picker-input::-webkit-calendar-picker-indicator:hover{filter:brightness(.2) invert(1)}[data-theme=dark] .datetime-picker-input::-moz-calendar-picker-indicator{filter:brightness(0) invert(1)}[data-theme=dark] .datetime-picker-input::-moz-calendar-picker-indicator:hover{filter:brightness(.2) invert(1)}
|