@mastra/playground-ui 21.0.0 → 21.0.1-alpha.1

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 CHANGED
@@ -1,5 +1,45 @@
1
1
  # @mastra/playground-ui
2
2
 
3
+ ## 21.0.1-alpha.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated scores metrics to use aggregate and time series APIs instead of fetching raw scores. Scores Over Time chart now always shows hourly data points regardless of the selected time range. Avg Score KPI uses weighted averages via the aggregate API. Updated metrics card layout to use flexible row heights and refined chart legend styling. ([#14937](https://github.com/mastra-ai/mastra/pull/14937))
8
+
9
+ - Fixed deep links for scorer, observability, and agent trace dialogs. ([#14970](https://github.com/mastra-ai/mastra/pull/14970))
10
+
11
+ Developers can now share and reload URLs that keep the selected trace, scoring tab, span, and score in Studio.
12
+
13
+ **Before**
14
+ `/observability`
15
+ `/agents/chef-agent/traces`
16
+ `/evaluation/scorers/response-quality`
17
+
18
+ **After**
19
+ `/observability?traceId=...&spanId=...&tab=scores&scoreId=...`
20
+ `/agents/chef-agent/traces?traceId=...&spanId=...&tab=scores&scoreId=...`
21
+ `/evaluation/scorers/response-quality?entity=...&scoreId=...`
22
+
23
+ This makes review links reliable across the scorer page, observability, and the agent traces view.
24
+
25
+ - Fixed light theme visibility for metrics charts: CartesianGrid lines now adapt to the current theme, and HorizontalBars use reduced opacity in light mode so labels remain readable. Updated MetricsCard minimum height. ([#14975](https://github.com/mastra-ai/mastra/pull/14975))
26
+
27
+ - Updated dependencies [[`81e4259`](https://github.com/mastra-ai/mastra/commit/81e425939b4ceeb4f586e9b6d89c3b1c1f2d2fe7), [`951b8a1`](https://github.com/mastra-ai/mastra/commit/951b8a1b5ef7e1474c59dc4f2b9fc1a8b1e508b6)]:
28
+ - @mastra/core@1.22.0-alpha.1
29
+ - @mastra/client-js@1.12.1-alpha.1
30
+ - @mastra/react@0.2.22-alpha.1
31
+
32
+ ## 21.0.1-alpha.0
33
+
34
+ ### Patch Changes
35
+
36
+ - Added gateway memory indicator in Studio agent memory panel. When an agent uses gateway-backed memory, the panel hides local-only sections (search, working memory, config) and shows a "Memory Gateway" badge instead. ([#14952](https://github.com/mastra-ai/mastra/pull/14952))
37
+
38
+ - Updated dependencies [[`2b4ea10`](https://github.com/mastra-ai/mastra/commit/2b4ea10b053e4ea1ab232d536933a4a3c4cba999), [`c8c86aa`](https://github.com/mastra-ai/mastra/commit/c8c86aa1458017fbd1c0776fdc0c520d129df8a6), [`a0544f0`](https://github.com/mastra-ai/mastra/commit/a0544f0a1e6bd52ac12676228967c1938e43648d), [`c8c86aa`](https://github.com/mastra-ai/mastra/commit/c8c86aa1458017fbd1c0776fdc0c520d129df8a6)]:
39
+ - @mastra/core@1.22.0-alpha.0
40
+ - @mastra/client-js@1.12.1-alpha.0
41
+ - @mastra/react@0.2.22-alpha.0
42
+
3
43
  ## 21.0.0
4
44
 
5
45
  ### Patch Changes
package/dist/index.cjs.js CHANGED
@@ -14644,6 +14644,14 @@ const GroqIcon = (props) => /* @__PURE__ */ jsxRuntime.jsxs(
14644
14644
  }
14645
14645
  );
14646
14646
 
14647
+ const MastraIcon = (props) => /* @__PURE__ */ jsxRuntime.jsx("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 34 21", fill: "none", ...props, children: /* @__PURE__ */ jsxRuntime.jsx(
14648
+ "path",
14649
+ {
14650
+ d: "M4.49805 11.6934C6.98237 11.6934 8.99609 13.7081 8.99609 16.1924C8.9959 18.6765 6.98225 20.6904 4.49805 20.6904C2.01394 20.6903 0.000196352 18.6765 0 16.1924C0 13.7081 2.01382 11.6935 4.49805 11.6934ZM10.3867 0C12.8709 0 14.8846 2.01388 14.8848 4.49805C14.8848 4.8377 14.847 5.16846 14.7755 5.48643C14.4618 6.88139 14.1953 8.4633 14.9928 9.65L16.2575 11.5319C16.3363 11.6491 16.4727 11.7115 16.6137 11.703C16.7369 11.6957 16.8525 11.6343 16.9214 11.5318L18.1876 9.64717C18.9772 8.47198 18.7236 6.90783 18.4205 5.52484C18.3523 5.21392 18.3164 4.89094 18.3164 4.55957C18.3167 2.07546 20.3313 0.0615234 22.8154 0.0615234C25.2994 0.0617476 27.3132 2.0756 27.3135 4.55957C27.3135 4.93883 27.2665 5.30712 27.178 5.65896C26.8547 6.94441 26.5817 8.37932 27.2446 9.52714L28.459 11.6301C28.4819 11.6697 28.5245 11.6934 28.5703 11.6934C31.0545 11.6935 33.0684 13.7081 33.0684 16.1924C33.0682 18.6765 31.0544 20.6903 28.5703 20.6904C26.0861 20.6904 24.0725 18.6765 24.0723 16.1924C24.0723 15.8049 24.1212 15.4288 24.2133 15.0701C24.5458 13.7746 24.8298 12.3251 24.1609 11.1668L23.0044 9.16384C22.9656 9.09659 22.8931 9.05859 22.8154 9.05859C22.7983 9.05859 22.7824 9.06614 22.7728 9.08033L21.4896 10.9895C20.686 12.1851 20.9622 13.781 21.284 15.1851C21.3582 15.5089 21.3975 15.8461 21.3975 16.1924C21.3973 18.6764 19.3834 20.6902 16.8994 20.6904C14.4152 20.6904 12.4006 18.6765 12.4004 16.1924C12.4004 15.932 12.4226 15.6768 12.4651 15.4286C12.6859 14.14 12.8459 12.7122 12.1167 11.6271L11.2419 10.3253C10.6829 9.49347 9.71913 9.05932 8.78286 8.70188C7.0906 8.05584 5.88867 6.41734 5.88867 4.49805C5.88886 2.0139 7.90254 3.29835e-05 10.3867 0Z",
14651
+ fill: props.fill || "currentColor"
14652
+ }
14653
+ ) });
14654
+
14647
14655
  const MistralIcon = (props) => /* @__PURE__ */ jsxRuntime.jsxs(
14648
14656
  "svg",
14649
14657
  {
@@ -14878,7 +14886,8 @@ const providerMapToIcon = {
14878
14886
  GROQ: /* @__PURE__ */ jsxRuntime.jsx(GroqIcon, {}),
14879
14887
  X_GROK: /* @__PURE__ */ jsxRuntime.jsx(XGroqIcon, {}),
14880
14888
  MISTRAL: /* @__PURE__ */ jsxRuntime.jsx(MistralIcon, {}),
14881
- netlify: /* @__PURE__ */ jsxRuntime.jsx(NetlifyIcon, {})
14889
+ netlify: /* @__PURE__ */ jsxRuntime.jsx(NetlifyIcon, {}),
14890
+ mastra: /* @__PURE__ */ jsxRuntime.jsx(MastraIcon, {})
14882
14891
  };
14883
14892
 
14884
14893
  const cleanProviderId$1 = (providerId) => {
@@ -14902,12 +14911,13 @@ const ProviderLogo$1 = ({ providerId, className = "", size = 20 }) => {
14902
14911
  perplexity: "perplexity",
14903
14912
  fireworks_ai: "fireworks",
14904
14913
  openrouter: "openrouter",
14905
- netlify: "netlify"
14914
+ netlify: "netlify",
14915
+ mastra: "mastra"
14906
14916
  };
14907
14917
  return iconMap[id] || "DEFAULT";
14908
14918
  };
14909
14919
  const fallbackIcon = getFallbackProviderIcon(cleanedProviderId);
14910
- const isGateway = ["netlify"].includes(cleanProviderId);
14920
+ const isGateway = ["netlify", "mastra"].includes(cleanProviderId);
14911
14921
  if (isGateway || imageError || !providerId) {
14912
14922
  if (providerMapToIcon[fallbackIcon]) {
14913
14923
  return /* @__PURE__ */ jsxRuntime.jsx(Icon, { children: providerMapToIcon[fallbackIcon] });
@@ -15550,12 +15560,13 @@ const ProviderLogo = ({ providerId, className = "", size = 20 }) => {
15550
15560
  perplexity: "perplexity",
15551
15561
  fireworks_ai: "fireworks",
15552
15562
  openrouter: "openrouter",
15553
- netlify: "netlify"
15563
+ netlify: "netlify",
15564
+ mastra: "mastra"
15554
15565
  };
15555
15566
  return iconMap[id] || "DEFAULT";
15556
15567
  };
15557
15568
  const fallbackIcon = getFallbackProviderIcon(cleanedProviderId);
15558
- const isGateway = ["netlify"].includes(cleanProviderId$1);
15569
+ const isGateway = ["netlify", "mastra"].includes(cleanProviderId$1);
15559
15570
  if (isGateway || imageError || !providerId) {
15560
15571
  if (providerMapToIcon[fallbackIcon]) {
15561
15572
  return /* @__PURE__ */ jsxRuntime.jsx(Icon, { children: providerMapToIcon[fallbackIcon] });
@@ -19763,8 +19774,9 @@ function ScoreDialog({
19763
19774
  usageContext = "scorerPage"
19764
19775
  }) {
19765
19776
  const [datasetDialogOpen, setDatasetDialogOpen] = React.useState(false);
19766
- const { Link } = useLinkComponent();
19777
+ const { Link, paths } = useLinkComponent();
19767
19778
  const isCodeBased = isCodeBasedScorer$1(score);
19779
+ const scorerDetailHref = score?.scorerId && score?.entityId ? `${paths.scorerLink(score.scorerId)}?entity=${encodeURIComponent(score.entityId)}&scoreId=${encodeURIComponent(score.id)}` : void 0;
19768
19780
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
19769
19781
  /* @__PURE__ */ jsxRuntime.jsxs(
19770
19782
  SideDialog,
@@ -19827,7 +19839,7 @@ function ScoreDialog({
19827
19839
  ...usageContext === "SpanDialog" ? [
19828
19840
  {
19829
19841
  label: "Scorer",
19830
- value: score?.scorer?.name || "-",
19842
+ value: scorerDetailHref ? /* @__PURE__ */ jsxRuntime.jsx(Link, { href: scorerDetailHref, children: score?.scorer?.name || "-" }) : score?.scorer?.name || "-",
19831
19843
  key: "scorer-name"
19832
19844
  }
19833
19845
  ] : [],
@@ -23265,11 +23277,11 @@ const AgentObservationalMemory = ({ agentId, resourceId, threadId }) => {
23265
23277
  return threshold.min;
23266
23278
  };
23267
23279
  const isAdaptiveMode = omAgentConfig?.messageTokens !== void 0 && typeof omAgentConfig.messageTokens !== "number";
23268
- const totalBudget = isAdaptiveMode ? getThresholdValue(omAgentConfig?.messageTokens, 1e4) : 0;
23269
- const baseMessageTokens = isAdaptiveMode ? getBaseThresholdValue(omAgentConfig?.messageTokens, 1e4) : void 0;
23270
- const baseObservationTokens = isAdaptiveMode ? getBaseThresholdValue(omAgentConfig?.observationTokens, 3e4) : void 0;
23271
- const messageTokensThreshold = streamProgress?.windows?.active?.messages?.threshold ?? recordConfig?.observation?.messageTokens ?? getThresholdValue(omAgentConfig?.messageTokens, 1e4);
23272
- const configObservationTokens = getThresholdValue(omAgentConfig?.observationTokens, 3e4);
23280
+ const totalBudget = isAdaptiveMode ? getThresholdValue(omAgentConfig?.messageTokens, 3e4) : 0;
23281
+ const baseMessageTokens = isAdaptiveMode ? getBaseThresholdValue(omAgentConfig?.messageTokens, 3e4) : void 0;
23282
+ const baseObservationTokens = isAdaptiveMode ? getBaseThresholdValue(omAgentConfig?.observationTokens, 4e4) : void 0;
23283
+ const messageTokensThreshold = streamProgress?.windows?.active?.messages?.threshold ?? recordConfig?.observation?.messageTokens ?? getThresholdValue(omAgentConfig?.messageTokens, 3e4);
23284
+ const configObservationTokens = getThresholdValue(omAgentConfig?.observationTokens, 4e4);
23273
23285
  const observationTokensThreshold = streamProgress?.windows?.active?.observations?.threshold ?? recordConfig?.reflection?.observationTokens ?? configObservationTokens;
23274
23286
  const pendingMessageTokens = streamProgress?.windows?.active?.messages?.tokens ?? record?.pendingMessageTokens ?? 0;
23275
23287
  const observationTokenCount = streamProgress?.windows?.active?.observations?.tokens ?? record?.observationTokenCount ?? 0;
@@ -23900,7 +23912,8 @@ const MemorySearch = ({
23900
23912
  ] });
23901
23913
  };
23902
23914
 
23903
- function AgentMemory({ agentId, threadId }) {
23915
+ function AgentMemory({ agentId, threadId, memoryType }) {
23916
+ const isGatewayMemory = memoryType === "gateway";
23904
23917
  const { threadInput: chatInputValue } = useThreadInput();
23905
23918
  const { paths, navigate } = useLinkComponent();
23906
23919
  const { data: thread } = useThread({ threadId, agentId });
@@ -23964,7 +23977,7 @@ function AgentMemory({ agentId, threadId }) {
23964
23977
  ] })
23965
23978
  ] }) }),
23966
23979
  isOMEnabled && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-b border-border1 min-w-0 overflow-hidden", children: /* @__PURE__ */ jsxRuntime.jsx(AgentObservationalMemory, { agentId, resourceId: effectiveResourceId, threadId }) }),
23967
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-4 border-b border-border1", children: [
23980
+ !isGatewayMemory && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-4 border-b border-border1", children: [
23968
23981
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-2", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 mb-2", children: [
23969
23982
  /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-sm font-medium text-neutral5", children: "Semantic Recall" }),
23970
23983
  searchMemoryData?.searchScope && /* @__PURE__ */ jsxRuntime.jsx(
@@ -24005,10 +24018,17 @@ function AgentMemory({ agentId, threadId }) {
24005
24018
  )
24006
24019
  ] })
24007
24020
  ] }),
24008
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 overflow-y-auto", children: [
24021
+ !isGatewayMemory && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 overflow-y-auto", children: [
24009
24022
  /* @__PURE__ */ jsxRuntime.jsx(AgentWorkingMemory, { agentId }),
24010
24023
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-t border-border1", children: /* @__PURE__ */ jsxRuntime.jsx(AgentMemoryConfig, { agentId }) })
24011
- ] })
24024
+ ] }),
24025
+ isGatewayMemory && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-4 border-b border-border1", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-surface3 border border-border1 rounded-lg p-4", children: [
24026
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 mb-1", children: [
24027
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-medium px-2 py-0.5 rounded bg-green-500/20 text-green-400", children: "Gateway" }),
24028
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-sm font-medium text-neutral5", children: "Memory Gateway" })
24029
+ ] }),
24030
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-neutral3", children: "Memory is managed by the Memory Gateway. Threads and observations are stored remotely." })
24031
+ ] }) })
24012
24032
  ] });
