mirror of
https://wget.la/https://github.com/leookun/cursor-byok
synced 2026-10-06 13:44:21 +08:00
chore: update dependencies and improve chart components
- Updated `foreign-types` to version 0.5.0 and added new dependencies in `Cargo.lock`. - Removed unused dependencies `chart.js` and `react-chartjs-2` from `package.json` and `package-lock.json`. - Enhanced `CacheHitRateChart` to use `EChart` for rendering, improving performance and visual fidelity. - Adjusted styles for `CacheHitRateChart` to ensure proper layout and responsiveness. - Refactored API query construction in `api.ts` for better readability. - Updated `HomeMetrics` to support refresh functionality for dynamic data updates.
This commit is contained in:
@@ -300,8 +300,8 @@ export const api = {
|
||||
params.set("end_ms", String(filter.endMs));
|
||||
if (filter.modelHashes?.length) params.set("model_hashes", JSON.stringify(filter.modelHashes));
|
||||
}
|
||||
const query = params.size ? `?${params}` : "";
|
||||
return request<Overview>(`/overview${query}`);
|
||||
const query = params.toString();
|
||||
return request<Overview>(`/overview${query ? `?${query}` : ""}`);
|
||||
},
|
||||
cursorHarness: () => request<CursorHarnessStatus>("/harness/cursor/status"),
|
||||
initializeCursorCa: () => request<CursorHarnessStatus>("/harness/cursor/ca/initialize", { method: "POST" }),
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { BarChart, LineChart } from "echarts/charts";
|
||||
import { BarChart, GaugeChart, LineChart } from "echarts/charts";
|
||||
import { GridComponent, LegendComponent, MarkLineComponent, TooltipComponent } from "echarts/components";
|
||||
import { getInstanceByDom, init, use, type EChartsCoreOption } from "echarts/core";
|
||||
import { CanvasRenderer } from "echarts/renderers";
|
||||
import { useEffect, useRef, type MouseEventHandler } from "react";
|
||||
import styles from "./EChart.module.scss";
|
||||
|
||||
use([BarChart, LineChart, GridComponent, LegendComponent, MarkLineComponent, TooltipComponent, CanvasRenderer]);
|
||||
use([BarChart, GaugeChart, LineChart, GridComponent, LegendComponent, MarkLineComponent, TooltipComponent, CanvasRenderer]);
|
||||
|
||||
type EChartProps = {
|
||||
option: EChartsCoreOption;
|
||||
|
||||
@@ -1,18 +1,22 @@
|
||||
@use "../../styles/typography" as type;
|
||||
|
||||
.root {
|
||||
--cache-hit-track-color: color-mix(in srgb, var(--vscode-foreground) 12%, transparent);
|
||||
--cache-hit-value-color: var(--vscode-gitDecoration-addedResourceForeground, #4ade80);
|
||||
position: relative;
|
||||
width: 132px;
|
||||
width: 200px;
|
||||
max-width: 100%;
|
||||
height: 82px;
|
||||
overflow: hidden;
|
||||
align-self: center;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.canvas {
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
.chart {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 140px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.label {
|
||||
|
||||
@@ -1,75 +1,62 @@
|
||||
import { ArcElement, Chart as ChartJS, Tooltip, type ChartOptions, type ScriptableContext } from "chart.js";
|
||||
import { useMemo } from "react";
|
||||
import { Doughnut } from "react-chartjs-2";
|
||||
import type { EChartsCoreOption } from "echarts/core";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { EChart } from "../charts/EChart";
|
||||
import styles from "./CacheHitRateChart.module.scss";
|
||||
|
||||
ChartJS.register(ArcElement, Tooltip);
|
||||
const valueColor = "#40c463";
|
||||
const trackColor = "rgba(139, 148, 158, 0.20)";
|
||||
|
||||
type SegmentRadius = number | {
|
||||
outerStart: number;
|
||||
outerEnd: number;
|
||||
innerStart: number;
|
||||
innerEnd: number;
|
||||
};
|
||||
|
||||
function chartColor(context: ScriptableContext<"doughnut">) {
|
||||
const styles = getComputedStyle(context.chart.canvas);
|
||||
const variable = context.dataIndex === 0 ? "--cache-hit-value-color" : "--cache-hit-track-color";
|
||||
return styles.getPropertyValue(variable).trim();
|
||||
}
|
||||
|
||||
function segmentBorderRadius(percentage: number, dataIndex: number): SegmentRadius {
|
||||
const radius = 5;
|
||||
|
||||
if (percentage <= 0) {
|
||||
return dataIndex === 1
|
||||
? { outerStart: radius, outerEnd: radius, innerStart: radius, innerEnd: radius }
|
||||
: 0;
|
||||
}
|
||||
|
||||
if (percentage >= 100) {
|
||||
return dataIndex === 0
|
||||
? { outerStart: radius, outerEnd: radius, innerStart: radius, innerEnd: radius }
|
||||
: 0;
|
||||
}
|
||||
|
||||
return dataIndex === 0
|
||||
? { outerStart: radius, outerEnd: 0, innerStart: radius, innerEnd: 0 }
|
||||
: { outerStart: 0, outerEnd: radius, innerStart: 0, innerEnd: radius };
|
||||
}
|
||||
|
||||
const options: ChartOptions<"doughnut"> = {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
cutout: "82%",
|
||||
rotation: -90,
|
||||
circumference: 180,
|
||||
animation: { duration: 450 },
|
||||
events: [],
|
||||
plugins: {
|
||||
legend: { display: false },
|
||||
tooltip: { enabled: false },
|
||||
},
|
||||
};
|
||||
|
||||
export function CacheHitRateChart({ rate }: { rate: number }) {
|
||||
export function CacheHitRateChart({ rate, animationKey = 0 }: { rate: number; animationKey?: number }) {
|
||||
const finiteRate = Number.isFinite(rate) ? rate : 0;
|
||||
const percentage = Math.max(0, Math.min(100, finiteRate * 100));
|
||||
const [displayedPercentage, setDisplayedPercentage] = useState(0);
|
||||
const label = Number.isFinite(rate) ? `${percentage.toFixed(2)}%` : "--";
|
||||
const data = useMemo(() => ({
|
||||
labels: [t("命中"), t("未命中")],
|
||||
datasets: [{
|
||||
data: [percentage, Math.max(0, 100 - percentage)],
|
||||
backgroundColor: chartColor,
|
||||
borderWidth: 0,
|
||||
hoverBorderWidth: 0,
|
||||
selfJoin: false,
|
||||
borderRadius: (context: ScriptableContext<"doughnut">) => segmentBorderRadius(percentage, context.dataIndex),
|
||||
|
||||
useEffect(() => {
|
||||
setDisplayedPercentage(0);
|
||||
let frame = requestAnimationFrame(() => {
|
||||
frame = requestAnimationFrame(() => setDisplayedPercentage(percentage));
|
||||
});
|
||||
return () => cancelAnimationFrame(frame);
|
||||
}, [animationKey, percentage]);
|
||||
|
||||
const option = useMemo<EChartsCoreOption>(() => ({
|
||||
animationDuration: 0,
|
||||
animationDurationUpdate: displayedPercentage > 0 ? 1_000 : 0,
|
||||
animationEasing: "cubicOut",
|
||||
animationEasingUpdate: "cubicOut",
|
||||
series: [{
|
||||
type: "gauge",
|
||||
min: 0,
|
||||
max: 100,
|
||||
startAngle: 180,
|
||||
endAngle: 0,
|
||||
center: ["50%", "50%"],
|
||||
radius: "90%",
|
||||
silent: true,
|
||||
pointer: { show: false },
|
||||
progress: {
|
||||
show: true,
|
||||
roundCap: true,
|
||||
width: 11,
|
||||
itemStyle: { color: displayedPercentage > 0 ? valueColor : "transparent" },
|
||||
},
|
||||
axisLine: {
|
||||
roundCap: true,
|
||||
lineStyle: { width: 11, color: [[1, trackColor]] },
|
||||
},
|
||||
axisTick: { show: false },
|
||||
splitLine: { show: false },
|
||||
axisLabel: { show: false },
|
||||
anchor: { show: false },
|
||||
title: { show: false },
|
||||
detail: { show: false },
|
||||
data: [{ value: displayedPercentage }],
|
||||
}],
|
||||
}), [percentage]);
|
||||
}), [displayedPercentage]);
|
||||
|
||||
return <div className={styles.root} role="img" aria-label={t("缓存命中率 {rate}", { rate: label })}>
|
||||
<Doughnut className={styles.canvas} data={data} options={options} />
|
||||
<EChart className={styles.chart} option={option} />
|
||||
<div className={styles.label}>{label}</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ function InfoTooltip({ content }: { content: string }) {
|
||||
</>;
|
||||
}
|
||||
|
||||
export function HomeMetrics({ data }: { data: HomeMetricsData }) {
|
||||
export function HomeMetrics({ data, refreshVersion = 0 }: { data: HomeMetricsData; refreshVersion?: number }) {
|
||||
const inputTokens = Math.max(0, data.promptTokens - data.cacheReadTokens - data.cacheWriteTokens);
|
||||
const outputTokens = Math.max(0, data.tokenUsage - data.promptTokens);
|
||||
const defaultCacheHitRate = calculateRate(data.cacheReadTokens, data.cacheReadTokens + inputTokens);
|
||||
@@ -148,7 +148,7 @@ export function HomeMetrics({ data }: { data: HomeMetricsData }) {
|
||||
<section className={styles.root} aria-label={t("调用统计")}>
|
||||
<article className={styles.metric}>
|
||||
<div className={styles.label}>{t("缓存命中率")}<InfoTooltip content={cacheTooltip} /></div>
|
||||
<CacheHitRateChart rate={defaultCacheHitRate ?? 0} />
|
||||
<CacheHitRateChart rate={defaultCacheHitRate ?? 0} animationKey={refreshVersion} />
|
||||
</article>
|
||||
<article className={styles.metric}>
|
||||
<div className={styles.label}>{t("LLM 调用")}<InfoTooltip content={callsTooltip} /></div>
|
||||
|
||||
@@ -118,7 +118,7 @@ export function HomePage() {
|
||||
{
|
||||
key: "metrics",
|
||||
estimatedHeight: 130,
|
||||
content: <HomeMetrics data={metrics} />,
|
||||
content: <HomeMetrics data={metrics} refreshVersion={refreshVersion} />,
|
||||
},
|
||||
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user