dsh-token-pulse 0.4.1 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/client.js +413 -125
- package/lib/client.js.map +1 -1
- package/lib/index.mjs +12 -2
- package/lib/index.mjs.map +1 -1
- package/package.json +2 -2
package/lib/client.js
CHANGED
|
@@ -1106,8 +1106,10 @@ window.__ModuleLoader__.load({
|
|
|
1106
1106
|
if (n >= 1e3) return (n / 1e3).toFixed(2) + "k";
|
|
1107
1107
|
return n.toLocaleString();
|
|
1108
1108
|
}
|
|
1109
|
-
function ModelTop5({ t, topModels, topProviders, days, mode = "model", onModeChange }) {
|
|
1109
|
+
function ModelTop5({ t, topModels, topProviders, days, mode = "model", onModeChange, othersModelTokens, othersProviderTokens }) {
|
|
1110
1110
|
const active = mode === "provider" ? topProviders : topModels;
|
|
1111
|
+
const othersTokens = mode === "provider" ? othersProviderTokens : othersModelTokens;
|
|
1112
|
+
const hasOthers = (othersTokens ?? 0) > 0;
|
|
1111
1113
|
if (active.length === 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1112
1114
|
style: {
|
|
1113
1115
|
border: "1px solid var(--dsw-alias-border-l2)",
|
|
@@ -1193,12 +1195,12 @@ window.__ModuleLoader__.load({
|
|
|
1193
1195
|
children: t("view.provider")
|
|
1194
1196
|
})]
|
|
1195
1197
|
}) : null]
|
|
1196
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.
|
|
1198
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1197
1199
|
style: {
|
|
1198
1200
|
display: "flex",
|
|
1199
1201
|
flexDirection: "column"
|
|
1200
1202
|
},
|
|
1201
|
-
children: active.map((item, idx) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1203
|
+
children: [active.map((item, idx) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1202
1204
|
style: {
|
|
1203
1205
|
display: "flex",
|
|
1204
1206
|
alignItems: "center",
|
|
@@ -1254,7 +1256,63 @@ window.__ModuleLoader__.load({
|
|
|
1254
1256
|
children: formatTokens(item.tokens)
|
|
1255
1257
|
})
|
|
1256
1258
|
]
|
|
1257
|
-
}, item.name))
|
|
1259
|
+
}, item.name)), hasOthers ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1260
|
+
style: {
|
|
1261
|
+
display: "flex",
|
|
1262
|
+
alignItems: "center",
|
|
1263
|
+
gap: 12,
|
|
1264
|
+
padding: "10px 16px",
|
|
1265
|
+
borderTop: "1px solid var(--dsw-alias-border-l2)",
|
|
1266
|
+
opacity: .75
|
|
1267
|
+
},
|
|
1268
|
+
children: [
|
|
1269
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1270
|
+
style: {
|
|
1271
|
+
width: 20,
|
|
1272
|
+
textAlign: "center",
|
|
1273
|
+
fontSize: 12,
|
|
1274
|
+
color: "var(--dsw-alias-label-tertiary)"
|
|
1275
|
+
},
|
|
1276
|
+
children: "…"
|
|
1277
|
+
}),
|
|
1278
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1279
|
+
style: {
|
|
1280
|
+
flex: "0 1 40%",
|
|
1281
|
+
minWidth: 0,
|
|
1282
|
+
fontSize: 13,
|
|
1283
|
+
color: "var(--dsw-alias-label-secondary)",
|
|
1284
|
+
overflow: "hidden",
|
|
1285
|
+
textOverflow: "ellipsis",
|
|
1286
|
+
whiteSpace: "nowrap"
|
|
1287
|
+
},
|
|
1288
|
+
title: t("model.others"),
|
|
1289
|
+
children: t("model.others")
|
|
1290
|
+
}),
|
|
1291
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1292
|
+
style: {
|
|
1293
|
+
flex: 1,
|
|
1294
|
+
height: 6,
|
|
1295
|
+
borderRadius: 3,
|
|
1296
|
+
background: "var(--dsw-alias-bg-layer-2, #ebedf0)",
|
|
1297
|
+
overflow: "hidden"
|
|
1298
|
+
},
|
|
1299
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { style: {
|
|
1300
|
+
width: `${Math.min(100, Math.max(4, (othersTokens ?? 0) / max * 100))}%`,
|
|
1301
|
+
height: "100%",
|
|
1302
|
+
background: "#9aa3ad",
|
|
1303
|
+
borderRadius: 3
|
|
1304
|
+
} })
|
|
1305
|
+
}),
|
|
1306
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1307
|
+
style: {
|
|
1308
|
+
fontSize: 12,
|
|
1309
|
+
color: "var(--dsw-alias-label-secondary)",
|
|
1310
|
+
whiteSpace: "nowrap"
|
|
1311
|
+
},
|
|
1312
|
+
children: formatTokens(othersTokens ?? 0)
|
|
1313
|
+
})
|
|
1314
|
+
]
|
|
1315
|
+
}) : null]
|
|
1258
1316
|
})]
|
|
1259
1317
|
})
|
|
1260
1318
|
});
|
|
@@ -1283,6 +1341,22 @@ window.__ModuleLoader__.load({
|
|
|
1283
1341
|
return totals;
|
|
1284
1342
|
}
|
|
1285
1343
|
const byTotalDesc = (a, b) => b[1] - a[1] || (a[0] < b[0] ? -1 : 1);
|
|
1344
|
+
/** Synthetic series key for "all other models combined". */
|
|
1345
|
+
const OTHER_KEY = "__other__";
|
|
1346
|
+
/** Sum of every model NOT in `exclude`, per day, aligned to `dayKeys`. */
|
|
1347
|
+
function buildOtherSeries(days, dayKeys, exclude) {
|
|
1348
|
+
const index = new Map(dayKeys.map((k, i) => [k, i]));
|
|
1349
|
+
const points = new Array(dayKeys.length).fill(0);
|
|
1350
|
+
for (const d of days) {
|
|
1351
|
+
const i = index.get(d.dayKey);
|
|
1352
|
+
if (i === void 0) continue;
|
|
1353
|
+
for (const [m, v] of d.byModel) if (!exclude.has(m)) points[i] += v;
|
|
1354
|
+
}
|
|
1355
|
+
return {
|
|
1356
|
+
model: OTHER_KEY,
|
|
1357
|
+
points
|
|
1358
|
+
};
|
|
1359
|
+
}
|
|
1286
1360
|
/** Sum each model's tokens over `dayKeys`, return up to `k` names by total desc. */
|
|
1287
1361
|
function pickTopModels(days, dayKeys, k = 5) {
|
|
1288
1362
|
return [...modelTotals(days, dayKeys).entries()].sort(byTotalDesc).slice(0, k).map(([model]) => model);
|
|
@@ -1363,12 +1437,33 @@ window.__ModuleLoader__.load({
|
|
|
1363
1437
|
return segs;
|
|
1364
1438
|
}
|
|
1365
1439
|
const fmtNum = (v) => Math.round(v * 100) / 100;
|
|
1366
|
-
/**
|
|
1367
|
-
|
|
1440
|
+
/**
|
|
1441
|
+
* Same curve as seriesPath, but resampled at `samples` evenly spaced x
|
|
1442
|
+
* positions into a FIXED-structure polyline (M + samples L). Every path then
|
|
1443
|
+
* has an identical command count no matter how many days it represents, so
|
|
1444
|
+
* CSS `transition: d` can morph lines across range switches too — where raw
|
|
1445
|
+
* bezier paths (M + n-1 C) have mismatched structures and would jump.
|
|
1446
|
+
* Visual output is indistinguishable at this density.
|
|
1447
|
+
*/
|
|
1448
|
+
function seriesPathFixed(points, samples = 89) {
|
|
1368
1449
|
if (points.length === 0) return "";
|
|
1369
|
-
const
|
|
1450
|
+
const x0 = points[0][0];
|
|
1451
|
+
const x1 = points[points.length - 1][0];
|
|
1452
|
+
const head = `M ${fmtNum(x0)} ${fmtNum(points[0][1])}`;
|
|
1370
1453
|
if (points.length === 1) return head;
|
|
1371
|
-
|
|
1454
|
+
const segs = monotoneSegments(points);
|
|
1455
|
+
let out = "";
|
|
1456
|
+
let seg = 0;
|
|
1457
|
+
for (let s = 1; s <= samples; s++) {
|
|
1458
|
+
const x = x0 + (x1 - x0) * s / samples;
|
|
1459
|
+
while (seg < segs.length - 1 && x > segs[seg].p1[0]) seg++;
|
|
1460
|
+
const g = segs[seg];
|
|
1461
|
+
const t = g.p1[0] === g.p0[0] ? 0 : Math.min(1, Math.max(0, (x - g.p0[0]) / (g.p1[0] - g.p0[0])));
|
|
1462
|
+
const mt = 1 - t;
|
|
1463
|
+
const y = mt * mt * mt * g.p0[1] + 3 * mt * mt * t * g.c1[1] + 3 * mt * t * t * g.c2[1] + t * t * t * g.p1[1];
|
|
1464
|
+
out += ` L ${fmtNum(x)} ${fmtNum(y)}`;
|
|
1465
|
+
}
|
|
1466
|
+
return head + out;
|
|
1372
1467
|
}
|
|
1373
1468
|
/** Compact token count for axis labels (same semantics as the heatmap). */
|
|
1374
1469
|
function formatTokensShort(n) {
|
|
@@ -1398,6 +1493,11 @@ window.__ModuleLoader__.load({
|
|
|
1398
1493
|
const unit = Math.pow(10, Math.floor(Math.log10(v))) / 2;
|
|
1399
1494
|
return Math.ceil(v / unit) * unit;
|
|
1400
1495
|
}
|
|
1496
|
+
/** "z-ai/glm-5.3" -> "glm-5.3": display only the part after the last "/". */
|
|
1497
|
+
function shortName(model) {
|
|
1498
|
+
const i = model.lastIndexOf("/");
|
|
1499
|
+
return i >= 0 ? model.slice(i + 1) : model;
|
|
1500
|
+
}
|
|
1401
1501
|
const W = 720;
|
|
1402
1502
|
const H = 280;
|
|
1403
1503
|
const PAD = {
|
|
@@ -1408,37 +1508,96 @@ window.__ModuleLoader__.load({
|
|
|
1408
1508
|
};
|
|
1409
1509
|
/** chart container horizontal padding (must match the div below) */
|
|
1410
1510
|
const CX_PAD = 8;
|
|
1511
|
+
const LS_TYPE_KEY = "dsh-token-pulse:trendChartType";
|
|
1512
|
+
/** transition/animation duration for the value/axis morphs (one tempo everywhere) */
|
|
1513
|
+
const DUR = ".55s";
|
|
1514
|
+
const T_POS = `x ${DUR} ease, y ${DUR} ease, width ${DUR} ease, height ${DUR} ease`;
|
|
1515
|
+
const T_D = `d ${DUR} ease`;
|
|
1516
|
+
const T_POINT = `cx ${DUR} ease, cy ${DUR} ease, r .2s ease`;
|
|
1517
|
+
const T_FADE = `opacity ${DUR} ease`;
|
|
1518
|
+
const T_SHIFT = `transform ${DUR} ease`;
|
|
1519
|
+
const GHOST_MS = 650;
|
|
1520
|
+
/**
|
|
1521
|
+
* Stable color assignment per MODEL (not per rank): the same model keeps its
|
|
1522
|
+
* color across range switches and legend toggles, so the animation never
|
|
1523
|
+
* repaints a series. First-seen order locks the palette slot for good.
|
|
1524
|
+
* Module-level so the assignment survives closing/reopening the panel.
|
|
1525
|
+
*/
|
|
1526
|
+
const modelColorSlot = /* @__PURE__ */ new Map();
|
|
1527
|
+
function colorFor(model) {
|
|
1528
|
+
let slot = modelColorSlot.get(model);
|
|
1529
|
+
if (slot === void 0) {
|
|
1530
|
+
slot = modelColorSlot.size % COLORS.length;
|
|
1531
|
+
modelColorSlot.set(model, slot);
|
|
1532
|
+
}
|
|
1533
|
+
return COLORS[slot];
|
|
1534
|
+
}
|
|
1535
|
+
/**
|
|
1536
|
+
* Animation styles. Elements that ENTER on a range/type switch (CSS transitions
|
|
1537
|
+
* only animate elements that already existed) play a keyframe in the same 0.55s
|
|
1538
|
+
* tempo as the gliding elements, and elements that LEAVE stay mounted for one
|
|
1539
|
+
* beat as a "ghost" layer that collapses/fades — so the whole chart moves
|
|
1540
|
+
* together instead of mixing instant pops with in-flight marks.
|
|
1541
|
+
* The enter classes self-remove on animationend so live styles (hover opacity,
|
|
1542
|
+
* legend toggles) keep working afterwards.
|
|
1543
|
+
*/
|
|
1544
|
+
const ANIM_CSS = `
|
|
1545
|
+
@keyframes dtp-grow { from { transform: scaleY(.02); opacity: .3; } }
|
|
1546
|
+
@keyframes dtp-fadein { from { opacity: 0; } }
|
|
1547
|
+
@keyframes dtp-shrink { to { transform: scaleY(.02); opacity: 0; } }
|
|
1548
|
+
@keyframes dtp-fadeout { to { opacity: 0; } }
|
|
1549
|
+
.dtp-growin { transform-box: fill-box; transform-origin: bottom; animation: dtp-grow ${DUR} ease; }
|
|
1550
|
+
.dtp-fadein { animation: dtp-fadein ${DUR} ease; }
|
|
1551
|
+
.dtp-ghost-bars { transform-box: fill-box; transform-origin: bottom; animation: dtp-shrink ${DUR} ease forwards; }
|
|
1552
|
+
.dtp-ghost-lines { animation: dtp-fadeout ${DUR} ease forwards; }
|
|
1553
|
+
`;
|
|
1411
1554
|
/**
|
|
1412
|
-
*
|
|
1413
|
-
*
|
|
1414
|
-
*
|
|
1415
|
-
*
|
|
1416
|
-
*
|
|
1555
|
+
* Usage chart at the bottom of the settings section, two switchable forms:
|
|
1556
|
+
* - "line": one smoothed (monotone cubic) line per model over the days
|
|
1557
|
+
* - "bar": one stacked bar per day, visible models layered bottom-up
|
|
1558
|
+
* Both share the legend (top-10 + week's top-3, chips toggle models), the
|
|
1559
|
+
* 7 / 30 / 90-day ranges and a compact translucent hover tooltip BESIDE the
|
|
1560
|
+
* cursor (date, day total, per-model totals sorted desc, zero rows hidden).
|
|
1561
|
+
*
|
|
1562
|
+
* Range/type switches animate as ONE coordinated beat: retained marks glide
|
|
1563
|
+
* by date identity (the last 7 days of a 30-day view sit in the rightmost
|
|
1564
|
+
* slots, so 7→30 slides the week's bars right+down), entering marks grow from
|
|
1565
|
+
* the baseline / fade in, and leaving marks collapse in a ghost overlay.
|
|
1566
|
+
* Model colors are locked to model identity, never to rank position.
|
|
1567
|
+
*
|
|
1417
1568
|
* Pure SVG + absolutely-positioned HTML tooltip, no dependencies.
|
|
1418
1569
|
*/
|
|
1419
1570
|
function TrendChart({ t, days, isEn = false }) {
|
|
1420
1571
|
const [rangeDays, setRangeDays] = react.useState(7);
|
|
1572
|
+
const [chartType, setChartType] = react.useState(() => {
|
|
1573
|
+
try {
|
|
1574
|
+
return localStorage.getItem(LS_TYPE_KEY) === "line" ? "line" : "bar";
|
|
1575
|
+
} catch {
|
|
1576
|
+
return "bar";
|
|
1577
|
+
}
|
|
1578
|
+
});
|
|
1421
1579
|
const [hidden, setHidden] = react.useState(/* @__PURE__ */ new Set());
|
|
1422
1580
|
/** hovered column index + pointer position in wrapper px (for side placement) */
|
|
1423
1581
|
const [hover, setHover] = react.useState(null);
|
|
1582
|
+
/** one-beat overlay of the PREVIOUS layout's marks, collapsing out on switches */
|
|
1583
|
+
const [ghost, setGhost] = react.useState(null);
|
|
1424
1584
|
const endKey = toDayKey(Date.now());
|
|
1425
1585
|
const dayKeys = react.useMemo(() => recentDayKeys(endKey, rangeDays), [endKey, rangeDays]);
|
|
1426
1586
|
const weekKeys = react.useMemo(() => recentDayKeys(endKey, 7), [endKey]);
|
|
1427
|
-
const topModels = react.useMemo(() => pickTrendModels(days, dayKeys, weekKeys,
|
|
1587
|
+
const topModels = react.useMemo(() => pickTrendModels(days, dayKeys, weekKeys, 9, 3), [
|
|
1428
1588
|
days,
|
|
1429
1589
|
dayKeys,
|
|
1430
1590
|
weekKeys
|
|
1431
1591
|
]);
|
|
1432
|
-
const series = react.useMemo(() =>
|
|
1592
|
+
const series = react.useMemo(() => {
|
|
1593
|
+
const base = buildSeries(days, dayKeys, topModels);
|
|
1594
|
+
const other = buildOtherSeries(days, dayKeys, new Set(topModels));
|
|
1595
|
+
return other.points.some((v) => v > 0) ? [...base, other] : base;
|
|
1596
|
+
}, [
|
|
1433
1597
|
days,
|
|
1434
1598
|
dayKeys,
|
|
1435
1599
|
topModels
|
|
1436
1600
|
]);
|
|
1437
|
-
const totalsByModel = react.useMemo(() => {
|
|
1438
|
-
const m = /* @__PURE__ */ new Map();
|
|
1439
|
-
for (const s of series) m.set(s.model, s.points.reduce((a, b) => a + b, 0));
|
|
1440
|
-
return m;
|
|
1441
|
-
}, [series]);
|
|
1442
1601
|
const wrapRef = react.useRef(null);
|
|
1443
1602
|
const [wrap, setWrap] = react.useState({
|
|
1444
1603
|
w: 0,
|
|
@@ -1456,6 +1615,12 @@ window.__ModuleLoader__.load({
|
|
|
1456
1615
|
ro.observe(el);
|
|
1457
1616
|
return () => ro.disconnect();
|
|
1458
1617
|
}, []);
|
|
1618
|
+
const prevDaysRef = react.useRef(null);
|
|
1619
|
+
const prevModelsRef = react.useRef(null);
|
|
1620
|
+
const prevTickRef = react.useRef(null);
|
|
1621
|
+
const prevTypeRef = react.useRef(null);
|
|
1622
|
+
const marksRef = react.useRef(null);
|
|
1623
|
+
const labelsRef = react.useRef(null);
|
|
1459
1624
|
const toggle = (model) => {
|
|
1460
1625
|
setHidden((prev) => {
|
|
1461
1626
|
const next = new Set(prev);
|
|
@@ -1464,18 +1629,62 @@ window.__ModuleLoader__.load({
|
|
|
1464
1629
|
return next;
|
|
1465
1630
|
});
|
|
1466
1631
|
};
|
|
1467
|
-
const colorOf = (model) =>
|
|
1468
|
-
|
|
1632
|
+
const colorOf = (model) => model === "__other__" ? "#8b949e" : colorFor(model);
|
|
1633
|
+
/** 其它 series 用固定中性灰;显示名:其它系列显示「其它/Other」,模型只显示 "/" 后半段 */
|
|
1634
|
+
const dispName = (model) => model === "__other__" ? t("model.others") : shortName(model);
|
|
1635
|
+
const isHidden = (model) => hidden.has(model);
|
|
1636
|
+
const visible = series.filter((s) => !isHidden(s.model));
|
|
1469
1637
|
const plotW = W - PAD.left - PAD.right;
|
|
1470
1638
|
const plotH = H - PAD.top - PAD.bottom;
|
|
1471
|
-
const yMax = niceCeil(Math.max(1, ...visible.map((s) => Math.max(0, ...s.points))));
|
|
1472
1639
|
const step = dayKeys.length > 1 ? plotW / (dayKeys.length - 1) : plotW;
|
|
1473
1640
|
const xOf = (i) => PAD.left + (dayKeys.length > 1 ? i * step : plotW / 2);
|
|
1641
|
+
const slot = plotW / dayKeys.length;
|
|
1642
|
+
const barW = Math.max(2, slot * .6);
|
|
1643
|
+
const cxOf = (i) => PAD.left + slot * (i + .5);
|
|
1644
|
+
const anchorX = (i) => chartType === "bar" ? cxOf(i) : xOf(i);
|
|
1645
|
+
const dayTotals = dayKeys.map((_, i) => visible.reduce((a, s) => a + s.points[i], 0));
|
|
1646
|
+
const yMax = chartType === "bar" ? niceCeil(Math.max(1, ...dayTotals)) : niceCeil(Math.max(1, ...visible.map((s) => Math.max(0, ...s.points))));
|
|
1474
1647
|
const yOf = (v) => PAD.top + (1 - v / yMax) * plotH;
|
|
1475
1648
|
const fmtDay = (k) => `${Number(k.slice(5, 7))}/${Number(k.slice(8, 10))}`;
|
|
1476
1649
|
const fmtDayLong = (k) => isEn ? `${Number(k.slice(5, 7))}/${Number(k.slice(8, 10))}` : `${Number(k.slice(5, 7))}月${Number(k.slice(8, 10))}日`;
|
|
1477
1650
|
const tickEvery = rangeDays === 7 ? 1 : rangeDays === 30 ? 5 : 10;
|
|
1478
1651
|
const tickIdx = dayKeys.map((_, i) => i).filter((i) => i % tickEvery === 0 || i === dayKeys.length - 1);
|
|
1652
|
+
const typeChanged = prevTypeRef.current !== null && prevTypeRef.current !== chartType;
|
|
1653
|
+
const isNewDay = (k) => prevDaysRef.current !== null && (typeChanged || !prevDaysRef.current.includes(k));
|
|
1654
|
+
const isNewModel = (m) => prevModelsRef.current !== null && (typeChanged || !prevModelsRef.current.includes(m));
|
|
1655
|
+
const isNewTick = (k) => prevTickRef.current !== null && (typeChanged || !prevTickRef.current.includes(k));
|
|
1656
|
+
const enterCls = (isNew, grow) => isNew ? grow ? "dtp-growin" : "dtp-fadein" : void 0;
|
|
1657
|
+
const onAnimEnd = (e) => {
|
|
1658
|
+
e.currentTarget.classList.remove("dtp-growin", "dtp-fadein");
|
|
1659
|
+
};
|
|
1660
|
+
react.useEffect(() => {
|
|
1661
|
+
marksRef.current = marksNode;
|
|
1662
|
+
labelsRef.current = labelsNode;
|
|
1663
|
+
prevDaysRef.current = dayKeys;
|
|
1664
|
+
prevModelsRef.current = series.map((s) => s.model);
|
|
1665
|
+
prevTickRef.current = tickIdx.map((i) => dayKeys[i]);
|
|
1666
|
+
prevTypeRef.current = chartType;
|
|
1667
|
+
});
|
|
1668
|
+
react.useEffect(() => {
|
|
1669
|
+
if (!ghost) return;
|
|
1670
|
+
const timer = setTimeout(() => setGhost(null), GHOST_MS);
|
|
1671
|
+
return () => clearTimeout(timer);
|
|
1672
|
+
}, [ghost]);
|
|
1673
|
+
const switchTo = (patch) => {
|
|
1674
|
+
setGhost({
|
|
1675
|
+
marks: marksRef.current,
|
|
1676
|
+
labels: labelsRef.current,
|
|
1677
|
+
type: chartType
|
|
1678
|
+
});
|
|
1679
|
+
if (patch.range !== void 0) setRangeDays(patch.range);
|
|
1680
|
+
if (patch.type !== void 0) {
|
|
1681
|
+
setChartType(patch.type);
|
|
1682
|
+
try {
|
|
1683
|
+
localStorage.setItem(LS_TYPE_KEY, patch.type);
|
|
1684
|
+
} catch {}
|
|
1685
|
+
}
|
|
1686
|
+
setHover(null);
|
|
1687
|
+
};
|
|
1479
1688
|
const onMove = (e) => {
|
|
1480
1689
|
const wrapEl = wrapRef.current;
|
|
1481
1690
|
if (!wrapEl) return;
|
|
@@ -1483,7 +1692,7 @@ window.__ModuleLoader__.load({
|
|
|
1483
1692
|
const svgRect = e.currentTarget.getBoundingClientRect();
|
|
1484
1693
|
const scale = svgRect.width / W || 1;
|
|
1485
1694
|
const xvb = (e.clientX - svgRect.left) / scale;
|
|
1486
|
-
const i = Math.round((xvb - PAD.left) / step);
|
|
1695
|
+
const i = chartType === "bar" ? Math.floor((xvb - PAD.left) / slot) : Math.round((xvb - PAD.left) / step);
|
|
1487
1696
|
setHover({
|
|
1488
1697
|
idx: Math.max(0, Math.min(dayKeys.length - 1, i)),
|
|
1489
1698
|
px: e.clientX - wrapRect.left,
|
|
@@ -1500,7 +1709,7 @@ window.__ModuleLoader__.load({
|
|
|
1500
1709
|
const tipH = 56 + hoverRows.length * 19;
|
|
1501
1710
|
const gap = 14;
|
|
1502
1711
|
const svgW = Math.max(0, wrap.w - 16);
|
|
1503
|
-
const colPx = hover == null ? 0 : CX_PAD +
|
|
1712
|
+
const colPx = hover == null ? 0 : CX_PAD + anchorX(hover.idx) / W * svgW;
|
|
1504
1713
|
let tipLeft = hover == null ? 0 : colPx + gap;
|
|
1505
1714
|
if (hover != null && tipLeft + tipW > wrap.w - 4) tipLeft = colPx - gap - tipW;
|
|
1506
1715
|
tipLeft = Math.max(4, tipLeft);
|
|
@@ -1514,6 +1723,81 @@ window.__ModuleLoader__.load({
|
|
|
1514
1723
|
background: "var(--dsw-alias-bg-layer-3)",
|
|
1515
1724
|
color: "var(--dsw-alias-label-secondary)"
|
|
1516
1725
|
};
|
|
1726
|
+
const segBtn = (active) => ({
|
|
1727
|
+
...segStyle,
|
|
1728
|
+
background: active ? "var(--dsw-alias-label-primary)" : "transparent",
|
|
1729
|
+
color: active ? "var(--dsw-alias-bg-layer-3)" : "var(--dsw-alias-label-secondary)",
|
|
1730
|
+
transition: "background .25s ease, color .25s ease"
|
|
1731
|
+
});
|
|
1732
|
+
const labelsNode = /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: tickIdx.map((i) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("text", {
|
|
1733
|
+
x: 0,
|
|
1734
|
+
y: 0,
|
|
1735
|
+
textAnchor: "middle",
|
|
1736
|
+
fontSize: 14,
|
|
1737
|
+
fill: "var(--dsw-alias-label-tertiary)",
|
|
1738
|
+
className: enterCls(isNewTick(dayKeys[i]), false),
|
|
1739
|
+
onAnimationEnd: onAnimEnd,
|
|
1740
|
+
style: {
|
|
1741
|
+
transform: `translate(${anchorX(i)}px, 270px)`,
|
|
1742
|
+
transition: T_SHIFT
|
|
1743
|
+
},
|
|
1744
|
+
children: fmtDay(dayKeys[i])
|
|
1745
|
+
}, dayKeys[i])) });
|
|
1746
|
+
const marksNode = chartType === "bar" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: dayKeys.map((k, i) => {
|
|
1747
|
+
const dayNew = isNewDay(k);
|
|
1748
|
+
let cum = 0;
|
|
1749
|
+
const segs = series.map((s) => {
|
|
1750
|
+
const v = isHidden(s.model) ? 0 : s.points[i];
|
|
1751
|
+
const yTop = yOf(cum + v);
|
|
1752
|
+
const h = v > 0 ? Math.max(.5, yOf(cum) - yTop) : 0;
|
|
1753
|
+
cum += v;
|
|
1754
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
|
|
1755
|
+
x: cxOf(i) - barW / 2,
|
|
1756
|
+
y: yTop,
|
|
1757
|
+
width: barW,
|
|
1758
|
+
height: h,
|
|
1759
|
+
fill: colorOf(s.model),
|
|
1760
|
+
className: enterCls(!dayNew && isNewModel(s.model), true),
|
|
1761
|
+
onAnimationEnd: onAnimEnd,
|
|
1762
|
+
style: { transition: T_POS }
|
|
1763
|
+
}, s.model);
|
|
1764
|
+
});
|
|
1765
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("g", {
|
|
1766
|
+
opacity: hover?.idx === i ? 1 : .8,
|
|
1767
|
+
className: enterCls(dayNew, true),
|
|
1768
|
+
onAnimationEnd: onAnimEnd,
|
|
1769
|
+
style: { transition: T_FADE },
|
|
1770
|
+
children: segs
|
|
1771
|
+
}, k);
|
|
1772
|
+
}) }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: series.map((s) => {
|
|
1773
|
+
const color = colorOf(s.model);
|
|
1774
|
+
const off = isHidden(s.model);
|
|
1775
|
+
const modelNew = isNewModel(s.model);
|
|
1776
|
+
const pts = s.points.map((v, i) => [xOf(i), yOf(v)]);
|
|
1777
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("g", {
|
|
1778
|
+
opacity: off ? 0 : .8,
|
|
1779
|
+
className: enterCls(modelNew, false),
|
|
1780
|
+
onAnimationEnd: onAnimEnd,
|
|
1781
|
+
style: { transition: T_FADE },
|
|
1782
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
1783
|
+
d: seriesPathFixed(pts),
|
|
1784
|
+
fill: "none",
|
|
1785
|
+
stroke: color,
|
|
1786
|
+
strokeWidth: 2,
|
|
1787
|
+
strokeLinecap: "round",
|
|
1788
|
+
strokeLinejoin: "round",
|
|
1789
|
+
style: { transition: T_D }
|
|
1790
|
+
}), pts.map(([x, y], i) => s.points[i] > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
1791
|
+
cx: x,
|
|
1792
|
+
cy: y,
|
|
1793
|
+
r: hover?.idx === i ? 4 : 2,
|
|
1794
|
+
fill: color,
|
|
1795
|
+
className: enterCls(!modelNew && isNewDay(dayKeys[i]), false),
|
|
1796
|
+
onAnimationEnd: onAnimEnd,
|
|
1797
|
+
style: { transition: T_POINT }
|
|
1798
|
+
}, dayKeys[i]) : null)]
|
|
1799
|
+
}, s.model);
|
|
1800
|
+
}) });
|
|
1517
1801
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1518
1802
|
style: {
|
|
1519
1803
|
border: "1px solid var(--dsw-alias-border-l2)",
|
|
@@ -1522,6 +1806,7 @@ window.__ModuleLoader__.load({
|
|
|
1522
1806
|
overflow: "hidden"
|
|
1523
1807
|
},
|
|
1524
1808
|
children: [
|
|
1809
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("style", { children: ANIM_CSS }),
|
|
1525
1810
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1526
1811
|
style: {
|
|
1527
1812
|
padding: "12px 16px 8px",
|
|
@@ -1551,29 +1836,40 @@ window.__ModuleLoader__.load({
|
|
|
1551
1836
|
},
|
|
1552
1837
|
children: t("trend.hint")
|
|
1553
1838
|
})]
|
|
1554
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.
|
|
1839
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1555
1840
|
style: {
|
|
1556
|
-
display: "
|
|
1557
|
-
|
|
1558
|
-
borderRadius: 6,
|
|
1559
|
-
overflow: "hidden"
|
|
1841
|
+
display: "flex",
|
|
1842
|
+
gap: 8
|
|
1560
1843
|
},
|
|
1561
|
-
children: [
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
setRangeDays(n);
|
|
1568
|
-
setHover(null);
|
|
1844
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1845
|
+
style: {
|
|
1846
|
+
display: "inline-flex",
|
|
1847
|
+
border: "1px solid var(--dsw-alias-border-l2)",
|
|
1848
|
+
borderRadius: 6,
|
|
1849
|
+
overflow: "hidden"
|
|
1569
1850
|
},
|
|
1851
|
+
children: ["line", "bar"].map((ty) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1852
|
+
onClick: () => switchTo({ type: ty }),
|
|
1853
|
+
style: segBtn(chartType === ty),
|
|
1854
|
+
children: ty === "line" ? t("trend.type.line") : t("trend.type.bar")
|
|
1855
|
+
}, ty))
|
|
1856
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1570
1857
|
style: {
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1858
|
+
display: "inline-flex",
|
|
1859
|
+
border: "1px solid var(--dsw-alias-border-l2)",
|
|
1860
|
+
borderRadius: 6,
|
|
1861
|
+
overflow: "hidden"
|
|
1574
1862
|
},
|
|
1575
|
-
children:
|
|
1576
|
-
|
|
1863
|
+
children: [
|
|
1864
|
+
7,
|
|
1865
|
+
30,
|
|
1866
|
+
90
|
|
1867
|
+
].map((n) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1868
|
+
onClick: () => switchTo({ range: n }),
|
|
1869
|
+
style: segBtn(rangeDays === n),
|
|
1870
|
+
children: n === 7 ? t("trend.range7") : n === 30 ? t("trend.range30") : t("trend.range90")
|
|
1871
|
+
}, n))
|
|
1872
|
+
})]
|
|
1577
1873
|
})]
|
|
1578
1874
|
}),
|
|
1579
1875
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -1585,58 +1881,48 @@ window.__ModuleLoader__.load({
|
|
|
1585
1881
|
gap: 6
|
|
1586
1882
|
},
|
|
1587
1883
|
children: series.map((s) => {
|
|
1588
|
-
const off =
|
|
1884
|
+
const off = isHidden(s.model);
|
|
1589
1885
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1590
1886
|
onClick: () => toggle(s.model),
|
|
1591
|
-
title: s.model,
|
|
1887
|
+
title: s.model === "__other__" ? dispName(s.model) : s.model,
|
|
1592
1888
|
style: {
|
|
1593
1889
|
display: "inline-flex",
|
|
1594
1890
|
alignItems: "center",
|
|
1595
|
-
gap:
|
|
1596
|
-
padding: "
|
|
1891
|
+
gap: 6,
|
|
1892
|
+
padding: "2px 8px",
|
|
1597
1893
|
borderRadius: 999,
|
|
1598
1894
|
border: "1px solid var(--dsw-alias-border-l2)",
|
|
1599
1895
|
background: "transparent",
|
|
1600
1896
|
cursor: "pointer",
|
|
1601
1897
|
opacity: off ? .35 : 1,
|
|
1602
|
-
|
|
1898
|
+
transition: T_FADE,
|
|
1899
|
+
fontSize: 12,
|
|
1603
1900
|
color: "var(--dsw-alias-label-primary)",
|
|
1604
1901
|
minWidth: 0
|
|
1605
1902
|
},
|
|
1606
|
-
children: [
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
children: s.model
|
|
1625
|
-
}),
|
|
1626
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1627
|
-
style: {
|
|
1628
|
-
fontSize: 10,
|
|
1629
|
-
color: "var(--dsw-alias-label-tertiary)",
|
|
1630
|
-
flexShrink: 0
|
|
1631
|
-
},
|
|
1632
|
-
children: formatTokensShort(totalsByModel.get(s.model) ?? 0)
|
|
1633
|
-
})
|
|
1634
|
-
]
|
|
1903
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { style: {
|
|
1904
|
+
width: 8,
|
|
1905
|
+
height: 8,
|
|
1906
|
+
borderRadius: 4,
|
|
1907
|
+
background: colorOf(s.model),
|
|
1908
|
+
flexShrink: 0
|
|
1909
|
+
} }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1910
|
+
style: {
|
|
1911
|
+
flex: 1,
|
|
1912
|
+
minWidth: 0,
|
|
1913
|
+
overflow: "hidden",
|
|
1914
|
+
textOverflow: "ellipsis",
|
|
1915
|
+
whiteSpace: "nowrap",
|
|
1916
|
+
textAlign: "left",
|
|
1917
|
+
textDecoration: off ? "line-through" : "none"
|
|
1918
|
+
},
|
|
1919
|
+
children: dispName(s.model)
|
|
1920
|
+
})]
|
|
1635
1921
|
}, s.model);
|
|
1636
1922
|
})
|
|
1637
1923
|
}), series.length === 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1638
1924
|
style: {
|
|
1639
|
-
fontSize:
|
|
1925
|
+
fontSize: 11,
|
|
1640
1926
|
color: "var(--dsw-alias-label-tertiary)",
|
|
1641
1927
|
marginTop: 4
|
|
1642
1928
|
},
|
|
@@ -1674,61 +1960,44 @@ window.__ModuleLoader__.load({
|
|
|
1674
1960
|
1
|
|
1675
1961
|
].map((f) => {
|
|
1676
1962
|
const y = PAD.top + (1 - f) * plotH;
|
|
1677
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("g", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
x2: W - PAD.right,
|
|
1681
|
-
y2: y,
|
|
1963
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("g", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
1964
|
+
d: `M ${PAD.left} ${y} L ${W - PAD.right} ${y}`,
|
|
1965
|
+
fill: "none",
|
|
1682
1966
|
stroke: "var(--dsw-alias-border-l2)",
|
|
1683
1967
|
strokeWidth: 1,
|
|
1684
|
-
strokeDasharray: f === 0 ? void 0 : "3 3"
|
|
1968
|
+
strokeDasharray: f === 0 ? void 0 : "3 3",
|
|
1969
|
+
style: { transition: T_D }
|
|
1685
1970
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("text", {
|
|
1686
|
-
x:
|
|
1687
|
-
y:
|
|
1971
|
+
x: 0,
|
|
1972
|
+
y: 0,
|
|
1688
1973
|
textAnchor: "end",
|
|
1689
1974
|
fontSize: 14,
|
|
1690
1975
|
fill: "var(--dsw-alias-label-tertiary)",
|
|
1976
|
+
style: {
|
|
1977
|
+
transform: `translate(${PAD.left - 8}px, ${y + 5}px)`,
|
|
1978
|
+
transition: T_SHIFT
|
|
1979
|
+
},
|
|
1691
1980
|
children: formatTokensShort(yMax * f)
|
|
1692
1981
|
})] }, f);
|
|
1693
1982
|
}),
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
y2: PAD.top + plotH,
|
|
1983
|
+
ghost ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("g", {
|
|
1984
|
+
className: ghost.type === "bar" ? "dtp-ghost-bars" : "dtp-ghost-lines",
|
|
1985
|
+
children: ghost.marks
|
|
1986
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("g", {
|
|
1987
|
+
className: "dtp-ghost-lines",
|
|
1988
|
+
children: ghost.labels
|
|
1989
|
+
})] }) : null,
|
|
1990
|
+
labelsNode,
|
|
1991
|
+
marksNode,
|
|
1992
|
+
hover != null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
1993
|
+
d: `M ${anchorX(hover.idx)} ${PAD.top} L ${anchorX(hover.idx)} ${PAD.top + plotH}`,
|
|
1994
|
+
fill: "none",
|
|
1707
1995
|
stroke: "var(--dsw-alias-label-tertiary)",
|
|
1708
1996
|
strokeWidth: 1,
|
|
1709
1997
|
strokeDasharray: "4 3",
|
|
1710
|
-
opacity: .6
|
|
1998
|
+
opacity: .6,
|
|
1999
|
+
style: { transition: T_D }
|
|
1711
2000
|
}) : null,
|
|
1712
|
-
visible.map((s) => {
|
|
1713
|
-
const color = colorOf(s.model);
|
|
1714
|
-
const pts = s.points.map((v, i) => [xOf(i), yOf(v)]);
|
|
1715
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("g", {
|
|
1716
|
-
opacity: .8,
|
|
1717
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
1718
|
-
d: seriesPath(pts),
|
|
1719
|
-
fill: "none",
|
|
1720
|
-
stroke: color,
|
|
1721
|
-
strokeWidth: 2,
|
|
1722
|
-
strokeLinecap: "round",
|
|
1723
|
-
strokeLinejoin: "round"
|
|
1724
|
-
}), pts.map(([x, y], i) => s.points[i] > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
1725
|
-
cx: x,
|
|
1726
|
-
cy: y,
|
|
1727
|
-
r: hover?.idx === i ? 4 : 2,
|
|
1728
|
-
fill: color
|
|
1729
|
-
}, i) : null)]
|
|
1730
|
-
}, s.model);
|
|
1731
|
-
}),
|
|
1732
2001
|
visible.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("text", {
|
|
1733
2002
|
x: W / 2,
|
|
1734
2003
|
y: PAD.top + plotH / 2,
|
|
@@ -1807,7 +2076,7 @@ window.__ModuleLoader__.load({
|
|
|
1807
2076
|
whiteSpace: "nowrap"
|
|
1808
2077
|
},
|
|
1809
2078
|
title: r.model,
|
|
1810
|
-
children: r.model
|
|
2079
|
+
children: dispName(r.model)
|
|
1811
2080
|
}),
|
|
1812
2081
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1813
2082
|
style: {
|
|
@@ -1858,6 +2127,7 @@ window.__ModuleLoader__.load({
|
|
|
1858
2127
|
"stats.count": "{count} 次",
|
|
1859
2128
|
"model.top5": "Top 5 模型",
|
|
1860
2129
|
"model.top5.hint": "按 Token 用量排序",
|
|
2130
|
+
"model.others": "其它",
|
|
1861
2131
|
"model.winner": "当日最常用模型",
|
|
1862
2132
|
"model.none": "暂无模型数据",
|
|
1863
2133
|
"model.tokens": "{count} tokens",
|
|
@@ -1868,6 +2138,8 @@ window.__ModuleLoader__.load({
|
|
|
1868
2138
|
"trend.range7": "近一周",
|
|
1869
2139
|
"trend.range30": "近一月",
|
|
1870
2140
|
"trend.range90": "近90天",
|
|
2141
|
+
"trend.type.line": "折线",
|
|
2142
|
+
"trend.type.bar": "柱状",
|
|
1871
2143
|
"trend.empty": "所选范围内暂无数据",
|
|
1872
2144
|
"trend.legend.hint": "点击图例可显示/隐藏",
|
|
1873
2145
|
"common.tokens": "tokens"
|
|
@@ -1903,6 +2175,7 @@ window.__ModuleLoader__.load({
|
|
|
1903
2175
|
"stats.count": "{count} turns",
|
|
1904
2176
|
"model.top5": "Top 5 Models",
|
|
1905
2177
|
"model.top5.hint": "By token usage",
|
|
2178
|
+
"model.others": "Others",
|
|
1906
2179
|
"model.winner": "Daily winner (model)",
|
|
1907
2180
|
"model.none": "No model data",
|
|
1908
2181
|
"model.tokens": "{count} tokens",
|
|
@@ -1913,6 +2186,8 @@ window.__ModuleLoader__.load({
|
|
|
1913
2186
|
"trend.range7": "7 days",
|
|
1914
2187
|
"trend.range30": "30 days",
|
|
1915
2188
|
"trend.range90": "90 days",
|
|
2189
|
+
"trend.type.line": "Lines",
|
|
2190
|
+
"trend.type.bar": "Bars",
|
|
1916
2191
|
"trend.empty": "No data in this range",
|
|
1917
2192
|
"trend.legend.hint": "Click legend to toggle",
|
|
1918
2193
|
"common.tokens": "tokens"
|
|
@@ -2232,6 +2507,14 @@ window.__ModuleLoader__.load({
|
|
|
2232
2507
|
name: x.provider ?? x.name,
|
|
2233
2508
|
tokens: x.tokens
|
|
2234
2509
|
}));
|
|
2510
|
+
const othersScope = selectedKey && aggregated?.byDay.has(selectedKey) ? [aggregated.byDay.get(selectedKey)] : days ?? [];
|
|
2511
|
+
const sumField = (field) => {
|
|
2512
|
+
let total = 0;
|
|
2513
|
+
for (const d of othersScope) for (const [, v] of d[field]) total += v;
|
|
2514
|
+
return total;
|
|
2515
|
+
};
|
|
2516
|
+
const modelOthersTokens = sumField("byModel") - viewTopModels.reduce((a, x) => a + x.tokens, 0);
|
|
2517
|
+
const providerOthersTokens = sumField("byProvider") - viewTopProviders.reduce((a, x) => a + x.tokens, 0);
|
|
2235
2518
|
const injectedTop = injectedTop5;
|
|
2236
2519
|
const hasData = totals && (totals.today > 0 || totals.thisWeek > 0 || totals.thisMonth > 0 || totals.all > 0);
|
|
2237
2520
|
const tt = (key, params) => {
|
|
@@ -2324,7 +2607,7 @@ window.__ModuleLoader__.load({
|
|
|
2324
2607
|
color: "var(--dsw-alias-label-tertiary)",
|
|
2325
2608
|
verticalAlign: "middle"
|
|
2326
2609
|
},
|
|
2327
|
-
children: ["v", "0.4.
|
|
2610
|
+
children: ["v", "0.4.2"]
|
|
2328
2611
|
})]
|
|
2329
2612
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2330
2613
|
style: {
|
|
@@ -2753,7 +3036,9 @@ window.__ModuleLoader__.load({
|
|
|
2753
3036
|
topProviders: [],
|
|
2754
3037
|
days: days ?? void 0,
|
|
2755
3038
|
mode: topMode,
|
|
2756
|
-
onModeChange: setTopMode
|
|
3039
|
+
onModeChange: setTopMode,
|
|
3040
|
+
othersModelTokens: modelOthersTokens,
|
|
3041
|
+
othersProviderTokens: providerOthersTokens
|
|
2757
3042
|
}) : viewTopModelsNamed.length > 0 || viewTopProvidersNamed.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelTop5, {
|
|
2758
3043
|
t: lang === "en" ? (k, p) => ({
|
|
2759
3044
|
"model.top5": "Top 5 Models",
|
|
@@ -2761,13 +3046,16 @@ window.__ModuleLoader__.load({
|
|
|
2761
3046
|
"model.top5.hint": "By token usage",
|
|
2762
3047
|
"model.none": "No data",
|
|
2763
3048
|
"view.model": "Model",
|
|
2764
|
-
"view.provider": "Provider"
|
|
3049
|
+
"view.provider": "Provider",
|
|
3050
|
+
"model.others": "Others"
|
|
2765
3051
|
})[k] ?? t(k, p) : t,
|
|
2766
3052
|
topModels: viewTopModelsNamed,
|
|
2767
3053
|
topProviders: viewTopProvidersNamed,
|
|
2768
3054
|
days: days ?? void 0,
|
|
2769
3055
|
mode: topMode,
|
|
2770
|
-
onModeChange: setTopMode
|
|
3056
|
+
onModeChange: setTopMode,
|
|
3057
|
+
othersModelTokens: modelOthersTokens,
|
|
3058
|
+
othersProviderTokens: providerOthersTokens
|
|
2771
3059
|
}) : null,
|
|
2772
3060
|
!hasData && !days ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2773
3061
|
style: {
|