24013
24033
  }
24014
24034
 
@@ -24033,7 +24053,7 @@ function AgentInformation({ agentId, threadId }) {
24033
24053
  /* @__PURE__ */ jsxRuntime.jsx(TabContent, { value: "overview", children: /* @__PURE__ */ jsxRuntime.jsx(AgentMetadata, { agentId }) }),
24034
24054
  /* @__PURE__ */ jsxRuntime.jsx(TabContent, { value: "model-settings", children: /* @__PURE__ */ jsxRuntime.jsx(AgentSettings, { agentId }) }),
24035
24055
  agent?.requestContextSchema && /* @__PURE__ */ jsxRuntime.jsx(TabContent, { value: "request-context", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-5", children: /* @__PURE__ */ jsxRuntime.jsx(RequestContextSchemaForm, { requestContextSchema: agent.requestContextSchema }) }) }),
24036
- hasMemory && /* @__PURE__ */ jsxRuntime.jsx(TabContent, { value: "memory", children: /* @__PURE__ */ jsxRuntime.jsx(AgentMemory, { agentId, threadId }) }),
24056
+ hasMemory && /* @__PURE__ */ jsxRuntime.jsx(TabContent, { value: "memory", children: /* @__PURE__ */ jsxRuntime.jsx(AgentMemory, { agentId, threadId, memoryType: memory?.memoryType }) }),
24037
24057
  /* @__PURE__ */ jsxRuntime.jsx(TabContent, { value: "tracing-options", children: /* @__PURE__ */ jsxRuntime.jsx(TracingRunOptions, {}) })
24038
24058
  ] }) })
