@cryptiklemur/lattice 1.23.0 → 1.23.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/client/src/components/analytics/AnalyticsView.tsx +41 -27
- package/client/src/components/analytics/ChartCard.tsx +21 -17
- package/client/src/components/analytics/PeriodSelector.tsx +3 -3
- package/client/src/components/analytics/QuickStats.tsx +39 -20
- package/client/src/components/analytics/charts/ActivityCalendar.tsx +1 -1
- package/client/src/components/analytics/charts/CacheEfficiencyChart.tsx +4 -4
- package/client/src/components/analytics/charts/CostAreaChart.tsx +3 -3
- package/client/src/components/analytics/charts/CostDistributionChart.tsx +3 -3
- package/client/src/components/analytics/charts/CumulativeCostChart.tsx +3 -3
- package/client/src/components/analytics/charts/DailySummaryCards.tsx +11 -11
- package/client/src/components/analytics/charts/HourlyHeatmap.tsx +1 -1
- package/client/src/components/analytics/charts/SessionComplexityList.tsx +5 -3
- package/client/src/components/analytics/charts/SessionTimeline.tsx +1 -1
- package/client/src/components/analytics/charts/TokenFlowChart.tsx +3 -3
- package/package.json +1 -1
|
@@ -3,19 +3,25 @@ import { BarChart3 } from "lucide-react";
|
|
|
3
3
|
import { useAnalytics } from "../../hooks/useAnalytics";
|
|
4
4
|
import { useMesh } from "../../hooks/useMesh";
|
|
5
5
|
|
|
6
|
-
class ChartErrorBoundary extends Component<{ children: React.ReactNode; name: string }, {
|
|
6
|
+
class ChartErrorBoundary extends Component<{ children: React.ReactNode; name: string }, { hasError: boolean }> {
|
|
7
7
|
constructor(props: { children: React.ReactNode; name: string }) {
|
|
8
8
|
super(props);
|
|
9
|
-
this.state = {
|
|
9
|
+
this.state = { hasError: false };
|
|
10
10
|
}
|
|
11
|
-
static getDerivedStateFromError(
|
|
12
|
-
return {
|
|
11
|
+
static getDerivedStateFromError() {
|
|
12
|
+
return { hasError: true };
|
|
13
13
|
}
|
|
14
14
|
render() {
|
|
15
|
-
if (this.state.
|
|
15
|
+
if (this.state.hasError) {
|
|
16
16
|
return (
|
|
17
|
-
<div className="flex items-center justify-center h-[200px]
|
|
18
|
-
|
|
17
|
+
<div className="flex flex-col items-center justify-center h-[200px] gap-3">
|
|
18
|
+
<span className="text-base-content/30 font-mono text-[12px]">Unable to load chart</span>
|
|
19
|
+
<button
|
|
20
|
+
onClick={() => this.setState({ hasError: false })}
|
|
21
|
+
className="text-[11px] font-mono text-primary/60 hover:text-primary transition-colors cursor-pointer px-3 py-1 rounded-md border border-primary/20 hover:border-primary/40"
|
|
22
|
+
>
|
|
23
|
+
Retry
|
|
24
|
+
</button>
|
|
19
25
|
</div>
|
|
20
26
|
);
|
|
21
27
|
}
|
|
@@ -26,9 +32,9 @@ class ChartErrorBoundary extends Component<{ children: React.ReactNode; name: st
|
|
|
26
32
|
function SectionHeader(props: { label: string }) {
|
|
27
33
|
return (
|
|
28
34
|
<div className="flex items-center gap-3 pt-4 pb-1">
|
|
29
|
-
<div className="h-px flex-1 bg-base-content/
|
|
30
|
-
<span className="text-[
|
|
31
|
-
<div className="h-px flex-1 bg-base-content/
|
|
35
|
+
<div className="h-px flex-1 bg-base-content/10" />
|
|
36
|
+
<span className="text-[10px] font-mono font-bold uppercase tracking-[0.15em] text-base-content/40">{props.label}</span>
|
|
37
|
+
<div className="h-px flex-1 bg-base-content/10" />
|
|
32
38
|
</div>
|
|
33
39
|
);
|
|
34
40
|
}
|
|
@@ -55,15 +61,15 @@ import { SessionComplexityList } from "./charts/SessionComplexityList";
|
|
|
55
61
|
import { NodeFleetOverview } from "./charts/NodeFleetOverview";
|
|
56
62
|
|
|
57
63
|
export function AnalyticsView() {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
64
|
+
const analytics = useAnalytics();
|
|
65
|
+
const mesh = useMesh();
|
|
66
|
+
const nodes = mesh.nodes;
|
|
61
67
|
|
|
62
68
|
return (
|
|
63
69
|
<div className="flex flex-col h-full overflow-hidden bg-base-100 bg-lattice-grid">
|
|
64
70
|
<div className="flex items-center justify-between px-2 sm:px-4 min-h-10 sm:min-h-12 border-b border-base-300 flex-shrink-0">
|
|
65
71
|
<div className="flex items-center gap-2.5">
|
|
66
|
-
<h1 className="text-
|
|
72
|
+
<h1 className="text-[17px] font-mono font-bold text-base-content">Analytics</h1>
|
|
67
73
|
{analytics.loading && analytics.data && (
|
|
68
74
|
<span className="w-3.5 h-3.5 border-2 border-base-content/15 border-t-primary/60 rounded-full animate-spin" />
|
|
69
75
|
)}
|
|
@@ -71,7 +77,7 @@ export function AnalyticsView() {
|
|
|
71
77
|
<PeriodSelector value={analytics.period} onChange={analytics.setPeriod} />
|
|
72
78
|
</div>
|
|
73
79
|
|
|
74
|
-
<div className={"flex-1 overflow-y-auto px-6 py-4 transition-opacity duration-200 " + (analytics.loading && analytics.data ? "opacity-50" : "opacity-100")}>
|
|
80
|
+
<div className={"flex-1 overflow-y-auto px-3 py-3 sm:px-6 sm:py-4 transition-opacity duration-200 " + (analytics.loading && analytics.data ? "opacity-50" : "opacity-100")}>
|
|
75
81
|
{analytics.loading && !analytics.data && (
|
|
76
82
|
<div className="flex flex-col gap-4 max-w-[1200px] mx-auto">
|
|
77
83
|
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3">
|
|
@@ -98,24 +104,34 @@ export function AnalyticsView() {
|
|
|
98
104
|
<SectionHeader label="Cost" />
|
|
99
105
|
|
|
100
106
|
<ChartCard title="Cost Over Time">
|
|
101
|
-
<
|
|
107
|
+
<ChartErrorBoundary name="CostArea">
|
|
108
|
+
<CostAreaChart data={analytics.data.costOverTime} />
|
|
109
|
+
</ChartErrorBoundary>
|
|
102
110
|
</ChartCard>
|
|
103
111
|
|
|
104
112
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
105
113
|
<ChartCard title="Cost Breakdown">
|
|
106
|
-
<
|
|
114
|
+
<ChartErrorBoundary name="CostDonut">
|
|
115
|
+
<CostDonutChart modelUsage={analytics.data.modelUsage} totalCost={analytics.data.totalCost} />
|
|
116
|
+
</ChartErrorBoundary>
|
|
107
117
|
</ChartCard>
|
|
108
118
|
<ChartCard title="Cumulative Cost">
|
|
109
|
-
<
|
|
119
|
+
<ChartErrorBoundary name="CumulativeCost">
|
|
120
|
+
<CumulativeCostChart data={analytics.data.cumulativeCost} />
|
|
121
|
+
</ChartErrorBoundary>
|
|
110
122
|
</ChartCard>
|
|
111
123
|
</div>
|
|
112
124
|
|
|
113
125
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
114
126
|
<ChartCard title="Cost Distribution">
|
|
115
|
-
<
|
|
127
|
+
<ChartErrorBoundary name="CostDistribution">
|
|
128
|
+
<CostDistributionChart data={analytics.data.costDistribution} />
|
|
129
|
+
</ChartErrorBoundary>
|
|
116
130
|
</ChartCard>
|
|
117
131
|
<ChartCard title="Session Costs">
|
|
118
|
-
<
|
|
132
|
+
<ChartErrorBoundary name="SessionBubble">
|
|
133
|
+
<SessionBubbleChart data={analytics.data.sessionBubbles} />
|
|
134
|
+
</ChartErrorBoundary>
|
|
119
135
|
</ChartCard>
|
|
120
136
|
</div>
|
|
121
137
|
|
|
@@ -182,13 +198,11 @@ export function AnalyticsView() {
|
|
|
182
198
|
|
|
183
199
|
<SectionHeader label="Projects" />
|
|
184
200
|
|
|
185
|
-
<
|
|
186
|
-
<
|
|
187
|
-
<
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
</ChartCard>
|
|
191
|
-
</div>
|
|
201
|
+
<ChartCard title="Project Comparison">
|
|
202
|
+
<ChartErrorBoundary name="Radar">
|
|
203
|
+
<ProjectRadar data={analytics.data.projectRadar} />
|
|
204
|
+
</ChartErrorBoundary>
|
|
205
|
+
</ChartCard>
|
|
192
206
|
|
|
193
207
|
<ChartCard title="Session Complexity">
|
|
194
208
|
<ChartErrorBoundary name="Complexity">
|
|
@@ -3,14 +3,14 @@ import { useFocusTrap } from "../../hooks/useFocusTrap";
|
|
|
3
3
|
import { Maximize2, Minimize2 } from "lucide-react";
|
|
4
4
|
import type { ReactNode } from "react";
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
const ChartFullscreenContext = createContext<number | false>(false);
|
|
7
7
|
|
|
8
8
|
export function useChartFullscreen(): number | false {
|
|
9
9
|
return useContext(ChartFullscreenContext);
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
function useViewportChartHeight(): number {
|
|
13
|
-
|
|
13
|
+
const [h, setH] = useState(Math.round(window.innerHeight * 0.5));
|
|
14
14
|
useEffect(function () {
|
|
15
15
|
function onResize() { setH(Math.round(window.innerHeight * 0.5)); }
|
|
16
16
|
window.addEventListener("resize", onResize);
|
|
@@ -27,13 +27,13 @@ interface ChartCardProps {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
export function ChartCard(props: ChartCardProps) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
30
|
+
const [isFullscreen, setIsFullscreen] = useState(false);
|
|
31
|
+
const chartHeight = useViewportChartHeight();
|
|
32
|
+
const cardRef = useRef<HTMLDivElement>(null);
|
|
33
|
+
const [originRect, setOriginRect] = useState<DOMRect | null>(null);
|
|
34
|
+
const [animating, setAnimating] = useState(false);
|
|
35
|
+
const fullscreenModalRef = useRef<HTMLDivElement>(null);
|
|
36
|
+
const closeFullscreenCb = useCallback(function () { closeFullscreen(); }, []);
|
|
37
37
|
useFocusTrap(fullscreenModalRef, closeFullscreenCb, isFullscreen);
|
|
38
38
|
|
|
39
39
|
function openFullscreen() {
|
|
@@ -67,10 +67,10 @@ export function ChartCard(props: ChartCardProps) {
|
|
|
67
67
|
return function () { document.body.style.overflow = ""; };
|
|
68
68
|
}, [isFullscreen]);
|
|
69
69
|
|
|
70
|
-
|
|
70
|
+
const cardContent = (
|
|
71
71
|
<>
|
|
72
|
-
<div className="flex items-center justify-between mb-4">
|
|
73
|
-
<span className="text-[
|
|
72
|
+
<div className="flex items-center justify-between mb-3 sm:mb-4">
|
|
73
|
+
<span className="text-[11px] font-mono font-bold uppercase tracking-wider text-base-content/40">
|
|
74
74
|
{props.title}
|
|
75
75
|
</span>
|
|
76
76
|
<div className="flex items-center gap-2">
|
|
@@ -91,17 +91,19 @@ export function ChartCard(props: ChartCardProps) {
|
|
|
91
91
|
</button>
|
|
92
92
|
</div>
|
|
93
93
|
</div>
|
|
94
|
-
{props.
|
|
94
|
+
<div role="img" aria-label={props.title}>
|
|
95
|
+
{props.children}
|
|
96
|
+
</div>
|
|
95
97
|
</>
|
|
96
98
|
);
|
|
97
99
|
|
|
98
100
|
if (isFullscreen) {
|
|
99
|
-
|
|
101
|
+
const overlayStyle: React.CSSProperties = {
|
|
100
102
|
transition: "opacity 250ms cubic-bezier(0.4, 0, 0.2, 1)",
|
|
101
103
|
opacity: animating ? 0 : 1,
|
|
102
104
|
};
|
|
103
105
|
|
|
104
|
-
|
|
106
|
+
const modalStyle: React.CSSProperties = {
|
|
105
107
|
transition: "all 250ms cubic-bezier(0.4, 0, 0.2, 1)",
|
|
106
108
|
};
|
|
107
109
|
|
|
@@ -118,7 +120,8 @@ export function ChartCard(props: ChartCardProps) {
|
|
|
118
120
|
<>
|
|
119
121
|
<div
|
|
120
122
|
ref={cardRef}
|
|
121
|
-
|
|
123
|
+
aria-label={props.title}
|
|
124
|
+
className={"rounded-xl border border-base-content/8 bg-base-300/50 p-3 sm:p-4 invisible " + (props.className || "")}
|
|
122
125
|
>
|
|
123
126
|
{cardContent}
|
|
124
127
|
</div>
|
|
@@ -176,7 +179,8 @@ export function ChartCard(props: ChartCardProps) {
|
|
|
176
179
|
return (
|
|
177
180
|
<div
|
|
178
181
|
ref={cardRef}
|
|
179
|
-
|
|
182
|
+
aria-label={props.title}
|
|
183
|
+
className={"group rounded-xl border border-base-content/8 bg-base-300/50 p-3 sm:p-4 cursor-pointer hover:border-base-content/12 transition-all duration-200 " + (props.className || "")}
|
|
180
184
|
>
|
|
181
185
|
{cardContent}
|
|
182
186
|
</div>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
type Period = "24h" | "7d" | "30d" | "90d" | "all";
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
const PERIODS: Array<{ value: Period; label: string }> = [
|
|
4
4
|
{ value: "24h", label: "24h" },
|
|
5
5
|
{ value: "7d", label: "7d" },
|
|
6
6
|
{ value: "30d", label: "30d" },
|
|
@@ -17,7 +17,7 @@ export function PeriodSelector({ value, onChange }: PeriodSelectorProps) {
|
|
|
17
17
|
return (
|
|
18
18
|
<div role="radiogroup" aria-label="Time period" className="flex items-center gap-1">
|
|
19
19
|
{PERIODS.map(function (period) {
|
|
20
|
-
|
|
20
|
+
const isActive = period.value === value;
|
|
21
21
|
return (
|
|
22
22
|
<button
|
|
23
23
|
key={period.value}
|
|
@@ -25,7 +25,7 @@ export function PeriodSelector({ value, onChange }: PeriodSelectorProps) {
|
|
|
25
25
|
aria-checked={isActive}
|
|
26
26
|
onClick={function () { onChange(period.value); }}
|
|
27
27
|
className={[
|
|
28
|
-
"px-2.5 py-1 rounded-md border text-[10px] font-mono font-bold uppercase tracking-widest transition-colors",
|
|
28
|
+
"min-h-[44px] min-w-[36px] px-3 py-2.5 sm:px-2.5 sm:py-1 rounded-md border text-[10px] font-mono font-bold uppercase tracking-widest transition-colors",
|
|
29
29
|
isActive
|
|
30
30
|
? "bg-primary/15 text-primary border-primary/30"
|
|
31
31
|
: "text-base-content/35 border-base-content/8 hover:text-base-content/60 hover:border-base-content/20",
|
|
@@ -31,7 +31,7 @@ function Sparkline({ data, stroke }: SparklineProps) {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
export function QuickStats() {
|
|
34
|
-
|
|
34
|
+
const analytics = useAnalytics();
|
|
35
35
|
|
|
36
36
|
if (!analytics.data) {
|
|
37
37
|
return (
|
|
@@ -48,48 +48,67 @@ export function QuickStats() {
|
|
|
48
48
|
);
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
const d = analytics.data;
|
|
52
|
+
const colors = getChartColors();
|
|
53
53
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
const costSparkData = d.costOverTime.slice(-7).map(function (e: typeof d.costOverTime[number]) { return { v: e.total }; });
|
|
55
|
+
const sessionsSparkData = d.sessionsOverTime.slice(-7).map(function (e: typeof d.sessionsOverTime[number]) { return { v: e.count }; });
|
|
56
|
+
const tokensSparkData = d.tokensOverTime.slice(-7).map(function (e: typeof d.tokensOverTime[number]) { return { v: e.input + e.output }; });
|
|
57
57
|
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
const totalTokens = d.totalTokens.input + d.totalTokens.output;
|
|
59
|
+
const cacheHitPct = Math.round(d.cacheHitRate * 100);
|
|
60
60
|
|
|
61
61
|
return (
|
|
62
62
|
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3">
|
|
63
63
|
<div className="bg-base-content/[0.03] border border-base-content/8 rounded-xl p-3.5">
|
|
64
64
|
<div className="flex items-center justify-between mb-1">
|
|
65
|
-
<span className="text-[10px] font-mono font-
|
|
66
|
-
{costSparkData.length > 1 &&
|
|
65
|
+
<span className="text-[10px] font-mono font-bold uppercase tracking-wider text-base-content/40">Cost</span>
|
|
66
|
+
{costSparkData.length > 1 && (
|
|
67
|
+
<div role="img" aria-label={"Cost trend: " + (costSparkData[costSparkData.length - 1].v > costSparkData[0].v ? "increasing" : costSparkData[costSparkData.length - 1].v < costSparkData[0].v ? "decreasing" : "stable")}>
|
|
68
|
+
<Sparkline data={costSparkData} stroke={colors.primary} />
|
|
69
|
+
</div>
|
|
70
|
+
)}
|
|
67
71
|
</div>
|
|
68
|
-
<div className="text-[
|
|
72
|
+
<div className="text-[20px] font-mono text-base-content">${d.totalCost.toFixed(2)}</div>
|
|
69
73
|
</div>
|
|
70
74
|
|
|
71
75
|
<div className="bg-base-content/[0.03] border border-base-content/8 rounded-xl p-3.5">
|
|
72
76
|
<div className="flex items-center justify-between mb-1">
|
|
73
|
-
<span className="text-[10px] font-mono font-
|
|
74
|
-
{sessionsSparkData.length > 1 &&
|
|
77
|
+
<span className="text-[10px] font-mono font-bold uppercase tracking-wider text-base-content/40">Sessions</span>
|
|
78
|
+
{sessionsSparkData.length > 1 && (
|
|
79
|
+
<div role="img" aria-label={"Sessions trend: " + (sessionsSparkData[sessionsSparkData.length - 1].v > sessionsSparkData[0].v ? "increasing" : sessionsSparkData[sessionsSparkData.length - 1].v < sessionsSparkData[0].v ? "decreasing" : "stable")}>
|
|
80
|
+
<Sparkline data={sessionsSparkData} stroke={colors.success} />
|
|
81
|
+
</div>
|
|
82
|
+
)}
|
|
75
83
|
</div>
|
|
76
|
-
<div className="text-[
|
|
84
|
+
<div className="text-[20px] font-mono text-base-content">{d.totalSessions}</div>
|
|
77
85
|
</div>
|
|
78
86
|
|
|
79
87
|
<div className="bg-base-content/[0.03] border border-base-content/8 rounded-xl p-3.5">
|
|
80
88
|
<div className="flex items-center justify-between mb-1">
|
|
81
|
-
<span className="text-[10px] font-mono font-
|
|
82
|
-
{tokensSparkData.length > 1 &&
|
|
89
|
+
<span className="text-[10px] font-mono font-bold uppercase tracking-wider text-base-content/40">Tokens</span>
|
|
90
|
+
{tokensSparkData.length > 1 && (
|
|
91
|
+
<div role="img" aria-label={"Tokens trend: " + (tokensSparkData[tokensSparkData.length - 1].v > tokensSparkData[0].v ? "increasing" : tokensSparkData[tokensSparkData.length - 1].v < tokensSparkData[0].v ? "decreasing" : "stable")}>
|
|
92
|
+
<Sparkline data={tokensSparkData} stroke={colors.warning} />
|
|
93
|
+
</div>
|
|
94
|
+
)}
|
|
83
95
|
</div>
|
|
84
|
-
<div className="text-[
|
|
96
|
+
<div className="text-[20px] font-mono text-base-content">{formatTokens(totalTokens)}</div>
|
|
85
97
|
</div>
|
|
86
98
|
|
|
87
99
|
<div className="bg-base-content/[0.03] border border-base-content/8 rounded-xl p-3.5">
|
|
88
100
|
<div className="mb-1">
|
|
89
|
-
<span className="text-[10px] font-mono font-
|
|
101
|
+
<span className="text-[10px] font-mono font-bold uppercase tracking-wider text-base-content/40">Cache Hit</span>
|
|
90
102
|
</div>
|
|
91
|
-
<div className="text-[
|
|
92
|
-
<div
|
|
103
|
+
<div className="text-[20px] font-mono text-base-content mb-2">{cacheHitPct}%</div>
|
|
104
|
+
<div
|
|
105
|
+
className="w-full h-1 rounded-full bg-base-content/10 overflow-hidden"
|
|
106
|
+
role="progressbar"
|
|
107
|
+
aria-valuenow={cacheHitPct}
|
|
108
|
+
aria-valuemin={0}
|
|
109
|
+
aria-valuemax={100}
|
|
110
|
+
aria-label="Cache hit rate"
|
|
111
|
+
>
|
|
93
112
|
<div
|
|
94
113
|
className="h-full rounded-full bg-primary transition-all duration-300"
|
|
95
114
|
style={{ width: cacheHitPct + "%" }}
|
|
@@ -59,7 +59,7 @@ export function ActivityCalendar({ data }: ActivityCalendarProps) {
|
|
|
59
59
|
if (!data || data.length === 0) {
|
|
60
60
|
return (
|
|
61
61
|
<div className="flex items-center justify-center h-[120px] text-base-content/25 font-mono text-[11px]">
|
|
62
|
-
No data
|
|
62
|
+
No data for this period
|
|
63
63
|
</div>
|
|
64
64
|
);
|
|
65
65
|
}
|
|
@@ -30,15 +30,15 @@ function CustomTooltip({ active, payload, label }: { active?: boolean; payload?:
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
export function CacheEfficiencyChart({ data }: CacheEfficiencyChartProps) {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
constfullscreenHeight = useChartFullscreen();
|
|
34
|
+
constcolors = getChartColors();
|
|
35
|
+
constdisplayData = data.map(function (d) {
|
|
36
36
|
return { date: d.date, rate: d.rate * 100 };
|
|
37
37
|
});
|
|
38
38
|
|
|
39
39
|
return (
|
|
40
40
|
<ResponsiveContainer width="100%" height={fullscreenHeight || 200}>
|
|
41
|
-
<AreaChart data={displayData} margin={{ top: 4, right: 4, left: -
|
|
41
|
+
<AreaChart data={displayData} margin={{ top: 4, right: 4, left: -15, bottom: 0 }}>
|
|
42
42
|
<defs>
|
|
43
43
|
<linearGradient id="cacheEffGrad" x1="0" y1="0" x2="0" y2="1">
|
|
44
44
|
<stop offset="5%" stopColor={colors.success} stopOpacity={0.8} />
|
|
@@ -42,11 +42,11 @@ function CustomTooltip({ active, payload, label }: { active?: boolean; payload?:
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
export function CostAreaChart({ data }: CostAreaChartProps) {
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
constfullscreenHeight = useChartFullscreen();
|
|
46
|
+
constcolors = getChartColors();
|
|
47
47
|
return (
|
|
48
48
|
<ResponsiveContainer width="100%" height={fullscreenHeight || 200}>
|
|
49
|
-
<AreaChart data={data} margin={{ top: 4, right: 4, left: -
|
|
49
|
+
<AreaChart data={data} margin={{ top: 4, right: 4, left: -15, bottom: 0 }}>
|
|
50
50
|
<defs>
|
|
51
51
|
<linearGradient id="opusGrad" x1="0" y1="0" x2="0" y2="1">
|
|
52
52
|
<stop offset="5%" stopColor={colors.secondary} stopOpacity={0.8} />
|
|
@@ -30,11 +30,11 @@ function CustomTooltip({ active, payload, label }: { active?: boolean; payload?:
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
export function CostDistributionChart({ data }: CostDistributionChartProps) {
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
constfullscreenHeight = useChartFullscreen();
|
|
34
|
+
constcolors = getChartColors();
|
|
35
35
|
return (
|
|
36
36
|
<ResponsiveContainer width="100%" height={fullscreenHeight || 200}>
|
|
37
|
-
<AreaChart data={data} margin={{ top: 4, right: 4, left: -
|
|
37
|
+
<AreaChart data={data} margin={{ top: 4, right: 4, left: -15, bottom: 0 }}>
|
|
38
38
|
<defs>
|
|
39
39
|
<linearGradient id="distGrad" x1="0" y1="0" x2="0" y2="1">
|
|
40
40
|
<stop offset="5%" stopColor={colors.primary} stopOpacity={0.8} />
|
|
@@ -30,11 +30,11 @@ function CustomTooltip({ active, payload, label }: { active?: boolean; payload?:
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
export function CumulativeCostChart({ data }: CumulativeCostChartProps) {
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
constfullscreenHeight = useChartFullscreen();
|
|
34
|
+
constcolors = getChartColors();
|
|
35
35
|
return (
|
|
36
36
|
<ResponsiveContainer width="100%" height={fullscreenHeight || 200}>
|
|
37
|
-
<AreaChart data={data} margin={{ top: 4, right: 4, left: -
|
|
37
|
+
<AreaChart data={data} margin={{ top: 4, right: 4, left: -15, bottom: 0 }}>
|
|
38
38
|
<defs>
|
|
39
39
|
<linearGradient id="cumulativeGrad" x1="0" y1="0" x2="0" y2="1">
|
|
40
40
|
<stop offset="5%" stopColor={colors.primary} stopOpacity={0.7} />
|
|
@@ -14,14 +14,14 @@ interface DailySummaryCardsProps {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
function formatCardDate(dateStr: string): string {
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
const d = new Date(dateStr + "T00:00:00");
|
|
18
|
+
const days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
|
19
19
|
return days[d.getDay()] + " " + (d.getMonth() + 1) + "/" + d.getDate();
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
export function DailySummaryCards({ data }: DailySummaryCardsProps) {
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
const colors = getChartColors();
|
|
24
|
+
const MODEL_COLORS: Record<string, string> = {
|
|
25
25
|
opus: colors.model.opus,
|
|
26
26
|
sonnet: colors.model.sonnet,
|
|
27
27
|
haiku: colors.model.haiku,
|
|
@@ -30,28 +30,28 @@ export function DailySummaryCards({ data }: DailySummaryCardsProps) {
|
|
|
30
30
|
if (!data || data.length === 0) {
|
|
31
31
|
return (
|
|
32
32
|
<div className="flex items-center justify-center h-[100px] text-base-content/25 font-mono text-[11px]">
|
|
33
|
-
No data
|
|
33
|
+
No data for this period
|
|
34
34
|
</div>
|
|
35
35
|
);
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
const reversed = data.slice().reverse();
|
|
39
39
|
|
|
40
40
|
return (
|
|
41
41
|
<div className="flex gap-3 overflow-x-auto snap-x snap-mandatory pb-2 scrollbar-thin">
|
|
42
42
|
{reversed.map(function (d) {
|
|
43
|
-
|
|
43
|
+
const mixEntries = Object.entries(d.modelMix);
|
|
44
44
|
|
|
45
45
|
return (
|
|
46
46
|
<div
|
|
47
47
|
key={d.date}
|
|
48
48
|
className="flex-shrink-0 snap-start rounded-lg bg-base-300 border border-base-content/5 px-3 py-2.5 w-[140px]"
|
|
49
49
|
>
|
|
50
|
-
<p className="text-[
|
|
50
|
+
<p className="text-[10px] font-mono font-bold uppercase tracking-wider text-base-content/40 mb-1.5">
|
|
51
51
|
{formatCardDate(d.date)}
|
|
52
52
|
</p>
|
|
53
53
|
|
|
54
|
-
<div className="text-[
|
|
54
|
+
<div className="text-[11px] font-mono text-base-content space-y-0.5">
|
|
55
55
|
<p><span className="text-base-content/30">sessions </span>{d.sessions}</p>
|
|
56
56
|
<p><span className="text-base-content/30">cost </span>${d.cost.toFixed(2)}</p>
|
|
57
57
|
{d.topTool && (
|
|
@@ -62,8 +62,8 @@ export function DailySummaryCards({ data }: DailySummaryCardsProps) {
|
|
|
62
62
|
{mixEntries.length > 0 && (
|
|
63
63
|
<div className="mt-2 h-[4px] rounded-full overflow-hidden flex bg-base-content/5">
|
|
64
64
|
{mixEntries.map(function (entry) {
|
|
65
|
-
|
|
66
|
-
|
|
65
|
+
const model = entry[0];
|
|
66
|
+
const pct = entry[1];
|
|
67
67
|
if (pct <= 0) return null;
|
|
68
68
|
return (
|
|
69
69
|
<div
|
|
@@ -29,7 +29,7 @@ export function HourlyHeatmap({ data }: HourlyHeatmapProps) {
|
|
|
29
29
|
if (!data || data.length === 0) {
|
|
30
30
|
return (
|
|
31
31
|
<div className="flex items-center justify-center h-[200px] text-base-content/25 font-mono text-[11px]">
|
|
32
|
-
No data
|
|
32
|
+
No data for this period
|
|
33
33
|
</div>
|
|
34
34
|
);
|
|
35
35
|
}
|
|
@@ -13,11 +13,12 @@ export function SessionComplexityList({ data }: SessionComplexityListProps) {
|
|
|
13
13
|
);
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
const maxScore = data.length > 0 ? data[0].score : 1;
|
|
17
17
|
|
|
18
18
|
return (
|
|
19
|
-
<div className="
|
|
20
|
-
|
|
19
|
+
<div className="overflow-x-auto">
|
|
20
|
+
<div className="flex flex-col gap-1 max-h-[400px] overflow-y-auto min-w-[370px]">
|
|
21
|
+
<div className="grid grid-cols-[2.5rem_1fr_4rem_3rem_3rem_3.5rem] gap-2 px-2 py-1 text-[10px] font-mono text-base-content/30 uppercase tracking-wider">
|
|
21
22
|
<span>Rank</span>
|
|
22
23
|
<span>Session</span>
|
|
23
24
|
<span className="text-right">Score</span>
|
|
@@ -58,5 +59,6 @@ export function SessionComplexityList({ data }: SessionComplexityListProps) {
|
|
|
58
59
|
);
|
|
59
60
|
})}
|
|
60
61
|
</div>
|
|
62
|
+
</div>
|
|
61
63
|
);
|
|
62
64
|
}
|
|
@@ -33,7 +33,7 @@ export function SessionTimeline({ data }: SessionTimelineProps) {
|
|
|
33
33
|
if (!data || data.length === 0) {
|
|
34
34
|
return (
|
|
35
35
|
<div className="flex items-center justify-center h-[200px] text-base-content/25 font-mono text-[11px]">
|
|
36
|
-
No data
|
|
36
|
+
No data for this period
|
|
37
37
|
</div>
|
|
38
38
|
);
|
|
39
39
|
}
|
|
@@ -46,11 +46,11 @@ function formatTokens(v: number): string {
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
export function TokenFlowChart({ data }: TokenFlowChartProps) {
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
constfullscreenHeight = useChartFullscreen();
|
|
50
|
+
constcolors = getChartColors();
|
|
51
51
|
return (
|
|
52
52
|
<ResponsiveContainer width="100%" height={fullscreenHeight || 200}>
|
|
53
|
-
<AreaChart data={data} margin={{ top: 4, right: 4, left: -
|
|
53
|
+
<AreaChart data={data} margin={{ top: 4, right: 4, left: -15, bottom: 0 }}>
|
|
54
54
|
<defs>
|
|
55
55
|
<linearGradient id="inputGrad" x1="0" y1="0" x2="0" y2="1">
|
|
56
56
|
<stop offset="5%" stopColor={colors.primary} stopOpacity={0.8} />
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cryptiklemur/lattice",
|
|
3
|
-
"version": "1.23.
|
|
3
|
+
"version": "1.23.2",
|
|
4
4
|
"description": "Multi-machine agentic dashboard for Claude Code. Monitor sessions, manage MCP servers and skills, orchestrate across mesh-networked nodes.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Aaron Scherer <me@aaronscherer.me>",
|