diff --git a/components/message.tsx b/components/message.tsx index 8412d43..120b7d7 100644 --- a/components/message.tsx +++ b/components/message.tsx @@ -10,6 +10,7 @@ import { Markdown } from './markdown'; import { MessageActions } from './message-actions'; import { PreviewAttachment } from './preview-attachment'; import { Weather } from './weather'; +import { ToolCall } from './tool-call'; import equal from 'fast-deep-equal'; import { cn, sanitizeText } from '@/lib/utils'; import { Button } from './ui/button'; @@ -175,7 +176,13 @@ const PurePreviewMessage = ({
Processing...
- ) : null} + ) : ( + + )} ); } @@ -196,7 +203,12 @@ const PurePreviewMessage = ({ Completed ) : ( -
{JSON.stringify(result, null, 2)}
+ )} ); diff --git a/components/suggested-actions.tsx b/components/suggested-actions.tsx index 430ab7a..6319bf3 100644 --- a/components/suggested-actions.tsx +++ b/components/suggested-actions.tsx @@ -34,9 +34,9 @@ function PureSuggestedActions({ action: `Help me write an essay about silicon valley`, }, { - title: 'What is the weather', - label: 'in San Francisco?', - action: 'What is the weather in San Francisco?', + title: 'Fetch my Gmail details', + label: 'using the Gmail tool.', + action: 'Fetch my Gmail details using the Gmail tool.', }, ]; diff --git a/components/tool-call.tsx b/components/tool-call.tsx new file mode 100644 index 0000000..e9620dd --- /dev/null +++ b/components/tool-call.tsx @@ -0,0 +1,188 @@ +'use client'; + +import { useState } from 'react'; +import { ChevronDown, ChevronUp, Wrench } from 'lucide-react'; +import { Card, CardContent, CardHeader, CardTitle } from './ui/card'; +import { Tabs, TabsList, TabsTrigger } from './ui/tabs'; +import { Skeleton } from './ui/skeleton'; + +interface ToolCallProps { + toolName: string; + args?: any; + result?: any; + isLoading?: boolean; +} + +export function ToolCall({ + toolName, + args, + result, + isLoading = false, +}: ToolCallProps) { + const [isExpanded, setIsExpanded] = useState(false); + const [viewMode, setViewMode] = useState<'table' | 'json'>('table'); + + // Format tool name for display (e.g., "GITHUB_CREATE_ISSUE" -> "GitHub Create Issue") + const formatToolName = (name: string) => { + return name + .split('_') + .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()) + .join(' '); + }; + + // Render parameters in table format + const renderParametersTable = (params: any) => { + if (!params || typeof params !== 'object') return null; + + return ( +
+
+ {Object.entries(params).map(([key, value]) => ( +
+
+ {key} +
+
+ {typeof value === 'object' + ? JSON.stringify(value, null, 2) + : String(value)} +
+
+ ))} +
+
+ ); + }; + + // Render response in table format (simplified for now) + const renderResponseTable = (response: any) => { + if (!response || typeof response !== 'object') return null; + + return ( +
+
+ {Object.entries(response).map(([key, value]) => ( +
+
+ {key} +
+
+ {typeof value === 'object' ? ( +
+                    {JSON.stringify(value, null, 2)}
+                  
+ ) : ( + String(value) + )} +
+
+ ))} +
+
+ ); + }; + + return ( + + +
+
+ + + {formatToolName(toolName)} + +
+
+
+ + + {/* Calling tool section */} +
+ +
+ + {isExpanded && ( +
+ {/* View mode toggle */} + {result && ( +
+ setViewMode(v as 'table' | 'json')} + > + + Table + JSON + + +
+ )} + + {/* Parameters section */} + {args && ( +
+

+ Parameters +

+ {viewMode === 'table' ? ( + renderParametersTable(args) + ) : ( +
+                    {JSON.stringify(args, null, 2)}
+                  
+ )} +
+ )} + + {/* Loading state */} + {isLoading && ( +
+

+ Response +

+
+ + + +
+
+ )} + + {/* Response section */} + {result && !isLoading && ( +
+

+ Response +

+ {viewMode === 'table' ? ( + renderResponseTable(result) + ) : ( +
+                    {JSON.stringify(result, null, 2)}
+                  
+ )} +
+ )} +
+ )} +
+
+ ); +} diff --git a/components/ui/tabs.tsx b/components/ui/tabs.tsx new file mode 100644 index 0000000..d1fb058 --- /dev/null +++ b/components/ui/tabs.tsx @@ -0,0 +1,55 @@ +'use client'; + +import * as React from 'react'; +import * as TabsPrimitive from '@radix-ui/react-tabs'; + +import { cn } from '@/lib/utils'; + +const Tabs = TabsPrimitive.Root; + +const TabsList = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +TabsList.displayName = TabsPrimitive.List.displayName; + +const TabsTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +TabsTrigger.displayName = TabsPrimitive.Trigger.displayName; + +const TabsContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +TabsContent.displayName = TabsPrimitive.Content.displayName; + +export { Tabs, TabsList, TabsTrigger, TabsContent }; diff --git a/package.json b/package.json index 2f33967..d3b24d4 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "@radix-ui/react-separator": "^1.1.0", "@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-switch": "^1.2.5", + "@radix-ui/react-tabs": "^1.1.12", "@radix-ui/react-toast": "^1.2.14", "@radix-ui/react-tooltip": "^1.1.3", "@radix-ui/react-visually-hidden": "^1.1.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a94cc5e..56635b4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -68,6 +68,9 @@ importers: '@radix-ui/react-switch': specifier: ^1.2.5 version: 1.2.5(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@19.0.0-rc-45804af1-20241021(react@19.0.0-rc-45804af1-20241021))(react@19.0.0-rc-45804af1-20241021) + '@radix-ui/react-tabs': + specifier: ^1.1.12 + version: 1.1.12(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@19.0.0-rc-45804af1-20241021(react@19.0.0-rc-45804af1-20241021))(react@19.0.0-rc-45804af1-20241021) '@radix-ui/react-toast': specifier: ^1.2.14 version: 1.2.14(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@19.0.0-rc-45804af1-20241021(react@19.0.0-rc-45804af1-20241021))(react@19.0.0-rc-45804af1-20241021) @@ -1308,6 +1311,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-direction@1.1.1': + resolution: {integrity: sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-dismissable-layer@1.1.10': resolution: {integrity: sha512-IM1zzRV4W3HtVgftdQiiOmA0AdJlCtMLe00FXaHwgt3rAnNsIyDqshvkIW3hj/iu5hu8ERP7KIYki6NkqDxAwQ==} peerDependencies: @@ -1383,6 +1395,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-id@1.1.1': + resolution: {integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-label@2.1.2': resolution: {integrity: sha512-zo1uGMTaNlHehDyFQcDZXRJhUPDuukcnHz0/jnrup0JA6qL+AFpAnty+7VKa9esuU5xTblAZzTGYJKSKaBxBhw==} peerDependencies: @@ -1500,6 +1521,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-roving-focus@1.1.10': + resolution: {integrity: sha512-dT9aOXUen9JSsxnMPv/0VqySQf5eDQ6LCk5Sw28kamz8wSOW2bJdlX2Bg5VUIIcV+6XlHpWTIuTPCf/UNIyq8Q==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-roving-focus@1.1.2': resolution: {integrity: sha512-zgMQWkNO169GtGqRvYrzb0Zf8NhMHS2DuEB/TiEmVnpr5OqPU3i8lfbxaAmC2J/KYuIQxyoQQ6DxepyXp61/xw==} peerDependencies: @@ -1570,6 +1604,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-tabs@1.1.12': + resolution: {integrity: sha512-GTVAlRVrQrSw3cEARM0nAx73ixrWDPNZAruETn3oHCNP6SbZ/hNxdxp+u7VkIEv3/sFoLq1PfcHrl7Pnp0CDpw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-toast@1.2.14': resolution: {integrity: sha512-nAP5FBxBJGQ/YfUB+r+O6USFVkWq3gAInkxyEnmvEV5jtSbfDhfa4hwX8CraCnbjMLsE7XSf/K75l9xXY7joWg==} peerDependencies: @@ -5088,6 +5135,12 @@ snapshots: optionalDependencies: '@types/react': 18.3.18 + '@radix-ui/react-direction@1.1.1(@types/react@18.3.18)(react@19.0.0-rc-45804af1-20241021)': + dependencies: + react: 19.0.0-rc-45804af1-20241021 + optionalDependencies: + '@types/react': 18.3.18 + '@radix-ui/react-dismissable-layer@1.1.10(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@19.0.0-rc-45804af1-20241021(react@19.0.0-rc-45804af1-20241021))(react@19.0.0-rc-45804af1-20241021)': dependencies: '@radix-ui/primitive': 1.1.2 @@ -5157,6 +5210,13 @@ snapshots: optionalDependencies: '@types/react': 18.3.18 + '@radix-ui/react-id@1.1.1(@types/react@18.3.18)(react@19.0.0-rc-45804af1-20241021)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.18)(react@19.0.0-rc-45804af1-20241021) + react: 19.0.0-rc-45804af1-20241021 + optionalDependencies: + '@types/react': 18.3.18 + '@radix-ui/react-label@2.1.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@19.0.0-rc-45804af1-20241021(react@19.0.0-rc-45804af1-20241021))(react@19.0.0-rc-45804af1-20241021)': dependencies: '@radix-ui/react-primitive': 2.0.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@19.0.0-rc-45804af1-20241021(react@19.0.0-rc-45804af1-20241021))(react@19.0.0-rc-45804af1-20241021) @@ -5268,6 +5328,23 @@ snapshots: '@types/react': 18.3.18 '@types/react-dom': 18.3.5(@types/react@18.3.18) + '@radix-ui/react-roving-focus@1.1.10(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@19.0.0-rc-45804af1-20241021(react@19.0.0-rc-45804af1-20241021))(react@19.0.0-rc-45804af1-20241021)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@19.0.0-rc-45804af1-20241021(react@19.0.0-rc-45804af1-20241021))(react@19.0.0-rc-45804af1-20241021) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.18)(react@19.0.0-rc-45804af1-20241021) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.18)(react@19.0.0-rc-45804af1-20241021) + '@radix-ui/react-direction': 1.1.1(@types/react@18.3.18)(react@19.0.0-rc-45804af1-20241021) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.18)(react@19.0.0-rc-45804af1-20241021) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@19.0.0-rc-45804af1-20241021(react@19.0.0-rc-45804af1-20241021))(react@19.0.0-rc-45804af1-20241021) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.18)(react@19.0.0-rc-45804af1-20241021) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.18)(react@19.0.0-rc-45804af1-20241021) + react: 19.0.0-rc-45804af1-20241021 + react-dom: 19.0.0-rc-45804af1-20241021(react@19.0.0-rc-45804af1-20241021) + optionalDependencies: + '@types/react': 18.3.18 + '@types/react-dom': 18.3.5(@types/react@18.3.18) + '@radix-ui/react-roving-focus@1.1.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@19.0.0-rc-45804af1-20241021(react@19.0.0-rc-45804af1-20241021))(react@19.0.0-rc-45804af1-20241021)': dependencies: '@radix-ui/primitive': 1.1.1 @@ -5352,6 +5429,22 @@ snapshots: '@types/react': 18.3.18 '@types/react-dom': 18.3.5(@types/react@18.3.18) + '@radix-ui/react-tabs@1.1.12(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@19.0.0-rc-45804af1-20241021(react@19.0.0-rc-45804af1-20241021))(react@19.0.0-rc-45804af1-20241021)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-context': 1.1.2(@types/react@18.3.18)(react@19.0.0-rc-45804af1-20241021) + '@radix-ui/react-direction': 1.1.1(@types/react@18.3.18)(react@19.0.0-rc-45804af1-20241021) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.18)(react@19.0.0-rc-45804af1-20241021) + '@radix-ui/react-presence': 1.1.4(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@19.0.0-rc-45804af1-20241021(react@19.0.0-rc-45804af1-20241021))(react@19.0.0-rc-45804af1-20241021) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@19.0.0-rc-45804af1-20241021(react@19.0.0-rc-45804af1-20241021))(react@19.0.0-rc-45804af1-20241021) + '@radix-ui/react-roving-focus': 1.1.10(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@19.0.0-rc-45804af1-20241021(react@19.0.0-rc-45804af1-20241021))(react@19.0.0-rc-45804af1-20241021) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.18)(react@19.0.0-rc-45804af1-20241021) + react: 19.0.0-rc-45804af1-20241021 + react-dom: 19.0.0-rc-45804af1-20241021(react@19.0.0-rc-45804af1-20241021) + optionalDependencies: + '@types/react': 18.3.18 + '@types/react-dom': 18.3.5(@types/react@18.3.18) + '@radix-ui/react-toast@1.2.14(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@19.0.0-rc-45804af1-20241021(react@19.0.0-rc-45804af1-20241021))(react@19.0.0-rc-45804af1-20241021)': dependencies: '@radix-ui/primitive': 1.1.2