24039
24059
  ] });
@@ -34467,7 +34487,10 @@ function TraceDialog({
34467
34487
  if (initialSpanId) {
34468
34488
  setSelectedSpanId(initialSpanId);
34469
34489
  setDialogIsOpen(true);
34490
+ return;
34470
34491
  }
34492
+ setSelectedSpanId(void 0);
34493
+ setDialogIsOpen(false);
34471
34494
  }, [initialSpanId]);
34472
34495
  React.useEffect(() => {
34473
34496
  if (spanScoresPage > 0) {
@@ -34498,11 +34521,17 @@ function TraceDialog({
34498
34521
  });
34499
34522
  const handleSpanClick = (id) => {
34500
34523
  if (selectedSpanId === id) {
34524
+ if (traceId) {
34525
+ navigate(computeTraceLink(traceId));
34526
+ }
34501
34527
  setSelectedSpanId(void 0);
34502
- } else {
34503
- setSelectedSpanId(id);
34504
- setSpanDialogDefaultTab("details");
34505
- setDialogIsOpen(true);
34528
+ return;
34529
+ }
34530
+ setSelectedSpanId(id);
34531
+ setSpanDialogDefaultTab("details");
34532
+ setDialogIsOpen(true);
34533
+ if (traceId) {
34534
+ navigate(computeTraceLink(traceId, id));
34506
34535
  }
34507
34536
  };
34508
34537
  const handleToScoring = () => {
@@ -39735,11 +39764,31 @@ function BulkAddToDatasetBar({
39735
39764
  ] })
39736
39765
  ] });
39737
39766
  }
39738
- function AgentTracesPanel({ agentId }) {
39767
+ function AgentTracesPanel({
39768
+ agentId,
39769
+ basePath,
39770
+ initialTraceId,
39771
+ initialSpanId,
39772
+ initialSpanTab,
39773
+ initialScoreId
39774
+ }) {
39739
39775
  const client = react.useMastraClient();
39740
39776
  const filters = useAgentTracesFilters(agentId);
39741
- const [selectedTraceId, setSelectedTraceId] = React.useState();
39742
- const [dialogIsOpen, setDialogIsOpen] = React.useState(false);
39777
+ const { navigate } = useLinkComponent();
39778
+ const buildTraceUrl = React.useCallback(
39779
+ (traceId, spanId, scoreId, tab) => {
39780
+ const params = new URLSearchParams();
39781
+ if (traceId) params.set("traceId", traceId);
39782
+ if (spanId) params.set("spanId", spanId);
39783
+ if (tab) params.set("tab", tab);
39784
+ if (scoreId) params.set("scoreId", scoreId);
39785
+ const query = params.toString();
39786
+ return query ? `${basePath ?? `/agents/${agentId}/traces`}?${query}` : basePath ?? `/agents/${agentId}/traces`;
39787
+ },
39788
+ [agentId, basePath]
39789
+ );
39790
+ const [selectedTraceId, setSelectedTraceId] = React.useState(initialTraceId);
39791
+ const [dialogIsOpen, setDialogIsOpen] = React.useState(Boolean(initialTraceId));
39743
39792
  const [checkedTraceIds, setCheckedTraceIds] = React.useState(/* @__PURE__ */ new Set());
39744
39793
  const [sort, setSort] = React.useState(null);
39745
39794
  const {
@@ -39852,20 +39901,31 @@ function AgentTracesPanel({ agentId }) {
39852
39901
  return next.size === prev.size ? prev : next;
39853
39902
  });
39854
39903
  }, [displayTraces, checkedTraceIds.size]);
