From 11b09ed31041c858afba26d6fcc4be52cd7f682a Mon Sep 17 00:00:00 2001 From: Kiet Ho Date: Sun, 22 Feb 2026 20:56:25 -0800 Subject: [PATCH 01/13] Update shimmer --- .../components/MessageList/MessageList.tsx | 11 ++++---- .../src/components/ai-elements/bash-tool.tsx | 1 - .../components/ai-elements/file-diff-tool.tsx | 1 - .../ui/src/components/ai-elements/message.tsx | 27 ++++++++++++------- .../ui/src/components/ai-elements/plan.tsx | 4 +-- .../src/components/ai-elements/reasoning.tsx | 2 +- .../ui/src/components/ai-elements/shimmer.tsx | 11 +++++--- .../src/components/ai-elements/tool-call.tsx | 1 - .../components/ai-elements/web-fetch-tool.tsx | 1 - .../ai-elements/web-search-tool.tsx | 1 - 10 files changed, 33 insertions(+), 27 deletions(-) diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/MessageList/MessageList.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/MessageList/MessageList.tsx index 2d1d7e856c2..19476fcd13e 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/MessageList/MessageList.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/MessageList/MessageList.tsx @@ -77,6 +77,8 @@ export function MessageList({ messages.map((msg, index) => { const isLastAssistant = msg.role === "assistant" && index === messages.length - 1; + const shouldAnimateStreaming = + isLastAssistant && (isStreaming || submitStatus === "submitted"); if (msg.role === "user") { const textContent = msg.parts @@ -144,17 +146,14 @@ export function MessageList({ {isLastAssistant && isThinking && msg.parts.length === 0 ? ( - + Thinking... ) : ( @@ -167,7 +166,7 @@ export function MessageList({ {isThinking && messages[messages.length - 1]?.role === "user" && ( - + Thinking... diff --git a/packages/ui/src/components/ai-elements/bash-tool.tsx b/packages/ui/src/components/ai-elements/bash-tool.tsx index 3d3b2847bb5..e93a02964dd 100644 --- a/packages/ui/src/components/ai-elements/bash-tool.tsx +++ b/packages/ui/src/components/ai-elements/bash-tool.tsx @@ -89,7 +89,6 @@ export const BashTool = ({ Generating command diff --git a/packages/ui/src/components/ai-elements/file-diff-tool.tsx b/packages/ui/src/components/ai-elements/file-diff-tool.tsx index bfa57941d09..bd1884f7331 100644 --- a/packages/ui/src/components/ai-elements/file-diff-tool.tsx +++ b/packages/ui/src/components/ai-elements/file-diff-tool.tsx @@ -145,7 +145,6 @@ export const FileDiffTool = ({ {isStreaming && !filePath ? ( {isWriteMode ? "Writing file..." : "Editing file..."} diff --git a/packages/ui/src/components/ai-elements/message.tsx b/packages/ui/src/components/ai-elements/message.tsx index c831aa805eb..fccc89b87cd 100644 --- a/packages/ui/src/components/ai-elements/message.tsx +++ b/packages/ui/src/components/ai-elements/message.tsx @@ -22,6 +22,12 @@ import { } from "../ui/tooltip"; const streamdownPlugins = { mermaid }; +const defaultMessageAnimation = { + animation: "blurIn", + sep: "char", + duration: 180, + easing: "cubic-bezier(0.22, 1, 0.36, 1)", +} as const; export type MessageProps = HTMLAttributes & { from: UIMessage["role"]; @@ -31,7 +37,7 @@ export const Message = ({ className, from, ...props }: MessageProps) => (
(
& { - from: UIMessage["role"]; -}; +export type MessageBranchSelectorProps = ComponentProps; export const MessageBranchSelector = ({ className, - from, ...props }: MessageBranchSelectorProps) => { const { totalBranches } = useMessageBranch(); @@ -229,7 +233,10 @@ export const MessageBranchSelector = ({ return ( *:not(:first-child)]:rounded-l-md [&>*:not(:last-child)]:rounded-r-md", + className, + )} orientation="horizontal" {...props} /> @@ -263,7 +270,6 @@ export type MessageBranchNextProps = ComponentProps; export const MessageBranchNext = ({ children, - className, ...props }: MessageBranchNextProps) => { const { goToNext, totalBranches } = useMessageBranch(); @@ -309,12 +315,13 @@ export type MessageResponseProps = ComponentProps; export const MessageResponse = memo( ({ className, animated, isAnimating, ...props }: MessageResponseProps) => ( *:first-child]:mt-0 [&>*:last-child]:mb-0 [&_ol]:list-outside [&_ol]:pl-6 [&_ul]:list-outside [&_ul]:pl-6 [&_:not(pre)>code]:break-all", className, )} isAnimating={isAnimating} + mode="streaming" plugins={isAnimating ? undefined : streamdownPlugins} {...props} /> diff --git a/packages/ui/src/components/ai-elements/plan.tsx b/packages/ui/src/components/ai-elements/plan.tsx index 64eaf7143bd..cff62af6b5c 100644 --- a/packages/ui/src/components/ai-elements/plan.tsx +++ b/packages/ui/src/components/ai-elements/plan.tsx @@ -74,7 +74,7 @@ export const PlanTitle = ({ children, ...props }: PlanTitleProps) => { return ( - {isStreaming ? {children} : children} + {isStreaming ? {children} : children} ); }; @@ -99,7 +99,7 @@ export const PlanDescription = ({ data-slot="plan-description" {...props} > - {isStreaming ? {children} : children} + {isStreaming ? {children} : children} ); }; diff --git a/packages/ui/src/components/ai-elements/reasoning.tsx b/packages/ui/src/components/ai-elements/reasoning.tsx index 0a67fc55281..86ec2e0d011 100644 --- a/packages/ui/src/components/ai-elements/reasoning.tsx +++ b/packages/ui/src/components/ai-elements/reasoning.tsx @@ -119,7 +119,7 @@ export type ReasoningTriggerProps = ComponentProps< const defaultGetThinkingMessage = (isStreaming: boolean, duration?: number) => { if (isStreaming || duration === 0) { - return Thinking...; + return Thinking...; } if (duration === undefined) { return

Thought for a few seconds

; diff --git a/packages/ui/src/components/ai-elements/shimmer.tsx b/packages/ui/src/components/ai-elements/shimmer.tsx index ec12b15252a..84047394c5b 100644 --- a/packages/ui/src/components/ai-elements/shimmer.tsx +++ b/packages/ui/src/components/ai-elements/shimmer.tsx @@ -16,14 +16,16 @@ export type TextShimmerProps = { className?: string; duration?: number; spread?: number; + variant?: "tool" | "text"; }; const ShimmerComponent = ({ children, - as: Component = "p", + as: Component = "span", className, duration = 2, spread = 2, + variant = "tool", }: TextShimmerProps) => { const MotionComponent = motion.create( Component as keyof JSX.IntrinsicElements, @@ -38,9 +40,12 @@ const ShimmerComponent = ({ {title} diff --git a/packages/ui/src/components/ai-elements/web-fetch-tool.tsx b/packages/ui/src/components/ai-elements/web-fetch-tool.tsx index d451dacff19..fac23962b69 100644 --- a/packages/ui/src/components/ai-elements/web-fetch-tool.tsx +++ b/packages/ui/src/components/ai-elements/web-fetch-tool.tsx @@ -73,7 +73,6 @@ export const WebFetchTool = ({ {isPending ? ( Fetching diff --git a/packages/ui/src/components/ai-elements/web-search-tool.tsx b/packages/ui/src/components/ai-elements/web-search-tool.tsx index 645426f25c8..0751858c441 100644 --- a/packages/ui/src/components/ai-elements/web-search-tool.tsx +++ b/packages/ui/src/components/ai-elements/web-search-tool.tsx @@ -57,7 +57,6 @@ export const WebSearchTool = ({ {isPending ? ( Searching From f16f1c10ff70adf1c515f81830964e5d30f23608 Mon Sep 17 00:00:00 2001 From: Kiet Ho Date: Sun, 22 Feb 2026 20:58:39 -0800 Subject: [PATCH 02/13] Animate --- .../components/MessageList/MessageList.tsx | 22 +++++-- .../MessagePartsRenderer.tsx | 64 +++++++++++++++++-- 2 files changed, 74 insertions(+), 12 deletions(-) diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/MessageList/MessageList.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/MessageList/MessageList.tsx index 19476fcd13e..cfa74019adc 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/MessageList/MessageList.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/MessageList/MessageList.tsx @@ -146,9 +146,14 @@ export function MessageList({ {isLastAssistant && isThinking && msg.parts.length === 0 ? ( - - Thinking... - + + + Thinking... + + ) : ( - - Thinking... - + + + Thinking... + + )} diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/MessagePartsRenderer/MessagePartsRenderer.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/MessagePartsRenderer/MessagePartsRenderer.tsx index a231883bc97..6445f0bcd56 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/MessagePartsRenderer/MessagePartsRenderer.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/MessagePartsRenderer/MessagePartsRenderer.tsx @@ -1,5 +1,8 @@ import { ExploringGroup } from "@superset/ui/ai-elements/exploring-group"; -import { MessageResponse } from "@superset/ui/ai-elements/message"; +import { + MessageResponse, + type MessageResponseProps, +} from "@superset/ui/ai-elements/message"; import type { UIMessage } from "ai"; import { getToolName, isToolUIPart } from "ai"; import { @@ -10,7 +13,7 @@ import { SearchIcon, } from "lucide-react"; import type React from "react"; -import { useCallback, useMemo } from "react"; +import { useCallback, useEffect, useMemo, useState } from "react"; import { electronTrpc } from "renderer/lib/electron-trpc"; import { useTheme } from "renderer/stores"; import { useTabsStore } from "renderer/stores/tabs/store"; @@ -29,6 +32,56 @@ interface MessagePartsRendererProps { onAnswer?: (toolCallId: string, answers: Record) => void; } +const STREAM_TEXT_TICK_MS = 16; +const STREAM_TEXT_CHARS_PER_TICK = 2; + +function StreamingMessageText({ + text, + isAnimating, + mermaid, + components, +}: { + text: string; + isAnimating: boolean; + mermaid: MessageResponseProps["mermaid"]; + components?: MessageResponseProps["components"]; +}) { + const [displayText, setDisplayText] = useState(text); + + useEffect(() => { + if (!isAnimating) { + setDisplayText(text); + return; + } + + setDisplayText((previous) => (text.startsWith(previous) ? previous : text)); + + const intervalId = window.setInterval(() => { + setDisplayText((previous) => { + if (previous.length >= text.length) return previous; + const nextLength = Math.min( + text.length, + previous.length + STREAM_TEXT_CHARS_PER_TICK, + ); + return text.slice(0, nextLength); + }); + }, STREAM_TEXT_TICK_MS); + + return () => window.clearInterval(intervalId); + }, [text, isAnimating]); + + return ( + + {displayText} + + ); +} + export function MessagePartsRenderer({ parts, isLastAssistant, @@ -96,14 +149,13 @@ export function MessagePartsRenderer({ if (part.type === "text") { nodes.push( - - {part.text} - , + />, ); i++; continue; From 77012ca09b0c7d34ad9878430e3f3571aced40a3 Mon Sep 17 00:00:00 2001 From: Kiet Ho Date: Sun, 22 Feb 2026 20:59:33 -0800 Subject: [PATCH 03/13] Refactor --- .../MessagePartsRenderer.tsx | 57 +------------------ .../StreamingMessageText.tsx | 57 +++++++++++++++++++ .../components/StreamingMessageText/index.ts | 1 + 3 files changed, 60 insertions(+), 55 deletions(-) create mode 100644 apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/MessagePartsRenderer/components/StreamingMessageText/StreamingMessageText.tsx create mode 100644 apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/MessagePartsRenderer/components/StreamingMessageText/index.ts diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/MessagePartsRenderer/MessagePartsRenderer.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/MessagePartsRenderer/MessagePartsRenderer.tsx index 6445f0bcd56..4d4e8884066 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/MessagePartsRenderer/MessagePartsRenderer.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/MessagePartsRenderer/MessagePartsRenderer.tsx @@ -1,8 +1,4 @@ import { ExploringGroup } from "@superset/ui/ai-elements/exploring-group"; -import { - MessageResponse, - type MessageResponseProps, -} from "@superset/ui/ai-elements/message"; import type { UIMessage } from "ai"; import { getToolName, isToolUIPart } from "ai"; import { @@ -13,7 +9,7 @@ import { SearchIcon, } from "lucide-react"; import type React from "react"; -import { useCallback, useEffect, useMemo, useState } from "react"; +import { useCallback, useMemo } from "react"; import { electronTrpc } from "renderer/lib/electron-trpc"; import { useTheme } from "renderer/stores"; import { useTabsStore } from "renderer/stores/tabs/store"; @@ -23,6 +19,7 @@ import { getArgs } from "../../utils/tool-helpers"; import { MastraToolCallBlock } from "../MastraToolCallBlock"; import { ReadOnlyToolCall } from "../ReadOnlyToolCall"; import { ReasoningBlock } from "../ReasoningBlock"; +import { StreamingMessageText } from "./components/StreamingMessageText"; interface MessagePartsRendererProps { parts: UIMessage["parts"]; @@ -32,56 +29,6 @@ interface MessagePartsRendererProps { onAnswer?: (toolCallId: string, answers: Record) => void; } -const STREAM_TEXT_TICK_MS = 16; -const STREAM_TEXT_CHARS_PER_TICK = 2; - -function StreamingMessageText({ - text, - isAnimating, - mermaid, - components, -}: { - text: string; - isAnimating: boolean; - mermaid: MessageResponseProps["mermaid"]; - components?: MessageResponseProps["components"]; -}) { - const [displayText, setDisplayText] = useState(text); - - useEffect(() => { - if (!isAnimating) { - setDisplayText(text); - return; - } - - setDisplayText((previous) => (text.startsWith(previous) ? previous : text)); - - const intervalId = window.setInterval(() => { - setDisplayText((previous) => { - if (previous.length >= text.length) return previous; - const nextLength = Math.min( - text.length, - previous.length + STREAM_TEXT_CHARS_PER_TICK, - ); - return text.slice(0, nextLength); - }); - }, STREAM_TEXT_TICK_MS); - - return () => window.clearInterval(intervalId); - }, [text, isAnimating]); - - return ( - - {displayText} - - ); -} - export function MessagePartsRenderer({ parts, isLastAssistant, diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/MessagePartsRenderer/components/StreamingMessageText/StreamingMessageText.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/MessagePartsRenderer/components/StreamingMessageText/StreamingMessageText.tsx new file mode 100644 index 00000000000..17d16b53905 --- /dev/null +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/MessagePartsRenderer/components/StreamingMessageText/StreamingMessageText.tsx @@ -0,0 +1,57 @@ +import { + MessageResponse, + type MessageResponseProps, +} from "@superset/ui/ai-elements/message"; +import { useEffect, useState } from "react"; + +const STREAM_TEXT_TICK_MS = 16; +const STREAM_TEXT_CHARS_PER_TICK = 2; + +interface StreamingMessageTextProps { + text: string; + isAnimating: boolean; + mermaid: MessageResponseProps["mermaid"]; + components?: MessageResponseProps["components"]; +} + +export function StreamingMessageText({ + text, + isAnimating, + mermaid, + components, +}: StreamingMessageTextProps) { + const [displayText, setDisplayText] = useState(text); + + useEffect(() => { + if (!isAnimating) { + setDisplayText(text); + return; + } + + setDisplayText((previous) => (text.startsWith(previous) ? previous : text)); + + const intervalId = window.setInterval(() => { + setDisplayText((previous) => { + if (previous.length >= text.length) return previous; + const nextLength = Math.min( + text.length, + previous.length + STREAM_TEXT_CHARS_PER_TICK, + ); + return text.slice(0, nextLength); + }); + }, STREAM_TEXT_TICK_MS); + + return () => window.clearInterval(intervalId); + }, [text, isAnimating]); + + return ( + + {displayText} + + ); +} diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/MessagePartsRenderer/components/StreamingMessageText/index.ts b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/MessagePartsRenderer/components/StreamingMessageText/index.ts new file mode 100644 index 00000000000..f6d9625ae45 --- /dev/null +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/MessagePartsRenderer/components/StreamingMessageText/index.ts @@ -0,0 +1 @@ +export { StreamingMessageText } from "./StreamingMessageText"; From 44246bab5408a978f1e20718883577670b2d14d1 Mon Sep 17 00:00:00 2001 From: Kiet Ho Date: Sun, 22 Feb 2026 21:02:32 -0800 Subject: [PATCH 04/13] Refactor more --- .../components/MessageList/MessageList.tsx | 24 ++++---------- .../src/components/ai-elements/bash-tool.tsx | 11 ++----- .../components/ai-elements/file-diff-tool.tsx | 9 ++--- .../components/ai-elements/shimmer-label.tsx | 33 +++++++++++++++++++ .../src/components/ai-elements/tool-call.tsx | 15 ++------- .../components/ai-elements/web-fetch-tool.tsx | 9 ++--- .../ai-elements/web-search-tool.tsx | 9 ++--- 7 files changed, 53 insertions(+), 57 deletions(-) create mode 100644 packages/ui/src/components/ai-elements/shimmer-label.tsx diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/MessageList/MessageList.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/MessageList/MessageList.tsx index cfa74019adc..5d1ea13d364 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/MessageList/MessageList.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/MessageList/MessageList.tsx @@ -5,7 +5,7 @@ import { ConversationScrollButton, } from "@superset/ui/ai-elements/conversation"; import { Message, MessageContent } from "@superset/ui/ai-elements/message"; -import { Shimmer } from "@superset/ui/ai-elements/shimmer"; +import { ShimmerLabel } from "@superset/ui/ai-elements/shimmer-label"; import type { ChatStatus, UIMessage } from "ai"; import { FileIcon, FileTextIcon, ImageIcon } from "lucide-react"; import { useCallback } from "react"; @@ -146,14 +146,9 @@ export function MessageList({ {isLastAssistant && isThinking && msg.parts.length === 0 ? ( - - - Thinking... - - + + Thinking... + ) : ( - - - Thinking... - - + + Thinking... + )} diff --git a/packages/ui/src/components/ai-elements/bash-tool.tsx b/packages/ui/src/components/ai-elements/bash-tool.tsx index e93a02964dd..90825b1212a 100644 --- a/packages/ui/src/components/ai-elements/bash-tool.tsx +++ b/packages/ui/src/components/ai-elements/bash-tool.tsx @@ -4,7 +4,7 @@ import { CheckIcon, XIcon } from "lucide-react"; import { useMemo, useState } from "react"; import { cn } from "../../lib/utils"; import { Loader } from "./loader"; -import { Shimmer } from "./shimmer"; +import { ShimmerLabel } from "./shimmer-label"; type BashToolState = | "input-streaming" @@ -86,14 +86,7 @@ export const BashTool = ({ >
- - - Generating command - - + Generating command
diff --git a/packages/ui/src/components/ai-elements/file-diff-tool.tsx b/packages/ui/src/components/ai-elements/file-diff-tool.tsx index bd1884f7331..7a84b7988b3 100644 --- a/packages/ui/src/components/ai-elements/file-diff-tool.tsx +++ b/packages/ui/src/components/ai-elements/file-diff-tool.tsx @@ -3,7 +3,7 @@ import { FileCode2Icon } from "lucide-react"; import { useMemo, useState } from "react"; import { cn } from "../../lib/utils"; -import { Shimmer } from "./shimmer"; +import { ShimmerLabel } from "./shimmer-label"; type FileDiffToolState = | "input-streaming" @@ -143,12 +143,9 @@ export const FileDiffTool = ({
{isStreaming && !filePath ? ( - + {isWriteMode ? "Writing file..." : "Editing file..."} - + ) : ( {isWriteMode ? "Wrote" : "Edited"}{" "} diff --git a/packages/ui/src/components/ai-elements/shimmer-label.tsx b/packages/ui/src/components/ai-elements/shimmer-label.tsx new file mode 100644 index 00000000000..2cb8646c32d --- /dev/null +++ b/packages/ui/src/components/ai-elements/shimmer-label.tsx @@ -0,0 +1,33 @@ +"use client"; + +import type { TextShimmerProps } from "./shimmer"; +import { cn } from "../../lib/utils"; +import { Shimmer } from "./shimmer"; + +export type ShimmerLabelProps = Omit< + TextShimmerProps, + "children" | "className" +> & { + children: string; + className?: string; + shimmerClassName?: string; + isShimmering?: boolean; +}; + +export const ShimmerLabel = ({ + children, + className, + shimmerClassName, + isShimmering = true, + ...props +}: ShimmerLabelProps) => ( + + {isShimmering ? ( + + {children} + + ) : ( + children + )} + +); diff --git a/packages/ui/src/components/ai-elements/tool-call.tsx b/packages/ui/src/components/ai-elements/tool-call.tsx index 40565975159..765afaa4126 100644 --- a/packages/ui/src/components/ai-elements/tool-call.tsx +++ b/packages/ui/src/components/ai-elements/tool-call.tsx @@ -2,7 +2,7 @@ import type { ComponentType } from "react"; import { cn } from "../../lib/utils"; -import { Shimmer } from "./shimmer"; +import { ShimmerLabel } from "./shimmer-label"; export type ToolCallProps = { icon: ComponentType<{ className?: string }>; @@ -33,18 +33,7 @@ export const ToolCall = ({ >
- - {isPending ? ( - - {title} - - ) : ( - title - )} - + {title} {subtitle && ( // biome-ignore lint/a11y/noStaticElementInteractions lint/a11y/useKeyWithClickEvents: clickable subtitle {isPending ? ( - + Fetching - + ) : ( Fetched )} diff --git a/packages/ui/src/components/ai-elements/web-search-tool.tsx b/packages/ui/src/components/ai-elements/web-search-tool.tsx index 0751858c441..19ffe63b6e1 100644 --- a/packages/ui/src/components/ai-elements/web-search-tool.tsx +++ b/packages/ui/src/components/ai-elements/web-search-tool.tsx @@ -4,7 +4,7 @@ import { ExternalLinkIcon, SearchIcon } from "lucide-react"; import { useState } from "react"; import { cn } from "../../lib/utils"; import { Loader } from "./loader"; -import { Shimmer } from "./shimmer"; +import { ShimmerLabel } from "./shimmer-label"; type WebSearchToolState = | "input-streaming" @@ -55,12 +55,9 @@ export const WebSearchTool = ({
{isPending ? ( - + Searching - + ) : ( Searched )} From 27cfe7b14c38bb4c7c8f39873e409dddf5b6ef1c Mon Sep 17 00:00:00 2001 From: Kiet Ho Date: Sun, 22 Feb 2026 21:06:32 -0800 Subject: [PATCH 05/13] Styles --- .../ChatInterface/components/MessageList/MessageList.tsx | 4 ++-- packages/ui/src/components/ai-elements/reasoning.tsx | 8 ++++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/MessageList/MessageList.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/MessageList/MessageList.tsx index 5d1ea13d364..b1ac34397b5 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/MessageList/MessageList.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/MessageList/MessageList.tsx @@ -146,7 +146,7 @@ export function MessageList({ {isLastAssistant && isThinking && msg.parts.length === 0 ? ( - + Thinking... ) : ( @@ -166,7 +166,7 @@ export function MessageList({ {isThinking && messages[messages.length - 1]?.role === "user" && ( - + Thinking... diff --git a/packages/ui/src/components/ai-elements/reasoning.tsx b/packages/ui/src/components/ai-elements/reasoning.tsx index 86ec2e0d011..57de9622cc9 100644 --- a/packages/ui/src/components/ai-elements/reasoning.tsx +++ b/packages/ui/src/components/ai-elements/reasoning.tsx @@ -11,7 +11,7 @@ import { CollapsibleContent, CollapsibleTrigger, } from "../ui/collapsible"; -import { Shimmer } from "./shimmer"; +import { ShimmerLabel } from "./shimmer-label"; type ReasoningContextValue = { isStreaming: boolean; @@ -119,7 +119,11 @@ export type ReasoningTriggerProps = ComponentProps< const defaultGetThinkingMessage = (isStreaming: boolean, duration?: number) => { if (isStreaming || duration === 0) { - return Thinking...; + return ( + + Thinking... + + ); } if (duration === undefined) { return

Thought for a few seconds

; From 85152cc2810e9935be723cf0b843e7c447b2ee40 Mon Sep 17 00:00:00 2001 From: Kiet Ho Date: Sun, 22 Feb 2026 21:13:24 -0800 Subject: [PATCH 06/13] Tool styling --- .../ui/src/components/ai-elements/tool.tsx | 66 ++++++++----------- 1 file changed, 29 insertions(+), 37 deletions(-) diff --git a/packages/ui/src/components/ai-elements/tool.tsx b/packages/ui/src/components/ai-elements/tool.tsx index 13e0b9b5609..46d3da2371a 100644 --- a/packages/ui/src/components/ai-elements/tool.tsx +++ b/packages/ui/src/components/ai-elements/tool.tsx @@ -2,6 +2,7 @@ import { CheckCircleIcon, + CheckIcon, ChevronDownIcon, CircleIcon, ClockIcon, @@ -11,7 +12,6 @@ import { import type { ComponentProps, ReactNode } from "react"; import { isValidElement } from "react"; import { cn } from "../../lib/utils"; -import { Badge } from "../ui/badge"; import { Collapsible, CollapsibleContent, @@ -35,7 +35,10 @@ export type ToolProps = ComponentProps; export const Tool = ({ className, ...props }: ToolProps) => ( ); @@ -53,37 +56,24 @@ function getToolDisplayName(title?: string, type?: string): string { return "tool"; } -const getStatusBadge = (status: ToolDisplayState) => { - const labels: Record = { - "awaiting-input": "Pending", - "input-streaming": "Pending", - "input-complete": "Running", - "input-available": "Running", - "approval-requested": "Awaiting Approval", - "approval-responded": "Responded", - "output-available": "Completed", - "output-error": "Error", - "output-denied": "Denied", - }; - +const getStatusIcon = (status: ToolDisplayState) => { const icons: Record = { - "awaiting-input": , - "input-streaming": , - "input-complete": , - "input-available": , - "approval-requested": , - "approval-responded": , - "output-available": , - "output-error": , - "output-denied": , + "awaiting-input": , + "input-streaming": , + "input-complete": , + "input-available": , + "approval-requested": ( + + ), + "approval-responded": ( + + ), + "output-available": , + "output-error": , + "output-denied": , }; - return ( - - {icons[status]} - {labels[status]} - - ); + return icons[status]; }; export const ToolHeader = ({ @@ -95,19 +85,21 @@ export const ToolHeader = ({ }: ToolHeaderProps) => ( -
- - +
+ + {getToolDisplayName(title, type)} - {getStatusBadge(state)}
- +
+ {getStatusIcon(state)} + +
); @@ -116,7 +108,7 @@ export type ToolContentProps = ComponentProps; export const ToolContent = ({ className, ...props }: ToolContentProps) => ( Date: Sun, 22 Feb 2026 21:15:59 -0800 Subject: [PATCH 07/13] More tools --- .../MastraToolCallBlock.tsx | 19 ++- .../src/components/ai-elements/bash-tool.tsx | 130 +++++++++--------- 2 files changed, 78 insertions(+), 71 deletions(-) diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/MastraToolCallBlock/MastraToolCallBlock.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/MastraToolCallBlock/MastraToolCallBlock.tsx index 9558155927a..9c25c0f37bc 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/MastraToolCallBlock/MastraToolCallBlock.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/MastraToolCallBlock/MastraToolCallBlock.tsx @@ -194,11 +194,24 @@ export function MastraToolCallBlock({ // --- Fallback: generic tool UI --- const output = "output" in part ? (part as { output: unknown }).output : undefined; - const isError = part.state === "output-error"; + const isOutputError = + output != null && + typeof output === "object" && + "error" in output && + (output as { error?: boolean }).error === true; + const isError = part.state === "output-error" || isOutputError; + const displayState = + isOutputError && toToolDisplayState(part) === "output-available" + ? "output-error" + : toToolDisplayState(part); + const errorText = + isError && output && typeof output === "object" && "message" in output + ? String((output as { message?: unknown }).message ?? "") + : undefined; return ( - + {part.input != null && } {(output != null || isError) && ( @@ -208,7 +221,7 @@ export function MastraToolCallBlock({ isError ? typeof output === "string" ? output - : JSON.stringify(output) + : errorText : undefined } /> diff --git a/packages/ui/src/components/ai-elements/bash-tool.tsx b/packages/ui/src/components/ai-elements/bash-tool.tsx index 90825b1212a..ebd83f56980 100644 --- a/packages/ui/src/components/ai-elements/bash-tool.tsx +++ b/packages/ui/src/components/ai-elements/bash-tool.tsx @@ -1,6 +1,6 @@ "use client"; -import { CheckIcon, XIcon } from "lucide-react"; +import { CheckIcon, TerminalIcon, XIcon } from "lucide-react"; import { useMemo, useState } from "react"; import { cn } from "../../lib/utils"; import { Loader } from "./loader"; @@ -78,20 +78,7 @@ export const BashTool = ({ [command], ); - // Input still streaming - if (state === "input-streaming") { - return ( -
-
-
- Generating command -
-
-
- ); - } + const hasOutput = Boolean(command || stdout || stderr); return (
- {/* Header - fixed height to prevent layout shift */} + {/* Header */} {/* biome-ignore lint/a11y/noStaticElementInteractions lint/a11y/useKeyWithClickEvents: interactive tool header */}
- hasMoreOutput && !isPending && setIsOutputExpanded(!isOutputExpanded) - } + onClick={() => hasOutput && setIsOutputExpanded((prev) => !prev)} > - - {isPending ? "Running command: " : "Ran command: "} - {commandSummary} - +
+ + {isPending ? ( + + {commandSummary ? "Running command" : "Generating command"} + + ) : ( + Ran command + )} + {commandSummary && ( + {commandSummary} + )} +
- {/* Status and expand */} -
+ {/* Status */} +
{!isPending && (
{isSuccess && ( @@ -142,50 +135,51 @@ export const BashTool = ({
- {/* Content - always visible */} - {/* biome-ignore lint/a11y/noStaticElementInteractions lint/a11y/useKeyWithClickEvents: clickable to expand */} -
- hasMoreOutput && !isOutputExpanded && setIsOutputExpanded(true) - } - > - {/* Command */} - {command && ( -
- $ - - {command} - -
- )} + {/* Content */} + {hasOutput && ( +
+
+ {/* Command */} + {command && ( +
+ $ + + {command} + +
+ )} - {/* Stdout */} - {stdout && ( -
- {isOutputExpanded ? stdout : stdoutLimited.text} -
- )} + {/* Stdout */} + {stdout && ( +
+ {isOutputExpanded ? stdout : stdoutLimited.text} +
+ )} - {/* Stderr */} - {stderr && ( -
+ {isOutputExpanded ? stderr : stderrLimited.text} +
)} - > - {isOutputExpanded ? stderr : stderrLimited.text}
- )} -
+
+ )}
); }; From dcec6050d944bd45dc6f8bb0ddff534a01136a68 Mon Sep 17 00:00:00 2001 From: Kiet Ho Date: Sun, 22 Feb 2026 21:17:51 -0800 Subject: [PATCH 08/13] bash tool --- .../MastraToolCallBlock/MastraToolCallBlock.tsx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/MastraToolCallBlock/MastraToolCallBlock.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/MastraToolCallBlock/MastraToolCallBlock.tsx index 9c25c0f37bc..a7fc3df98dc 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/MastraToolCallBlock/MastraToolCallBlock.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/MastraToolCallBlock/MastraToolCallBlock.tsx @@ -40,7 +40,16 @@ export function MastraToolCallBlock({ // --- Execute command → BashTool --- if (toolName === "mastra_workspace_execute_command") { const command = String(args.command ?? args.cmd ?? ""); - const stdout = result.stdout != null ? String(result.stdout) : undefined; + const stdout = + result.stdout != null + ? String(result.stdout) + : result.output != null + ? typeof result.output === "string" + ? result.output + : JSON.stringify(result.output, null, 2) + : result.text != null + ? String(result.text) + : undefined; const stderr = result.stderr != null ? String(result.stderr) : undefined; const exitCode = result.exitCode != null ? Number(result.exitCode) : undefined; From b9437f20819d6483279a2615f4391e92867c724a Mon Sep 17 00:00:00 2001 From: Kiet Ho Date: Sun, 22 Feb 2026 21:23:55 -0800 Subject: [PATCH 09/13] Animate collapse --- .../src/components/ai-elements/bash-tool.tsx | 65 ++++++------------- 1 file changed, 20 insertions(+), 45 deletions(-) diff --git a/packages/ui/src/components/ai-elements/bash-tool.tsx b/packages/ui/src/components/ai-elements/bash-tool.tsx index ebd83f56980..e1a5f07091f 100644 --- a/packages/ui/src/components/ai-elements/bash-tool.tsx +++ b/packages/ui/src/components/ai-elements/bash-tool.tsx @@ -3,6 +3,11 @@ import { CheckIcon, TerminalIcon, XIcon } from "lucide-react"; import { useMemo, useState } from "react"; import { cn } from "../../lib/utils"; +import { + Collapsible, + CollapsibleContent, + CollapsibleTrigger, +} from "../ui/collapsible"; import { Loader } from "./loader"; import { ShimmerLabel } from "./shimmer-label"; @@ -33,21 +38,6 @@ function extractCommandSummary(command: string): string { return limited.join(", "); } -/** Limit text to N lines, returning whether it was truncated. */ -function limitLines( - text: string, - maxLines: number, -): { text: string; truncated: boolean } { - if (!text) return { text: "", truncated: false }; - const lines = text.split("\n"); - if (lines.length <= maxLines) { - return { text, truncated: false }; - } - return { text: lines.slice(0, maxLines).join("\n"), truncated: true }; -} - -const MAX_COLLAPSED_LINES = 3; - export const BashTool = ({ command, stdout, @@ -61,17 +51,6 @@ export const BashTool = ({ const isPending = state === "input-streaming" || state === "input-available"; const isSuccess = exitCode === 0; const isError = exitCode !== undefined && exitCode !== 0; - const _hasOutput = Boolean(stdout || stderr); - - const stdoutLimited = useMemo( - () => limitLines(stdout ?? "", MAX_COLLAPSED_LINES), - [stdout], - ); - const stderrLimited = useMemo( - () => limitLines(stderr ?? "", MAX_COLLAPSED_LINES), - [stderr], - ); - const hasMoreOutput = stdoutLimited.truncated || stderrLimited.truncated; const commandSummary = useMemo( () => (command ? extractCommandSummary(command) : ""), @@ -81,21 +60,22 @@ export const BashTool = ({ const hasOutput = Boolean(command || stdout || stderr); return ( -
hasOutput && setIsOutputExpanded(open)} + open={hasOutput ? isOutputExpanded : false} > - {/* Header */} - {/* biome-ignore lint/a11y/noStaticElementInteractions lint/a11y/useKeyWithClickEvents: interactive tool header */} -
hasOutput && setIsOutputExpanded((prev) => !prev)} + disabled={!hasOutput} >
@@ -133,18 +113,13 @@ export const BashTool = ({ {isPending && }
-
+ - {/* Content */} {hasOutput && ( -
{/* Command */} @@ -160,7 +135,7 @@ export const BashTool = ({ {/* Stdout */} {stdout && (
- {isOutputExpanded ? stdout : stdoutLimited.text} + {stdout}
)} @@ -174,12 +149,12 @@ export const BashTool = ({ : "text-rose-500 dark:text-rose-400", )} > - {isOutputExpanded ? stderr : stderrLimited.text} + {stderr}
)}
-
+ )} -
+ ); }; From b2ca005c384342bff6b49591a30bdcdd3000a5d4 Mon Sep 17 00:00:00 2001 From: Kiet Ho Date: Sun, 22 Feb 2026 21:27:45 -0800 Subject: [PATCH 10/13] style --- .../src/components/ai-elements/bash-tool.tsx | 85 ++++++++++--------- .../ui/src/components/ai-elements/queue.tsx | 2 +- .../ui/src/components/ai-elements/task.tsx | 2 +- .../ui/src/components/ai-elements/tool.tsx | 3 +- 4 files changed, 48 insertions(+), 44 deletions(-) diff --git a/packages/ui/src/components/ai-elements/bash-tool.tsx b/packages/ui/src/components/ai-elements/bash-tool.tsx index e1a5f07091f..2accf441247 100644 --- a/packages/ui/src/components/ai-elements/bash-tool.tsx +++ b/packages/ui/src/components/ai-elements/bash-tool.tsx @@ -68,51 +68,54 @@ export const BashTool = ({ onOpenChange={(open) => hasOutput && setIsOutputExpanded(open)} open={hasOutput ? isOutputExpanded : false} > - -
- - {isPending ? ( - - {commandSummary ? "Running command" : "Generating command"} - - ) : ( - Ran command - )} - {commandSummary && ( - {commandSummary} + + {hasOutput && ( diff --git a/packages/ui/src/components/ai-elements/queue.tsx b/packages/ui/src/components/ai-elements/queue.tsx index fa33738eeb4..e3a80cab2fa 100644 --- a/packages/ui/src/components/ai-elements/queue.tsx +++ b/packages/ui/src/components/ai-elements/queue.tsx @@ -198,7 +198,7 @@ export type QueueSectionProps = ComponentProps; export const QueueSection = ({ className, - defaultOpen = true, + defaultOpen = false, ...props }: QueueSectionProps) => ( diff --git a/packages/ui/src/components/ai-elements/task.tsx b/packages/ui/src/components/ai-elements/task.tsx index accfe4f9cbd..50e5c7ad5f5 100644 --- a/packages/ui/src/components/ai-elements/task.tsx +++ b/packages/ui/src/components/ai-elements/task.tsx @@ -38,7 +38,7 @@ export const TaskItem = ({ children, className, ...props }: TaskItemProps) => ( export type TaskProps = ComponentProps; export const Task = ({ - defaultOpen = true, + defaultOpen = false, className, ...props }: TaskProps) => ( diff --git a/packages/ui/src/components/ai-elements/tool.tsx b/packages/ui/src/components/ai-elements/tool.tsx index 46d3da2371a..a03445ac6f8 100644 --- a/packages/ui/src/components/ai-elements/tool.tsx +++ b/packages/ui/src/components/ai-elements/tool.tsx @@ -7,6 +7,7 @@ import { CircleIcon, ClockIcon, WrenchIcon, + XIcon, XCircleIcon, } from "lucide-react"; import type { ComponentProps, ReactNode } from "react"; @@ -69,7 +70,7 @@ const getStatusIcon = (status: ToolDisplayState) => { ), "output-available": , - "output-error": , + "output-error": , "output-denied": , }; From 761d8b4305aa27972d57bec222d58391a369ac2d Mon Sep 17 00:00:00 2001 From: Kiet Ho Date: Sun, 22 Feb 2026 21:27:59 -0800 Subject: [PATCH 11/13] CI --- .../ui/src/components/ai-elements/shimmer-label.tsx | 2 +- packages/ui/src/components/ai-elements/tool.tsx | 10 +++------- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/packages/ui/src/components/ai-elements/shimmer-label.tsx b/packages/ui/src/components/ai-elements/shimmer-label.tsx index 2cb8646c32d..7537748bfae 100644 --- a/packages/ui/src/components/ai-elements/shimmer-label.tsx +++ b/packages/ui/src/components/ai-elements/shimmer-label.tsx @@ -1,7 +1,7 @@ "use client"; -import type { TextShimmerProps } from "./shimmer"; import { cn } from "../../lib/utils"; +import type { TextShimmerProps } from "./shimmer"; import { Shimmer } from "./shimmer"; export type ShimmerLabelProps = Omit< diff --git a/packages/ui/src/components/ai-elements/tool.tsx b/packages/ui/src/components/ai-elements/tool.tsx index a03445ac6f8..ca5e6a707ba 100644 --- a/packages/ui/src/components/ai-elements/tool.tsx +++ b/packages/ui/src/components/ai-elements/tool.tsx @@ -7,8 +7,8 @@ import { CircleIcon, ClockIcon, WrenchIcon, - XIcon, XCircleIcon, + XIcon, } from "lucide-react"; import type { ComponentProps, ReactNode } from "react"; import { isValidElement } from "react"; @@ -63,12 +63,8 @@ const getStatusIcon = (status: ToolDisplayState) => { "input-streaming": , "input-complete": , "input-available": , - "approval-requested": ( - - ), - "approval-responded": ( - - ), + "approval-requested": , + "approval-responded": , "output-available": , "output-error": , "output-denied": , From a02f263ab6181fc4f95f186eba4e34c1eca2d332 Mon Sep 17 00:00:00 2001 From: Kiet Ho Date: Sun, 22 Feb 2026 21:38:23 -0800 Subject: [PATCH 12/13] Fix --- .../MastraToolCallBlock.tsx | 53 +---------------- .../GenericToolCall/GenericToolCall.tsx | 34 +++++++++++ .../getGenericToolCallState.ts | 57 +++++++++++++++++++ .../components/GenericToolCall/index.ts | 2 + .../ui/src/components/ai-elements/message.tsx | 2 +- 5 files changed, 97 insertions(+), 51 deletions(-) create mode 100644 apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/MastraToolCallBlock/components/GenericToolCall/GenericToolCall.tsx create mode 100644 apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/MastraToolCallBlock/components/GenericToolCall/getGenericToolCallState.ts create mode 100644 apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/MastraToolCallBlock/components/GenericToolCall/index.ts diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/MastraToolCallBlock/MastraToolCallBlock.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/MastraToolCallBlock/MastraToolCallBlock.tsx index a7fc3df98dc..71f2101e3f3 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/MastraToolCallBlock/MastraToolCallBlock.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/MastraToolCallBlock/MastraToolCallBlock.tsx @@ -1,12 +1,5 @@ import { BashTool } from "@superset/ui/ai-elements/bash-tool"; import { FileDiffTool } from "@superset/ui/ai-elements/file-diff-tool"; -import { - Tool, - ToolContent, - ToolHeader, - ToolInput, - ToolOutput, -} from "@superset/ui/ai-elements/tool"; import { ToolCall } from "@superset/ui/ai-elements/tool-call"; import { UserQuestionTool } from "@superset/ui/ai-elements/user-question-tool"; import { WebFetchTool } from "@superset/ui/ai-elements/web-fetch-tool"; @@ -15,13 +8,9 @@ import { getToolName } from "ai"; import { FileIcon, FolderIcon, MessageCircleQuestionIcon } from "lucide-react"; import { READ_ONLY_TOOLS } from "../../constants"; import type { ToolPart } from "../../utils/tool-helpers"; -import { - getArgs, - getResult, - toToolDisplayState, - toWsToolState, -} from "../../utils/tool-helpers"; +import { getArgs, getResult, toWsToolState } from "../../utils/tool-helpers"; import { ReadOnlyToolCall } from "../ReadOnlyToolCall"; +import { GenericToolCall } from "./components/GenericToolCall"; interface MastraToolCallBlockProps { part: ToolPart; @@ -201,41 +190,5 @@ export function MastraToolCallBlock({ } // --- Fallback: generic tool UI --- - const output = - "output" in part ? (part as { output: unknown }).output : undefined; - const isOutputError = - output != null && - typeof output === "object" && - "error" in output && - (output as { error?: boolean }).error === true; - const isError = part.state === "output-error" || isOutputError; - const displayState = - isOutputError && toToolDisplayState(part) === "output-available" - ? "output-error" - : toToolDisplayState(part); - const errorText = - isError && output && typeof output === "object" && "message" in output - ? String((output as { message?: unknown }).message ?? "") - : undefined; - - return ( - - - - {part.input != null && } - {(output != null || isError) && ( - - )} - - - ); + return ; } diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/MastraToolCallBlock/components/GenericToolCall/GenericToolCall.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/MastraToolCallBlock/components/GenericToolCall/GenericToolCall.tsx new file mode 100644 index 00000000000..2d6f67af5be --- /dev/null +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/MastraToolCallBlock/components/GenericToolCall/GenericToolCall.tsx @@ -0,0 +1,34 @@ +import { + Tool, + ToolContent, + ToolHeader, + ToolInput, + ToolOutput, +} from "@superset/ui/ai-elements/tool"; +import type { ToolPart } from "../../../../utils/tool-helpers"; +import { getGenericToolCallState } from "./getGenericToolCallState"; + +type GenericToolCallProps = { + part: ToolPart; + toolName: string; +}; + +export function GenericToolCall({ part, toolName }: GenericToolCallProps) { + const { output, isError, displayState, errorText } = + getGenericToolCallState(part); + + return ( + + + + {part.input != null && } + {(output != null || isError) && ( + + )} + + + ); +} diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/MastraToolCallBlock/components/GenericToolCall/getGenericToolCallState.ts b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/MastraToolCallBlock/components/GenericToolCall/getGenericToolCallState.ts new file mode 100644 index 00000000000..8ae55c160d8 --- /dev/null +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/MastraToolCallBlock/components/GenericToolCall/getGenericToolCallState.ts @@ -0,0 +1,57 @@ +import type { ToolDisplayState } from "@superset/ui/ai-elements/tool"; +import type { ToolPart } from "../../../../utils/tool-helpers"; +import { toToolDisplayState } from "../../../../utils/tool-helpers"; + +export type GenericToolCallState = { + output: unknown; + isError: boolean; + displayState: ToolDisplayState; + errorText?: string; +}; + +function stringifyValue(value: unknown): string { + try { + return JSON.stringify(value); + } catch { + return String(value); + } +} + +export function getGenericToolCallState(part: ToolPart): GenericToolCallState { + const output = + "output" in part ? (part as { output: unknown }).output : undefined; + const outputObject = + output != null && typeof output === "object" + ? (output as Record) + : undefined; + const outputError = outputObject?.error; + const isOutputError = + outputObject != null && "error" in outputObject && Boolean(outputError); + const isError = part.state === "output-error" || isOutputError; + + const baseDisplayState = toToolDisplayState(part); + const displayState = + isOutputError && baseDisplayState === "output-available" + ? "output-error" + : baseDisplayState; + + let errorText: string | undefined; + if (isError) { + if (typeof output === "string") { + errorText = output; + } else if (typeof outputError === "string") { + errorText = outputError; + } else if (typeof outputObject?.message === "string") { + errorText = outputObject.message; + } else if (outputError !== undefined) { + errorText = stringifyValue(outputError); + } + } + + return { + output, + isError, + displayState, + errorText, + }; +} diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/MastraToolCallBlock/components/GenericToolCall/index.ts b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/MastraToolCallBlock/components/GenericToolCall/index.ts new file mode 100644 index 00000000000..c44d4e2d968 --- /dev/null +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/MastraToolCallBlock/components/GenericToolCall/index.ts @@ -0,0 +1,2 @@ +export { GenericToolCall } from "./GenericToolCall"; +export { getGenericToolCallState } from "./getGenericToolCallState"; diff --git a/packages/ui/src/components/ai-elements/message.tsx b/packages/ui/src/components/ai-elements/message.tsx index fccc89b87cd..a4c7cde028c 100644 --- a/packages/ui/src/components/ai-elements/message.tsx +++ b/packages/ui/src/components/ai-elements/message.tsx @@ -234,7 +234,7 @@ export const MessageBranchSelector = ({ return ( *:not(:first-child)]:rounded-l-md [&>*:not(:last-child)]:rounded-r-md", + "[&>*:first-child]:rounded-l-md [&>*:last-child]:rounded-r-md", className, )} orientation="horizontal" From 593e3e7d18bc8ecc7538090789105db143ae507c Mon Sep 17 00:00:00 2001 From: Kiet Ho Date: Sun, 22 Feb 2026 21:38:48 -0800 Subject: [PATCH 13/13] Fix animating --- .../components/StreamingMessageText/StreamingMessageText.tsx | 2 +- packages/ui/src/components/ai-elements/message.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/MessagePartsRenderer/components/StreamingMessageText/StreamingMessageText.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/MessagePartsRenderer/components/StreamingMessageText/StreamingMessageText.tsx index 17d16b53905..fb362f53e3f 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/MessagePartsRenderer/components/StreamingMessageText/StreamingMessageText.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/MessagePartsRenderer/components/StreamingMessageText/StreamingMessageText.tsx @@ -47,7 +47,7 @@ export function StreamingMessageText({ return ( diff --git a/packages/ui/src/components/ai-elements/message.tsx b/packages/ui/src/components/ai-elements/message.tsx index a4c7cde028c..669841d2a8b 100644 --- a/packages/ui/src/components/ai-elements/message.tsx +++ b/packages/ui/src/components/ai-elements/message.tsx @@ -53,7 +53,7 @@ export const MessageContent = ({ }: MessageContentProps) => (