Upgrades (#1419)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
3bc77653ad
commit
453f5bb3e6
94 changed files with 4029 additions and 8199 deletions
|
|
@ -1,6 +1,15 @@
|
|||
"use client";
|
||||
|
||||
import type { ToolUIPart } from "ai";
|
||||
import type { DynamicToolUIPart, ToolUIPart } from "ai";
|
||||
import type { ComponentProps, ReactNode } from "react";
|
||||
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import {
|
||||
Collapsible,
|
||||
CollapsibleContent,
|
||||
CollapsibleTrigger,
|
||||
} from "@/components/ui/collapsible";
|
||||
import { cn } from "@/lib/utils";
|
||||
import {
|
||||
CheckCircleIcon,
|
||||
ChevronDownIcon,
|
||||
|
|
@ -9,92 +18,95 @@ import {
|
|||
WrenchIcon,
|
||||
XCircleIcon,
|
||||
} from "lucide-react";
|
||||
import type { ComponentProps, ReactNode } from "react";
|
||||
import { isValidElement } from "react";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import {
|
||||
Collapsible,
|
||||
CollapsibleContent,
|
||||
CollapsibleTrigger,
|
||||
} from "@/components/ui/collapsible";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
import { CodeBlock } from "./code-block";
|
||||
|
||||
export type ToolProps = ComponentProps<typeof Collapsible>;
|
||||
|
||||
export const Tool = ({ className, ...props }: ToolProps) => (
|
||||
<Collapsible
|
||||
className={cn("not-prose mb-4 w-full rounded-md border", className)}
|
||||
className={cn("group not-prose mb-4 w-full rounded-md border", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
||||
export type ToolPart = ToolUIPart | DynamicToolUIPart;
|
||||
|
||||
export type ToolHeaderProps = {
|
||||
title?: string;
|
||||
type: ToolUIPart["type"];
|
||||
state: ToolUIPart["state"];
|
||||
className?: string;
|
||||
} & (
|
||||
| { type: ToolUIPart["type"]; state: ToolUIPart["state"]; toolName?: never }
|
||||
| {
|
||||
type: DynamicToolUIPart["type"];
|
||||
state: DynamicToolUIPart["state"];
|
||||
toolName: string;
|
||||
}
|
||||
);
|
||||
|
||||
const statusLabels: Record<ToolPart["state"], string> = {
|
||||
"approval-requested": "Awaiting Approval",
|
||||
"approval-responded": "Responded",
|
||||
"input-available": "Running",
|
||||
"input-streaming": "Pending",
|
||||
"output-available": "Completed",
|
||||
"output-denied": "Denied",
|
||||
"output-error": "Error",
|
||||
};
|
||||
|
||||
const getStatusBadge = (status: ToolUIPart["state"]) => {
|
||||
const labels: Record<ToolUIPart["state"], string> = {
|
||||
"input-streaming": "Pending",
|
||||
"input-available": "Running",
|
||||
"approval-requested": "Awaiting Approval",
|
||||
"approval-responded": "Responded",
|
||||
"output-available": "Completed",
|
||||
"output-error": "Error",
|
||||
"output-denied": "Denied",
|
||||
};
|
||||
|
||||
const icons: Record<ToolUIPart["state"], ReactNode> = {
|
||||
"input-streaming": <CircleIcon className="size-4" />,
|
||||
"input-available": <ClockIcon className="size-4 animate-pulse" />,
|
||||
"approval-requested": <ClockIcon className="size-4 text-yellow-600" />,
|
||||
"approval-responded": <CheckCircleIcon className="size-4 text-blue-600" />,
|
||||
"output-available": <CheckCircleIcon className="size-4 text-green-600" />,
|
||||
"output-error": <XCircleIcon className="size-4 text-red-600" />,
|
||||
"output-denied": <XCircleIcon className="size-4 text-orange-600" />,
|
||||
};
|
||||
|
||||
return (
|
||||
<Badge className="gap-1.5 rounded-full text-xs" variant="secondary">
|
||||
{icons[status]}
|
||||
{labels[status]}
|
||||
</Badge>
|
||||
);
|
||||
const statusIcons: Record<ToolPart["state"], ReactNode> = {
|
||||
"approval-requested": <ClockIcon className="size-4 text-yellow-600" />,
|
||||
"approval-responded": <CheckCircleIcon className="size-4 text-blue-600" />,
|
||||
"input-available": <ClockIcon className="size-4 animate-pulse" />,
|
||||
"input-streaming": <CircleIcon className="size-4" />,
|
||||
"output-available": <CheckCircleIcon className="size-4 text-green-600" />,
|
||||
"output-denied": <XCircleIcon className="size-4 text-orange-600" />,
|
||||
"output-error": <XCircleIcon className="size-4 text-red-600" />,
|
||||
};
|
||||
|
||||
export const getStatusBadge = (status: ToolPart["state"]) => (
|
||||
<Badge className="gap-1.5 rounded-full text-xs" variant="secondary">
|
||||
{statusIcons[status]}
|
||||
{statusLabels[status]}
|
||||
</Badge>
|
||||
);
|
||||
|
||||
export const ToolHeader = ({
|
||||
className,
|
||||
title,
|
||||
type,
|
||||
state,
|
||||
toolName,
|
||||
...props
|
||||
}: ToolHeaderProps) => (
|
||||
<CollapsibleTrigger
|
||||
className={cn(
|
||||
"flex w-full items-center justify-between gap-4 p-3",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<WrenchIcon className="size-4 text-muted-foreground" />
|
||||
<span className="font-medium text-sm">
|
||||
{title ?? type.split("-").slice(1).join("-")}
|
||||
</span>
|
||||
{getStatusBadge(state)}
|
||||
</div>
|
||||
<ChevronDownIcon className="size-4 text-muted-foreground transition-transform group-data-[state=open]:rotate-180" />
|
||||
</CollapsibleTrigger>
|
||||
);
|
||||
}: ToolHeaderProps) => {
|
||||
const derivedName =
|
||||
type === "dynamic-tool" ? toolName : type.split("-").slice(1).join("-");
|
||||
|
||||
return (
|
||||
<CollapsibleTrigger
|
||||
className={cn(
|
||||
"flex w-full items-center justify-between gap-4 p-3",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<WrenchIcon className="size-4 text-muted-foreground" />
|
||||
<span className="font-medium text-sm">{title ?? derivedName}</span>
|
||||
{getStatusBadge(state)}
|
||||
</div>
|
||||
<ChevronDownIcon className="size-4 text-muted-foreground transition-transform group-data-[state=open]:rotate-180" />
|
||||
</CollapsibleTrigger>
|
||||
);
|
||||
};
|
||||
|
||||
export type ToolContentProps = ComponentProps<typeof CollapsibleContent>;
|
||||
|
||||
export const ToolContent = ({ className, ...props }: ToolContentProps) => (
|
||||
<CollapsibleContent
|
||||
className={cn(
|
||||
"data-[state=closed]:fade-out-0 data-[state=closed]:slide-out-to-top-2 data-[state=open]:slide-in-from-top-2 text-popover-foreground outline-none data-[state=closed]:animate-out data-[state=open]:animate-in",
|
||||
"data-[state=closed]:fade-out-0 data-[state=closed]:slide-out-to-top-2 data-[state=open]:slide-in-from-top-2 space-y-4 p-4 text-popover-foreground outline-none data-[state=closed]:animate-out data-[state=open]:animate-in",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
|
@ -102,23 +114,23 @@ export const ToolContent = ({ className, ...props }: ToolContentProps) => (
|
|||
);
|
||||
|
||||
export type ToolInputProps = ComponentProps<"div"> & {
|
||||
input: ToolUIPart["input"];
|
||||
input: ToolPart["input"];
|
||||
};
|
||||
|
||||
export const ToolInput = ({ className, input, ...props }: ToolInputProps) => (
|
||||
<div className={cn("space-y-2 overflow-hidden p-4", className)} {...props}>
|
||||
<div className={cn("space-y-2 overflow-hidden", className)} {...props}>
|
||||
<h4 className="font-medium text-muted-foreground text-xs uppercase tracking-wide">
|
||||
Parameters
|
||||
</h4>
|
||||
<pre className="overflow-x-auto rounded-md bg-muted/50 p-3 font-mono text-xs">
|
||||
{JSON.stringify(input, null, 2)}
|
||||
</pre>
|
||||
<div className="rounded-md bg-muted/50">
|
||||
<CodeBlock code={JSON.stringify(input, null, 2)} language="json" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
export type ToolOutputProps = ComponentProps<"div"> & {
|
||||
output: ToolUIPart["output"];
|
||||
errorText: ToolUIPart["errorText"];
|
||||
output: ToolPart["output"];
|
||||
errorText: ToolPart["errorText"];
|
||||
};
|
||||
|
||||
export const ToolOutput = ({
|
||||
|
|
@ -131,24 +143,18 @@ export const ToolOutput = ({
|
|||
return null;
|
||||
}
|
||||
|
||||
const renderOutput = () => {
|
||||
if (typeof output === "object" && !isValidElement(output)) {
|
||||
return (
|
||||
<pre className="overflow-x-auto p-3 font-mono text-xs">
|
||||
{JSON.stringify(output, null, 2)}
|
||||
</pre>
|
||||
);
|
||||
}
|
||||
if (typeof output === "string") {
|
||||
return (
|
||||
<pre className="overflow-x-auto p-3 font-mono text-xs">{output}</pre>
|
||||
);
|
||||
}
|
||||
return <div className="p-3">{output as ReactNode}</div>;
|
||||
};
|
||||
let Output = <div>{output as ReactNode}</div>;
|
||||
|
||||
if (typeof output === "object" && !isValidElement(output)) {
|
||||
Output = (
|
||||
<CodeBlock code={JSON.stringify(output, null, 2)} language="json" />
|
||||
);
|
||||
} else if (typeof output === "string") {
|
||||
Output = <CodeBlock code={output} language="json" />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cn("space-y-2 p-4", className)} {...props}>
|
||||
<div className={cn("space-y-2", className)} {...props}>
|
||||
<h4 className="font-medium text-muted-foreground text-xs uppercase tracking-wide">
|
||||
{errorText ? "Error" : "Result"}
|
||||
</h4>
|
||||
|
|
@ -160,8 +166,8 @@ export const ToolOutput = ({
|
|||
: "bg-muted/50 text-foreground"
|
||||
)}
|
||||
>
|
||||
{errorText && <div className="p-3">{errorText}</div>}
|
||||
{!errorText && renderOutput()}
|
||||
{errorText && <div>{errorText}</div>}
|
||||
{Output}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue