Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
3ba3b5f
feat(console): selected_at anchor recovers item window on stale share…
May 15, 2026
8716662
feat(console): TPS instead of TPOT, agent-turns column reorder, sideb…
May 15, 2026
2352934
feat(console/charts): x-axis tick adapts to time-range duration
May 15, 2026
498ab0d
feat(ts-turn): proxy_pair module — passive llmproxy pair classification
May 18, 2026
a510d14
feat(ts-storage): pair sweeper + DuckDB pair-detection queries
May 18, 2026
0a8ed8a
feat(api): expose proxy_role / proxy_peer_turn_id + include_proxy_hops
May 18, 2026
77c3092
feat(console): ProxyBadge + Show-proxy-hops toggle on Agent Turns
May 18, 2026
7b80464
tune: pair sweeper lookback 5min → 30min
May 18, 2026
06d1421
feat: fold N-leg proxy duplicates into a single group (haproxy 3-leg)
May 18, 2026
f09b350
feat: proxy-view tab — surface what the proxy mutated across legs
May 19, 2026
55a7fd4
fix(api): wire /api/agent-turns/{id}/proxy-view route
May 19, 2026
8d6d506
Merge remote-tracking branch 'origin/feat/selected-id-in-url' into fe…
May 19, 2026
a0dbbe2
Merge remote-tracking branch 'origin/feat/ui-tps-and-column-reorder' …
May 19, 2026
33115d8
Merge remote-tracking branch 'origin/feat/selected-at-anchor' into fe…
May 19, 2026
17c0da9
Merge remote-tracking branch 'origin/feat/axis-time-multi-day' into f…
May 19, 2026
98d4c64
feat(console/gantt): badge multi-leg turns in the Timeline sidebar
May 19, 2026
132b1d6
feat(console): fold call-level proxy duplicates within a turn
May 19, 2026
1cc5aa7
fix(console): drop unused ContentKey interface (vite tsc strict)
May 19, 2026
7dc5fa5
fix(call-pair): drop request_path from content key — proxies rewrite …
May 19, 2026
306ab00
feat(console): timeline hop indicator + in-turn Proxy view fallback
May 19, 2026
90abdfc
Merge remote-tracking branch 'origin/feat/settings-in-app' into feat/…
May 19, 2026
1f8d169
fix(url-sync): apply selected_at anchor only on the first hydration
May 19, 2026
5c9936a
fix(call-pair): drop model from content key — LiteLLM rewrites it
May 19, 2026
397ce95
fix(pair): drop wire_api / finish_reason / model from content fingerp…
May 19, 2026
4db2817
feat: lite mode for /calls — unblock mega-turn detail page
May 20, 2026
03c0ead
fix(console/agent-sessions): keep prior data during refetch (no flash)
May 20, 2026
de35f22
feat(services): per-endpoint Services page (server_ip:port → models +…
May 20, 2026
bfcbdf3
feat(services): identify serving software (vllm/sglang/ollama/llamacp…
May 20, 2026
f2096f3
fix(services): skip malformed-short header blobs when sampling
May 20, 2026
327265e
fix(services): use MAX() over arg_min(LENGTH) for header sampling
May 20, 2026
e5309c2
feat(services): definitive vllm vs sglang split (no more openai-compat)
May 20, 2026
6882cd3
feat(services): Path view — service-to-service topology graph
May 20, 2026
7b2f0aa
feat(services/path): inferred edges — caller_ip → known service
May 20, 2026
bf4887f
perf(services): 10x faster 7d window via top-N body sampling
May 20, 2026
fea1d83
fix(services/apps): drop uvicorn + ≥3-models → litellm fallback
May 20, 2026
3b35166
feat(console): Model view as tab inside Services; sidebar tidy
May 20, 2026
8e191a1
feat(overview): agent activity + distribution charts
May 20, 2026
7e631ca
feat(filters): Server Port head-filter on agent-turns + llm-calls
May 20, 2026
d1ff46e
Merge remote-tracking branch 'origin/main' into feat/deploy-services-…
May 21, 2026
b889681
Merge remote-tracking branch 'origin/main' into feat/deploy-services-…
May 21, 2026
d851cf3
fix(ts-storage): add query_distinct_agent_kinds to pair_sweeper stub
May 21, 2026
158d78f
Merge remote-tracking branch 'origin/main' into feat/deploy-services-…
May 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions console/src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { PerformancePage } from "@/pages/performance"
import { TrafficPage } from "@/pages/traffic"
import { ErrorsPage } from "@/pages/errors"
import { ModelsPage } from "@/pages/models"
import { ServicesPage } from "@/pages/services"
import { LlmCallsPage } from "@/pages/llm-calls"
import { AgentSessionsPage } from "@/pages/agent-sessions"
import { AgentSessionDetailPage } from "@/pages/agent-session-detail"
Expand Down Expand Up @@ -36,6 +37,7 @@ export default function App() {
<Route path="/traffic" element={<TrafficPage />} />
<Route path="/errors" element={<ErrorsPage />} />
<Route path="/models" element={<ModelsPage />} />
<Route path="/services" element={<ServicesPage />} />
<Route path="/agent-sessions" element={<AgentSessionsPage />} />
<Route path="/agent-sessions/:source_id/:session_id" element={<AgentSessionDetailPage />} />
<Route path="/agent-turns" element={<AgentTurnsPage />} />
Expand Down
115 changes: 115 additions & 0 deletions console/src/components/charts/agent-activity-chart.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import {
AreaChart,
Area,
XAxis,
YAxis,
CartesianGrid,
Tooltip,
ResponsiveContainer,
Legend,
} from "recharts"
import { formatNumber, formatAxisTime } from "@/lib/format"
import type { AgentActivityPoint } from "@/types/api"

// Same palette as RequestVolumeChart — keeps the Overview row
// internally consistent.
const SERIES_COLORS = [
"#3b82f6",
"#10b981",
"#f59e0b",
"#ef4444",
"#8b5cf6",
"#ec4899",
"#06b6d4",
"#84cc16",
]

interface Props {
points: AgentActivityPoint[]
}

export function AgentActivityChart({ points }: Props) {
if (points.length === 0) {
return (
<div className="flex h-[240px] items-center justify-center text-sm text-muted-foreground">
No agent activity in the selected window
</div>
)
}

// Pivot the long-form rows `(ts, kind, count)` into wide-form
// recharts data: `{time: <sec>, kind1: n, kind2: n, ...}`.
const tsSet = new Set<number>()
const kindSet = new Set<string>()
const byTsKind = new Map<number, Map<string, number>>()
for (const p of points) {
tsSet.add(p.timestamp_ms)
kindSet.add(p.agent_kind)
if (!byTsKind.has(p.timestamp_ms)) byTsKind.set(p.timestamp_ms, new Map())
byTsKind.get(p.timestamp_ms)!.set(p.agent_kind, p.turn_count)
}
const tsAsc = [...tsSet].sort((a, b) => a - b)
// Sort kinds by total turns desc so the dominant agent renders
// closest to the X axis — same convention as RequestVolumeChart.
const totals = new Map<string, number>()
for (const p of points) {
totals.set(p.agent_kind, (totals.get(p.agent_kind) ?? 0) + p.turn_count)
}
const kindsByVolume = [...kindSet].sort(
(a, b) => (totals.get(b) ?? 0) - (totals.get(a) ?? 0),
)
const chartData = tsAsc.map((ms) => {
const row: Record<string, number> = { time: Math.floor(ms / 1000) }
const m = byTsKind.get(ms)!
for (const k of kindsByVolume) {
row[k] = m.get(k) ?? 0
}
return row
})
const spanSec =
tsAsc.length > 1 ? (tsAsc[tsAsc.length - 1] - tsAsc[0]) / 1000 : 0

return (
<ResponsiveContainer width="100%" height={240}>
<AreaChart data={chartData} margin={{ top: 4, right: 8, bottom: 0, left: -12 }}>
<CartesianGrid strokeDasharray="3 3" className="stroke-border" />
<XAxis
dataKey="time"
tickFormatter={(v: number) => formatAxisTime(v, spanSec)}
className="text-[11px] fill-muted-foreground"
tickLine={false}
axisLine={false}
/>
<YAxis
tickFormatter={(v: number) => formatNumber(v)}
className="text-[11px] fill-muted-foreground"
tickLine={false}
axisLine={false}
/>
<Tooltip
labelFormatter={(v) => new Date(Number(v) * 1000).toLocaleString()}
formatter={(value, name) => [formatNumber(Number(value)), String(name)]}
contentStyle={{
backgroundColor: "hsl(var(--card))",
borderColor: "hsl(var(--border))",
borderRadius: "8px",
fontSize: "12px",
}}
/>
<Legend wrapperStyle={{ fontSize: "12px" }} />
{kindsByVolume.map((kind, i) => (
<Area
key={kind}
type="monotone"
dataKey={kind}
stackId="1"
fill={SERIES_COLORS[i % SERIES_COLORS.length]}
stroke={SERIES_COLORS[i % SERIES_COLORS.length]}
fillOpacity={0.4}
isAnimationActive={false}
/>
))}
</AreaChart>
</ResponsiveContainer>
)
}
88 changes: 88 additions & 0 deletions console/src/components/charts/agent-distribution-chart.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import {
BarChart,
Bar,
XAxis,
YAxis,
CartesianGrid,
Tooltip,
ResponsiveContainer,
} from "recharts"
import { formatNumber } from "@/lib/format"
import type { AgentKindSummary } from "@/types/api"

interface Props {
rows: AgentKindSummary[]
}

export function AgentDistributionChart({ rows }: Props) {
if (rows.length === 0) {
return (
<div className="flex h-[240px] items-center justify-center text-sm text-muted-foreground">
No agents observed in the selected window
</div>
)
}

// Trim to top 10 — agent_kind cardinality is usually <10 anyway,
// but a misconfigured client can dump dozens of arbitrary strings.
const sorted = [...rows].sort((a, b) => b.turn_count - a.turn_count).slice(0, 10)
const chartData = sorted.map((r) => ({
label: r.agent_kind.length > 24 ? r.agent_kind.slice(0, 22) + "…" : r.agent_kind,
full: r.agent_kind,
turns: r.turn_count,
in: r.total_input_tokens,
out: r.total_output_tokens,
}))

return (
<ResponsiveContainer
width="100%"
height={Math.max(240, sorted.length * 36 + 40)}
>
<BarChart
data={chartData}
layout="vertical"
margin={{ top: 4, right: 8, bottom: 0, left: 0 }}
>
<CartesianGrid strokeDasharray="3 3" horizontal={false} className="stroke-border" />
<XAxis
type="number"
tickFormatter={(v: number) => formatNumber(v)}
className="text-[11px] fill-muted-foreground"
tickLine={false}
axisLine={false}
/>
<YAxis
type="category"
dataKey="label"
width={150}
className="text-[11px] fill-muted-foreground"
tickLine={false}
axisLine={false}
/>
<Tooltip
formatter={(value, name) => {
if (name === "turns") return [formatNumber(Number(value)), "Turns"]
return [String(value), String(name)]
}}
labelFormatter={(_label, payload) =>
(payload[0]?.payload as Record<string, string>)?.full ?? String(_label)
}
contentStyle={{
backgroundColor: "hsl(var(--card))",
borderColor: "hsl(var(--border))",
borderRadius: "8px",
fontSize: "12px",
}}
/>
<Bar
dataKey="turns"
fill="#8b5cf6"
radius={[0, 4, 4, 0]}
barSize={20}
isAnimationActive={false}
/>
</BarChart>
</ResponsiveContainer>
)
}
8 changes: 5 additions & 3 deletions console/src/components/layout/sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
Gauge,
BarChart3,
AlertTriangle,
Cpu,
Server,
Sparkles,
MessageSquare,
MessagesSquare,
Expand All @@ -22,9 +22,11 @@ const TOOLBAR_KEYS = ["preset", "start", "end", "wire_api", "model", "server_ip"
const navItems = [
{ to: "/", icon: LayoutDashboard, label: "Overview" },
{ to: "/performance", icon: Gauge, label: "Performance" },
{ to: "/traffic", icon: BarChart3, label: "Traffic" },
// Models view is now a tab inside Services; route /models still
// resolves for shared links but the sidebar entry was redundant.
{ to: "/traffic", icon: BarChart3, label: "Usage" },
{ to: "/errors", icon: AlertTriangle, label: "Errors" },
{ to: "/models", icon: Cpu, label: "Models" },
{ to: "/services", icon: Server, label: "Services" },
{ to: "/agent-sessions", icon: MessageSquare, label: "Agent Sessions" },
{ to: "/agent-turns", icon: MessagesSquare, label: "Agent Turns" },
{ to: "/llm-calls", icon: Sparkles, label: "LLM Calls" },
Expand Down
Loading
Loading