diff --git a/apps/web/components/chat/message/agent-message.tsx b/apps/web/components/chat/message/agent-message.tsx index eaf151ce..28f34919 100644 --- a/apps/web/components/chat/message/agent-message.tsx +++ b/apps/web/components/chat/message/agent-message.tsx @@ -4,17 +4,18 @@ import { useState } from "react" import type { UIMessage } from "@ai-sdk/react" import { Streamdown } from "streamdown" import { + BookOpenIcon, ChevronDownIcon, ChevronRightIcon, - Loader2, - SearchIcon, - GlobeIcon, - PlusIcon, - BookOpenIcon, ClockIcon, + GlobeIcon, ListIcon, - XCircleIcon, + Loader2, + PlusIcon, + SearchIcon, + TerminalIcon, WrenchIcon, + XCircleIcon, } from "lucide-react" import { cn } from "@lib/utils" import { isWebSearchToolName } from "@/lib/chat-web-search-tools" @@ -22,9 +23,11 @@ import { RelatedMemories } from "./related-memories" import { MessageActions } from "./message-actions" const TOOL_META: Record = { - searchMemories: { label: "Search Memories", icon: SearchIcon }, + bash: { label: "Memory", icon: TerminalIcon }, web_search: { label: "Web search", icon: GlobeIcon }, google_search: { label: "Google search", icon: GlobeIcon }, + // legacy tool names kept for existing persisted messages + searchMemories: { label: "Search Memories", icon: SearchIcon }, addMemory: { label: "Add Memory", icon: PlusIcon }, fetchMemory: { label: "Fetch Memory", icon: BookOpenIcon }, scheduleTask: { label: "Schedule Task", icon: ClockIcon }, @@ -95,9 +98,119 @@ function WebSourcesGroup({ sources }: { sources: SourceUrlPart[] }) { ) } +function BashToolDisplay({ part }: { part: ToolCallDisplayPart }) { + const [expanded, setExpanded] = useState(false) + const isLoading = + part.state === "input-streaming" || part.state === "input-available" + const isDone = part.state === "output-available" + const isError = part.state === "error" || part.state === "output-error" + + const cmd = + part.input && typeof part.input === "object" && "cmd" in part.input + ? String((part.input as { cmd: string }).cmd) + : undefined + + const output = + isDone && part.output && typeof part.output === "object" + ? (part.output as { stdout?: string; stderr?: string; exitCode?: number }) + : undefined + + const hasOutput = + output && + ((output.stdout && output.stdout.length > 0) || + (output.stderr && output.stderr.length > 0)) + const errorText = part.errorText + const hasExpandable = hasOutput || (isError && errorText) + + return ( +
+ + + {expanded && (hasOutput || (isError && errorText)) && ( +
+ {output?.stdout && output.stdout.length > 0 && ( +
+							{output.stdout}
+						
+ )} + {output?.stderr && output.stderr.length > 0 && ( +
+							{output.stderr}
+						
+ )} + {isError && errorText && ( +
+							{errorText}
+						
+ )} +
+ )} +
+ ) +} + function ToolCallDisplay({ part }: { part: ToolCallDisplayPart }) { const [expanded, setExpanded] = useState(false) const toolName = part.type.replace("tool-", "") + if (toolName === "bash") { + return + } const meta = TOOL_META[toolName] ?? (isWebSearchToolName(toolName)