39904
+ React.useEffect(() => {
39905
+ if (initialTraceId) {
39906
+ setSelectedTraceId(initialTraceId);
39907
+ setDialogIsOpen(true);
39908
+ return;
39909
+ }
39910
+ setSelectedTraceId(void 0);
39911
+ setDialogIsOpen(false);
39912
+ }, [initialTraceId]);
39855
39913
  const { batchInsertItems } = useDatasetMutations();
39856
39914
  const allSelected = displayTraces.length > 0 && displayTraces.every((t) => checkedTraceIds.has(t.traceId));
39857
39915
  const someSelected = checkedTraceIds.size > 0;
39858
39916
  const handleTraceClick = React.useCallback(
39859
39917
  (traceId) => {
39860
39918
  if (selectedTraceId === traceId) {
39919
+ navigate(buildTraceUrl());
39861
39920
  setSelectedTraceId(void 0);
39862
39921
  setDialogIsOpen(false);
39863
39922
  } else {
39923
+ navigate(buildTraceUrl(traceId));
39864
39924
  setSelectedTraceId(traceId);
39865
39925
  setDialogIsOpen(true);
39866
39926
  }
39867
39927
  },
39868
- [selectedTraceId]
39928
+ [buildTraceUrl, navigate, selectedTraceId]
39869
39929
  );
