From 8e327a57dae722e2bd0e61a1c8a02c2c6b4f43c6 Mon Sep 17 00:00:00 2001 From: haikdc Date: Sat, 18 Apr 2026 15:17:02 -0700 Subject: [PATCH] =?UTF-8?q?[Haik]:=20refactor:=20restructure=20OpenSwarmTh?= =?UTF-8?q?read=20component=20hierarchy=20=E2=80=94=20move=20AssistantMess?= =?UTF-8?q?age=20from=20flat=20file=20into=20AssistantMessage/=20directory?= =?UTF-8?q?=20with=20colocated=20MarkdownText/=20(MarkdownText,=20DEFAULT?= =?UTF-8?q?=5FCOMPONENTS,=20CodeHeader)=20and=20ToolFallback/=20(ToolFallb?= =?UTF-8?q?ack,=20ToolFallbackRoot,=20ToolFallbackTrigger,=20ToolFallbackC?= =?UTF-8?q?ontent,=20ToolFallbackArgs,=20ToolFallbackResult,=20ToolFallbac?= =?UTF-8?q?kError);=20move=20UserMessage=20into=20UserMessage/=20directory?= =?UTF-8?q?=20extracting=20UserMessageAttachments;=20relocate=20TooltipIco?= =?UTF-8?q?nButton=20from=20global=20components/assistant-ui/tooltip-icon-?= =?UTF-8?q?button=20into=20OpenSwarmThread/components/;=20delete=20shared?= =?UTF-8?q?=20modules=20attachment.tsx,=20markdown-text.tsx,=20tool-fallba?= =?UTF-8?q?ck.tsx,=20and=20tooltip-icon-button.tsx=20from=20components/ass?= =?UTF-8?q?istant-ui/;=20update=20import=20paths=20in=20OpenSwarmThread.ts?= =?UTF-8?q?x,=20BranchPicker.tsx,=20and=20MessageActions.tsx?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../OpenSwarmThread/OpenSwarmThread.tsx | 6 +- .../AssistantMessage.tsx | 8 +- .../DEFAULT_COMPONENTS/CodeHeader.tsx | 52 +++ .../DEFAULT_COMPONENTS.tsx} | 60 +--- .../MarkdownText/MarkdownText.tsx | 15 + .../ToolFallback/ToolFallback.tsx | 64 ++++ .../components/ToolFallbackArgs.tsx | 25 ++ .../components/ToolFallbackContent.tsx | 31 ++ .../components/ToolFallbackError.tsx | 43 +++ .../components/ToolFallbackResult.tsx | 29 ++ .../components/ToolFallbackRoot.tsx | 69 ++++ .../components/ToolFallbackTrigger.tsx | 88 +++++ .../components/BranchPicker.tsx | 2 +- .../components/MessageActions.tsx | 2 +- .../components/TooltipIconButton.tsx} | 0 .../{ => UserMessage}/UserMessage.tsx | 8 +- .../UserMessage/UserMessageAttachments.tsx} | 2 +- .../components/assistant-ui/tool-fallback.tsx | 318 ------------------ 18 files changed, 432 insertions(+), 390 deletions(-) rename frontend/src/app/pages/AgentChat/OpenSwarmThread/components/{ => AssistantMessage}/AssistantMessage.tsx (84%) create mode 100644 frontend/src/app/pages/AgentChat/OpenSwarmThread/components/AssistantMessage/MarkdownText/DEFAULT_COMPONENTS/CodeHeader.tsx rename frontend/src/{components/assistant-ui/markdown-text.tsx => app/pages/AgentChat/OpenSwarmThread/components/AssistantMessage/MarkdownText/DEFAULT_COMPONENTS/DEFAULT_COMPONENTS.tsx} (73%) create mode 100644 frontend/src/app/pages/AgentChat/OpenSwarmThread/components/AssistantMessage/MarkdownText/MarkdownText.tsx create mode 100644 frontend/src/app/pages/AgentChat/OpenSwarmThread/components/AssistantMessage/ToolFallback/ToolFallback.tsx create mode 100644 frontend/src/app/pages/AgentChat/OpenSwarmThread/components/AssistantMessage/ToolFallback/components/ToolFallbackArgs.tsx create mode 100644 frontend/src/app/pages/AgentChat/OpenSwarmThread/components/AssistantMessage/ToolFallback/components/ToolFallbackContent.tsx create mode 100644 frontend/src/app/pages/AgentChat/OpenSwarmThread/components/AssistantMessage/ToolFallback/components/ToolFallbackError.tsx create mode 100644 frontend/src/app/pages/AgentChat/OpenSwarmThread/components/AssistantMessage/ToolFallback/components/ToolFallbackResult.tsx create mode 100644 frontend/src/app/pages/AgentChat/OpenSwarmThread/components/AssistantMessage/ToolFallback/components/ToolFallbackRoot.tsx create mode 100644 frontend/src/app/pages/AgentChat/OpenSwarmThread/components/AssistantMessage/ToolFallback/components/ToolFallbackTrigger.tsx rename frontend/src/{components/assistant-ui/tooltip-icon-button.tsx => app/pages/AgentChat/OpenSwarmThread/components/TooltipIconButton.tsx} (100%) rename frontend/src/app/pages/AgentChat/OpenSwarmThread/components/{ => UserMessage}/UserMessage.tsx (96%) rename frontend/src/{components/assistant-ui/attachment.tsx => app/pages/AgentChat/OpenSwarmThread/components/UserMessage/UserMessageAttachments.tsx} (98%) delete mode 100644 frontend/src/components/assistant-ui/tool-fallback.tsx diff --git a/frontend/src/app/pages/AgentChat/OpenSwarmThread/OpenSwarmThread.tsx b/frontend/src/app/pages/AgentChat/OpenSwarmThread/OpenSwarmThread.tsx index 3faf14bc..8b723495 100644 --- a/frontend/src/app/pages/AgentChat/OpenSwarmThread/OpenSwarmThread.tsx +++ b/frontend/src/app/pages/AgentChat/OpenSwarmThread/OpenSwarmThread.tsx @@ -8,9 +8,9 @@ import { import { ArrowDownIcon } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { TooltipProvider } from '@/components/ui/tooltip'; -import { TooltipIconButton } from '@/components/assistant-ui/tooltip-icon-button'; -import { UserMessage } from './components/UserMessage'; -import { AssistantMessage } from './components/AssistantMessage'; +import { TooltipIconButton } from './components/TooltipIconButton'; +import { UserMessage } from './components/UserMessage/UserMessage'; +import { AssistantMessage } from './components/AssistantMessage/AssistantMessage'; import { SessionIdContext, BranchChatContext } from './utils'; interface OpenSwarmThreadProps { diff --git a/frontend/src/app/pages/AgentChat/OpenSwarmThread/components/AssistantMessage.tsx b/frontend/src/app/pages/AgentChat/OpenSwarmThread/components/AssistantMessage/AssistantMessage.tsx similarity index 84% rename from frontend/src/app/pages/AgentChat/OpenSwarmThread/components/AssistantMessage.tsx rename to frontend/src/app/pages/AgentChat/OpenSwarmThread/components/AssistantMessage/AssistantMessage.tsx index 6923e8ba..2f9bf8e9 100644 --- a/frontend/src/app/pages/AgentChat/OpenSwarmThread/components/AssistantMessage.tsx +++ b/frontend/src/app/pages/AgentChat/OpenSwarmThread/components/AssistantMessage/AssistantMessage.tsx @@ -1,9 +1,9 @@ import { type FC } from 'react'; import { MessagePrimitive, ErrorPrimitive } from '@assistant-ui/react'; -import { MarkdownText } from '@/components/assistant-ui/markdown-text'; -import { ToolFallback } from '@/components/assistant-ui/tool-fallback'; -import { AssistantActionBar } from './MessageActions'; -import { BranchPicker } from './BranchPicker'; +import { MarkdownText } from './MarkdownText/MarkdownText'; +import { ToolFallback } from './ToolFallback/ToolFallback'; +import { AssistantActionBar } from '../MessageActions'; +import { BranchPicker } from '../BranchPicker'; export const AssistantMessage: FC = () => { return ( diff --git a/frontend/src/app/pages/AgentChat/OpenSwarmThread/components/AssistantMessage/MarkdownText/DEFAULT_COMPONENTS/CodeHeader.tsx b/frontend/src/app/pages/AgentChat/OpenSwarmThread/components/AssistantMessage/MarkdownText/DEFAULT_COMPONENTS/CodeHeader.tsx new file mode 100644 index 00000000..066b0b6e --- /dev/null +++ b/frontend/src/app/pages/AgentChat/OpenSwarmThread/components/AssistantMessage/MarkdownText/DEFAULT_COMPONENTS/CodeHeader.tsx @@ -0,0 +1,52 @@ +"use client"; + +import "@assistant-ui/react-markdown/styles/dot.css"; + +import { + type CodeHeaderProps, +} from "@assistant-ui/react-markdown"; +import { type FC, useState } from "react"; +import { CheckIcon, CopyIcon } from "lucide-react"; + +import { TooltipIconButton } from "@/app/pages/AgentChat/OpenSwarmThread/components/TooltipIconButton"; + + + +const useCopyToClipboard = ({ + copiedDuration = 3000, +}: { + copiedDuration?: number; +} = {}) => { + const [isCopied, setIsCopied] = useState(false); + + const copyToClipboard = (value: string) => { + if (!value) return; + + navigator.clipboard.writeText(value).then(() => { + setIsCopied(true); + setTimeout(() => setIsCopied(false), copiedDuration); + }); + }; + + return { isCopied, copyToClipboard }; +}; + +export const CodeHeader: FC = ({ language, code }) => { + const { isCopied, copyToClipboard } = useCopyToClipboard(); + const onCopy = () => { + if (!code || isCopied) return; + copyToClipboard(code); + }; + + return ( +
+ + {language} + + + {!isCopied && } + {isCopied && } + +
+ ); +}; diff --git a/frontend/src/components/assistant-ui/markdown-text.tsx b/frontend/src/app/pages/AgentChat/OpenSwarmThread/components/AssistantMessage/MarkdownText/DEFAULT_COMPONENTS/DEFAULT_COMPONENTS.tsx similarity index 73% rename from frontend/src/components/assistant-ui/markdown-text.tsx rename to frontend/src/app/pages/AgentChat/OpenSwarmThread/components/AssistantMessage/MarkdownText/DEFAULT_COMPONENTS/DEFAULT_COMPONENTS.tsx index 23662fcf..f474fca9 100644 --- a/frontend/src/components/assistant-ui/markdown-text.tsx +++ b/frontend/src/app/pages/AgentChat/OpenSwarmThread/components/AssistantMessage/MarkdownText/DEFAULT_COMPONENTS/DEFAULT_COMPONENTS.tsx @@ -3,70 +3,14 @@ import "@assistant-ui/react-markdown/styles/dot.css"; import { - type CodeHeaderProps, - MarkdownTextPrimitive, unstable_memoizeMarkdownComponents as memoizeMarkdownComponents, useIsMarkdownCodeBlock, } from "@assistant-ui/react-markdown"; -import remarkGfm from "remark-gfm"; -import { type FC, memo, useState } from "react"; -import { CheckIcon, CopyIcon } from "lucide-react"; - -import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button"; import { cn } from "@/lib/utils"; +import { CodeHeader } from "./CodeHeader"; -const MarkdownTextImpl = () => { - return ( - - ); -}; -export const MarkdownText = memo(MarkdownTextImpl); - -const CodeHeader: FC = ({ language, code }) => { - const { isCopied, copyToClipboard } = useCopyToClipboard(); - const onCopy = () => { - if (!code || isCopied) return; - copyToClipboard(code); - }; - - return ( -
- - {language} - - - {!isCopied && } - {isCopied && } - -
- ); -}; - -const useCopyToClipboard = ({ - copiedDuration = 3000, -}: { - copiedDuration?: number; -} = {}) => { - const [isCopied, setIsCopied] = useState(false); - - const copyToClipboard = (value: string) => { - if (!value) return; - - navigator.clipboard.writeText(value).then(() => { - setIsCopied(true); - setTimeout(() => setIsCopied(false), copiedDuration); - }); - }; - - return { isCopied, copyToClipboard }; -}; - -const defaultComponents = memoizeMarkdownComponents({ +export const DEFAULT_COMPONENTS = memoizeMarkdownComponents({ h1: ({ className, ...props }) => (

( + +); \ No newline at end of file diff --git a/frontend/src/app/pages/AgentChat/OpenSwarmThread/components/AssistantMessage/ToolFallback/ToolFallback.tsx b/frontend/src/app/pages/AgentChat/OpenSwarmThread/components/AssistantMessage/ToolFallback/ToolFallback.tsx new file mode 100644 index 00000000..7c29bc6f --- /dev/null +++ b/frontend/src/app/pages/AgentChat/OpenSwarmThread/components/AssistantMessage/ToolFallback/ToolFallback.tsx @@ -0,0 +1,64 @@ +"use client"; + +import { memo } from "react"; +import { + type ToolCallMessagePartComponent, +} from "@assistant-ui/react"; +import { cn } from "@/lib/utils"; +import { ToolFallbackRoot } from "./components/ToolFallbackRoot"; +import { ToolFallbackTrigger } from "./components/ToolFallbackTrigger"; +import { ToolFallbackContent } from "./components/ToolFallbackContent"; +import { ToolFallbackError } from "./components/ToolFallbackError"; +import { ToolFallbackArgs } from "./components/ToolFallbackArgs"; +import { ToolFallbackResult } from "./components/ToolFallbackResult"; + + + +const ToolFallbackImpl: ToolCallMessagePartComponent = ({ + toolName, + argsText, + result, + status, +}) => { + const isCancelled = + status?.type === "incomplete" && status.reason === "cancelled"; + + return ( + + + + + + {!isCancelled && } + + + ); +}; + +const ToolFallback = memo( + ToolFallbackImpl, +) as unknown as ToolCallMessagePartComponent & { + Root: typeof ToolFallbackRoot; + Trigger: typeof ToolFallbackTrigger; + Content: typeof ToolFallbackContent; + Args: typeof ToolFallbackArgs; + Result: typeof ToolFallbackResult; + Error: typeof ToolFallbackError; +}; + +ToolFallback.displayName = "ToolFallback"; +ToolFallback.Root = ToolFallbackRoot; +ToolFallback.Trigger = ToolFallbackTrigger; +ToolFallback.Content = ToolFallbackContent; +ToolFallback.Args = ToolFallbackArgs; +ToolFallback.Result = ToolFallbackResult; +ToolFallback.Error = ToolFallbackError; + +export { + ToolFallback, +}; diff --git a/frontend/src/app/pages/AgentChat/OpenSwarmThread/components/AssistantMessage/ToolFallback/components/ToolFallbackArgs.tsx b/frontend/src/app/pages/AgentChat/OpenSwarmThread/components/AssistantMessage/ToolFallback/components/ToolFallbackArgs.tsx new file mode 100644 index 00000000..6b82a047 --- /dev/null +++ b/frontend/src/app/pages/AgentChat/OpenSwarmThread/components/AssistantMessage/ToolFallback/components/ToolFallbackArgs.tsx @@ -0,0 +1,25 @@ +"use client"; + +import { cn } from "@/lib/utils"; + +export function ToolFallbackArgs({ + argsText, + className, + ...props +}: React.ComponentProps<"div"> & { + argsText?: string; +}) { + if (!argsText) return null; + + return ( +
+
+        {argsText}
+      
+
+ ); +} \ No newline at end of file diff --git a/frontend/src/app/pages/AgentChat/OpenSwarmThread/components/AssistantMessage/ToolFallback/components/ToolFallbackContent.tsx b/frontend/src/app/pages/AgentChat/OpenSwarmThread/components/AssistantMessage/ToolFallback/components/ToolFallbackContent.tsx new file mode 100644 index 00000000..c36b2910 --- /dev/null +++ b/frontend/src/app/pages/AgentChat/OpenSwarmThread/components/AssistantMessage/ToolFallback/components/ToolFallbackContent.tsx @@ -0,0 +1,31 @@ +"use client"; + +import { CollapsibleContent } from "@/components/ui/collapsible"; +import { cn } from "@/lib/utils"; + + +export function ToolFallbackContent({ + className, + children, + ...props +}: React.ComponentProps) { + return ( + +
{children}
+
+ ); +} \ No newline at end of file diff --git a/frontend/src/app/pages/AgentChat/OpenSwarmThread/components/AssistantMessage/ToolFallback/components/ToolFallbackError.tsx b/frontend/src/app/pages/AgentChat/OpenSwarmThread/components/AssistantMessage/ToolFallback/components/ToolFallbackError.tsx new file mode 100644 index 00000000..5d8d6ec5 --- /dev/null +++ b/frontend/src/app/pages/AgentChat/OpenSwarmThread/components/AssistantMessage/ToolFallback/components/ToolFallbackError.tsx @@ -0,0 +1,43 @@ +"use client"; + +import { type ToolCallMessagePartStatus } from "@assistant-ui/react"; +import { cn } from "@/lib/utils"; + + + +export function ToolFallbackError({ + status, + className, + ...props +}: React.ComponentProps<"div"> & { + status?: ToolCallMessagePartStatus; +}) { + if (status?.type !== "incomplete") return null; + + const error = status.error; + const errorText = error + ? typeof error === "string" + ? error + : JSON.stringify(error) + : null; + + if (!errorText) return null; + + const isCancelled = status.reason === "cancelled"; + const headerText = isCancelled ? "Cancelled reason:" : "Error:"; + + return ( +
+

+ {headerText} +

+

+ {errorText} +

+
+ ); +} \ No newline at end of file diff --git a/frontend/src/app/pages/AgentChat/OpenSwarmThread/components/AssistantMessage/ToolFallback/components/ToolFallbackResult.tsx b/frontend/src/app/pages/AgentChat/OpenSwarmThread/components/AssistantMessage/ToolFallback/components/ToolFallbackResult.tsx new file mode 100644 index 00000000..a8cb1b93 --- /dev/null +++ b/frontend/src/app/pages/AgentChat/OpenSwarmThread/components/AssistantMessage/ToolFallback/components/ToolFallbackResult.tsx @@ -0,0 +1,29 @@ +"use client"; +import { cn } from "@/lib/utils"; + +export function ToolFallbackResult({ + result, + className, + ...props +}: React.ComponentProps<"div"> & { + result?: unknown; +}) { + if (result === undefined) return null; + + return ( +
+

Result:

+
+        {typeof result === "string" ? result : JSON.stringify(result, null, 2)}
+      
+
+ ); +} + diff --git a/frontend/src/app/pages/AgentChat/OpenSwarmThread/components/AssistantMessage/ToolFallback/components/ToolFallbackRoot.tsx b/frontend/src/app/pages/AgentChat/OpenSwarmThread/components/AssistantMessage/ToolFallback/components/ToolFallbackRoot.tsx new file mode 100644 index 00000000..4f41f33c --- /dev/null +++ b/frontend/src/app/pages/AgentChat/OpenSwarmThread/components/AssistantMessage/ToolFallback/components/ToolFallbackRoot.tsx @@ -0,0 +1,69 @@ +"use client"; + +import { useCallback, useRef, useState } from "react"; +import { + useScrollLock, +} from "@assistant-ui/react"; +import { Collapsible } from "@/components/ui/collapsible"; +import { cn } from "@/lib/utils"; + +const ANIMATION_DURATION = 200; + +type ToolFallbackRootProps = Omit< + React.ComponentProps, + "open" | "onOpenChange" +> & { + open?: boolean; + onOpenChange?: (open: boolean) => void; + defaultOpen?: boolean; +}; + +export function ToolFallbackRoot({ + className, + open: controlledOpen, + onOpenChange: controlledOnOpenChange, + defaultOpen = false, + children, + ...props +}: ToolFallbackRootProps) { + const collapsibleRef = useRef(null); + const [uncontrolledOpen, setUncontrolledOpen] = useState(defaultOpen); + const lockScroll = useScrollLock(collapsibleRef, ANIMATION_DURATION); + + const isControlled = controlledOpen !== undefined; + const isOpen = isControlled ? controlledOpen : uncontrolledOpen; + + const handleOpenChange = useCallback( + (open: boolean) => { + if (!open) { + lockScroll(); + } + if (!isControlled) { + setUncontrolledOpen(open); + } + controlledOnOpenChange?.(open); + }, + [lockScroll, isControlled, controlledOnOpenChange], + ); + + return ( + + {children} + + ); +} diff --git a/frontend/src/app/pages/AgentChat/OpenSwarmThread/components/AssistantMessage/ToolFallback/components/ToolFallbackTrigger.tsx b/frontend/src/app/pages/AgentChat/OpenSwarmThread/components/AssistantMessage/ToolFallback/components/ToolFallbackTrigger.tsx new file mode 100644 index 00000000..8ba37222 --- /dev/null +++ b/frontend/src/app/pages/AgentChat/OpenSwarmThread/components/AssistantMessage/ToolFallback/components/ToolFallbackTrigger.tsx @@ -0,0 +1,88 @@ +"use client"; + +import { + AlertCircleIcon, + CheckIcon, + ChevronDownIcon, + LoaderIcon, + XCircleIcon, +} from "lucide-react"; +import { type ToolCallMessagePartStatus } from "@assistant-ui/react"; +import { CollapsibleTrigger } from "@/components/ui/collapsible"; +import { cn } from "@/lib/utils"; + +type ToolStatus = ToolCallMessagePartStatus["type"]; + +const statusIconMap: Record = { + running: LoaderIcon, + complete: CheckIcon, + incomplete: XCircleIcon, + "requires-action": AlertCircleIcon, +}; + +export function ToolFallbackTrigger({ + toolName, + status, + className, + ...props +}: React.ComponentProps & { + toolName: string; + status?: ToolCallMessagePartStatus; +}) { + const statusType = status?.type ?? "complete"; + const isRunning = statusType === "running"; + const isCancelled = + status?.type === "incomplete" && status.reason === "cancelled"; + + const Icon = statusIconMap[statusType]; + const label = isCancelled ? "Cancelled tool" : "Used tool"; + + return ( + + + + + {label}: {toolName} + + {isRunning && ( + + {label}: {toolName} + + )} + + + + ); +} \ No newline at end of file diff --git a/frontend/src/app/pages/AgentChat/OpenSwarmThread/components/BranchPicker.tsx b/frontend/src/app/pages/AgentChat/OpenSwarmThread/components/BranchPicker.tsx index 576a459a..87582432 100644 --- a/frontend/src/app/pages/AgentChat/OpenSwarmThread/components/BranchPicker.tsx +++ b/frontend/src/app/pages/AgentChat/OpenSwarmThread/components/BranchPicker.tsx @@ -1,7 +1,7 @@ import { type FC } from 'react'; import { BranchPickerPrimitive } from '@assistant-ui/react'; import { ChevronLeftIcon, ChevronRightIcon } from 'lucide-react'; -import { TooltipIconButton } from '@/components/assistant-ui/tooltip-icon-button'; +import { TooltipIconButton } from './TooltipIconButton'; import { cn } from '@/lib/utils'; export const BranchPicker: FC = ({ diff --git a/frontend/src/app/pages/AgentChat/OpenSwarmThread/components/MessageActions.tsx b/frontend/src/app/pages/AgentChat/OpenSwarmThread/components/MessageActions.tsx index 56231b0e..365f5383 100644 --- a/frontend/src/app/pages/AgentChat/OpenSwarmThread/components/MessageActions.tsx +++ b/frontend/src/app/pages/AgentChat/OpenSwarmThread/components/MessageActions.tsx @@ -11,7 +11,7 @@ import { PencilIcon, RefreshCwIcon, } from 'lucide-react'; -import { TooltipIconButton } from '@/components/assistant-ui/tooltip-icon-button'; +import { TooltipIconButton } from './TooltipIconButton'; import { useAppDispatch } from '@/shared/hooks'; import { setActiveSession, diff --git a/frontend/src/components/assistant-ui/tooltip-icon-button.tsx b/frontend/src/app/pages/AgentChat/OpenSwarmThread/components/TooltipIconButton.tsx similarity index 100% rename from frontend/src/components/assistant-ui/tooltip-icon-button.tsx rename to frontend/src/app/pages/AgentChat/OpenSwarmThread/components/TooltipIconButton.tsx diff --git a/frontend/src/app/pages/AgentChat/OpenSwarmThread/components/UserMessage.tsx b/frontend/src/app/pages/AgentChat/OpenSwarmThread/components/UserMessage/UserMessage.tsx similarity index 96% rename from frontend/src/app/pages/AgentChat/OpenSwarmThread/components/UserMessage.tsx rename to frontend/src/app/pages/AgentChat/OpenSwarmThread/components/UserMessage/UserMessage.tsx index f96ab542..5ceab5b1 100644 --- a/frontend/src/app/pages/AgentChat/OpenSwarmThread/components/UserMessage.tsx +++ b/frontend/src/app/pages/AgentChat/OpenSwarmThread/components/UserMessage/UserMessage.tsx @@ -4,12 +4,12 @@ import { useAui, useMessagePartText, } from '@assistant-ui/react'; -import { UserMessageAttachments } from '@/components/assistant-ui/attachment'; +import { UserMessageAttachments } from './UserMessageAttachments'; import { useAppSelector } from '@/shared/hooks'; import type { AgentMessage } from '@/shared/state/agentsSlice'; -import { useSessionId } from '../utils'; -import { UserActionBar } from './MessageActions'; -import { BranchPicker } from './BranchPicker'; +import { useSessionId } from '../../utils'; +import { UserActionBar } from '../MessageActions'; +import { BranchPicker } from '../BranchPicker'; const ELEMENT_SEPARATOR = '\n\n---\nSelected UI Elements:\n'; const SKILL_PILL_RE = /\{\{skill:([^}]+)\}\}/g; diff --git a/frontend/src/components/assistant-ui/attachment.tsx b/frontend/src/app/pages/AgentChat/OpenSwarmThread/components/UserMessage/UserMessageAttachments.tsx similarity index 98% rename from frontend/src/components/assistant-ui/attachment.tsx rename to frontend/src/app/pages/AgentChat/OpenSwarmThread/components/UserMessage/UserMessageAttachments.tsx index 3f189805..0f49b041 100644 --- a/frontend/src/components/assistant-ui/attachment.tsx +++ b/frontend/src/app/pages/AgentChat/OpenSwarmThread/components/UserMessage/UserMessageAttachments.tsx @@ -21,7 +21,7 @@ import { DialogTrigger, } from "@/components/ui/dialog"; import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar"; -import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button"; +import { TooltipIconButton } from "../TooltipIconButton"; import { cn } from "@/lib/utils"; const useFileSrc = (file: File | undefined) => { diff --git a/frontend/src/components/assistant-ui/tool-fallback.tsx b/frontend/src/components/assistant-ui/tool-fallback.tsx deleted file mode 100644 index 31756942..00000000 --- a/frontend/src/components/assistant-ui/tool-fallback.tsx +++ /dev/null @@ -1,318 +0,0 @@ -"use client"; - -import { memo, useCallback, useRef, useState } from "react"; -import { - AlertCircleIcon, - CheckIcon, - ChevronDownIcon, - LoaderIcon, - XCircleIcon, -} from "lucide-react"; -import { - useScrollLock, - type ToolCallMessagePartStatus, - type ToolCallMessagePartComponent, -} from "@assistant-ui/react"; -import { - Collapsible, - CollapsibleContent, - CollapsibleTrigger, -} from "@/components/ui/collapsible"; -import { cn } from "@/lib/utils"; - -const ANIMATION_DURATION = 200; - -type ToolFallbackRootProps = Omit< - React.ComponentProps, - "open" | "onOpenChange" -> & { - open?: boolean; - onOpenChange?: (open: boolean) => void; - defaultOpen?: boolean; -}; - -function ToolFallbackRoot({ - className, - open: controlledOpen, - onOpenChange: controlledOnOpenChange, - defaultOpen = false, - children, - ...props -}: ToolFallbackRootProps) { - const collapsibleRef = useRef(null); - const [uncontrolledOpen, setUncontrolledOpen] = useState(defaultOpen); - const lockScroll = useScrollLock(collapsibleRef, ANIMATION_DURATION); - - const isControlled = controlledOpen !== undefined; - const isOpen = isControlled ? controlledOpen : uncontrolledOpen; - - const handleOpenChange = useCallback( - (open: boolean) => { - if (!open) { - lockScroll(); - } - if (!isControlled) { - setUncontrolledOpen(open); - } - controlledOnOpenChange?.(open); - }, - [lockScroll, isControlled, controlledOnOpenChange], - ); - - return ( - - {children} - - ); -} - -type ToolStatus = ToolCallMessagePartStatus["type"]; - -const statusIconMap: Record = { - running: LoaderIcon, - complete: CheckIcon, - incomplete: XCircleIcon, - "requires-action": AlertCircleIcon, -}; - -function ToolFallbackTrigger({ - toolName, - status, - className, - ...props -}: React.ComponentProps & { - toolName: string; - status?: ToolCallMessagePartStatus; -}) { - const statusType = status?.type ?? "complete"; - const isRunning = statusType === "running"; - const isCancelled = - status?.type === "incomplete" && status.reason === "cancelled"; - - const Icon = statusIconMap[statusType]; - const label = isCancelled ? "Cancelled tool" : "Used tool"; - - return ( - - - - - {label}: {toolName} - - {isRunning && ( - - {label}: {toolName} - - )} - - - - ); -} - -function ToolFallbackContent({ - className, - children, - ...props -}: React.ComponentProps) { - return ( - -
{children}
-
- ); -} - -function ToolFallbackArgs({ - argsText, - className, - ...props -}: React.ComponentProps<"div"> & { - argsText?: string; -}) { - if (!argsText) return null; - - return ( -
-
-        {argsText}
-      
-
- ); -} - -function ToolFallbackResult({ - result, - className, - ...props -}: React.ComponentProps<"div"> & { - result?: unknown; -}) { - if (result === undefined) return null; - - return ( -
-

Result:

-
-        {typeof result === "string" ? result : JSON.stringify(result, null, 2)}
-      
-
- ); -} - -function ToolFallbackError({ - status, - className, - ...props -}: React.ComponentProps<"div"> & { - status?: ToolCallMessagePartStatus; -}) { - if (status?.type !== "incomplete") return null; - - const error = status.error; - const errorText = error - ? typeof error === "string" - ? error - : JSON.stringify(error) - : null; - - if (!errorText) return null; - - const isCancelled = status.reason === "cancelled"; - const headerText = isCancelled ? "Cancelled reason:" : "Error:"; - - return ( -
-

- {headerText} -

-

- {errorText} -

-
- ); -} - -const ToolFallbackImpl: ToolCallMessagePartComponent = ({ - toolName, - argsText, - result, - status, -}) => { - const isCancelled = - status?.type === "incomplete" && status.reason === "cancelled"; - - return ( - - - - - - {!isCancelled && } - - - ); -}; - -const ToolFallback = memo( - ToolFallbackImpl, -) as unknown as ToolCallMessagePartComponent & { - Root: typeof ToolFallbackRoot; - Trigger: typeof ToolFallbackTrigger; - Content: typeof ToolFallbackContent; - Args: typeof ToolFallbackArgs; - Result: typeof ToolFallbackResult; - Error: typeof ToolFallbackError; -}; - -ToolFallback.displayName = "ToolFallback"; -ToolFallback.Root = ToolFallbackRoot; -ToolFallback.Trigger = ToolFallbackTrigger; -ToolFallback.Content = ToolFallbackContent; -ToolFallback.Args = ToolFallbackArgs; -ToolFallback.Result = ToolFallbackResult; -ToolFallback.Error = ToolFallbackError; - -export { - ToolFallback, -};