coding-tool-x 3.5.11 → 3.5.13
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 +13 -0
- package/README.md +11 -1
- package/dist/web/assets/{Analytics-Bw-0r4Ys.js → Analytics-B9Shk-uq.js} +1 -1
- package/dist/web/assets/{ConfigTemplates-C6QRIeDj.js → ConfigTemplates-Dlkn-_zW.js} +1 -1
- package/dist/web/assets/Home-AGf32LvJ.js +1 -0
- package/dist/web/assets/Home-BeGkC4bb.css +1 -0
- package/dist/web/assets/{PluginManager-Ce-mws6Q.js → PluginManager-BnAjHN4u.js} +1 -1
- package/dist/web/assets/{ProjectList-9Z7FFCIT.js → ProjectList-7YV_zk9L.js} +1 -1
- package/dist/web/assets/{SessionList-CyETQcs8.js → SessionList-D0un-UKS.js} +1 -1
- package/dist/web/assets/{SkillManager-DSUcP1se.js → SkillManager-DhYBmbtd.js} +1 -1
- package/dist/web/assets/{WorkspaceManager-Cvbc11bJ.js → WorkspaceManager-JOZIPAGo.js} +1 -1
- package/dist/web/assets/icons-QFXzEgqM.js +1 -0
- package/dist/web/assets/index-DM3yF1Oo.js +2 -0
- package/dist/web/assets/{index-BS7aplYp.css → index-dzKgnRhx.css} +1 -1
- package/dist/web/index.html +4 -4
- package/package.json +2 -1
- package/src/commands/daemon.js +6 -2
- package/src/commands/ui.js +8 -3
- package/src/index.js +2 -0
- package/src/server/api/hooks.js +18 -1
- package/src/server/codex-proxy-server.js +22 -6
- package/src/server/gemini-proxy-server.js +3 -1
- package/src/server/index.js +37 -5
- package/src/server/opencode-proxy-server.js +39 -14
- package/src/server/proxy-server.js +3 -1
- package/src/server/services/base/proxy-utils.js +41 -0
- package/src/server/services/base/response-usage-parser.js +137 -59
- package/src/server/services/codex-statistics-service.js +1 -0
- package/src/server/services/gemini-statistics-service.js +1 -0
- package/src/server/services/https-cert.js +202 -0
- package/src/server/services/notification-hooks.js +138 -6
- package/src/server/services/opencode-gateway-converter.js +3 -0
- package/src/server/services/opencode-statistics-service.js +1 -0
- package/src/server/services/proxy-log-helper.js +44 -12
- package/src/server/services/statistics-service.js +73 -28
- package/src/server/services/web-build.js +172 -0
- package/src/server/websocket-server.js +18 -1
- package/dist/web/assets/Home-BQxQ1LhR.css +0 -1
- package/dist/web/assets/Home-B_Z9ythw.js +0 -1
- package/dist/web/assets/icons-lKCTySl7.js +0 -1
- package/dist/web/assets/index-CTeg1xG-.js +0 -2
|
@@ -12,6 +12,30 @@ function normalizeToolSource(source = '') {
|
|
|
12
12
|
return 'claude';
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
function normalizeModelName(value = '') {
|
|
16
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function resolveActualModel(model = '', metadata = {}) {
|
|
20
|
+
const candidates = [
|
|
21
|
+
model,
|
|
22
|
+
metadata.redirectedModel,
|
|
23
|
+
metadata.modelFromUrl,
|
|
24
|
+
metadata.requestModel,
|
|
25
|
+
metadata.model,
|
|
26
|
+
metadata.originalModel
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
for (const candidate of candidates) {
|
|
30
|
+
const normalized = normalizeModelName(candidate);
|
|
31
|
+
if (normalized) {
|
|
32
|
+
return normalized;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return '';
|
|
37
|
+
}
|
|
38
|
+
|
|
15
39
|
function normalizeUsageTokens(source, tokens = {}) {
|
|
16
40
|
const normalizedSource = normalizeToolSource(source);
|
|
17
41
|
const input = toNumber(tokens.input);
|
|
@@ -68,7 +92,8 @@ function buildSuccessLogPayload({
|
|
|
68
92
|
redirectedModel,
|
|
69
93
|
tokens,
|
|
70
94
|
cost = 0,
|
|
71
|
-
timestamp = Date.now()
|
|
95
|
+
timestamp = Date.now(),
|
|
96
|
+
usageMissing = false
|
|
72
97
|
}) {
|
|
73
98
|
const normalized = normalizeUsageTokens(source, tokens);
|
|
74
99
|
const payload = {
|
|
@@ -87,7 +112,8 @@ function buildSuccessLogPayload({
|
|
|
87
112
|
totalTokens: normalized.total,
|
|
88
113
|
cost,
|
|
89
114
|
source: normalizeToolSource(source),
|
|
90
|
-
timestamp
|
|
115
|
+
timestamp,
|
|
116
|
+
usageMissing: Boolean(usageMissing)
|
|
91
117
|
};
|
|
92
118
|
if (originalModel) {
|
|
93
119
|
payload.originalModel = originalModel;
|
|
@@ -151,40 +177,43 @@ function publishUsageLog({
|
|
|
151
177
|
recordSuccess,
|
|
152
178
|
allowBroadcast = true
|
|
153
179
|
}) {
|
|
154
|
-
if (!hasMeaningfulUsage(source, tokens)) {
|
|
155
|
-
return null;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
180
|
const normalizedSource = normalizeToolSource(source);
|
|
181
|
+
const actualModel = resolveActualModel(model, metadata);
|
|
159
182
|
const normalizedTokens = normalizeUsageTokens(normalizedSource, tokens);
|
|
183
|
+
const usageAvailable = hasMeaningfulUsage(normalizedSource, normalizedTokens);
|
|
160
184
|
const requestId = metadata.id || `${normalizedSource}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
161
185
|
const timestamp = Date.now();
|
|
162
186
|
const cost = typeof calculateCost === 'function'
|
|
163
|
-
? calculateCost(
|
|
187
|
+
? calculateCost(actualModel || '', normalizedTokens)
|
|
164
188
|
: 0;
|
|
165
189
|
|
|
190
|
+
if (!usageAvailable && !actualModel) {
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
|
|
166
194
|
if (allowBroadcast && typeof broadcastLog === 'function') {
|
|
167
195
|
broadcastLog(buildSuccessLogPayload({
|
|
168
196
|
source: normalizedSource,
|
|
169
197
|
requestId,
|
|
170
198
|
channel: metadata.channel,
|
|
171
|
-
model,
|
|
199
|
+
model: actualModel,
|
|
172
200
|
originalModel: metadata.originalModel,
|
|
173
201
|
redirectedModel: metadata.redirectedModel,
|
|
174
202
|
tokens: normalizedTokens,
|
|
175
203
|
cost,
|
|
176
|
-
timestamp
|
|
204
|
+
timestamp,
|
|
205
|
+
usageMissing: !usageAvailable
|
|
177
206
|
}));
|
|
178
207
|
}
|
|
179
208
|
|
|
180
|
-
if (typeof recordRequest === 'function') {
|
|
209
|
+
if (usageAvailable && typeof recordRequest === 'function') {
|
|
181
210
|
const entry = {
|
|
182
211
|
id: requestId,
|
|
183
212
|
timestamp: new Date(metadata.startTime || timestamp).toISOString(),
|
|
184
213
|
toolType: normalizedSource === 'claude' ? 'claude-code' : normalizedSource,
|
|
185
214
|
channel: metadata.channel,
|
|
186
215
|
channelId: metadata.channelId,
|
|
187
|
-
model:
|
|
216
|
+
model: actualModel || '',
|
|
188
217
|
tokens: {
|
|
189
218
|
input: normalizedTokens.input,
|
|
190
219
|
output: normalizedTokens.output,
|
|
@@ -213,8 +242,10 @@ function publishUsageLog({
|
|
|
213
242
|
|
|
214
243
|
return {
|
|
215
244
|
cost,
|
|
245
|
+
model: actualModel,
|
|
216
246
|
tokens: normalizedTokens,
|
|
217
|
-
timestamp
|
|
247
|
+
timestamp,
|
|
248
|
+
usageMissing: !usageAvailable
|
|
218
249
|
};
|
|
219
250
|
}
|
|
220
251
|
|
|
@@ -250,6 +281,7 @@ function publishFailureLog({
|
|
|
250
281
|
module.exports = {
|
|
251
282
|
toNumber,
|
|
252
283
|
normalizeToolSource,
|
|
284
|
+
resolveActualModel,
|
|
253
285
|
normalizeUsageTokens,
|
|
254
286
|
hasMeaningfulUsage,
|
|
255
287
|
formatRealtimeTime,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const { PATHS } = require('../../config/paths');
|
|
4
|
+
const { normalizeUsageTokens, resolveActualModel } = require('./proxy-log-helper');
|
|
4
5
|
|
|
5
6
|
// 北京时间辅助(UTC+8),统一所有时间计算
|
|
6
7
|
const CST_OFFSET_MS = 8 * 60 * 60 * 1000;
|
|
@@ -189,6 +190,8 @@ function initStatsObject() {
|
|
|
189
190
|
output: 0,
|
|
190
191
|
cacheCreation: 0,
|
|
191
192
|
cacheRead: 0,
|
|
193
|
+
cached: 0,
|
|
194
|
+
reasoning: 0,
|
|
192
195
|
total: 0
|
|
193
196
|
},
|
|
194
197
|
cost: 0
|
|
@@ -202,7 +205,9 @@ function updateStats(stats, tokens, cost) {
|
|
|
202
205
|
stats.tokens.output += tokens.output || 0;
|
|
203
206
|
stats.tokens.cacheCreation += tokens.cacheCreation || 0;
|
|
204
207
|
stats.tokens.cacheRead += tokens.cacheRead || 0;
|
|
205
|
-
stats.tokens.
|
|
208
|
+
stats.tokens.cached += tokens.cached || 0;
|
|
209
|
+
stats.tokens.reasoning += tokens.reasoning || 0;
|
|
210
|
+
stats.tokens.total += getTokenTotal(tokens);
|
|
206
211
|
stats.cost += cost || 0;
|
|
207
212
|
}
|
|
208
213
|
|
|
@@ -226,6 +231,9 @@ function recordRequest(requestData) {
|
|
|
226
231
|
session,
|
|
227
232
|
project
|
|
228
233
|
} = requestData;
|
|
234
|
+
const resolvedModel = resolveActualModel(model, requestData);
|
|
235
|
+
const modelKey = resolvedModel || 'unknown';
|
|
236
|
+
const totalTokens = getTokenTotal(tokens);
|
|
229
237
|
|
|
230
238
|
// 1. 写入详细日志
|
|
231
239
|
const logEntry = {
|
|
@@ -234,7 +242,7 @@ function recordRequest(requestData) {
|
|
|
234
242
|
toolType,
|
|
235
243
|
channel,
|
|
236
244
|
channelId,
|
|
237
|
-
model,
|
|
245
|
+
model: resolvedModel,
|
|
238
246
|
tokens,
|
|
239
247
|
duration,
|
|
240
248
|
success,
|
|
@@ -256,7 +264,7 @@ function recordRequest(requestData) {
|
|
|
256
264
|
|
|
257
265
|
// 更新全局统计
|
|
258
266
|
globalStats.global.totalRequests += 1;
|
|
259
|
-
globalStats.global.totalTokens +=
|
|
267
|
+
globalStats.global.totalTokens += totalTokens;
|
|
260
268
|
globalStats.global.totalCost += cost || 0;
|
|
261
269
|
|
|
262
270
|
// 按工具类型统计
|
|
@@ -283,10 +291,10 @@ function recordRequest(requestData) {
|
|
|
283
291
|
updateStats(globalStats.byToolType[toolType].channels[channelId], tokens, cost);
|
|
284
292
|
|
|
285
293
|
// 按工具类型 -> 模型统计
|
|
286
|
-
if (!globalStats.byToolType[toolType].models[
|
|
287
|
-
globalStats.byToolType[toolType].models[
|
|
294
|
+
if (!globalStats.byToolType[toolType].models[modelKey]) {
|
|
295
|
+
globalStats.byToolType[toolType].models[modelKey] = initStatsObject();
|
|
288
296
|
}
|
|
289
|
-
updateStats(globalStats.byToolType[toolType].models[
|
|
297
|
+
updateStats(globalStats.byToolType[toolType].models[modelKey], tokens, cost);
|
|
290
298
|
|
|
291
299
|
// 按渠道统计(跨工具)
|
|
292
300
|
if (!globalStats.byChannel[channelId]) {
|
|
@@ -303,13 +311,13 @@ function recordRequest(requestData) {
|
|
|
303
311
|
updateStats(globalStats.byChannel[channelId], tokens, cost);
|
|
304
312
|
|
|
305
313
|
// 按模型统计(跨工具)
|
|
306
|
-
if (!globalStats.byModel[
|
|
307
|
-
globalStats.byModel[
|
|
314
|
+
if (!globalStats.byModel[modelKey]) {
|
|
315
|
+
globalStats.byModel[modelKey] = {
|
|
308
316
|
toolType,
|
|
309
317
|
...initStatsObject()
|
|
310
318
|
};
|
|
311
319
|
}
|
|
312
|
-
updateStats(globalStats.byModel[
|
|
320
|
+
updateStats(globalStats.byModel[modelKey], tokens, cost);
|
|
313
321
|
|
|
314
322
|
saveStatistics(globalStats);
|
|
315
323
|
|
|
@@ -321,7 +329,7 @@ function recordRequest(requestData) {
|
|
|
321
329
|
|
|
322
330
|
// 更新每日汇总
|
|
323
331
|
dailyStats.summary.requests += 1;
|
|
324
|
-
dailyStats.summary.tokens +=
|
|
332
|
+
dailyStats.summary.tokens += totalTokens;
|
|
325
333
|
dailyStats.summary.cost += cost || 0;
|
|
326
334
|
|
|
327
335
|
// 按小时统计
|
|
@@ -361,6 +369,15 @@ function recordRequest(requestData) {
|
|
|
361
369
|
}
|
|
362
370
|
updateStats(dailyStats.byToolType[toolType].channels[channelId], tokens, cost);
|
|
363
371
|
|
|
372
|
+
// 按工具类型 -> 模型
|
|
373
|
+
if (!dailyStats.byToolType[toolType].models) {
|
|
374
|
+
dailyStats.byToolType[toolType].models = {};
|
|
375
|
+
}
|
|
376
|
+
if (!dailyStats.byToolType[toolType].models[modelKey]) {
|
|
377
|
+
dailyStats.byToolType[toolType].models[modelKey] = initStatsObject();
|
|
378
|
+
}
|
|
379
|
+
updateStats(dailyStats.byToolType[toolType].models[modelKey], tokens, cost);
|
|
380
|
+
|
|
364
381
|
// 按渠道统计
|
|
365
382
|
if (!dailyStats.byChannel[channelId]) {
|
|
366
383
|
dailyStats.byChannel[channelId] = {
|
|
@@ -372,13 +389,13 @@ function recordRequest(requestData) {
|
|
|
372
389
|
updateStats(dailyStats.byChannel[channelId], tokens, cost);
|
|
373
390
|
|
|
374
391
|
// 按模型统计
|
|
375
|
-
if (!dailyStats.byModel[
|
|
376
|
-
dailyStats.byModel[
|
|
392
|
+
if (!dailyStats.byModel[modelKey]) {
|
|
393
|
+
dailyStats.byModel[modelKey] = {
|
|
377
394
|
toolType,
|
|
378
395
|
...initStatsObject()
|
|
379
396
|
};
|
|
380
397
|
}
|
|
381
|
-
updateStats(dailyStats.byModel[
|
|
398
|
+
updateStats(dailyStats.byModel[modelKey], tokens, cost);
|
|
382
399
|
|
|
383
400
|
saveDailyStats(date, dailyStats);
|
|
384
401
|
|
|
@@ -447,21 +464,23 @@ function readJsonlForHour(year, month, day, hour, groupBy, filters) {
|
|
|
447
464
|
const ts = new Date(entry.timestamp);
|
|
448
465
|
if (getCSTHour(ts) !== hour) continue;
|
|
449
466
|
|
|
467
|
+
const actualModel = resolveActualModel(entry.model, entry);
|
|
468
|
+
|
|
450
469
|
// Apply filters
|
|
451
470
|
if (filters) {
|
|
452
471
|
if (filters.toolType && entry.toolType !== filters.toolType) continue;
|
|
453
472
|
if (filters.channel && entry.channel !== filters.channel) continue;
|
|
454
|
-
if (filters.model &&
|
|
473
|
+
if (filters.model && actualModel !== filters.model) continue;
|
|
455
474
|
}
|
|
456
475
|
|
|
457
476
|
let key;
|
|
458
|
-
if (groupBy === 'model') key =
|
|
477
|
+
if (groupBy === 'model') key = actualModel || 'unknown';
|
|
459
478
|
else if (groupBy === 'channel') key = entry.channel || entry.channelId || 'unknown';
|
|
460
479
|
else if (groupBy === 'toolType') key = entry.toolType || 'claude-code';
|
|
461
480
|
else continue;
|
|
462
481
|
|
|
463
482
|
if (!result[key]) result[key] = { tokens: { total: 0 }, cost: 0, requests: 0 };
|
|
464
|
-
result[key].tokens.total += entry.tokens
|
|
483
|
+
result[key].tokens.total += getTokenTotal(entry.tokens);
|
|
465
484
|
result[key].cost += entry.cost || 0;
|
|
466
485
|
result[key].requests += 1;
|
|
467
486
|
}
|
|
@@ -493,21 +512,23 @@ function readJsonlForDay(year, month, day, groupBy, filters) {
|
|
|
493
512
|
let entry;
|
|
494
513
|
try { entry = JSON.parse(line); } catch { continue; }
|
|
495
514
|
|
|
515
|
+
const actualModel = resolveActualModel(entry.model, entry);
|
|
516
|
+
|
|
496
517
|
// Apply filters
|
|
497
518
|
if (filters) {
|
|
498
519
|
if (filters.toolType && entry.toolType !== filters.toolType) continue;
|
|
499
520
|
if (filters.channel && entry.channel !== filters.channel) continue;
|
|
500
|
-
if (filters.model &&
|
|
521
|
+
if (filters.model && actualModel !== filters.model) continue;
|
|
501
522
|
}
|
|
502
523
|
|
|
503
524
|
let key;
|
|
504
|
-
if (groupBy === 'model') key =
|
|
525
|
+
if (groupBy === 'model') key = actualModel || 'unknown';
|
|
505
526
|
else if (groupBy === 'channel') key = entry.channel || entry.channelId || 'unknown';
|
|
506
527
|
else if (groupBy === 'toolType') key = entry.toolType || 'claude-code';
|
|
507
528
|
else continue;
|
|
508
529
|
|
|
509
530
|
if (!result[key]) result[key] = { tokens: { total: 0 }, cost: 0, requests: 0 };
|
|
510
|
-
result[key].tokens.total += entry.tokens
|
|
531
|
+
result[key].tokens.total += getTokenTotal(entry.tokens);
|
|
511
532
|
result[key].cost += entry.cost || 0;
|
|
512
533
|
result[key].requests += 1;
|
|
513
534
|
}
|
|
@@ -541,7 +562,7 @@ function filterProxyLogsForHour(logs, dateStr, hour, groupBy) {
|
|
|
541
562
|
const result = {};
|
|
542
563
|
|
|
543
564
|
for (const entry of logs) {
|
|
544
|
-
if (!entry || entry.type === 'action') continue;
|
|
565
|
+
if (!entry || entry.type === 'action' || entry.status === 'error') continue;
|
|
545
566
|
|
|
546
567
|
const ts = new Date(entry.timestamp || Date.now());
|
|
547
568
|
if (Number.isNaN(ts.getTime())) continue;
|
|
@@ -549,11 +570,22 @@ function filterProxyLogsForHour(logs, dateStr, hour, groupBy) {
|
|
|
549
570
|
const entryDate = getCSTDateStr(ts);
|
|
550
571
|
if (entryDate !== dateStr || getCSTHour(ts) !== hour) continue;
|
|
551
572
|
|
|
573
|
+
const actualModel = resolveActualModel(entry.model, entry);
|
|
574
|
+
const normalizedTokens = normalizeUsageTokens(entry.source || mapSourceToToolType(entry.source), {
|
|
575
|
+
input: entry.inputTokens ?? entry.tokens?.input,
|
|
576
|
+
output: entry.outputTokens ?? entry.tokens?.output,
|
|
577
|
+
cacheCreation: entry.cacheCreation ?? entry.tokens?.cacheCreation,
|
|
578
|
+
cacheRead: entry.cacheRead ?? entry.tokens?.cacheRead,
|
|
579
|
+
cached: entry.cachedTokens ?? entry.tokens?.cached,
|
|
580
|
+
reasoning: entry.reasoningTokens ?? entry.tokens?.reasoning,
|
|
581
|
+
total: entry.totalTokens ?? entry.tokens?.total
|
|
582
|
+
});
|
|
583
|
+
|
|
552
584
|
let key;
|
|
553
585
|
if (groupBy === 'toolType') {
|
|
554
586
|
key = mapSourceToToolType(entry.source);
|
|
555
587
|
} else if (groupBy === 'model') {
|
|
556
|
-
key =
|
|
588
|
+
key = actualModel || 'unknown';
|
|
557
589
|
} else if (groupBy === 'channel') {
|
|
558
590
|
key = entry.channel || 'unknown';
|
|
559
591
|
} else {
|
|
@@ -564,11 +596,7 @@ function filterProxyLogsForHour(logs, dateStr, hour, groupBy) {
|
|
|
564
596
|
result[key] = { tokens: { total: 0 }, cost: 0, requests: 0 };
|
|
565
597
|
}
|
|
566
598
|
|
|
567
|
-
|
|
568
|
-
entry.tokens?.total ||
|
|
569
|
-
(entry.inputTokens || 0) + (entry.outputTokens || 0) + (entry.reasoningTokens || 0);
|
|
570
|
-
|
|
571
|
-
result[key].tokens.total += totalTokens || 0;
|
|
599
|
+
result[key].tokens.total += normalizedTokens.total || 0;
|
|
572
600
|
result[key].cost += entry.cost || 0;
|
|
573
601
|
result[key].requests += 1;
|
|
574
602
|
}
|
|
@@ -741,7 +769,7 @@ function aggregateDailyStatistics(dateStr) {
|
|
|
741
769
|
}
|
|
742
770
|
|
|
743
771
|
for (const [modelName, modelStats] of Object.entries(toolStats.models || {})) {
|
|
744
|
-
const key =
|
|
772
|
+
const key = modelName || 'unknown';
|
|
745
773
|
if (!aggregated.byModel[key]) {
|
|
746
774
|
aggregated.byModel[key] = createEmptyEntry(toolType);
|
|
747
775
|
}
|
|
@@ -835,6 +863,7 @@ function getAvailableFilters(startDate, endDate) {
|
|
|
835
863
|
const toolTypes = new Set();
|
|
836
864
|
const channels = new Set();
|
|
837
865
|
const models = new Set();
|
|
866
|
+
const includeProxyLogs = startDate <= getCSTDateStr(Date.now()) && getCSTDateStr(Date.now()) <= endDate;
|
|
838
867
|
|
|
839
868
|
const start = new Date(startDate + 'T00:00:00');
|
|
840
869
|
const end = new Date(endDate + 'T00:00:00');
|
|
@@ -852,15 +881,31 @@ function getAvailableFilters(startDate, endDate) {
|
|
|
852
881
|
if (!line.trim()) continue;
|
|
853
882
|
let entry;
|
|
854
883
|
try { entry = JSON.parse(line); } catch { continue; }
|
|
884
|
+
const actualModel = resolveActualModel(entry.model, entry);
|
|
855
885
|
if (entry.toolType) toolTypes.add(entry.toolType);
|
|
856
886
|
if (entry.channel) channels.add(entry.channel);
|
|
857
|
-
if (
|
|
887
|
+
if (actualModel) models.add(actualModel);
|
|
858
888
|
}
|
|
859
889
|
} catch (err) {
|
|
860
890
|
console.error('Failed to scan JSONL for filters:', err);
|
|
861
891
|
}
|
|
862
892
|
}
|
|
863
893
|
|
|
894
|
+
if (includeProxyLogs) {
|
|
895
|
+
for (const entry of loadProxyLogs()) {
|
|
896
|
+
if (!entry || entry.type === 'action' || entry.status === 'error') continue;
|
|
897
|
+
const ts = new Date(entry.timestamp || Date.now());
|
|
898
|
+
if (Number.isNaN(ts.getTime())) continue;
|
|
899
|
+
const entryDate = getCSTDateStr(ts);
|
|
900
|
+
if (entryDate < startDate || entryDate > endDate) continue;
|
|
901
|
+
|
|
902
|
+
toolTypes.add(mapSourceToToolType(entry.source));
|
|
903
|
+
if (entry.channel) channels.add(entry.channel);
|
|
904
|
+
const actualModel = resolveActualModel(entry.model, entry);
|
|
905
|
+
if (actualModel) models.add(actualModel);
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
|
|
864
909
|
return {
|
|
865
910
|
toolTypes: Array.from(toolTypes).sort(),
|
|
866
911
|
channels: Array.from(channels).sort(),
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { spawn } = require('child_process');
|
|
6
|
+
|
|
7
|
+
const DEFAULT_PROJECT_ROOT = path.resolve(__dirname, '../../..');
|
|
8
|
+
const IGNORED_DIR_NAMES = new Set(['node_modules', '.git', 'dist']);
|
|
9
|
+
const WATCHED_WEB_FILES = ['index.html', 'package.json', 'package-lock.json', 'pnpm-lock.yaml', 'vite.config.js'];
|
|
10
|
+
const WATCHED_WEB_DIRS = ['src', 'public'];
|
|
11
|
+
|
|
12
|
+
function pathExists(targetPath) {
|
|
13
|
+
return fs.existsSync(targetPath);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function getLatestMtimeMs(targetPath) {
|
|
17
|
+
if (!pathExists(targetPath)) {
|
|
18
|
+
return 0;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const stat = fs.statSync(targetPath);
|
|
22
|
+
if (!stat.isDirectory()) {
|
|
23
|
+
return stat.mtimeMs;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let latest = stat.mtimeMs;
|
|
27
|
+
for (const entry of fs.readdirSync(targetPath, { withFileTypes: true })) {
|
|
28
|
+
if (entry.isDirectory() && IGNORED_DIR_NAMES.has(entry.name)) {
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
const childPath = path.join(targetPath, entry.name);
|
|
32
|
+
latest = Math.max(latest, getLatestMtimeMs(childPath));
|
|
33
|
+
}
|
|
34
|
+
return latest;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function getWatchedWebSourceMtimeMs(sourceRoot) {
|
|
38
|
+
let latest = 0;
|
|
39
|
+
for (const fileName of WATCHED_WEB_FILES) {
|
|
40
|
+
latest = Math.max(latest, getLatestMtimeMs(path.join(sourceRoot, fileName)));
|
|
41
|
+
}
|
|
42
|
+
for (const dirName of WATCHED_WEB_DIRS) {
|
|
43
|
+
latest = Math.max(latest, getLatestMtimeMs(path.join(sourceRoot, dirName)));
|
|
44
|
+
}
|
|
45
|
+
return latest;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function inspectWebBuildState(options = {}) {
|
|
49
|
+
const projectRoot = options.projectRoot || DEFAULT_PROJECT_ROOT;
|
|
50
|
+
const sourceRoot = options.sourceRoot || path.join(projectRoot, 'src/web');
|
|
51
|
+
const distRoot = options.distRoot || path.join(projectRoot, 'dist/web');
|
|
52
|
+
const distIndexPath = path.join(distRoot, 'index.html');
|
|
53
|
+
const sourceAvailable = pathExists(sourceRoot) && pathExists(path.join(sourceRoot, 'package.json'));
|
|
54
|
+
const distExists = pathExists(distIndexPath);
|
|
55
|
+
|
|
56
|
+
if (!sourceAvailable) {
|
|
57
|
+
return {
|
|
58
|
+
projectRoot,
|
|
59
|
+
sourceRoot,
|
|
60
|
+
distRoot,
|
|
61
|
+
distIndexPath,
|
|
62
|
+
sourceAvailable: false,
|
|
63
|
+
distExists,
|
|
64
|
+
sourceMtimeMs: 0,
|
|
65
|
+
distMtimeMs: getLatestMtimeMs(distRoot),
|
|
66
|
+
needsBuild: false,
|
|
67
|
+
reason: 'source-unavailable'
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const sourceMtimeMs = getWatchedWebSourceMtimeMs(sourceRoot);
|
|
72
|
+
const distMtimeMs = getLatestMtimeMs(distRoot);
|
|
73
|
+
const hasDistAssets = distExists && distMtimeMs > 0;
|
|
74
|
+
const isStale = hasDistAssets && sourceMtimeMs > distMtimeMs + 1;
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
projectRoot,
|
|
78
|
+
sourceRoot,
|
|
79
|
+
distRoot,
|
|
80
|
+
distIndexPath,
|
|
81
|
+
sourceAvailable: true,
|
|
82
|
+
distExists,
|
|
83
|
+
sourceMtimeMs,
|
|
84
|
+
distMtimeMs,
|
|
85
|
+
needsBuild: !hasDistAssets || isStale,
|
|
86
|
+
reason: !hasDistAssets ? 'dist-missing' : (isStale ? 'dist-stale' : 'up-to-date')
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function getNpmCommand() {
|
|
91
|
+
return process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function runWebBuild(options = {}) {
|
|
95
|
+
const sourceRoot = options.sourceRoot || path.join(DEFAULT_PROJECT_ROOT, 'src/web');
|
|
96
|
+
const npmCommand = options.npmCommand || getNpmCommand();
|
|
97
|
+
const env = options.env || process.env;
|
|
98
|
+
const stdio = options.stdio || 'inherit';
|
|
99
|
+
|
|
100
|
+
return new Promise((resolve, reject) => {
|
|
101
|
+
const child = spawn(npmCommand, ['run', 'build'], {
|
|
102
|
+
cwd: sourceRoot,
|
|
103
|
+
env,
|
|
104
|
+
stdio
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
child.once('error', (error) => {
|
|
108
|
+
reject(error);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
child.once('close', (code) => {
|
|
112
|
+
if (code === 0) {
|
|
113
|
+
resolve();
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
reject(new Error(`Web UI build exited with code ${code}`));
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async function ensureWebDistReady(options = {}) {
|
|
122
|
+
const initialState = options.state || inspectWebBuildState(options);
|
|
123
|
+
if (!initialState.needsBuild) {
|
|
124
|
+
return {
|
|
125
|
+
...initialState,
|
|
126
|
+
built: false,
|
|
127
|
+
skipped: true,
|
|
128
|
+
previousReason: initialState.reason
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const runBuild = options.runBuild || runWebBuild;
|
|
133
|
+
|
|
134
|
+
try {
|
|
135
|
+
await runBuild({
|
|
136
|
+
sourceRoot: initialState.sourceRoot,
|
|
137
|
+
distRoot: initialState.distRoot,
|
|
138
|
+
npmCommand: options.npmCommand || getNpmCommand(),
|
|
139
|
+
env: options.env,
|
|
140
|
+
stdio: options.stdio
|
|
141
|
+
});
|
|
142
|
+
} catch (error) {
|
|
143
|
+
const dependencyHint = pathExists(path.join(initialState.sourceRoot, 'node_modules'))
|
|
144
|
+
? ''
|
|
145
|
+
: '(未检测到 src/web/node_modules,前端依赖可能尚未安装)';
|
|
146
|
+
throw new Error(`Web UI 静态资源构建失败: ${error.message}${dependencyHint}`);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const finalState = inspectWebBuildState({
|
|
150
|
+
projectRoot: initialState.projectRoot,
|
|
151
|
+
sourceRoot: initialState.sourceRoot,
|
|
152
|
+
distRoot: initialState.distRoot
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
...finalState,
|
|
157
|
+
built: true,
|
|
158
|
+
skipped: false,
|
|
159
|
+
previousReason: initialState.reason
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
module.exports = {
|
|
164
|
+
inspectWebBuildState,
|
|
165
|
+
ensureWebDistReady,
|
|
166
|
+
_test: {
|
|
167
|
+
getLatestMtimeMs,
|
|
168
|
+
getWatchedWebSourceMtimeMs,
|
|
169
|
+
getNpmCommand,
|
|
170
|
+
runWebBuild
|
|
171
|
+
}
|
|
172
|
+
};
|
|
@@ -504,11 +504,28 @@ function broadcastSchedulerState(source, schedulerState) {
|
|
|
504
504
|
}
|
|
505
505
|
}
|
|
506
506
|
|
|
507
|
+
function broadcastBrowserNotification(notification = {}) {
|
|
508
|
+
if (wss && wsClients.size > 0) {
|
|
509
|
+
const message = JSON.stringify({
|
|
510
|
+
type: 'browser-notification',
|
|
511
|
+
...notification,
|
|
512
|
+
timestamp: notification.timestamp || Date.now()
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
wsClients.forEach(client => {
|
|
516
|
+
if (client.readyState === WebSocket.OPEN) {
|
|
517
|
+
client.send(message);
|
|
518
|
+
}
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
507
523
|
module.exports = {
|
|
508
524
|
startWebSocketServer,
|
|
509
525
|
stopWebSocketServer,
|
|
510
526
|
broadcastLog,
|
|
511
527
|
clearAllLogs,
|
|
512
528
|
broadcastProxyState,
|
|
513
|
-
broadcastSchedulerState
|
|
529
|
+
broadcastSchedulerState,
|
|
530
|
+
broadcastBrowserNotification
|
|
514
531
|
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
.channel-column[data-v-0bad694b]{display:flex;flex-direction:column;height:100%;min-height:0;background:var(--gradient-card);border:1px solid var(--border-primary);border-radius:8px;overflow:hidden;box-shadow:var(--shadow-sm)}.channel-header[data-v-0bad694b]{display:flex;align-items:center;gap:10px;padding:12px 14px;background:var(--bg-primary);position:relative}.drag-handle[data-v-0bad694b]{cursor:grab;color:var(--text-tertiary);display:flex;align-items:center;padding:4px;margin:-4px 0 -4px -4px;border-radius:4px;transition:all .2s}.drag-handle[data-v-0bad694b]:hover{color:var(--text-secondary);background:var(--bg-tertiary)}.drag-handle[data-v-0bad694b]:active{cursor:grabbing}.channel-header[data-v-0bad694b]:after{content:"";position:absolute;bottom:0;left:14px;right:14px;height:2px;border-radius:1px}.channel-header.claude[data-v-0bad694b]{background:linear-gradient(135deg,rgba(24,160,88,.08) 0%,transparent 100%)}.channel-header.claude[data-v-0bad694b]:after{background:linear-gradient(90deg,#18a058,#18a0584d)}.channel-header.codex[data-v-0bad694b]{background:linear-gradient(135deg,rgba(59,130,246,.08) 0%,transparent 100%)}.channel-header.codex[data-v-0bad694b]:after{background:linear-gradient(90deg,#3b82f6,#3b82f64d)}.channel-header.gemini[data-v-0bad694b]{background:linear-gradient(135deg,rgba(168,85,247,.08) 0%,transparent 100%)}.channel-header.gemini[data-v-0bad694b]:after{background:linear-gradient(90deg,#a855f7,#a855f74d)}.channel-header.opencode[data-v-0bad694b]{background:linear-gradient(135deg,rgba(234,88,12,.08) 0%,transparent 100%)}.channel-header.opencode[data-v-0bad694b]:after{background:linear-gradient(90deg,#ea580c,#ea580c4d)}.header-icon[data-v-0bad694b]{width:32px;height:32px;border-radius:6px;display:flex;align-items:center;justify-content:center;box-shadow:0 2px 8px #0000001a}.channel-header.claude .header-icon[data-v-0bad694b]{background:linear-gradient(135deg,#18a058,#15803d);color:#fff}.channel-header.codex .header-icon[data-v-0bad694b]{background:linear-gradient(135deg,#3b82f6,#2563eb);color:#fff}.channel-header.gemini .header-icon[data-v-0bad694b]{background:linear-gradient(135deg,#a855f7,#9333ea);color:#fff}.channel-header.opencode .header-icon[data-v-0bad694b]{background:linear-gradient(135deg,#ea580c,#c2410c);color:#fff}.channel-title[data-v-0bad694b]{font-size:15px;font-weight:700;margin:0;color:var(--text-primary);letter-spacing:.3px}.channel-content[data-v-0bad694b]{flex:1;min-height:0;overflow:hidden;padding:10px;display:flex;flex-direction:column;gap:8px}.card[data-v-0bad694b]{background:var(--bg-primary);border:1px solid var(--border-primary);border-radius:8px;overflow:hidden;transition:all .3s cubic-bezier(.4,0,.2,1);box-shadow:0 2px 12px #0000000d;position:relative}.card[data-v-0bad694b]:before{content:"";position:absolute;top:0;left:0;right:0;height:1px;background:linear-gradient(90deg,transparent,rgba(255,255,255,.1),transparent);opacity:0;transition:opacity .3s ease}.card[data-v-0bad694b]:hover{box-shadow:0 6px 20px #0000001a;border-color:var(--border-secondary)}.card[data-v-0bad694b]:hover:before{opacity:1}.card.clickable[data-v-0bad694b]{cursor:pointer}.card.clickable[data-v-0bad694b]:hover{border-color:#18a058;transform:translateY(-2px);box-shadow:0 8px 24px #18a0582e}.card.clickable[data-v-0bad694b]:after{content:"";position:absolute;top:0;right:0;bottom:0;left:0;background:linear-gradient(135deg,rgba(24,160,88,.02) 0%,transparent 100%);opacity:0;transition:opacity .3s ease;pointer-events:none}.card.clickable[data-v-0bad694b]:hover:after{opacity:1}.card-header[data-v-0bad694b]{display:flex;align-items:center;gap:8px;padding:10px 12px;background:linear-gradient(180deg,var(--bg-secondary) 0%,var(--bg-primary) 100%);border-bottom:1px solid var(--border-primary);position:relative;min-height:24px}.card-header.compact[data-v-0bad694b]{padding:8px 12px}.card-header .n-icon[data-v-0bad694b]{color:var(--text-tertiary);transition:color .2s ease}.card:hover .card-header .n-icon[data-v-0bad694b]{color:var(--text-secondary)}.card-title[data-v-0bad694b]{font-size:11px;font-weight:700;margin:0;color:var(--text-secondary);text-transform:uppercase;letter-spacing:.8px}.runtime-badge[data-v-0bad694b]{display:inline-flex;align-items:center;padding:3px 8px;margin-left:8px;font-size:10px;font-weight:600;color:#10b981;background:linear-gradient(135deg,#10b9811a,#34d3991a);border:1px solid rgba(16,185,129,.2);border-radius:4px;white-space:nowrap;animation:pulse-runtime-0bad694b 2s ease-in-out infinite}@keyframes pulse-runtime-0bad694b{0%,to{opacity:1;transform:scale(1)}50%{opacity:.8;transform:scale(.98)}}.card-body[data-v-0bad694b]{padding:12px;background:var(--bg-primary)}.card-body.compact[data-v-0bad694b]{padding:10px 12px}.proxy-control[data-v-0bad694b]{display:flex;justify-content:space-between;align-items:center;gap:16px}.proxy-info[data-v-0bad694b]{display:flex;flex-direction:column;gap:4px}.proxy-info-row[data-v-0bad694b]{display:flex;align-items:center;justify-content:space-between}.proxy-status[data-v-0bad694b]{display:flex;align-items:center;gap:6px}.proxy-port[data-v-0bad694b]{font-size:11px;color:var(--text-secondary);margin-left:6px}.channel-selector[data-v-0bad694b]{display:flex;align-items:center;padding:4px 8px;background:linear-gradient(135deg,var(--bg-secondary) 0%,var(--bg-tertiary) 100%);border:1px solid var(--border-primary);border-radius:4px;color:var(--text-secondary);transition:all .2s ease}.channel-selector[data-v-0bad694b]:hover{background:var(--hover-bg);border-color:var(--border-secondary);color:var(--text-primary)}.channel-selector[data-v-0bad694b]:disabled{opacity:.5;cursor:not-allowed}.channel-name[data-v-0bad694b]{font-size:11px;font-weight:500;max-width:90px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.status-dot[data-v-0bad694b]{width:8px;height:8px;border-radius:50%;background:var(--border-secondary);transition:all .3s ease}.status-dot.active[data-v-0bad694b]{background:#18a058;box-shadow:0 0 8px #18a05880}.quick-stats[data-v-0bad694b]{display:flex;gap:16px;padding:4px 0}.stat-item[data-v-0bad694b]{flex:1;display:flex;flex-direction:column;align-items:center;gap:2px;padding:4px 0}.stat-divider[data-v-0bad694b]{width:1px;background:linear-gradient(180deg,transparent 0%,var(--border-primary) 50%,transparent 100%)}.quick-access-list[data-v-0bad694b]{display:grid;grid-template-columns:repeat(3,1fr);gap:6px}.access-card[data-v-0bad694b]{display:flex;align-items:center;gap:8px;padding:8px 10px;border-radius:6px;background:var(--bg-secondary);border:1px solid var(--border-color);transition:all .3s cubic-bezier(.4,0,.2,1);position:relative;min-height:52px;overflow:hidden}.access-card[data-v-0bad694b]:before{content:"";position:absolute;top:0;right:0;bottom:0;left:0;opacity:0;transition:opacity .3s ease;pointer-events:none}.access-icon[data-v-0bad694b]{width:32px;height:32px;border-radius:5px;display:flex;align-items:center;justify-content:center;flex-shrink:0;transition:all .3s ease}.access-content[data-v-0bad694b]{display:flex;flex-direction:column;gap:4px;flex:1}.access-card-projects .access-icon[data-v-0bad694b]{background:linear-gradient(135deg,#6366f126,#8b5cf626);color:#6366f1}.access-card-projects[data-v-0bad694b]:before{background:linear-gradient(135deg,#6366f114,#8b5cf614)}.access-card-projects:hover .access-icon[data-v-0bad694b]{background:linear-gradient(135deg,#6366f140,#8b5cf640);transform:scale(1.1) rotate(-5deg)}.access-card-sessions .access-icon[data-v-0bad694b]{background:linear-gradient(135deg,#10b98126,#05966926);color:#10b981}.access-card-sessions[data-v-0bad694b]:before{background:linear-gradient(135deg,#10b98114,#05966914)}.access-card-sessions:hover .access-icon[data-v-0bad694b]{background:linear-gradient(135deg,#10b98140,#05966940);transform:scale(1.1) rotate(5deg)}.access-card-goto .access-icon[data-v-0bad694b]{background:linear-gradient(135deg,#f59e0b26,#fb923c26);color:#f59e0b}.access-card-goto[data-v-0bad694b]:before{background:linear-gradient(135deg,#f59e0b14,#fb923c14)}.access-card-goto:hover .access-icon[data-v-0bad694b]{background:linear-gradient(135deg,#f59e0b40,#fb923c40);transform:scale(1.1) translate(3px)}.access-card.clickable[data-v-0bad694b]:hover{transform:translateY(-2px);box-shadow:0 8px 20px #0000001a;border-color:var(--border-secondary)}.access-card.clickable[data-v-0bad694b]:hover:before{opacity:1}.access-card.clickable[data-v-0bad694b]:active{transform:translateY(0)}.access-label[data-v-0bad694b]{display:block;font-size:10px;font-weight:600;color:var(--text-secondary);text-transform:uppercase;letter-spacing:.3px}.access-value[data-v-0bad694b]{display:block;font-size:18px;font-weight:800;color:var(--text-primary);line-height:1}.access-goto[data-v-0bad694b]{display:block;font-size:12px;font-weight:700;color:var(--text-primary);line-height:1.2;white-space:nowrap}.stats-inline[data-v-0bad694b]{display:grid;grid-template-columns:repeat(4,1fr);gap:8px}.stats-inline.stats-3col[data-v-0bad694b]{grid-template-columns:repeat(3,1fr)}.stat-inline-item[data-v-0bad694b]{display:flex;align-items:center;gap:8px;padding:10px;background:var(--bg-secondary);border-radius:6px;border:1px solid var(--border-primary);transition:all .2s ease}.stat-inline-item[data-v-0bad694b]:hover{border-color:var(--border-secondary);background:var(--hover-bg)}.stat-icon-dot[data-v-0bad694b]{width:8px;height:8px;border-radius:50%;flex-shrink:0}.stat-icon-dot.requests[data-v-0bad694b]{background:#3b82f6;box-shadow:0 0 6px #3b82f680}.stat-icon-dot.tokens[data-v-0bad694b]{background:#18a058;box-shadow:0 0 6px #18a05880}.stat-icon-dot.cost[data-v-0bad694b]{background:#f59e0b;box-shadow:0 0 6px #f59e0b80}.stat-info[data-v-0bad694b]{display:flex;flex-direction:column;gap:1px;min-width:0}.stat-label[data-v-0bad694b]{font-size:10px;color:var(--text-tertiary);font-weight:500;text-transform:uppercase;letter-spacing:.2px}.stat-value[data-v-0bad694b]{font-size:16px;font-weight:700;color:var(--text-primary);line-height:1.2;transition:all .3s ease}.stat-value.animating[data-v-0bad694b]{animation:numberChange-0bad694b .6s ease}@keyframes numberChange-0bad694b{0%{transform:translateY(-6px);opacity:.5}50%{transform:translateY(0);opacity:1;color:var(--primary-color)}to{transform:translateY(0);opacity:1}}.stats-card[data-v-0bad694b],.chart-card[data-v-0bad694b]{border-left:2px solid transparent}.stats-card-claude[data-v-0bad694b],.chart-card[data-v-0bad694b]:has(+.stats-card-claude),.card[data-v-0bad694b]:has(.panel-card):nth-child(4){border-left-color:#18a058}.chart-card.chart-card-claude[data-v-0bad694b]{border-left-color:#18a058}.stats-card-codex[data-v-0bad694b],.chart-card[data-v-0bad694b]:has(+.stats-card-codex),.card[data-v-0bad694b]:has(.panel-card):nth-child(4){border-left-color:#3b82f6}.chart-card.chart-card-codex[data-v-0bad694b]{border-left-color:#3b82f6}.stats-card-gemini[data-v-0bad694b],.chart-card[data-v-0bad694b]:has(+.stats-card-gemini),.card[data-v-0bad694b]:has(.panel-card):nth-child(4){border-left-color:#a855f7}.chart-card.chart-card-gemini[data-v-0bad694b]{border-left-color:#a855f7}.stats-card-opencode[data-v-0bad694b],.chart-card[data-v-0bad694b]:has(+.stats-card-opencode),.card[data-v-0bad694b]:has(.panel-card):nth-child(4){border-left-color:#ea580c}.chart-card.chart-card-opencode[data-v-0bad694b]{border-left-color:#ea580c}.chart-card[data-v-0bad694b]{padding:0;overflow:hidden}.chart-card[data-v-0bad694b] .panel-card{border:none;border-radius:0;box-shadow:none;background:transparent}.stat-requests .stat-value[data-v-0bad694b]{color:#3b82f6}.stat-input .stat-value[data-v-0bad694b]{color:#18a058}.stat-output .stat-value[data-v-0bad694b]{color:#f59e0b}.token-label[data-v-0bad694b]{display:flex;align-items:center;gap:8px}.token-dot[data-v-0bad694b]{width:8px;height:8px;border-radius:50%}.logs-card[data-v-0bad694b]{flex:1;min-height:0;display:flex;flex-direction:column;overflow:hidden;border:1px solid var(--border-primary);border-radius:6px}.logs-card-body[data-v-0bad694b]{flex:1;min-height:0;display:flex;flex-direction:column;padding:0}.logs-card .card-header[data-v-0bad694b]{background:linear-gradient(135deg,var(--bg-secondary) 0%,var(--bg-tertiary) 100%)}.logs-table-wrapper[data-v-0bad694b]{flex:1;min-height:0;display:flex;flex-direction:column;overflow:hidden;background:var(--bg-primary)}.logs-table-header[data-v-0bad694b]{display:flex;padding:10px 12px;background:linear-gradient(180deg,var(--bg-tertiary) 0%,var(--bg-secondary) 100%);border-bottom:2px solid var(--border-primary);font-size:11px;font-weight:700;color:var(--text-tertiary);text-transform:uppercase;letter-spacing:.6px;flex-shrink:0}.logs-header-claude .log-col[data-v-0bad694b]{color:#18a058b3;font-weight:600}.logs-header-codex .log-col[data-v-0bad694b]{color:#3b82f6b3;font-weight:600}.logs-header-gemini .log-col[data-v-0bad694b]{color:#a855f7b3;font-weight:600}.logs-header-opencode .log-col[data-v-0bad694b]{color:#ea580cb3;font-weight:600}[data-theme=dark] .logs-header-claude .log-col[data-v-0bad694b]{color:#34d399a6}[data-theme=dark] .logs-header-codex .log-col[data-v-0bad694b]{color:#60a5faa6}[data-theme=dark] .logs-header-gemini .log-col[data-v-0bad694b]{color:#c084fca6}[data-theme=dark] .logs-header-opencode .log-col[data-v-0bad694b]{color:#fb923ca6}.logs-container[data-v-0bad694b]{flex:1;min-height:0;overflow-y:auto;overflow-x:hidden}.logs-container[data-v-0bad694b]::-webkit-scrollbar{width:4px}.logs-container[data-v-0bad694b]::-webkit-scrollbar-thumb{background:#18a0584d;border-radius:2px}.logs-container[data-v-0bad694b]::-webkit-scrollbar-thumb:hover{background:#18a05880}.empty-logs[data-v-0bad694b]{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:40px 20px;color:var(--text-tertiary);min-height:200px;text-align:center}.log-row[data-v-0bad694b]{display:flex;align-items:center;padding:10px 12px;min-height:40px;border-bottom:1px solid var(--border-primary);font-size:12px;transition:all .2s ease;background:var(--bg-primary);position:relative}.log-row[data-v-0bad694b]:nth-child(2n){background:var(--bg-secondary)}.log-row[data-v-0bad694b]:hover{background:var(--hover-bg);transform:translate(2px)}.log-row.new-log[data-v-0bad694b]{animation:newLogPulse-0bad694b 4.5s cubic-bezier(.25,.46,.45,.94) forwards;border-left:3px solid #18a058}@keyframes newLogPulse-0bad694b{0%{background:linear-gradient(90deg,#18a05826,#18a0580f);box-shadow:0 0 8px #18a05833}5%{background:linear-gradient(90deg,#18a0582e,#18a05814);box-shadow:0 0 12px 1px #18a05840}15%{background:linear-gradient(90deg,#18a05829,#18a05812);box-shadow:0 0 10px 1px #18a05833}30%{background:linear-gradient(90deg,#18a0581f,#18a0580d);box-shadow:0 0 6px #18a05826}50%{background:linear-gradient(90deg,#18a05814,#18a05808);box-shadow:0 0 4px #18a0581a}70%{background:linear-gradient(90deg,#18a0580a,#18a05804);box-shadow:0 0 2px #18a0580f}85%{background:linear-gradient(90deg,#18a05805,#18a05802);box-shadow:0 0 1px #18a05808}to{background:transparent;box-shadow:0 0 #18a05800;border-left-color:transparent}}[data-theme=dark] .log-row.new-log[data-v-0bad694b]{animation:newLogPulseDark-0bad694b 4.5s cubic-bezier(.25,.46,.45,.94) forwards}@keyframes newLogPulseDark-0bad694b{0%{background:linear-gradient(90deg,#18a05833,#18a05814);box-shadow:0 0 10px #18a05840}5%{background:linear-gradient(90deg,#18a0583d,#18a0581a);box-shadow:0 0 14px 2px #18a0584d}15%{background:linear-gradient(90deg,#18a05836,#18a05817);box-shadow:0 0 12px 1px #18a05840}30%{background:linear-gradient(90deg,#18a05829,#18a05812);box-shadow:0 0 8px 1px #18a0582e}50%{background:linear-gradient(90deg,#18a0581c,#18a0580b);box-shadow:0 0 5px #18a0581f}70%{background:linear-gradient(90deg,#18a0580f,#18a05806);box-shadow:0 0 3px #18a05814}85%{background:linear-gradient(90deg,#18a05808,#18a05803);box-shadow:0 0 1px #18a0580a}to{background:transparent;box-shadow:0 0 #18a05800;border-left-color:transparent}}.log-row.new-log.action-row[data-v-0bad694b]{border-left:3px solid #18a058}.log-row.action-row[data-v-0bad694b]{background:linear-gradient(90deg,#18a0581f,#18a0580a);border-left:3px solid #18a058;padding-left:8px}.log-row.error-row[data-v-0bad694b]{background:linear-gradient(90deg,#d0305024,#d030500a);border-left:3px solid #d03050;padding-left:8px}.log-row.action-row[data-v-0bad694b]:hover{background:linear-gradient(90deg,#18a0582e,#18a05814)}.log-row.error-row[data-v-0bad694b]:hover{background:linear-gradient(90deg,#d0305033,#d0305014)}.action-content[data-v-0bad694b]{display:flex;align-items:center;gap:8px;width:100%}.action-msg[data-v-0bad694b]{flex:1;font-size:11px;color:#18a058;font-weight:600;letter-spacing:.2px}.action-msg.error-msg[data-v-0bad694b]{color:#d03050}.action-time[data-v-0bad694b]{font-size:10px;font-family:SF Mono,Monaco,monospace;color:var(--text-tertiary);background:#18a0581a;padding:2px 6px;border-radius:4px}.action-time.error-time[data-v-0bad694b]{background:#d030501f}.log-col[data-v-0bad694b]{display:flex;align-items:center;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.col-channel[data-v-0bad694b]{min-width:0}.col-channel .n-tag[data-v-0bad694b]{max-width:100%;font-size:11px;font-weight:600;border-radius:4px;padding:3px 10px}.col-token[data-v-0bad694b]{justify-content:center;font-family:SF Mono,Monaco,Consolas,monospace;font-size:12px;font-weight:600;color:var(--text-secondary);background:#00000008;padding:4px 8px;border-radius:4px;margin:0 3px}[data-theme=dark] .col-token[data-v-0bad694b]{background:#ffffff0f}.col-time[data-v-0bad694b]{justify-content:flex-end;font-family:SF Mono,Monaco,Consolas,monospace;font-size:11px;font-weight:500;color:var(--text-tertiary);padding-right:2px;opacity:.85}.log-row:hover .col-time[data-v-0bad694b]{opacity:1;color:var(--text-secondary)}.col-channel-claude[data-v-0bad694b]{flex:2 1 60px;min-width:50px}.col-token-claude[data-v-0bad694b]{flex:1 1 40px;min-width:35px}.col-time-claude[data-v-0bad694b]{flex:1.5 1 55px;min-width:50px}.col-channel-codex[data-v-0bad694b]{flex:2 1 60px;min-width:50px}.col-token-codex[data-v-0bad694b]{flex:1 1 40px;min-width:35px}.col-time-codex[data-v-0bad694b]{flex:1.5 1 55px;min-width:50px}.col-channel-gemini[data-v-0bad694b]{flex:2.5 1 70px;min-width:55px}.col-token-gemini[data-v-0bad694b]{flex:1.2 1 45px;min-width:40px}.col-time-gemini[data-v-0bad694b]{flex:1.8 1 60px;min-width:55px}.col-channel-opencode[data-v-0bad694b]{flex:2.5 1 70px;min-width:55px}.col-token-opencode[data-v-0bad694b]{flex:1.2 1 45px;min-width:40px}.col-time-opencode[data-v-0bad694b]{flex:1.8 1 60px;min-width:55px}.claude-extra-area[data-v-0bad694b]{display:flex;align-items:center;gap:6px;margin-left:auto}.mcp-count-tag[data-v-0bad694b],.skills-count-tag[data-v-0bad694b]{font-size:10px;font-weight:500;padding:0 8px;height:22px;line-height:22px;border-radius:4px;cursor:pointer;transition:all .2s ease}.mcp-count-tag.clickable[data-v-0bad694b]:hover,.skills-count-tag.clickable[data-v-0bad694b]:hover{transform:scale(1.03);filter:brightness(1.1)}.mcp-quick-panel[data-v-0bad694b],.skills-quick-panel[data-v-0bad694b]{padding:2px 0}.mcp-quick-panel .panel-title[data-v-0bad694b],.skills-quick-panel .panel-title[data-v-0bad694b]{display:flex;justify-content:space-between;align-items:center;padding:6px 10px 8px;border-bottom:1px solid var(--border-primary);margin-bottom:4px}.mcp-quick-panel .panel-title span[data-v-0bad694b]:first-child,.skills-quick-panel .panel-title span[data-v-0bad694b]:first-child{font-size:13px;font-weight:600;color:var(--text-primary)}.no-items[data-v-0bad694b]{padding:16px 10px;text-align:center}.mcp-quick-list[data-v-0bad694b],.skills-quick-list[data-v-0bad694b]{max-height:300px;overflow-y:auto;padding:0 4px 4px}.mcp-quick-item[data-v-0bad694b],.skill-quick-item[data-v-0bad694b]{display:flex;align-items:center;gap:8px;padding:8px 10px;border-radius:5px;margin-bottom:4px;background:var(--bg-secondary);transition:all .2s ease}.mcp-quick-item[data-v-0bad694b]:hover,.skill-quick-item[data-v-0bad694b]:hover{background:var(--hover-bg)}.mcp-item-icon[data-v-0bad694b],.skill-item-icon[data-v-0bad694b]{width:24px;height:24px;border-radius:5px;display:flex;align-items:center;justify-content:center;flex-shrink:0}.mcp-item-icon[data-v-0bad694b]{background:linear-gradient(135deg,#3b82f626,#3b82f60d);color:#3b82f6}.skill-item-icon[data-v-0bad694b]{background:linear-gradient(135deg,#18a05826,#18a0580d);color:#18a058}.mcp-item-info[data-v-0bad694b],.skill-item-info[data-v-0bad694b]{flex:1;min-width:0;display:flex;flex-direction:column;gap:1px}.mcp-item-name[data-v-0bad694b],.skill-item-name[data-v-0bad694b]{font-size:12px;font-weight:600;color:var(--text-primary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.mcp-item-type[data-v-0bad694b]{font-size:10px;color:var(--text-tertiary);text-transform:uppercase}.skill-item-desc[data-v-0bad694b]{font-size:10px;color:var(--text-tertiary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.mcp-item-status[data-v-0bad694b]{flex-shrink:0}.mcp-item-status .status-dot[data-v-0bad694b]{width:8px;height:8px;border-radius:50%;background:var(--border-secondary)}.mcp-item-status.online .status-dot[data-v-0bad694b]{background:#18a058;box-shadow:0 0 6px #18a05880}.mcp-item-status.error .status-dot[data-v-0bad694b]{background:#d03050;box-shadow:0 0 6px #d0305080}.mcp-quick-list[data-v-0bad694b]::-webkit-scrollbar,.skills-quick-list[data-v-0bad694b]::-webkit-scrollbar{width:4px}.mcp-quick-list[data-v-0bad694b]::-webkit-scrollbar-thumb,.skills-quick-list[data-v-0bad694b]::-webkit-scrollbar-thumb{background:#00000026;border-radius:2px}[data-theme=dark] .mcp-quick-list[data-v-0bad694b]::-webkit-scrollbar-thumb,[data-theme=dark] .skills-quick-list[data-v-0bad694b]::-webkit-scrollbar-thumb{background:#ffffff26}.skills-button[data-v-0bad694b]{padding:6px!important;width:30px;height:30px;display:flex;align-items:center;justify-content:center;border-radius:5px;background:var(--bg-secondary);border:1px solid var(--border-primary);transition:all .25s cubic-bezier(.4,0,.2,1);box-shadow:0 2px 4px #0000000d}.skills-button .n-icon[data-v-0bad694b]{color:var(--text-color-3);transition:all .25s ease}.skills-button[data-v-0bad694b]:hover{background:linear-gradient(135deg,#18a0581a,#18a0580d);border-color:#18a0584d;box-shadow:0 4px 12px #18a05826;transform:translateY(-1px)}.skills-button:hover .n-icon[data-v-0bad694b]{color:#18a058;transform:scale(1.1)}.skills-button[data-v-0bad694b]:active{transform:translateY(0);box-shadow:0 2px 4px #0000000d}.lock-button[data-v-0bad694b]{margin-left:6px;padding:6px!important;width:30px;height:30px;display:flex;align-items:center;justify-content:center;border-radius:5px;background:var(--bg-secondary);border:1px solid var(--border-primary);transition:all .25s cubic-bezier(.4,0,.2,1);box-shadow:0 2px 4px #0000000d}.lock-button .n-icon[data-v-0bad694b]{color:var(--text-color-3);transition:all .25s ease}.lock-button[data-v-0bad694b]:hover{background:linear-gradient(135deg,#6b72801a,#6b72800d);border-color:#6b72804d;box-shadow:0 4px 12px #0000001a;transform:translateY(-1px)}.lock-button:hover .n-icon[data-v-0bad694b]{color:var(--text-color-1);transform:scale(1.1)}.lock-button[data-v-0bad694b]:active{transform:translateY(0);box-shadow:0 2px 4px #0000000d}.locked-overlay[data-v-0bad694b]{flex:1;display:flex;align-items:center;justify-content:center;border-radius:6px;margin:10px;position:relative;overflow:hidden;background:var(--bg-secondary);border:1px solid var(--border-primary);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px)}.locked-overlay[data-v-0bad694b]:before{content:"";position:absolute;width:400px;height:400px;border-radius:50%;top:50%;left:50%;transform:translate(-50%,-50%);opacity:.4;filter:blur(60px);pointer-events:none}.locked-claude[data-v-0bad694b]{background:linear-gradient(135deg,rgba(24,160,88,.03) 0%,var(--bg-secondary) 50%,rgba(24,160,88,.02) 100%)}.locked-claude[data-v-0bad694b]:before{background:radial-gradient(circle,rgba(24,160,88,.15) 0%,transparent 70%)}.locked-codex[data-v-0bad694b]{background:linear-gradient(135deg,rgba(59,130,246,.03) 0%,var(--bg-secondary) 50%,rgba(59,130,246,.02) 100%)}.locked-codex[data-v-0bad694b]:before{background:radial-gradient(circle,rgba(59,130,246,.15) 0%,transparent 70%)}.locked-gemini[data-v-0bad694b]{background:linear-gradient(135deg,rgba(168,85,247,.03) 0%,var(--bg-secondary) 50%,rgba(168,85,247,.02) 100%)}.locked-gemini[data-v-0bad694b]:before{background:radial-gradient(circle,rgba(168,85,247,.15) 0%,transparent 70%)}.locked-opencode[data-v-0bad694b]{background:linear-gradient(135deg,rgba(234,88,12,.03) 0%,var(--bg-secondary) 50%,rgba(234,88,12,.02) 100%)}.locked-opencode[data-v-0bad694b]:before{background:radial-gradient(circle,rgba(234,88,12,.15) 0%,transparent 70%)}.locked-content[data-v-0bad694b]{display:flex;flex-direction:column;align-items:center;gap:20px;padding:60px 40px;text-align:center;position:relative;z-index:1}.lock-icon[data-v-0bad694b]{width:96px;height:96px;display:flex;align-items:center;justify-content:center;border-radius:10px;background:var(--bg-primary);border:2px solid var(--border-primary);margin-bottom:8px;position:relative;box-shadow:0 8px 24px #0000001f;animation:lock-float-0bad694b 3s ease-in-out infinite}@keyframes lock-float-0bad694b{0%,to{transform:translateY(0)}50%{transform:translateY(-6px)}}.lock-icon[data-v-0bad694b]:before{content:"";position:absolute;top:-8px;right:-8px;bottom:-8px;left:-8px;border-radius:20px;opacity:.6;filter:blur(12px)}.locked-claude .lock-icon[data-v-0bad694b]{background:linear-gradient(135deg,rgba(24,160,88,.12) 0%,var(--bg-primary) 100%);border-color:#18a05840;box-shadow:0 8px 24px #18a05826,0 0 0 1px #18a0581a inset}.locked-claude .lock-icon[data-v-0bad694b]:before{background:radial-gradient(circle,rgba(24,160,88,.3) 0%,transparent 60%)}.locked-claude .lock-icon .n-icon[data-v-0bad694b]{color:#18a058;filter:drop-shadow(0 2px 4px rgba(24,160,88,.3))}.locked-codex .lock-icon[data-v-0bad694b]{background:linear-gradient(135deg,rgba(59,130,246,.12) 0%,var(--bg-primary) 100%);border-color:#3b82f640;box-shadow:0 8px 24px #3b82f626,0 0 0 1px #3b82f61a inset}.locked-codex .lock-icon[data-v-0bad694b]:before{background:radial-gradient(circle,rgba(59,130,246,.3) 0%,transparent 60%)}.locked-codex .lock-icon .n-icon[data-v-0bad694b]{color:#3b82f6;filter:drop-shadow(0 2px 4px rgba(59,130,246,.3))}.locked-gemini .lock-icon[data-v-0bad694b]{background:linear-gradient(135deg,rgba(168,85,247,.12) 0%,var(--bg-primary) 100%);border-color:#a855f740;box-shadow:0 8px 24px #a855f726,0 0 0 1px #a855f71a inset}.locked-gemini .lock-icon[data-v-0bad694b]:before{background:radial-gradient(circle,rgba(168,85,247,.3) 0%,transparent 60%)}.locked-gemini .lock-icon .n-icon[data-v-0bad694b]{color:#a855f7;filter:drop-shadow(0 2px 4px rgba(168,85,247,.3))}.locked-opencode .lock-icon[data-v-0bad694b]{background:linear-gradient(135deg,rgba(234,88,12,.12) 0%,var(--bg-primary) 100%);border-color:#ea580c40;box-shadow:0 8px 24px #ea580c26,0 0 0 1px #ea580c1a inset}.locked-opencode .lock-icon[data-v-0bad694b]:before{background:radial-gradient(circle,rgba(234,88,12,.3) 0%,transparent 60%)}.locked-opencode .lock-icon .n-icon[data-v-0bad694b]{color:#ea580c;filter:drop-shadow(0 2px 4px rgba(234,88,12,.3))}.lock-icon .n-icon[data-v-0bad694b]{opacity:.85;position:relative;z-index:1}.locked-title[data-v-0bad694b]{font-size:17px;font-weight:600;color:var(--text-color-1);margin:0;letter-spacing:.3px}.locked-hint[data-v-0bad694b]{font-size:13px;color:var(--text-color-3);max-width:220px;line-height:1.7;opacity:.9}.channel-status[data-v-0bad694b]{cursor:pointer;padding:4px 8px!important;border-radius:4px;transition:all .2s ease}.channel-status[data-v-0bad694b]:hover{background:var(--hover-bg)}.channel-quick-panel[data-v-0bad694b]{padding:4px 0}.panel-title[data-v-0bad694b]{display:flex;justify-content:space-between;align-items:center;padding:8px 12px 12px;border-bottom:1px solid var(--border-primary);margin-bottom:8px}.panel-title span[data-v-0bad694b]:first-child{font-size:13px;font-weight:600;color:var(--text-primary)}.no-channels[data-v-0bad694b]{padding:24px 12px;text-align:center}.channel-quick-list[data-v-0bad694b]{max-height:360px;overflow-y:auto}.channel-quick-item[data-v-0bad694b]{display:flex;flex-direction:column;gap:8px;padding:10px 12px;border-radius:6px;margin:0 4px 6px;background:var(--bg-secondary);transition:all .2s ease}.channel-quick-item[data-v-0bad694b]:hover{background:var(--hover-bg)}.channel-quick-item.disabled[data-v-0bad694b]{opacity:.6}.channel-quick-info[data-v-0bad694b]{display:flex;align-items:center;gap:8px;width:100%}.channel-quick-name[data-v-0bad694b]{font-size:13px;font-weight:600;color:var(--text-primary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.channel-metrics[data-v-0bad694b]{display:flex;gap:6px;width:100%}.channel-metrics .metric-item[data-v-0bad694b]{flex:1;display:flex;flex-direction:column;align-items:center;gap:2px;padding:6px 4px;background:var(--bg-primary);border-radius:4px;border:1px solid var(--border-primary)}.channel-metrics .metric-label[data-v-0bad694b]{font-size:10px;color:var(--text-tertiary);font-weight:500}.channel-metrics .metric-value[data-v-0bad694b]{font-size:12px;font-weight:700;color:var(--text-primary);font-family:SF Mono,Monaco,monospace}.channel-metrics .metric-value.active[data-v-0bad694b]{color:#f59e0b}.channel-switching-tip[data-v-0bad694b]{font-size:11px;color:var(--text-tertiary);padding:0 2px}.channel-quick-item.disabled .channel-metrics .metric-value[data-v-0bad694b]{color:var(--text-tertiary)}.channel-quick-list[data-v-0bad694b]::-webkit-scrollbar{width:4px}.channel-quick-list[data-v-0bad694b]::-webkit-scrollbar-thumb{background:#00000026;border-radius:2px}[data-theme=dark] .channel-quick-list[data-v-0bad694b]::-webkit-scrollbar-thumb{background:#ffffff26}@media (max-width: 1024px){.channel-column[data-v-0bad694b]{border-radius:6px}.card-header[data-v-0bad694b]{padding:8px 10px}.card-title[data-v-0bad694b]{font-size:13px}.stats-inline[data-v-0bad694b]{gap:6px}.stat-inline-item[data-v-0bad694b]{padding:8px}.stat-value[data-v-0bad694b]{font-size:14px}.stat-label[data-v-0bad694b]{font-size:9px}.logs-table-header[data-v-0bad694b]{padding:8px 10px;font-size:10px}.log-row[data-v-0bad694b]{padding:8px 10px;font-size:11px}}@media (max-width: 768px){.channel-column[data-v-0bad694b]{border-radius:6px;min-height:300px;height:auto;max-height:400px}.card-header[data-v-0bad694b]{padding:8px;gap:6px}.card-title[data-v-0bad694b]{font-size:12px}.stats-inline[data-v-0bad694b]{gap:4px}.stat-inline-item[data-v-0bad694b]{padding:6px;gap:6px}.stat-icon-dot[data-v-0bad694b]{width:6px;height:6px}.stat-value[data-v-0bad694b]{font-size:13px}.stat-label[data-v-0bad694b]{font-size:8px}.logs-card[data-v-0bad694b]{min-height:150px}.logs-table-header[data-v-0bad694b]{padding:6px 8px;font-size:9px}.log-row[data-v-0bad694b]{padding:6px 8px;font-size:10px;min-height:32px}.col-token[data-v-0bad694b]{font-size:10px;padding:2px 4px}.col-time[data-v-0bad694b]{font-size:9px}.mcp-count-tag[data-v-0bad694b],.skills-count-tag[data-v-0bad694b]{font-size:9px;padding:0 6px;height:18px;line-height:18px}.skills-button[data-v-0bad694b]{padding:4px 8px;font-size:10px;gap:4px}.lock-button[data-v-0bad694b]{width:26px;height:26px}}@media (max-width: 640px){.channel-column[data-v-0bad694b]{min-height:250px;max-height:350px}.card-header[data-v-0bad694b]{padding:6px;gap:4px;flex-wrap:wrap}.card-title[data-v-0bad694b]{font-size:11px}.stats-card .card-content[data-v-0bad694b]{padding:6px}.stats-inline[data-v-0bad694b]{gap:3px;flex-wrap:wrap}.stat-inline-item[data-v-0bad694b]{padding:5px;gap:4px;min-width:calc(33% - 3px);flex:1 1 auto}.stat-icon-dot[data-v-0bad694b]{width:5px;height:5px}.stat-value[data-v-0bad694b]{font-size:12px}.stat-label[data-v-0bad694b]{font-size:7px}.logs-card[data-v-0bad694b]{min-height:120px}.logs-table-header[data-v-0bad694b]{padding:5px 6px;font-size:8px;letter-spacing:.3px}.log-row[data-v-0bad694b]{padding:5px 6px;font-size:9px;min-height:28px}.col-channel .n-tag[data-v-0bad694b]{font-size:9px;padding:2px 6px}.col-token[data-v-0bad694b]{font-size:9px;padding:2px 3px;margin:0 1px}.col-time[data-v-0bad694b]{font-size:8px}.claude-extra-area[data-v-0bad694b]{gap:4px}.mcp-count-tag[data-v-0bad694b],.skills-count-tag[data-v-0bad694b]{font-size:8px;padding:0 4px;height:16px;line-height:16px}.skills-button[data-v-0bad694b]{padding:3px 6px;font-size:9px;gap:3px}.skills-button .n-icon[data-v-0bad694b]{font-size:12px!important}.lock-button[data-v-0bad694b]{width:24px;height:24px;margin-left:4px}.lock-button .n-icon[data-v-0bad694b]{font-size:12px!important}.locked-content[data-v-0bad694b]{padding:30px 20px;gap:12px}.lock-icon[data-v-0bad694b]{width:64px;height:64px}.lock-icon .n-icon[data-v-0bad694b]{font-size:28px!important}.lock-title[data-v-0bad694b]{font-size:14px}.lock-subtitle[data-v-0bad694b]{font-size:10px}.unlock-btn[data-v-0bad694b]{padding:8px 16px;font-size:11px}}@media (max-width: 480px){.channel-column[data-v-0bad694b]{min-height:220px;max-height:300px}.card-header[data-v-0bad694b]{padding:5px}.card-title[data-v-0bad694b]{font-size:10px}.stats-inline[data-v-0bad694b]{gap:2px}.stat-inline-item[data-v-0bad694b]{padding:4px;gap:3px}.stat-value[data-v-0bad694b]{font-size:11px}.stat-label[data-v-0bad694b]{font-size:6px}.logs-table-header[data-v-0bad694b]{padding:4px 5px;font-size:7px}.log-row[data-v-0bad694b]{padding:4px 5px;font-size:8px;min-height:24px}.col-token[data-v-0bad694b]{font-size:8px}.col-time[data-v-0bad694b]{font-size:7px}}.card[data-v-0bad694b]:not(.logs-card) .n-collapse{background:transparent}.card[data-v-0bad694b]:not(.logs-card) .n-collapse-item{background:transparent}.card[data-v-0bad694b] .n-collapse-item__header{padding:12px 16px;background:transparent;transition:all .3s ease}.card[data-v-0bad694b] .n-collapse-item__header:hover{background:#00000005}.card[data-v-0bad694b] .n-collapse-item__header-main{width:100%}.card[data-v-0bad694b]:not(.logs-card) .n-collapse-item__content-wrapper{padding:0 16px 12px}.card[data-v-0bad694b]:not(.logs-card) .n-collapse-item__content-inner{padding-top:0}.chart-card[data-v-0bad694b]:has(.n-collapse){padding:0}.dashboard-container[data-v-06100f0f]{height:100%;background:var(--bg-primary);overflow:hidden;padding:12px;box-sizing:border-box;display:flex;flex-direction:column}.dashboard-toolbar[data-v-06100f0f]{display:flex;justify-content:flex-end;margin-bottom:8px;flex-shrink:0}.analytics-link[data-v-06100f0f]{display:inline-flex;align-items:center;gap:5px;font-size:12px;color:var(--primary-color, #18a058);text-decoration:none;padding:4px 10px;border-radius:4px;background:#18a05814;transition:background .2s}.analytics-link[data-v-06100f0f]:hover{background:#18a05826}.analytics-link-icon[data-v-06100f0f]{font-size:14px}.dashboard-grid[data-v-06100f0f]{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;flex:1;min-height:0;overflow:hidden;box-sizing:border-box}.dashboard-grid[data-v-06100f0f] .sortable-ghost{opacity:.4}.dashboard-grid[data-v-06100f0f] .sortable-chosen{box-shadow:0 8px 24px #00000026}@media (max-width: 1600px){.dashboard-grid[data-v-06100f0f]{grid-template-columns:repeat(3,1fr)}}@media (max-width: 1200px){.dashboard-grid[data-v-06100f0f]{grid-template-columns:repeat(2,1fr)}}@media (max-width: 1024px){.dashboard-container[data-v-06100f0f]{padding:10px}.dashboard-grid[data-v-06100f0f]{gap:10px}}@media (max-width: 900px){.dashboard-grid[data-v-06100f0f]{grid-template-columns:1fr}}@media (max-width: 768px){.dashboard-container[data-v-06100f0f]{padding:8px;overflow-y:auto}.dashboard-grid[data-v-06100f0f]{gap:8px;overflow:visible;min-height:auto}}@media (max-width: 640px){.dashboard-container[data-v-06100f0f]{padding:6px}.dashboard-grid[data-v-06100f0f]{gap:6px}}@media (max-width: 480px){.dashboard-container[data-v-06100f0f]{padding:4px}.dashboard-grid[data-v-06100f0f]{gap:4px}}
|