39870
39930
  const handleCheckToggle = React.useCallback((traceId, checked) => {
39871
39931
  setCheckedTraceIds((prev) => {
@@ -39904,14 +39964,16 @@ function AgentTracesPanel({ agentId }) {
39904
39964
  },
39905
39965
  [displayTraces, checkedTraceIds, batchInsertItems]
39906
39966
  );
39907
- const computeTraceLink = React.useCallback((traceId, spanId) => {
39908
- return `/observability?traceId=${traceId}${spanId ? `&spanId=${spanId}` : ""}`;
39909
- }, []);
39967
+ const computeTraceLink = React.useCallback(
39968
+ (traceId, spanId, tab) => buildTraceUrl(traceId, spanId, void 0, tab),
39969
+ [buildTraceUrl]
39970
+ );
39910
39971
  const toNextTrace = React.useMemo(
39911
39972
  () => getToNextEntryFn({
39912
39973
  entries: displayTraces.map((t) => ({ id: t.traceId })),
39913
39974
  id: selectedTraceId,
39914
39975
  update: (id) => {
39976
+ navigate(buildTraceUrl(id));
39915
39977
  setSelectedTraceId(id);
39916
39978
  setDialogIsOpen(true);
39917
39979
  }
@@ -39923,6 +39985,7 @@ function AgentTracesPanel({ agentId }) {
39923
39985
  entries: displayTraces.map((t) => ({ id: t.traceId })),
39924
39986
  id: selectedTraceId,
39925
39987
  update: (id) => {
39988
+ navigate(buildTraceUrl(id));
39926
39989
  setSelectedTraceId(id);
39927
39990
  setDialogIsOpen(true);
39928
39991
  }
@@ -40024,6 +40087,7 @@ function AgentTracesPanel({ agentId }) {
40024
40087
  traceDetails: selectedTrace?.spans?.find((s) => s.traceId === selectedTraceId && !s.parentSpanId),
40025
40088
  isOpen: dialogIsOpen,
40026
40089
  onClose: () => {
40090
+ navigate(buildTraceUrl());
40027
40091
  setDialogIsOpen(false);
40028
40092
  setSelectedTraceId(void 0);
40029
40093
  },
@@ -40031,6 +40095,9 @@ function AgentTracesPanel({ agentId }) {
40031
40095
  onPrevious: toPreviousTrace,
40032
40096
  isLoadingSpans: isSelectedTraceLoading,
40033
40097
  computeTraceLink,
40098
+ initialSpanId,
40099
+ initialSpanTab,
40100
+ initialScoreId,
40034
40101
  scorers: scorersMap,
40035
40102
  isLoadingScorers
40036
40103
  }
@@ -41887,7 +41954,7 @@ function MetricsCardRoot({ children, className }) {
41887
41954
  DashboardCard,
41888
41955
  {
41889
41956
  className: cn(
41890
- "flex-1 grid grid-rows-[4rem_20rem] gap-2 min-w-80 md:min-w-[22rem] lg:min-w-[24rem] xl:min-w-[26rem] 2xl:min-w-[30rem] 3xl:min-w-[32rem]",
41957
+ "flex-1 grid grid-rows-[4rem_1fr] min-h-72 gap-2 min-w-80 md:min-w-[22rem] lg:min-w-[24rem] xl:min-w-[26rem] 2xl:min-w-[30rem]",
41891
41958
  className
41892
41959
  ),
41893
41960
  children
@@ -42038,12 +42105,12 @@ function MetricsLineChart({
42038
42105
  yDomain
42039
42106
  }) {
42040
42107
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
42041
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap w-full items-end gap-4 gap-y-1 mb-4", children: series.map((s) => {
42108
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap w-full items-end gap-4 gap-y-1 mb-4 ", children: series.map((s) => {
42042
42109
  const aggregated = s.aggregate?.(data);
42043
42110
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "inline-flex items-baseline gap-2", children: [
42044
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "size-2 shrink-0 rounded-full translate-y-[-1px]", style: { backgroundColor: s.color } }),
42111
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "size-2 shrink-0 rounded-full -translate-y-px", style: { backgroundColor: s.color } }),
42045
42112
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-ui-sm text-neutral3 truncate max-w-24", children: s.label }),
42046
- aggregated && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-ui-lg text-neutral4", children: [
42113
+ aggregated && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-ui-sm text-neutral4", children: [
42047
42114
  aggregated.value,
42048
42115
  aggregated.suffix && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-ui-sm text-neutral2", children: [
42049
42116
  " ",
@@ -42053,7 +42120,15 @@ function MetricsLineChart({
42053
42120
  ] }, s.dataKey);
42054
42121
  }) }),
42055
42122
  /* @__PURE__ */ jsxRuntime.jsx("div", { style: { height }, children: /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxRuntime.jsxs(recharts.LineChart, { data, children: [
42056
- /* @__PURE__ */ jsxRuntime.jsx(recharts.CartesianGrid, { stroke: "rgba(255,255,255,0.08)", vertical: false }),
42123
+ /* @__PURE__ */ jsxRuntime.jsx(
42124
+ recharts.CartesianGrid,
42125
+ {
42126
+ stroke: "currentColor",
42127
+ strokeOpacity: 0.08,
42128
+ vertical: false,
42129
+ className: "text-black dark:text-white"
42130
+ }
42131
+ ),
42057
42132
  /* @__PURE__ */ jsxRuntime.jsx(
42058
42133
  recharts.XAxis,
42059
42134
  {
@@ -42164,24 +42239,25 @@ function useAvgScoreKpiMetrics() {
42164
42239
  if (scorerIds.length === 0) {
42165
42240
  return { value: null, previousValue: null, changePercent: null };
42166
42241
  }
42167
- const allResults = await Promise.all(
42168
- scorerIds.map((scorerId) => client.listScoresByScorerId({ scorerId, perPage: 100 }))
42242
+ const filters = {
42243
+ timestamp: { start: timestamp.start, end: timestamp.end }
42244
+ };
42245
+ const results = await Promise.all(
42246
+ scorerIds.map(async (scorerId) => {
42247
+ const [avg2, count] = await Promise.all([
42248
+ client.getScoreAggregate({ scorerId, aggregation: "avg", filters }),
42249
+ client.getScoreAggregate({ scorerId, aggregation: "count", filters })
42250
+ ]);
42251
+ return { avg: avg2.value ?? 0, count: count.value ?? 0 };
42252
+ })
42169
42253
  );
42170
- const startMs = timestamp.start.getTime();
42171
- const endMs = timestamp.end.getTime();
42172
- const allScoreValues = [];
42173
- for (const result of allResults) {
42174
- for (const s of result?.scores ?? []) {
42175
- const ts = new Date(s.createdAt).getTime();
42176
- if (ts >= startMs && ts <= endMs) {
42177
- allScoreValues.push(s.score);
42178
- }
42179
- }
42180
- }
42181
- if (allScoreValues.length === 0) {
42254
+ const withData = results.filter((r) => r.count > 0);
42255
+ if (withData.length === 0) {
42182
42256
  return { value: null, previousValue: null, changePercent: null };
42183
42257
  }
42184
- const avg = allScoreValues.reduce((sum, v) => sum + v, 0) / allScoreValues.length;
42258
+ const totalCount = withData.reduce((sum, r) => sum + r.count, 0);
42259
+ const weightedSum = withData.reduce((sum, r) => sum + r.avg * r.count, 0);
42260
+ const avg = weightedSum / totalCount;
42185
42261
  return { value: Math.round(avg * 100) / 100, previousValue: null, changePercent: null };
42186
42262
  }
42187
42263
  });
@@ -42460,78 +42536,84 @@ function useScoresMetrics() {
42460
42536
  return reactQuery.useQuery({
42461
42537
  queryKey: ["metrics", "scores-card", datePreset, customRange],
42462
42538
  queryFn: async () => {
42539
+ const filters = {
42540
+ timestamp: { start: timestamp.start, end: timestamp.end }
42541
+ };
42463
42542
  const scorersMap = await client.listScorers();
42464
42543
  const scorerIds = Object.keys(scorersMap ?? {});
42465
42544
  if (scorerIds.length === 0) {
42466
42545
  return { summaryData: [], overTimeData: [], scorerNames: [], avgScore: null };
42467
42546
  }
42468
- const allResults = await Promise.all(
42469
- // Limited to 100 most recent scores per scorer; pagination not yet implemented
42470
- scorerIds.map((scorerId) => client.listScoresByScorerId({ scorerId, perPage: 100 }))
42547
+ const summaryResults = await Promise.all(
42548
+ scorerIds.map(async (scorerId) => {
42549
+ const [avg, min, max, count] = await Promise.all([
42550
+ client.getScoreAggregate({ scorerId, aggregation: "avg", filters }),
42551
+ client.getScoreAggregate({ scorerId, aggregation: "min", filters }),
42552
+ client.getScoreAggregate({ scorerId, aggregation: "max", filters }),
42553
+ client.getScoreAggregate({ scorerId, aggregation: "count", filters })
42554
+ ]);
42555
+ return {
42556
+ scorer: scorerId,
42557
+ avg: avg.value ?? 0,
42558
+ min: min.value ?? 0,
42559
+ max: max.value ?? 0,
42560
+ count: count.value ?? 0
42561
+ };
42562
+ })
42471
42563
  );
42472
- const startMs = timestamp.start.getTime();
42473
- const endMs = timestamp.end.getTime();
42474
- const allScores = [];
42475
- for (let i = 0; i < scorerIds.length; i++) {
42476
- const scores = allResults[i]?.scores ?? [];
42477
- for (const s of scores) {
42478
- const ts = new Date(s.createdAt).getTime();
42479
- if (ts >= startMs && ts <= endMs) {
42480
- allScores.push({
42481
- scorerId: scorerIds[i],
42482
- score: s.score,
42483
- createdAt: s.createdAt
42484
- });
42485
- }
42486
- }
42487
- }
42488
- if (allScores.length === 0) {
42489
- return { summaryData: [], overTimeData: [], scorerNames: [], avgScore: null };
42490
- }
42491
- const byScorer = /* @__PURE__ */ new Map();
42492
- for (const s of allScores) {
42493
- if (!byScorer.has(s.scorerId)) byScorer.set(s.scorerId, []);
42494
- byScorer.get(s.scorerId).push(s.score);
42495
- }
42496
- const summaryData = Array.from(byScorer.entries()).map(([scorer, vals]) => ({
42497
- scorer,
42498
- avg: vals.reduce((a, b) => a + b, 0) / vals.length,
42499
- min: Math.min(...vals),
42500
- max: Math.max(...vals),
42501
- count: vals.length
42502
- }));
42564
+ const summaryData = summaryResults.filter((s) => s.count > 0);
42503
42565
  const scorerNames = summaryData.map((s) => s.scorer);
42504
- const avgScore = summaryData.reduce((s, d) => s + d.avg, 0) / summaryData.length;
42505
- const bucketMap = /* @__PURE__ */ new Map();
42506
- for (const s of allScores) {
42507
- const ts = new Date(s.createdAt);
42508
- const bucket = Math.floor(ts.getTime() / 36e5) * 36e5;
42509
- if (!bucketMap.has(bucket)) bucketMap.set(bucket, /* @__PURE__ */ new Map());
42510
- const scorerMap = bucketMap.get(bucket);
42511
- if (!scorerMap.has(s.scorerId)) scorerMap.set(s.scorerId, []);
42512
- scorerMap.get(s.scorerId).push(s.score);
42566
+ if (summaryData.length === 0) {
42567
+ return { summaryData: [], overTimeData: [], scorerNames: [], avgScore: null };
42513
42568
  }
42514
- const overTimeData = Array.from(bucketMap.entries()).sort(([a], [b]) => a - b).map(([bucket, scorerMap]) => {
42515
- const point = {
42516
- time: new Date(bucket).toLocaleTimeString("en-US", {
42517
- hour: "2-digit",
42518
- minute: "2-digit",
42519
- hour12: false
42569
+ const totalWeighted = summaryData.reduce((s, d) => s + d.avg * d.count, 0);
42570
+ const totalCount = summaryData.reduce((s, d) => s + d.count, 0);
42571
+ const avgScore = totalCount ? Math.round(totalWeighted / totalCount * 100) / 100 : 0;
42572
+ const interval = "1h";
42573
+ const timeSeriesResults = await Promise.all(
42574
+ scorerNames.map(
42575
+ (scorerId) => client.getScoreTimeSeries({
42576
+ scorerId,
42577
+ interval,
42578
+ aggregation: "avg",
42579
+ filters
42520
42580
  })
42521
- };
42522
- for (const name of scorerNames) {
42523
- const vals = scorerMap.get(name);
42524
- if (vals && vals.length > 0) {
42525
- point[name] = +(vals.reduce((a, b) => a + b, 0) / vals.length).toFixed(2);
42581
+ )
42582
+ );
42583
+ const bucketMap = /* @__PURE__ */ new Map();
42584
+ const rangeSpansDays = timestamp.end.toDateString() !== timestamp.start.toDateString();
42585
+ for (let i = 0; i < scorerNames.length; i++) {
42586
+ const scorerId = scorerNames[i];
42587
+ const series = timeSeriesResults[i]?.series ?? [];
42588
+ for (const s of series) {
42589
+ for (const point of s.points) {
42590
+ const ts = new Date(point.timestamp);
42591
+ const key = ts.toISOString();
42592
+ if (!bucketMap.has(key)) {
42593
+ bucketMap.set(key, {
42594
+ time: rangeSpansDays ? ts.toLocaleString("en-US", {
42595
+ month: "short",
42596
+ day: "numeric",
42597
+ hour: "2-digit",
42598
+ minute: "2-digit",
42599
+ hour12: false
42600
+ }) : ts.toLocaleTimeString("en-US", {
42601
+ hour: "2-digit",
42602
+ minute: "2-digit",
42603
+ hour12: false
42604
+ })
42605
+ });
42606
+ }
42607
+ bucketMap.get(key)[scorerId] = +point.value.toFixed(2);
42526
42608
  }
42527
42609
  }
42528
- return point;
42529
- });
42610
+ }
42611
+ const overTimeData = Array.from(bucketMap.entries()).sort(([a], [b]) => a.localeCompare(b)).map(([, point]) => point);
42530
42612
  return {
42531
42613
  summaryData,
42532
42614
  overTimeData,
42533
42615
  scorerNames,
42534
- avgScore: Math.round(avgScore * 100) / 100
42616
+ avgScore
42535
42617
  };
42536
42618
  }
42537
42619
  });
@@ -42576,7 +42658,7 @@ function ScoresCard() {
42576
42658
  /* @__PURE__ */ jsxRuntime.jsx(Tab, { value: "over-time", children: "Over Time" }),
42577
42659
  /* @__PURE__ */ jsxRuntime.jsx(Tab, { value: "summary", children: "Summary" })
42578
42660
  ] }),
42579
- /* @__PURE__ */ jsxRuntime.jsx(TabContent, { value: "over-time", children: data.overTimeData.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(MetricsLineChart, { data: data.overTimeData, series, yDomain: [0, 1] }) : /* @__PURE__ */ jsxRuntime.jsx(MetricsCard.NoData, { message: "No time series data yet" }) }),
42661
+ /* @__PURE__ */ jsxRuntime.jsx(TabContent, { value: "over-time", className: "pb-0", children: data.overTimeData.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(MetricsLineChart, { data: data.overTimeData, series, yDomain: [0, 1] }) : /* @__PURE__ */ jsxRuntime.jsx(MetricsCard.NoData, { message: "No time series data yet" }) }),
42580
42662
  /* @__PURE__ */ jsxRuntime.jsx(TabContent, { value: "summary", children: /* @__PURE__ */ jsxRuntime.jsx(
42581
42663
  MetricsDataTable,
42582
42664
  {
@@ -42714,7 +42796,7 @@ function HorizontalBars({
42714
42796
  "div",
42715
42797
  {
42716
42798
  className: cn(
42717
- "absolute inset-y-0",
42799
+ "absolute inset-y-0 opacity-40 dark:opacity-100",
42718
42800
  si === 0 && "rounded-l",
42719
42801
  isLastWithValue && "rounded-r"
42720
42802
  ),
@@ -42730,7 +42812,7 @@ function HorizontalBars({
42730
42812
  return /* @__PURE__ */ jsxRuntime.jsx(
42731
42813
  "div",
42732
42814
  {
42733
- className: "absolute inset-y-0 left-0 rounded",
42815
+ className: "absolute inset-y-0 left-0 rounded opacity-40 dark:opacity-100",
42734
42816
  style: { width: `${pct}%`, backgroundColor: seg.color }
42735
42817
  },
42736
42818
  seg.label
@@ -43826,6 +43908,9 @@ function SpanScoreList({
43826
43908
  const score = scoresData?.scores?.find((s) => s?.id === scoreId);
43827
43909
  setSelectedScore(score);
43828
43910
  setDialogIsOpen(true);
43911
+ if (traceId) {
43912
+ navigate(`${computeTraceLink(traceId, spanId)}&tab=scores&scoreId=${encodeURIComponent(scoreId)}`);
43913
+ }
43829
43914
  };
43830
43915
  if (isLoadingScoresData) {
43831
43916
  return /* @__PURE__ */ jsxRuntime.jsx(EntryListSkeleton, { columns: traceScoresListColumns });