@dbx-tools/ui-mastra 0.9.175 → 0.9.177

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/index.react.ts CHANGED
@@ -33,7 +33,7 @@ export type { GenieFeedbackTarget } from "./src/react/chat-feedback.ts";
33
33
  export { useChatHistory } from "./src/react/chat-history.ts";
34
34
  export { useChatSessions } from "./src/react/chat-sessions.ts";
35
35
  export type { ThreadSessionReader, ThreadSessionUpdater, ThreadMessageWriter } from "./src/react/chat-sessions.ts";
36
- export { readMlflowTracePart, closeOnTerminalChunk, waitForTerminalChunk, useChatStream } from "./src/react/chat-stream.ts";
36
+ export { readMlflowTracePart, chatStatusForChunk, closeOnTerminalChunk, waitForTerminalChunk, useChatStream } from "./src/react/chat-stream.ts";
37
37
  export { ChatThreadLayout } from "./src/react/chat-thread-layout.tsx";
38
38
  export { useChatTranscriptController, ChatTranscript } from "./src/react/chat-transcript.tsx";
39
39
  export type { ChatTranscriptController } from "./src/react/chat-transcript.tsx";
@@ -57,5 +57,6 @@ export { ThreadActions, ThreadTabs } from "./src/react/thread-tabs.tsx";
57
57
  export type { ThreadTabsProps, ThreadActionsProps } from "./src/react/thread-tabs.tsx";
58
58
  export { humanizeToolName, formatRawToolPayload, structuredToolPayload, detectToolSourceLanguage, toolInputPresentation, toolOutputPresentation, webSearchProgressGroups, webSearchQueryLabel, webSearchResultLabel, ToolSessionPill } from "./src/react/tool-pill.tsx";
59
59
  export type { ToolInputPresentation, WebSearchProgressGroup } from "./src/react/tool-pill.tsx";
60
+ export { isActiveChatStatus, isRespondingChatStatus } from "./src/react/types.ts";
60
61
  export type { ChatStatus, ToolEvent, ToolProgress, ChatModelOption, ChatInputMessage, QueuedSteer, FeedbackValue, FeedbackSubmission, MessageFeedback, ThreadSummary, ThreadPlacement, ChatViewProps, ApprovalDecision, PendingApproval } from "./src/react/types.ts";
61
62
  export { useChatThreadQuery } from "./src/react/use-chat-thread-query.ts";
package/index.ts CHANGED
@@ -3,4 +3,4 @@
3
3
  // Hand edits are overwritten on the next watch; this file is read-only.
4
4
 
5
5
  export const PACKAGE_IDENTIFIER = "@dbx-tools/ui-mastra";
6
- export const PACKAGE_VERSION = "0.9.175";
6
+ export const PACKAGE_VERSION = "0.9.177";
package/package.json CHANGED
@@ -32,12 +32,12 @@
32
32
  },
