@mastra/playground-ui 7.0.0-beta.6 → 7.0.0-beta.8

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
+ ## 7.0.0-beta.8
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [[`0d41fe2`](https://github.com/mastra-ai/mastra/commit/0d41fe245355dfc66d61a0d9c85d9400aac351ff), [`6b3ba91`](https://github.com/mastra-ai/mastra/commit/6b3ba91494cc10394df96782f349a4f7b1e152cc), [`3d3c9e7`](https://github.com/mastra-ai/mastra/commit/3d3c9e7dc0b7b291abe6a11cf3807dd130034961), [`7907fd1`](https://github.com/mastra-ai/mastra/commit/7907fd1c5059813b7b870b81ca71041dc807331b)]:
8
+ - @mastra/core@1.0.0-beta.8
9
+ - @mastra/ai-sdk@1.0.0-beta.6
10
+ - @mastra/client-js@1.0.0-beta.8
11
+ - @mastra/react@0.1.0-beta.8
12
+
13
+ ## 7.0.0-beta.7
14
+
15
+ ### Patch Changes
16
+
17
+ - Add StudioConfig and associated context to manage the headers and base URL of a given Mastra instance. ([#10804](https://github.com/mastra-ai/mastra/pull/10804))
18
+
19
+ This also introduces a header form available from the side bar to edit those headers.
20
+
21
+ - Fix select options overflow when list is long by adding maximum height ([#10813](https://github.com/mastra-ai/mastra/pull/10813))
22
+
23
+ - Removed uneeded calls to the message endpoint when the user is on a new thread ([#10872](https://github.com/mastra-ai/mastra/pull/10872))
24
+
25
+ - Add prominent warning banner in observability UI when token limits are exceeded (finishReason: 'length'). ([#10835](https://github.com/mastra-ai/mastra/pull/10835))
26
+
27
+ When a model stops generating due to token limits, the span details now display:
28
+ - Clear warning with alert icon
29
+ - Detailed token usage breakdown (input + output = total)
30
+ - Explanation that the response was truncated
31
+
32
+ This helps developers quickly identify and debug token limit issues in the observation page.
33
+
34
+ Fixes #8828
35
+
36
+ - Add a specific page for the studio settings and moved the headers configuration form here instead of in a dialog ([#10812](https://github.com/mastra-ai/mastra/pull/10812))
37
+
38
+ - Updated dependencies [[`3076c67`](https://github.com/mastra-ai/mastra/commit/3076c6778b18988ae7d5c4c5c466366974b2d63f), [`85d7ee1`](https://github.com/mastra-ai/mastra/commit/85d7ee18ff4e14d625a8a30ec6656bb49804989b), [`c6c1092`](https://github.com/mastra-ai/mastra/commit/c6c1092f8fbf76109303f69e000e96fd1960c4ce), [`81dc110`](https://github.com/mastra-ai/mastra/commit/81dc11008d147cf5bdc8996ead1aa61dbdebb6fc), [`7aedb74`](https://github.com/mastra-ai/mastra/commit/7aedb74883adf66af38e270e4068fd42e7a37036), [`8f02d80`](https://github.com/mastra-ai/mastra/commit/8f02d800777397e4b45d7f1ad041988a8b0c6630), [`d7aad50`](https://github.com/mastra-ai/mastra/commit/d7aad501ce61646b76b4b511e558ac4eea9884d0), [`ce0a73a`](https://github.com/mastra-ai/mastra/commit/ce0a73abeaa75b10ca38f9e40a255a645d50ebfb), [`a02e542`](https://github.com/mastra-ai/mastra/commit/a02e542d23179bad250b044b17ff023caa61739f), [`a372c64`](https://github.com/mastra-ai/mastra/commit/a372c640ad1fd12e8f0613cebdc682fc156b4d95), [`5fe71bc`](https://github.com/mastra-ai/mastra/commit/5fe71bc925dfce597df69c89241f33b378028c63), [`8846867`](https://github.com/mastra-ai/mastra/commit/8846867ffa9a3746767618e314bebac08eb77d87), [`42a42cf`](https://github.com/mastra-ai/mastra/commit/42a42cf3132b9786feecbb8c13c583dce5b0e198), [`ae08bf0`](https://github.com/mastra-ai/mastra/commit/ae08bf0ebc6a4e4da992b711c4a389c32ba84cf4), [`21735a7`](https://github.com/mastra-ai/mastra/commit/21735a7ef306963554a69a89b44f06c3bcd85141), [`1d877b8`](https://github.com/mastra-ai/mastra/commit/1d877b8d7b536a251c1a7a18db7ddcf4f68d6f8b)]:
39
+ - @mastra/core@1.0.0-beta.7
40
+ - @mastra/client-js@1.0.0-beta.7
41
+ - @mastra/react@0.1.0-beta.7
42
+
3
43
  ## 7.0.0-beta.6
4
44
 
5
45
  ### Patch Changes
package/dist/index.cjs.js CHANGED
@@ -6660,7 +6660,7 @@ const SelectContent = React__namespace.forwardRef(({ className, children, positi
6660
6660
  {
6661
6661
  ref,
6662
6662
  className: cn(
6663
- "relative z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
6663
+ "relative z-50 min-w-[8rem] max-h-dropdown-max-height overflow-y-auto overflow-x-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
6664
6664
  position === "popper" && "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
6665
6665
  className
6666
6666
  ),
@@ -9627,7 +9627,7 @@ function Combobox({
9627
9627
  ref: listRef,
9628
9628
  id: "combobox-options",
9629
9629
  role: "listbox",
9630
- className: "max-h-[300px] overflow-y-auto overflow-x-hidden p-1",
9630
+ className: "max-h-dropdown-max-height overflow-y-auto overflow-x-hidden p-1",
9631
9631
  children: filteredOptions.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "py-6 text-center text-sm", children: emptyText }) : filteredOptions.map((option, index) => {
9632
9632
  const isSelected = value === option.value;
9633
9633
  const isHighlighted = index === highlightedIndex;
@@ -9836,16 +9836,12 @@ const AgentBadge = ({ agentId, messages = [], metadata, toolCallId, toolApproval
9836
9836
  );
9837
9837
  };
9838
9838
 
9839
- const useAgentMessages = ({
9840
- threadId,
9841
- agentId,
9842
- memory
9843
- }) => {
9839
+ const useAgentMessages = ({ threadId, agentId, memory }) => {
9844
9840
  const client = react$1.useMastraClient();
9845
9841
  const { requestContext } = usePlaygroundStore();
9846
9842
  return reactQuery.useQuery({
9847
9843
  queryKey: ["memory", "messages", threadId, agentId, "requestContext"],
9848
- queryFn: () => client.listThreadMessages(threadId, { agentId, requestContext }),
9844
+ queryFn: () => threadId ? client.listThreadMessages(threadId, { agentId, requestContext }) : null,
9849
9845
  enabled: memory && Boolean(threadId),
9850
9846
  staleTime: 0,
9851
9847
  gcTime: 0,
@@ -9862,7 +9858,7 @@ const AgentBadgeWrapper = ({
9862
9858
  toolApprovalMetadata
9863
9859
  }) => {
9864
9860
  const { data } = useAgentMessages({
9865
- threadId: result?.subAgentThreadId ?? "",
9861
+ threadId: result?.subAgentThreadId,
9866
9862
  agentId,
9867
9863
  memory: true
9868
9864
  });
@@ -12062,13 +12058,15 @@ const AgentChat = ({
12062
12058
  refreshThreadList,
12063
12059
  modelVersion,
12064
12060
  modelList,
12065
- messageId
12061
+ messageId,
12062
+ isNewThread
12066
12063
  }) => {
12067
12064
  const { settings } = useAgentSettings();
12068
12065
  const { requestContext } = usePlaygroundStore();
12069
12066
  const { data, isLoading: isMessagesLoading } = useAgentMessages({
12070
12067
  agentId,
12071
- threadId: threadId ?? "",
12068
+ threadId: isNewThread ? void 0 : threadId,
12069
+ // Prevent fetching when thread is new
12072
12070
  memory: memory ?? false
12073
12071
  });
12074
12072
  React.useEffect(() => {
@@ -12848,7 +12846,7 @@ function InputField({
12848
12846
  className: cn("text-[0.8125rem] text-icon3 flex justify-between items-center"),
12849
12847
  children: [
12850
12848
  label,
12851
- required && /* @__PURE__ */ jsxRuntime.jsx("i", { className: "text-icon2", children: "(required)" })
12849
+ required && /* @__PURE__ */ jsxRuntime.jsx("i", { className: "text-icon2 text-xs", children: "(required)" })
12852
12850
  ]
12853
12851
  }
12854
12852
  ) }),
@@ -18280,6 +18278,13 @@ function getSpanInfo({ span, withTraceId = true, withSpanId = true }) {
18280
18278
  value: span?.endedAt ? dateFns.format(new Date(span.endedAt), "MMM dd, h:mm:ss.SSS aaa") : "-"
18281
18279
  }
18282
18280
  ];
18281
+ if (span?.attributes?.finishReason) {
18282
+ baseInfo.push({
18283
+ key: "finishReason",
18284
+ label: "Finish Reason",
18285
+ value: span.attributes.finishReason
18286
+ });
18287
+ }
18283
18288
  if (withSpanId) {
18284
18289
  baseInfo.unshift({
18285
18290
  key: "spanId",
@@ -18366,11 +18371,38 @@ const Tabs = Object.assign(TabsRoot, {
18366
18371
  Content: TabContent
18367
18372
  });
18368
18373
 
18374
+ function isTokenLimitExceeded(span) {
18375
+ return span?.attributes?.finishReason === "length";
18376
+ }
18377
+ function getTokenLimitMessage(span) {
18378
+ const usage = span?.attributes?.usage;
18379
+ if (!usage) {
18380
+ return `The model stopped generating because it reached the maximum token limit. The response was truncated and may be incomplete.`;
18381
+ }
18382
+ const inputTokens = usage.inputTokens ?? 0;
18383
+ const outputTokens = usage.outputTokens ?? 0;
18384
+ const totalTokens = usage.totalTokens ?? inputTokens + outputTokens;
18385
+ if (inputTokens > 0 || outputTokens > 0) {
18386
+ return `The model stopped generating because it reached the maximum token limit. The response was truncated and may be incomplete.
18387
+
18388
+ Token usage: ${inputTokens} input + ${outputTokens} output = ${totalTokens} total`;
18389
+ }
18390
+ return `The model stopped generating because it reached the maximum token limit (${totalTokens} tokens). The response was truncated and may be incomplete.`;
18391
+ }
18392
+
18369
18393
  function SpanDetails({ span }) {
18370
18394
  if (!span) {
18371
18395
  return null;
18372
18396
  }
18397
+ const tokenLimitExceeded = isTokenLimitExceeded(span);
18373
18398
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
18399
+ tokenLimitExceeded && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-yellow-900/20 border border-yellow-200 rounded-md p-2 mb-4", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start gap-2", children: [
18400
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertTriangleIcon, { className: "text-yellow-200 mt-0.5 flex-shrink-0", size: 20 }),
18401
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1", children: [
18402
+ /* @__PURE__ */ jsxRuntime.jsx("h4", { className: "font-semibold text-yellow-200 mb-1 text-sm", children: "Token Limit Exceeded" }),
18403
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-mastra-el-3 whitespace-pre-line", children: getTokenLimitMessage(span) })
18404
+ ] })
18405
+ ] }) }),
18374
18406
  /* @__PURE__ */ jsxRuntime.jsx(
18375
18407
  SideDialog.CodeSection,
18376
18408
  {
@@ -19697,6 +19729,138 @@ function MCPServerCombobox({
19697
19729
  );
19698
19730
  }
19699
19731
 
19732
+ const StudioConfigContext = React.createContext({
19733
+ baseUrl: "",
19734
+ headers: {},
19735
+ isLoading: false,
19736
+ setConfig: () => {
19737
+ }
19738
+ });
19739
+ const useStudioConfig = () => {
19740
+ return React.useContext(StudioConfigContext);
19741
+ };
19742
+ const LOCAL_STORAGE_KEY = "mastra-studio-config";
19743
+ const StudioConfigProvider = ({ children }) => {
19744
+ const [config, setConfig] = React.useState({
19745
+ baseUrl: "",
19746
+ headers: {},
19747
+ isLoading: true
19748
+ });
19749
+ React.useLayoutEffect(() => {
19750
+ const storedConfig = localStorage.getItem(LOCAL_STORAGE_KEY);
19751
+ if (storedConfig) {
19752
+ const parsedConfig = JSON.parse(storedConfig);
19753
+ if (typeof parsedConfig === "object" && parsedConfig !== null) {
19754
+ return setConfig({ ...parsedConfig, isLoading: false });
19755
+ }
19756
+ }
19757
+ return setConfig({ baseUrl: "", headers: {}, isLoading: false });
19758
+ }, []);
19759
+ const doSetConfig = (partialNewConfig) => {
19760
+ setConfig((prev) => {
19761
+ const nextConfig = { ...prev, ...partialNewConfig };
19762
+ localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(nextConfig));
19763
+ return nextConfig;
19764
+ });
19765
+ };
19766
+ return /* @__PURE__ */ jsxRuntime.jsx(StudioConfigContext.Provider, { value: { ...config, setConfig: doSetConfig }, children });
19767
+ };
19768
+
19769
+ const HeaderListForm = ({ headers, onAddHeader, onRemoveHeader }) => {
19770
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
19771
+ /* @__PURE__ */ jsxRuntime.jsx(Txt, { as: "h2", variant: "header-md", className: "text-icon6", children: "Headers" }),
19772
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-surface4 rounded-lg p-4 border-sm border-border2 space-y-4", children: [
19773
+ headers.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "space-y-4", children: headers.map((header, index) => /* @__PURE__ */ jsxRuntime.jsx("li", { children: /* @__PURE__ */ jsxRuntime.jsx(HeaderListFormItem, { index, header, onRemove: () => onRemoveHeader(index) }) }, index)) }),
19774
+ /* @__PURE__ */ jsxRuntime.jsxs(
19775
+ Button$1,
19776
+ {
19777
+ type: "button",
19778
+ variant: "light",
19779
+ className: "w-full border-dashed !bg-surface4 !border-border2 hover:!bg-surface5",
19780
+ size: "lg",
19781
+ onClick: () => onAddHeader({ name: "", value: "" }),
19782
+ children: [
19783
+ /* @__PURE__ */ jsxRuntime.jsx(Icon, { children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, {}) }),
19784
+ "Add Header"
19785
+ ]
19786
+ }
19787
+ )
19788
+ ] })
19789
+ ] });
19790
+ };
19791
+ const HeaderListFormItem = ({ index, header, onRemove }) => {
19792
+ const nameId = React.useId();
19793
+ const valueId = React.useId();
19794
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-[1fr_1fr_auto] gap-4 items-end", children: [
19795
+ /* @__PURE__ */ jsxRuntime.jsx(
19796
+ InputField,
19797
+ {
19798
+ id: nameId,
19799
+ name: `headers.${index}.name`,
19800
+ label: "Name",
19801
+ placeholder: "e.g. Authorization",
19802
+ required: true,
19803
+ defaultValue: header.name
19804
+ }
19805
+ ),
19806
+ /* @__PURE__ */ jsxRuntime.jsx(
19807
+ InputField,
19808
+ {
19809
+ id: valueId,
19810
+ name: `headers.${index}.value`,
19811
+ label: "Value",
19812
+ placeholder: "e.g. Bearer <token>",
19813
+ required: true,
19814
+ defaultValue: header.value
19815
+ }
19816
+ ),
19817
+ /* @__PURE__ */ jsxRuntime.jsx(
19818
+ Button$1,
19819
+ {
19820
+ type: "button",
19821
+ variant: "light",
19822
+ className: "w-full !bg-surface4 !border-border2 hover:!bg-surface5",
19823
+ size: "lg",
19824
+ onClick: onRemove,
19825
+ children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash, { "aria-label": "Remove header" }) })
19826
+ }
19827
+ )
19828
+ ] });
19829
+ };
19830
+
19831
+ const StudioConfigForm = ({ initialConfig }) => {
19832
+ const { setConfig } = useStudioConfig();
19833
+ const [headers, setHeaders] = React.useState(() => {
19834
+ if (!initialConfig) return [];
19835
+ return Object.entries(initialConfig.headers).map(([name, value]) => ({ name, value }));
19836
+ });
19837
+ const handleSubmit = (e) => {
19838
+ e.preventDefault();
19839
+ const formData = new FormData(e.target);
19840
+ const formHeaders = {};
19841
+ for (let i = 0; i < headers.length; i++) {
19842
+ const headerName = formData.get(`headers.${i}.name`);
19843
+ const headerValue = formData.get(`headers.${i}.value`);
19844
+ formHeaders[headerName] = headerValue;
19845
+ }
19846
+ setConfig({ headers: formHeaders });
19847
+ sonner.toast.success("Configuration saved");
19848
+ };
19849
+ const handleAddHeader = (header) => {
19850
+ setHeaders((prev) => [...prev, header]);
19851
+ };
19852
+ const handleRemoveHeader = (index) => {
19853
+ setHeaders((prev) => prev.filter((_, i) => i !== index));
19854
+ };
19855
+ return /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: handleSubmit, className: "space-y-4", children: [
19856
+ /* @__PURE__ */ jsxRuntime.jsx(HeaderListForm, { headers, onAddHeader: handleAddHeader, onRemoveHeader: handleRemoveHeader }),
19857
+ /* @__PURE__ */ jsxRuntime.jsxs(Button$1, { type: "submit", variant: "light", className: "w-full", size: "lg", children: [
19858
+ /* @__PURE__ */ jsxRuntime.jsx(Icon, { children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Link2, {}) }),
19859
+ "Set Configuration"
19860
+ ] })
19861
+ ] });
19862
+ };
19863
+
19700
19864
  Object.defineProperty(exports, "Toaster", {
19701
19865
  enumerable: true,
19702
19866
  get: () => sonner.Toaster
@@ -19833,6 +19997,9 @@ exports.SlashIcon = SlashIcon;
19833
19997
  exports.SpanScoreList = SpanScoreList;
19834
19998
  exports.SpanScoring = SpanScoring;
19835
19999
  exports.SpanTabs = SpanTabs;
20000
+ exports.StudioConfigContext = StudioConfigContext;
20001
+ exports.StudioConfigForm = StudioConfigForm;
20002
+ exports.StudioConfigProvider = StudioConfigProvider;
19836
20003
  exports.Tab = Tab$1;
19837
20004
  exports.TabContent = TabContent$1;
19838
20005
  exports.TabList = TabList$1;
@@ -19937,6 +20104,7 @@ exports.useScorers = useScorers;
19937
20104
  exports.useScoresByScorerId = useScoresByScorerId;
19938
20105
  exports.useSpeechRecognition = useSpeechRecognition;
19939
20106
  exports.useStreamWorkflow = useStreamWorkflow;
20107
+ exports.useStudioConfig = useStudioConfig;
19940
20108
  exports.useThreadInput = useThreadInput;
19941
20109
  exports.useThreads = useThreads;
19942
20110
  exports.useTool = useTool;