Skip to content
This repository was archived by the owner on Feb 19, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions components/message.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -175,7 +176,13 @@ const PurePreviewMessage = ({
<div className="text-muted-foreground text-sm">
Processing...
</div>
) : null}
) : (
<ToolCall
toolName={toolName}
args={args}
isLoading={true}
/>
)}
</div>
);
}
Expand All @@ -196,7 +203,12 @@ const PurePreviewMessage = ({
Completed
</div>
) : (
<pre>{JSON.stringify(result, null, 2)}</pre>
<ToolCall
toolName={toolName}
args={toolInvocation.args}
result={result}
isLoading={false}
/>
)}
</div>
);
Expand Down
6 changes: 3 additions & 3 deletions components/suggested-actions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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.',
},
];

Expand Down
188 changes: 188 additions & 0 deletions components/tool-call.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className="max-h-64 overflow-auto rounded-md border p-4">
<div className="space-y-2">
{Object.entries(params).map(([key, value]) => (
<div
key={key}
className="grid grid-cols-3 gap-4 py-2 border-b last:border-0"
>
<div className="font-mono text-sm text-muted-foreground">
{key}
</div>
<div className="col-span-2 text-sm">
{typeof value === 'object'
? JSON.stringify(value, null, 2)
: String(value)}
</div>
</div>
))}
</div>
</div>
);
};

// Render response in table format (simplified for now)
const renderResponseTable = (response: any) => {
if (!response || typeof response !== 'object') return null;

return (
<div className="max-h-96 overflow-auto rounded-md border p-4">
<div className="space-y-2">
{Object.entries(response).map(([key, value]) => (
<div
key={key}
className="grid grid-cols-3 gap-4 py-2 border-b last:border-0"
>
<div className="font-mono text-sm text-muted-foreground">
{key}
</div>
<div className="col-span-2 text-sm">
{typeof value === 'object' ? (
<pre className="whitespace-pre-wrap">
{JSON.stringify(value, null, 2)}
</pre>
) : (
String(value)
)}
</div>
</div>
))}
</div>
</div>
);
};

return (
<Card className="w-full max-w-2xl">
<CardHeader className="pb-3">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<Wrench className="size-4" />
<CardTitle className="text-base">
{formatToolName(toolName)}
</CardTitle>
</div>
</div>
</CardHeader>

<CardContent>
{/* Calling tool section */}
<div className="mb-4">
<button
type="button"
onClick={() => setIsExpanded(!isExpanded)}
className="flex items-center gap-2 text-sm text-muted-foreground hover:text-foreground transition-colors"
>
{isExpanded ? (
<ChevronUp className="size-4" />
) : (
<ChevronDown className="size-4" />
)}
Calling tool {toolName}
</button>
</div>

{isExpanded && (
<div className="space-y-4">
{/* View mode toggle */}
{result && (
<div className="flex justify-end">
<Tabs
value={viewMode}
onValueChange={(v) => setViewMode(v as 'table' | 'json')}
>
<TabsList className="grid w-[200px] grid-cols-2">
<TabsTrigger value="table">Table</TabsTrigger>
<TabsTrigger value="json">JSON</TabsTrigger>
</TabsList>
</Tabs>
</div>
)}

{/* Parameters section */}
{args && (
<div className="space-y-2">
<h4 className="text-sm font-medium text-muted-foreground">
Parameters
</h4>
{viewMode === 'table' ? (
renderParametersTable(args)
) : (
<pre className="p-4 bg-muted rounded-md text-xs overflow-auto max-h-64">
{JSON.stringify(args, null, 2)}
</pre>
)}
</div>
)}

{/* Loading state */}
{isLoading && (
<div className="space-y-2">
<h4 className="text-sm font-medium text-muted-foreground">
Response
</h4>
<div className="space-y-2">
<Skeleton className="h-4 w-full" />
<Skeleton className="h-4 w-3/4" />
<Skeleton className="h-4 w-1/2" />
</div>
</div>
)}

{/* Response section */}
{result && !isLoading && (
<div className="space-y-2">
<h4 className="text-sm font-medium text-muted-foreground">
Response
</h4>
{viewMode === 'table' ? (
renderResponseTable(result)
) : (
<pre className="p-4 bg-muted rounded-md text-xs overflow-auto max-h-96">
{JSON.stringify(result, null, 2)}
</pre>
)}
</div>
)}
</div>
)}
</CardContent>
</Card>
);
}
55 changes: 55 additions & 0 deletions components/ui/tabs.tsx
Original file line number Diff line number Diff line change
@@ -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<typeof TabsPrimitive.List>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
>(({ className, ...props }, ref) => (
<TabsPrimitive.List
ref={ref}
className={cn(
'inline-flex h-9 items-center justify-center rounded-lg bg-muted p-1 text-muted-foreground',
className,
)}
{...props}
/>
));
TabsList.displayName = TabsPrimitive.List.displayName;

const TabsTrigger = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
>(({ className, ...props }, ref) => (
<TabsPrimitive.Trigger
ref={ref}
className={cn(
'inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow',
className,
)}
{...props}
/>
));
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;

const TabsContent = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
>(({ className, ...props }, ref) => (
<TabsPrimitive.Content
ref={ref}
className={cn(
'mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
className,
)}
{...props}
/>
));
TabsContent.displayName = TabsPrimitive.Content.displayName;

export { Tabs, TabsList, TabsTrigger, TabsContent };
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Loading
Loading