33
33
  "dependencies": {
34
34
  "@databricks/appkit-ui": "0.81.0",
35
- "@dbx-tools/shared-auth": "^0.9.175",
36
- "@dbx-tools/shared-core": "^0.9.175",
37
- "@dbx-tools/shared-genie": "^0.9.175",
38
- "@dbx-tools/shared-mastra": "^0.9.175",
39
- "@dbx-tools/shared-model": "^0.9.175",
40
- "@dbx-tools/ui": "^0.9.175",
35
+ "@dbx-tools/shared-auth": "^0.9.177",
36
+ "@dbx-tools/shared-core": "^0.9.177",
37
+ "@dbx-tools/shared-genie": "^0.9.177",
38
+ "@dbx-tools/shared-mastra": "^0.9.177",
39
+ "@dbx-tools/shared-model": "^0.9.177",
40
+ "@dbx-tools/ui": "^0.9.177",
41
41
  "@mastra/client-js": "1.50.0",
42
42
  "@mastra/core": "1.71.0",
43
43
  "@tanstack/react-table": "^8.21.3",
@@ -55,7 +55,7 @@
55
55
  "publishConfig": {
56
56
  "access": "public"
57
57
  },
58
- "version": "0.9.175",
58
+ "version": "0.9.177",
59
59
  "types": "index.ts",
60
60
  "dbxToolsConfig": {
61
61
  "tags": [
@@ -1,13 +1,13 @@
1
1
  import { Spinner } from "@databricks/appkit-ui/react";
2
2
 
3
- import type { ChatStatus } from "./types.ts";
3
+ import { isRespondingChatStatus, type ChatStatus } from "./types.ts";
4
4
 
5
5
  /** Whether an assistant bubble should show its active-response status. */
6
6
  export const shouldShowAssistantResponseStatus = (
7
7
  isLast: boolean,
8
8
  status: ChatStatus,
9
9
  hasVisibleResponse: boolean,
10
- ): boolean => isLast && hasVisibleResponse && (status === "submitted" || status === "streaming");
10
+ ): boolean => isLast && hasVisibleResponse && isRespondingChatStatus(status);
11
11
 
12
12
  /** Compact progress affordance for an active assistant response. */
13
13
  export const AssistantResponseSpinner = () => (
@@ -36,13 +36,14 @@ import { ExportMenu } from "./export-menu.tsx";
36
36
  import { FeedbackControls } from "./feedback-controls.tsx";
37
37
  import { ToolMarkdown } from "./markdown.tsx";
38
38
  import { ToolSessionPill, humanizeToolName } from "./tool-pill.tsx";
39
- import type {
40
- ApprovalDecision,
41
- ChatStatus,
42
- FeedbackSubmission,
43
- FeedbackValue,
44
- PendingApproval,
45
- ToolEvent,
39
+ import {
40
+ isRespondingChatStatus,
41
+ type ApprovalDecision,
42
+ type ChatStatus,
43
+ type FeedbackSubmission,
44
+ type FeedbackValue,
45
+ type PendingApproval,
46
+ type ToolEvent,
46
47
  } from "./types.ts";
47
48
  import { copyText } from "../support/clipboard.ts";
48
49
  import type { ExportFormat } from "../support/export.ts";
@@ -390,7 +391,7 @@ export const AssistantBubble = ({
390
391
  // pop in mid-stream. A bubble is "settled" if it isn't the active
391
392
  // streaming target - either the agent has returned to `ready` /
392
393
  // `error`, or a newer message has taken over the `isLast` slot.
393
- const isStreamingThisBubble = isLast && (status === "streaming" || status === "submitted");
394
+ const isStreamingThisBubble = isLast && isRespondingChatStatus(status);
394
395
  const showResponseStatus = shouldShowAssistantResponseStatus(
395
396
  isLast,
396
397
  status,
@@ -34,7 +34,7 @@ import { imageFileToUIPart, MAX_COMPOSER_IMAGES } from "./_image-attachments.ts"
34
34
  import { ModelSelector } from "./_model-selector.tsx";
35
35
  import { ExportMenu } from "./export-menu.tsx";
36
36
  import { SuggestionPills } from "./suggestion-pills.tsx";
37
- import type { ChatViewProps } from "./types.ts";
37
+ import { isActiveChatStatus, isRespondingChatStatus, type ChatViewProps } from "./types.ts";
38
38
 
39
39
  type QueuedSteerListProps = {
40
40
  queuedSteers: NonNullable<ChatViewProps["queuedSteers"]>;
@@ -316,7 +316,8 @@ export const ChatComposer = ({
316
316
  return observeComposerWidth(group, resizeTextarea);
317
317
  }, [resizeTextarea]);
318
318
 
319
- const isRunning = status === "submitted" || status === "streaming";
319
+ const isRunning = isActiveChatStatus(status);
320
+ const canStop = isRespondingChatStatus(status);
320
321
  const hasDraft = Boolean(input.trim() || files.length > 0);
321
322
  const submit = () => {
322
323
  const text = input.trim();
@@ -560,7 +561,7 @@ export const ChatComposer = ({
560
561
  )}
561
562
  </span>
562
563
  ))}
563
- {isRunning && onStop && !hasDraft ? (
564
+ {canStop && onStop && !hasDraft ? (
564
565
  <InputGroupButton
565
566
  type="button"
566
567
  size="icon-sm"
@@ -3,6 +3,7 @@ import {
3
3
  MLFLOW_TRACE_ID_HEADER,
4
4
  MLFLOW_TRACE_PART_TYPE,
5
5
  MlflowTracePartDataSchema,
6
+ RESPONSE_COMPLETE_PART_TYPE,
6
7
  isStaleMastraResumeError,
7
8
  } from "@dbx-tools/shared-mastra";
8
9
  import { readUIMessageStream, type UIMessage, type UIMessageChunk } from "ai";
@@ -12,6 +13,7 @@ import type {
12
13
  ThreadSessionReader,
13
14
  ThreadSessionUpdater,
14
15
  } from "./chat-sessions.ts";
16
+ import type { ChatStatus } from "./types.ts";
15
17
  import type { MastraStreamResponse } from "../support/mastra-client.ts";
16
18
 
17
19
  const logger = log.logger("ui-mastra/chat");
@@ -29,6 +31,13 @@ export const readMlflowTracePart = (chunk: UIMessageChunk): string | undefined =
29
31
  return parsed.success ? parsed.data.traceId : undefined;
30
32
  };
31
33
 
34
+ /** Map lifecycle-only chunks onto the UI's active-turn state. */
35
+ export const chatStatusForChunk = (chunk: UIMessageChunk): ChatStatus | undefined => {
36
+ if ((chunk as { type?: string }).type === RESPONSE_COMPLETE_PART_TYPE) return "finishing";
37
+ if (chunk.type === "finish" || chunk.type === "abort") return "ready";
38
+ return undefined;
39
+ };
40
+
32
41
  class StreamAborted extends Error {}
33
42
 
34
43
  /** Stop reading a background run once the AI SDK has delivered its terminal chunk. */
@@ -46,7 +55,7 @@ export function closeOnTerminalChunk(
46
55
  while (true) {
47
56
  const { done, value } = await reader.read();
48
57
  if (done) {
49
- controller.close();
58
+ controller.error(new Error("Chat stream ended before a terminal AI SDK chunk"));
50
59
  return;
51
60
  }
52
61
  onChunk?.(value);
@@ -140,7 +149,15 @@ export function useChatStream({ getSession, updateSession, writeMessages }: UseC
140
149
  () => {
141
150
  terminalReceived = true;
142
151
  },
143
- (chunk) => recordTraceId(readMlflowTracePart(chunk)),
152
+ (chunk) => {
153
+ recordTraceId(readMlflowTracePart(chunk));
154
+ const status = chatStatusForChunk(chunk);
155
+ if (!status) return;
156
+ updateSession(threadId, (current) => ({
157
+ ...current,
158
+ status,
159
+ }));
160
+ },
144
161
  ),
145
162
  terminateOnError: true,
146
163
  })) {
@@ -150,7 +167,12 @@ export function useChatStream({ getSession, updateSession, writeMessages }: UseC
150
167
  updateSession(threadId, (current) => ({
151
168
  ...current,
152
169
  runId: runIdRef.current,
153
- status: terminalReceived ? "ready" : "streaming",
170
+ status:
171
+ terminalReceived || current.status === "finishing"
172
+ ? terminalReceived
173
+ ? "ready"
174
+ : "finishing"
175
+ : "streaming",
154
176
  }));
155
177
  }
156
178
  } catch (error) {
@@ -17,7 +17,7 @@ import React, { useCallback, useEffect, useLayoutEffect, useRef, useState } from
17
17
  import { AssistantBubble, UserBubble } from "./bubbles.tsx";
18
18
  import { genieFeedbackTarget } from "./chat-feedback.ts";
19
19
  import { SuggestionPills } from "./suggestion-pills.tsx";
20
- import type { ChatViewProps } from "./types.ts";
20
+ import { isRespondingChatStatus, type ChatViewProps } from "./types.ts";
21
21
  import { messageText } from "../support/export.ts";
22
22
 
23
23
  const BOTTOM_THRESHOLD_PX = 24;
@@ -186,7 +186,7 @@ export const ChatTranscript = ({
186
186
  onFeedback,
187
187
  mlflowExperimentUrl,
188
188
  }: ChatTranscriptProps) => {
189
- const isRunning = status === "submitted" || status === "streaming";
189
+ const isRunning = isRespondingChatStatus(status);
190
190
  const lastMessage = messages.at(-1);
191
191
  const lastEvents = lastMessage ? toolEventsByMessage[lastMessage.id] : undefined;
192
192
  const lastAssistantParts = lastMessage?.role === "assistant" ? lastMessage.parts : [];
@@ -2,7 +2,7 @@ import { ChatComposer } from "./chat-composer.tsx";
2
2
  import { ChatThreadLayout } from "./chat-thread-layout.tsx";
3
3
  import { ChatTranscript, useChatTranscriptController } from "./chat-transcript.tsx";
4
4
  import { collectSuggestions } from "./suggestions.ts";
5
- import type { ChatViewProps } from "./types.ts";
5
+ import { isActiveChatStatus, type ChatViewProps } from "./types.ts";
6
6
 
7
7
  /**
8
8
  * Controlled chat facade for hosts that own message and transport state.
@@ -81,7 +81,7 @@ export const ChatView = ({
81
81
  });
82
82
  const latestMessage = messages[messages.length - 1];
83
83
  const latestAssistant = latestMessage?.role === "assistant" ? latestMessage : undefined;
84
- const running = status === "submitted" || status === "streaming";
84
+ const running = isActiveChatStatus(status);
85
85
  const followUpQuestions =
86
86
  latestAssistant && !running
87
87
  ? collectSuggestions(
@@ -10,7 +10,15 @@ import type { ExportFormat } from "../support/export.ts";
10
10
  // layer and the `useMastraChat` driver can share them.
11
11
 
12
12
  /** User-visible lifecycle state of the active chat turn. */
13
- export type ChatStatus = "submitted" | "streaming" | "ready" | "error";
13
+ export type ChatStatus = "submitted" | "streaming" | "finishing" | "ready" | "error";
14
+
15
+ /** Whether a turn still owns its session and queues new messages. */
16
+ export const isActiveChatStatus = (status: ChatStatus): boolean =>
17
+ status === "submitted" || status === "streaming" || status === "finishing";
18
+
19
+ /** Whether the assistant is still visibly producing its response. */
20
+ export const isRespondingChatStatus = (status: ChatStatus): boolean =>
21
+ status === "submitted" || status === "streaming";
14
22
 
15
23
  /**
16
24
  * Lifecycle of a single tool invocation surfaced inline in the assistant
@@ -1,6 +1,12 @@
1
1
  import type { RequestContext } from "@mastra/client-js";
2
2
  import type { UIMessage } from "ai";
3
- import type { ChatStatus, MessageFeedback, PendingApproval, QueuedSteer } from "../react/types.ts";
3
+ import {
4
+ isActiveChatStatus,
5
+ type ChatStatus,
6
+ type MessageFeedback,
7
+ type PendingApproval,
8
+ type QueuedSteer,
9
+ } from "../react/types.ts";
4
10
 
5
11
  /** A queued steer plus the application context captured when it was submitted. */
6
12
  export type SessionQueuedSteer = QueuedSteer & {
@@ -60,9 +66,9 @@ export function createThreadSession(): ThreadSession {
60
66
  };
61
67
  }
62
68
 
63
- /** Return whether a thread has a submitted or actively streaming run. */
69
+ /** Return whether a thread still owns an active run. */
64
70
  export function isSessionRunning(session: ThreadSession): boolean {
65
- return session.status === "submitted" || session.status === "streaming";
71
+ return isActiveChatStatus(session.status);
66
72
  }
67
73
 
68
74
  /** Return the newest user message in a transcript or turn. */