mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-18 22:35:51 +00:00
style: 完成所有文件的lint
This commit is contained in:
@@ -1,15 +1,9 @@
|
||||
"use client";
|
||||
'use client';
|
||||
|
||||
import { Button } from "../ui/button";
|
||||
import { cn } from "../../src/lib/utils";
|
||||
import { CheckIcon, CopyIcon } from "lucide-react";
|
||||
import {
|
||||
type ComponentProps,
|
||||
createContext,
|
||||
type HTMLAttributes,
|
||||
useContext,
|
||||
useState,
|
||||
} from "react";
|
||||
import { Button } from '../ui/button';
|
||||
import { cn } from '../../src/lib/utils';
|
||||
import { CheckIcon, CopyIcon } from 'lucide-react';
|
||||
import { type ComponentProps, createContext, type HTMLAttributes, useContext, useState } from 'react';
|
||||
|
||||
type CodeBlockProps = HTMLAttributes<HTMLDivElement> & {
|
||||
code: string;
|
||||
@@ -22,7 +16,7 @@ type CodeBlockContextType = {
|
||||
};
|
||||
|
||||
const CodeBlockContext = createContext<CodeBlockContextType>({
|
||||
code: "",
|
||||
code: '',
|
||||
});
|
||||
|
||||
export const CodeBlock = ({
|
||||
@@ -33,14 +27,14 @@ export const CodeBlock = ({
|
||||
children,
|
||||
...props
|
||||
}: CodeBlockProps) => {
|
||||
const lines = code.split("\n");
|
||||
const lines = code.split('\n');
|
||||
|
||||
return (
|
||||
<CodeBlockContext.Provider value={{ code }}>
|
||||
<div
|
||||
className={cn(
|
||||
"group relative w-full overflow-hidden rounded-md border bg-background text-foreground",
|
||||
className
|
||||
'group relative w-full overflow-hidden rounded-md border bg-background text-foreground',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
@@ -57,7 +51,7 @@ export const CodeBlock = ({
|
||||
)}
|
||||
<td className="p-0">
|
||||
<pre className="m-0 p-0 text-sm whitespace-pre font-mono">
|
||||
<code className="text-sm">{line || "\u00A0"}</code>
|
||||
<code className="text-sm">{line || '\u00A0'}</code>
|
||||
</pre>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -65,11 +59,7 @@ export const CodeBlock = ({
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{children && (
|
||||
<div className="absolute top-2 right-2 flex items-center gap-2">
|
||||
{children}
|
||||
</div>
|
||||
)}
|
||||
{children && <div className="absolute top-2 right-2 flex items-center gap-2">{children}</div>}
|
||||
</div>
|
||||
</div>
|
||||
</CodeBlockContext.Provider>
|
||||
@@ -94,8 +84,8 @@ export const CodeBlockCopyButton = ({
|
||||
const { code } = useContext(CodeBlockContext);
|
||||
|
||||
const copyToClipboard = async () => {
|
||||
if (typeof window === "undefined" || !navigator?.clipboard?.writeText) {
|
||||
onError?.(new Error("Clipboard API not available"));
|
||||
if (typeof window === 'undefined' || !navigator?.clipboard?.writeText) {
|
||||
onError?.(new Error('Clipboard API not available'));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -112,13 +102,7 @@ export const CodeBlockCopyButton = ({
|
||||
const Icon = isCopied ? CheckIcon : CopyIcon;
|
||||
|
||||
return (
|
||||
<Button
|
||||
className={cn("shrink-0", className)}
|
||||
onClick={copyToClipboard}
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
{...props}
|
||||
>
|
||||
<Button className={cn('shrink-0', className)} onClick={copyToClipboard} size="icon" variant="ghost" {...props}>
|
||||
{children ?? <Icon size={14} />}
|
||||
</Button>
|
||||
);
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
"use client";
|
||||
'use client';
|
||||
|
||||
import { Button } from "../ui/button";
|
||||
import { cn } from "../../src/lib/utils";
|
||||
import { ArrowDownIcon, UserIcon } from "lucide-react";
|
||||
import type { ComponentProps } from "react";
|
||||
import { useCallback } from "react";
|
||||
import { StickToBottom, useStickToBottomContext } from "use-stick-to-bottom";
|
||||
import { Button } from '../ui/button';
|
||||
import { cn } from '../../src/lib/utils';
|
||||
import { ArrowDownIcon, UserIcon } from 'lucide-react';
|
||||
import type { ComponentProps } from 'react';
|
||||
import { useCallback } from 'react';
|
||||
import { StickToBottom, useStickToBottomContext } from 'use-stick-to-bottom';
|
||||
|
||||
export type ConversationProps = ComponentProps<typeof StickToBottom>;
|
||||
|
||||
export const Conversation = ({ className, ...props }: ConversationProps) => (
|
||||
<StickToBottom
|
||||
className={cn("relative flex-1 overflow-y-hidden overflow-x-hidden", className)}
|
||||
className={cn('relative flex-1 overflow-y-hidden overflow-x-hidden', className)}
|
||||
initial="smooth"
|
||||
resize="smooth"
|
||||
role="log"
|
||||
@@ -19,21 +19,16 @@ export const Conversation = ({ className, ...props }: ConversationProps) => (
|
||||
/>
|
||||
);
|
||||
|
||||
export type ConversationContentProps = ComponentProps<
|
||||
typeof StickToBottom.Content
|
||||
>;
|
||||
export type ConversationContentProps = ComponentProps<typeof StickToBottom.Content>;
|
||||
|
||||
export const ConversationContent = ({
|
||||
className,
|
||||
...props
|
||||
}: ConversationContentProps) => (
|
||||
export const ConversationContent = ({ className, ...props }: ConversationContentProps) => (
|
||||
<StickToBottom.Content
|
||||
className={cn("mx-auto flex max-w-3xl flex-col gap-2 px-4 py-8 sm:px-8 sm:py-12 min-w-0", className)}
|
||||
className={cn('mx-auto flex max-w-3xl flex-col gap-2 px-4 py-8 sm:px-8 sm:py-12 min-w-0', className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
||||
export type ConversationEmptyStateProps = ComponentProps<"div"> & {
|
||||
export type ConversationEmptyStateProps = ComponentProps<'div'> & {
|
||||
title?: string;
|
||||
description?: string;
|
||||
icon?: React.ReactNode;
|
||||
@@ -41,17 +36,14 @@ export type ConversationEmptyStateProps = ComponentProps<"div"> & {
|
||||
|
||||
export const ConversationEmptyState = ({
|
||||
className,
|
||||
title = "No messages yet",
|
||||
description = "Start a conversation to see messages here",
|
||||
title = 'No messages yet',
|
||||
description = 'Start a conversation to see messages here',
|
||||
icon,
|
||||
children,
|
||||
...props
|
||||
}: ConversationEmptyStateProps) => (
|
||||
<div
|
||||
className={cn(
|
||||
"flex size-full flex-col items-center justify-center gap-4 p-8 text-center",
|
||||
className
|
||||
)}
|
||||
className={cn('flex size-full flex-col items-center justify-center gap-4 p-8 text-center', className)}
|
||||
{...props}
|
||||
>
|
||||
{children ?? (
|
||||
@@ -59,9 +51,7 @@ export const ConversationEmptyState = ({
|
||||
{icon && <div className="text-text-muted">{icon}</div>}
|
||||
<div className="space-y-2">
|
||||
<h3 className="font-semibold text-base font-display text-text-primary">{title}</h3>
|
||||
{description && (
|
||||
<p className="text-text-muted text-sm leading-relaxed max-w-xs">{description}</p>
|
||||
)}
|
||||
{description && <p className="text-text-muted text-sm leading-relaxed max-w-xs">{description}</p>}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
@@ -76,10 +66,7 @@ export type ConversationScrollButtonProps = ComponentProps<typeof Button>;
|
||||
* When used standalone, it handles its own visibility based on isAtBottom.
|
||||
* When used in ConversationScrollButtons, the container manages visibility.
|
||||
*/
|
||||
export const ConversationScrollButton = ({
|
||||
className,
|
||||
...props
|
||||
}: ConversationScrollButtonProps) => {
|
||||
export const ConversationScrollButton = ({ className, ...props }: ConversationScrollButtonProps) => {
|
||||
const { scrollToBottom } = useStickToBottomContext();
|
||||
|
||||
const handleScrollToBottom = useCallback(() => {
|
||||
@@ -88,10 +75,7 @@ export const ConversationScrollButton = ({
|
||||
|
||||
return (
|
||||
<Button
|
||||
className={cn(
|
||||
"rounded-full",
|
||||
className
|
||||
)}
|
||||
className={cn('rounded-full', className)}
|
||||
onClick={handleScrollToBottom}
|
||||
size="icon"
|
||||
type="button"
|
||||
@@ -108,7 +92,7 @@ export const ConversationScrollButton = ({
|
||||
* Data attribute used to mark the last user message element.
|
||||
* ChatInterface adds this attribute to the last user message for scroll targeting.
|
||||
*/
|
||||
export const LAST_USER_MESSAGE_ATTR = "data-last-user-message";
|
||||
export const LAST_USER_MESSAGE_ATTR = 'data-last-user-message';
|
||||
|
||||
export type ConversationScrollToLastUserMessageButtonProps = ComponentProps<typeof Button>;
|
||||
|
||||
@@ -124,16 +108,13 @@ export const ConversationScrollToLastUserMessageButton = ({
|
||||
// Find the last user message element by data attribute
|
||||
const lastUserMessage = document.querySelector(`[${LAST_USER_MESSAGE_ATTR}="true"]`);
|
||||
if (lastUserMessage) {
|
||||
lastUserMessage.scrollIntoView({ behavior: "smooth", block: "start" });
|
||||
lastUserMessage.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Button
|
||||
className={cn(
|
||||
"rounded-full",
|
||||
className
|
||||
)}
|
||||
className={cn('rounded-full', className)}
|
||||
onClick={handleScrollToLastUserMessage}
|
||||
size="icon"
|
||||
type="button"
|
||||
@@ -146,7 +127,7 @@ export const ConversationScrollToLastUserMessageButton = ({
|
||||
);
|
||||
};
|
||||
|
||||
export type ConversationScrollButtonsProps = ComponentProps<"div"> & {
|
||||
export type ConversationScrollButtonsProps = ComponentProps<'div'> & {
|
||||
/** Whether there are user messages to scroll to */
|
||||
hasUserMessages?: boolean;
|
||||
};
|
||||
@@ -166,16 +147,9 @@ export const ConversationScrollButtons = ({
|
||||
if (isAtBottom) return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"absolute bottom-4 left-1/2 -translate-x-1/2 flex gap-2",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<div className={cn('absolute bottom-4 left-1/2 -translate-x-1/2 flex gap-2', className)} {...props}>
|
||||
{hasUserMessages && <ConversationScrollToLastUserMessageButton />}
|
||||
<ConversationScrollButton />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
export * from "./code-block";
|
||||
export * from "./conversation";
|
||||
export * from "./message";
|
||||
export * from "./permission-request";
|
||||
export * from "./prompt-input";
|
||||
export * from "./reasoning";
|
||||
export * from "./shimmer";
|
||||
export * from "./tool";
|
||||
|
||||
export * from './code-block'
|
||||
export * from './conversation'
|
||||
export * from './message'
|
||||
export * from './permission-request'
|
||||
export * from './prompt-input'
|
||||
export * from './reasoning'
|
||||
export * from './shimmer'
|
||||
export * from './tool'
|
||||
|
||||
@@ -1,39 +1,26 @@
|
||||
"use client";
|
||||
'use client';
|
||||
|
||||
import { Button } from "../ui/button";
|
||||
import {
|
||||
ButtonGroup,
|
||||
ButtonGroupText,
|
||||
} from "../ui/button-group";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "../ui/tooltip";
|
||||
import { cn } from "../../src/lib/utils";
|
||||
import type { FileUIPart, UIMessage } from "ai";
|
||||
import {
|
||||
ChevronLeftIcon,
|
||||
ChevronRightIcon,
|
||||
PaperclipIcon,
|
||||
XIcon,
|
||||
} from "lucide-react";
|
||||
import type { ComponentProps, HTMLAttributes, ReactElement } from "react";
|
||||
import { createContext, lazy, memo, Suspense, useContext, useEffect, useState } from "react";
|
||||
import { Button } from '../ui/button';
|
||||
import { ButtonGroup, ButtonGroupText } from '../ui/button-group';
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../ui/tooltip';
|
||||
import { cn } from '../../src/lib/utils';
|
||||
import type { FileUIPart, UIMessage } from 'ai';
|
||||
import { ChevronLeftIcon, ChevronRightIcon, PaperclipIcon, XIcon } from 'lucide-react';
|
||||
import type { ComponentProps, HTMLAttributes, ReactElement } from 'react';
|
||||
import { createContext, lazy, memo, Suspense, useContext, useEffect, useState } from 'react';
|
||||
|
||||
const LazyStreamdown = lazy(() => import("streamdown").then((m) => ({ default: m.Streamdown })));
|
||||
const LazyStreamdown = lazy(() => import('streamdown').then(m => ({ default: m.Streamdown })));
|
||||
|
||||
export type MessageProps = HTMLAttributes<HTMLDivElement> & {
|
||||
from: UIMessage["role"];
|
||||
from: UIMessage['role'];
|
||||
};
|
||||
|
||||
export const Message = ({ className, from, ...props }: MessageProps) => (
|
||||
<div
|
||||
className={cn(
|
||||
"group flex w-full max-w-[85%] min-w-0 flex-col gap-2",
|
||||
from === "user" ? "is-user ml-auto justify-end" : "is-assistant",
|
||||
className
|
||||
'group flex w-full max-w-[85%] min-w-0 flex-col gap-2',
|
||||
from === 'user' ? 'is-user ml-auto justify-end' : 'is-assistant',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
@@ -41,33 +28,25 @@ export const Message = ({ className, from, ...props }: MessageProps) => (
|
||||
|
||||
export type MessageContentProps = HTMLAttributes<HTMLDivElement>;
|
||||
|
||||
export const MessageContent = ({
|
||||
children,
|
||||
className,
|
||||
...props
|
||||
}: MessageContentProps) => (
|
||||
export const MessageContent = ({ children, className, ...props }: MessageContentProps) => (
|
||||
<div
|
||||
className={cn(
|
||||
"is-user:dark flex w-fit max-w-full flex-col gap-2 overflow-hidden text-sm break-words",
|
||||
"group-[.is-user]:ml-auto group-[.is-user]:rounded-lg group-[.is-user]:bg-secondary group-[.is-user]:px-4 group-[.is-user]:py-3 group-[.is-user]:text-foreground",
|
||||
"group-[.is-assistant]:text-foreground",
|
||||
className
|
||||
'is-user:dark flex w-fit max-w-full flex-col gap-2 overflow-hidden text-sm break-words',
|
||||
'group-[.is-user]:ml-auto group-[.is-user]:rounded-lg group-[.is-user]:bg-secondary group-[.is-user]:px-4 group-[.is-user]:py-3 group-[.is-user]:text-foreground',
|
||||
'group-[.is-assistant]:text-foreground',
|
||||
className,
|
||||
)}
|
||||
style={{ overflowWrap: "anywhere" }}
|
||||
style={{ overflowWrap: 'anywhere' }}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
|
||||
export type MessageActionsProps = ComponentProps<"div">;
|
||||
export type MessageActionsProps = ComponentProps<'div'>;
|
||||
|
||||
export const MessageActions = ({
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
}: MessageActionsProps) => (
|
||||
<div className={cn("flex items-center gap-1", className)} {...props}>
|
||||
export const MessageActions = ({ className, children, ...props }: MessageActionsProps) => (
|
||||
<div className={cn('flex items-center gap-1', className)} {...props}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
@@ -81,8 +60,8 @@ export const MessageAction = ({
|
||||
tooltip,
|
||||
children,
|
||||
label,
|
||||
variant = "ghost",
|
||||
size = "icon-sm",
|
||||
variant = 'ghost',
|
||||
size = 'icon-sm',
|
||||
...props
|
||||
}: MessageActionProps) => {
|
||||
const button = (
|
||||
@@ -117,17 +96,13 @@ type MessageBranchContextType = {
|
||||
setBranches: (branches: ReactElement[]) => void;
|
||||
};
|
||||
|
||||
const MessageBranchContext = createContext<MessageBranchContextType | null>(
|
||||
null
|
||||
);
|
||||
const MessageBranchContext = createContext<MessageBranchContextType | null>(null);
|
||||
|
||||
const useMessageBranch = () => {
|
||||
const context = useContext(MessageBranchContext);
|
||||
|
||||
if (!context) {
|
||||
throw new Error(
|
||||
"MessageBranch components must be used within MessageBranch"
|
||||
);
|
||||
throw new Error('MessageBranch components must be used within MessageBranch');
|
||||
}
|
||||
|
||||
return context;
|
||||
@@ -138,12 +113,7 @@ export type MessageBranchProps = HTMLAttributes<HTMLDivElement> & {
|
||||
onBranchChange?: (branchIndex: number) => void;
|
||||
};
|
||||
|
||||
export const MessageBranch = ({
|
||||
defaultBranch = 0,
|
||||
onBranchChange,
|
||||
className,
|
||||
...props
|
||||
}: MessageBranchProps) => {
|
||||
export const MessageBranch = ({ defaultBranch = 0, onBranchChange, className, ...props }: MessageBranchProps) => {
|
||||
const [currentBranch, setCurrentBranch] = useState(defaultBranch);
|
||||
const [branches, setBranches] = useState<ReactElement[]>([]);
|
||||
|
||||
@@ -153,14 +123,12 @@ export const MessageBranch = ({
|
||||
};
|
||||
|
||||
const goToPrevious = () => {
|
||||
const newBranch =
|
||||
currentBranch > 0 ? currentBranch - 1 : branches.length - 1;
|
||||
const newBranch = currentBranch > 0 ? currentBranch - 1 : branches.length - 1;
|
||||
handleBranchChange(newBranch);
|
||||
};
|
||||
|
||||
const goToNext = () => {
|
||||
const newBranch =
|
||||
currentBranch < branches.length - 1 ? currentBranch + 1 : 0;
|
||||
const newBranch = currentBranch < branches.length - 1 ? currentBranch + 1 : 0;
|
||||
handleBranchChange(newBranch);
|
||||
};
|
||||
|
||||
@@ -175,20 +143,14 @@ export const MessageBranch = ({
|
||||
|
||||
return (
|
||||
<MessageBranchContext.Provider value={contextValue}>
|
||||
<div
|
||||
className={cn("grid w-full gap-2 [&>div]:pb-0", className)}
|
||||
{...props}
|
||||
/>
|
||||
<div className={cn('grid w-full gap-2 [&>div]:pb-0', className)} {...props} />
|
||||
</MessageBranchContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export type MessageBranchContentProps = HTMLAttributes<HTMLDivElement>;
|
||||
|
||||
export const MessageBranchContent = ({
|
||||
children,
|
||||
...props
|
||||
}: MessageBranchContentProps) => {
|
||||
export const MessageBranchContent = ({ children, ...props }: MessageBranchContentProps) => {
|
||||
const { currentBranch, setBranches, branches } = useMessageBranch();
|
||||
const childrenArray = Array.isArray(children) ? children : [children];
|
||||
|
||||
@@ -201,10 +163,7 @@ export const MessageBranchContent = ({
|
||||
|
||||
return childrenArray.map((branch, index) => (
|
||||
<div
|
||||
className={cn(
|
||||
"grid gap-2 overflow-hidden [&>div]:pb-0",
|
||||
index === currentBranch ? "block" : "hidden"
|
||||
)}
|
||||
className={cn('grid gap-2 overflow-hidden [&>div]:pb-0', index === currentBranch ? 'block' : 'hidden')}
|
||||
key={branch.key}
|
||||
{...props}
|
||||
>
|
||||
@@ -214,14 +173,10 @@ export const MessageBranchContent = ({
|
||||
};
|
||||
|
||||
export type MessageBranchSelectorProps = HTMLAttributes<HTMLDivElement> & {
|
||||
from: UIMessage["role"];
|
||||
from: UIMessage['role'];
|
||||
};
|
||||
|
||||
export const MessageBranchSelector = ({
|
||||
className,
|
||||
from,
|
||||
...props
|
||||
}: MessageBranchSelectorProps) => {
|
||||
export const MessageBranchSelector = ({ className, from, ...props }: MessageBranchSelectorProps) => {
|
||||
const { totalBranches } = useMessageBranch();
|
||||
|
||||
// Don't render if there's only one branch
|
||||
@@ -240,10 +195,7 @@ export const MessageBranchSelector = ({
|
||||
|
||||
export type MessageBranchPreviousProps = ComponentProps<typeof Button>;
|
||||
|
||||
export const MessageBranchPrevious = ({
|
||||
children,
|
||||
...props
|
||||
}: MessageBranchPreviousProps) => {
|
||||
export const MessageBranchPrevious = ({ children, ...props }: MessageBranchPreviousProps) => {
|
||||
const { goToPrevious, totalBranches } = useMessageBranch();
|
||||
|
||||
return (
|
||||
@@ -263,11 +215,7 @@ export const MessageBranchPrevious = ({
|
||||
|
||||
export type MessageBranchNextProps = ComponentProps<typeof Button>;
|
||||
|
||||
export const MessageBranchNext = ({
|
||||
children,
|
||||
className,
|
||||
...props
|
||||
}: MessageBranchNextProps) => {
|
||||
export const MessageBranchNext = ({ children, className, ...props }: MessageBranchNextProps) => {
|
||||
const { goToNext, totalBranches } = useMessageBranch();
|
||||
|
||||
return (
|
||||
@@ -287,18 +235,12 @@ export const MessageBranchNext = ({
|
||||
|
||||
export type MessageBranchPageProps = HTMLAttributes<HTMLSpanElement>;
|
||||
|
||||
export const MessageBranchPage = ({
|
||||
className,
|
||||
...props
|
||||
}: MessageBranchPageProps) => {
|
||||
export const MessageBranchPage = ({ className, ...props }: MessageBranchPageProps) => {
|
||||
const { currentBranch, totalBranches } = useMessageBranch();
|
||||
|
||||
return (
|
||||
<ButtonGroupText
|
||||
className={cn(
|
||||
"border-none bg-transparent text-muted-foreground shadow-none",
|
||||
className
|
||||
)}
|
||||
className={cn('border-none bg-transparent text-muted-foreground shadow-none', className)}
|
||||
{...props}
|
||||
>
|
||||
{currentBranch + 1} of {totalBranches}
|
||||
@@ -309,22 +251,16 @@ export const MessageBranchPage = ({
|
||||
export type MessageResponseProps = {
|
||||
children?: string;
|
||||
className?: string;
|
||||
mode?: "static" | "streaming";
|
||||
mode?: 'static' | 'streaming';
|
||||
};
|
||||
|
||||
export const MessageResponse = memo(
|
||||
({ className, children, ...props }: MessageResponseProps) => (
|
||||
<Suspense
|
||||
fallback={
|
||||
<div className={cn("whitespace-pre-wrap break-words", className)}>
|
||||
{children}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Suspense fallback={<div className={cn('whitespace-pre-wrap break-words', className)}>{children}</div>}>
|
||||
<LazyStreamdown
|
||||
className={cn(
|
||||
"size-full break-words [overflow-wrap:anywhere] [&>*:first-child]:mt-0 [&>*:last-child]:mb-0",
|
||||
className
|
||||
'size-full break-words [overflow-wrap:anywhere] [&>*:first-child]:mt-0 [&>*:last-child]:mb-0',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
@@ -332,10 +268,10 @@ export const MessageResponse = memo(
|
||||
</LazyStreamdown>
|
||||
</Suspense>
|
||||
),
|
||||
(prevProps, nextProps) => prevProps.children === nextProps.children
|
||||
(prevProps, nextProps) => prevProps.children === nextProps.children,
|
||||
);
|
||||
|
||||
MessageResponse.displayName = "MessageResponse";
|
||||
MessageResponse.displayName = 'MessageResponse';
|
||||
|
||||
export type MessageAttachmentProps = HTMLAttributes<HTMLDivElement> & {
|
||||
data: FileUIPart;
|
||||
@@ -343,30 +279,18 @@ export type MessageAttachmentProps = HTMLAttributes<HTMLDivElement> & {
|
||||
onRemove?: () => void;
|
||||
};
|
||||
|
||||
export function MessageAttachment({
|
||||
data,
|
||||
className,
|
||||
onRemove,
|
||||
...props
|
||||
}: MessageAttachmentProps) {
|
||||
const filename = data.filename || "";
|
||||
const mediaType =
|
||||
data.mediaType?.startsWith("image/") && data.url ? "image" : "file";
|
||||
const isImage = mediaType === "image";
|
||||
const attachmentLabel = filename || (isImage ? "Image" : "Attachment");
|
||||
export function MessageAttachment({ data, className, onRemove, ...props }: MessageAttachmentProps) {
|
||||
const filename = data.filename || '';
|
||||
const mediaType = data.mediaType?.startsWith('image/') && data.url ? 'image' : 'file';
|
||||
const isImage = mediaType === 'image';
|
||||
const attachmentLabel = filename || (isImage ? 'Image' : 'Attachment');
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"group relative size-24 overflow-hidden rounded-lg",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<div className={cn('group relative size-24 overflow-hidden rounded-lg', className)} {...props}>
|
||||
{isImage ? (
|
||||
<>
|
||||
<img
|
||||
alt={filename || "attachment"}
|
||||
alt={filename || 'attachment'}
|
||||
className="size-full object-cover"
|
||||
height={100}
|
||||
src={data.url}
|
||||
@@ -376,7 +300,7 @@ export function MessageAttachment({
|
||||
<Button
|
||||
aria-label="Remove attachment"
|
||||
className="absolute top-2 right-2 size-6 rounded-full bg-background/80 p-0 opacity-0 backdrop-blur-sm transition-opacity hover:bg-background group-hover:opacity-100 [&>svg]:size-3"
|
||||
onClick={(e) => {
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
onRemove();
|
||||
}}
|
||||
@@ -404,7 +328,7 @@ export function MessageAttachment({
|
||||
<Button
|
||||
aria-label="Remove attachment"
|
||||
className="size-6 shrink-0 rounded-full p-0 opacity-0 transition-opacity hover:bg-accent group-hover:opacity-100 [&>svg]:size-3"
|
||||
onClick={(e) => {
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
onRemove();
|
||||
}}
|
||||
@@ -421,45 +345,24 @@ export function MessageAttachment({
|
||||
);
|
||||
}
|
||||
|
||||
export type MessageAttachmentsProps = ComponentProps<"div">;
|
||||
export type MessageAttachmentsProps = ComponentProps<'div'>;
|
||||
|
||||
export function MessageAttachments({
|
||||
children,
|
||||
className,
|
||||
...props
|
||||
}: MessageAttachmentsProps) {
|
||||
export function MessageAttachments({ children, className, ...props }: MessageAttachmentsProps) {
|
||||
if (!children) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"ml-auto flex w-fit flex-wrap items-start gap-2",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<div className={cn('ml-auto flex w-fit flex-wrap items-start gap-2', className)} {...props}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export type MessageToolbarProps = ComponentProps<"div">;
|
||||
export type MessageToolbarProps = ComponentProps<'div'>;
|
||||
|
||||
export const MessageToolbar = ({
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
}: MessageToolbarProps) => (
|
||||
<div
|
||||
className={cn(
|
||||
"mt-4 flex w-full items-center justify-between gap-4",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
export const MessageToolbar = ({ className, children, ...props }: MessageToolbarProps) => (
|
||||
<div className={cn('mt-4 flex w-full items-center justify-between gap-4', className)} {...props}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
|
||||
|
||||
@@ -1,32 +1,32 @@
|
||||
"use client";
|
||||
'use client';
|
||||
|
||||
import { cn } from "../../src/lib/utils";
|
||||
import { Button } from "../ui/button";
|
||||
import { ShieldAlertIcon, CheckIcon, XIcon } from "lucide-react";
|
||||
import type { PermissionOption } from "../../src/acp/types";
|
||||
import { cn } from '../../src/lib/utils';
|
||||
import { Button } from '../ui/button';
|
||||
import { ShieldAlertIcon, CheckIcon, XIcon } from 'lucide-react';
|
||||
import type { PermissionOption } from '../../src/acp/types';
|
||||
|
||||
// Get button variant based on option kind
|
||||
function getButtonVariant(kind: PermissionOption["kind"]): "default" | "destructive" | "outline" | "secondary" {
|
||||
function getButtonVariant(kind: PermissionOption['kind']): 'default' | 'destructive' | 'outline' | 'secondary' {
|
||||
switch (kind) {
|
||||
case "allow_once":
|
||||
case "allow_always":
|
||||
return "default";
|
||||
case "reject_once":
|
||||
case "reject_always":
|
||||
return "destructive";
|
||||
case 'allow_once':
|
||||
case 'allow_always':
|
||||
return 'default';
|
||||
case 'reject_once':
|
||||
case 'reject_always':
|
||||
return 'destructive';
|
||||
default:
|
||||
return "outline";
|
||||
return 'outline';
|
||||
}
|
||||
}
|
||||
|
||||
// Get button icon based on option kind
|
||||
function getButtonIcon(kind: PermissionOption["kind"]) {
|
||||
function getButtonIcon(kind: PermissionOption['kind']) {
|
||||
switch (kind) {
|
||||
case "allow_once":
|
||||
case "allow_always":
|
||||
case 'allow_once':
|
||||
case 'allow_always':
|
||||
return <CheckIcon className="size-4" />;
|
||||
case "reject_once":
|
||||
case "reject_always":
|
||||
case 'reject_once':
|
||||
case 'reject_always':
|
||||
return <XIcon className="size-4" />;
|
||||
default:
|
||||
return null;
|
||||
@@ -37,7 +37,7 @@ function getButtonIcon(kind: PermissionOption["kind"]) {
|
||||
export interface ToolPermissionButtonsProps {
|
||||
requestId: string;
|
||||
options: PermissionOption[];
|
||||
onRespond: (requestId: string, optionId: string | null, optionKind: PermissionOption["kind"] | null) => void;
|
||||
onRespond: (requestId: string, optionId: string | null, optionKind: PermissionOption['kind'] | null) => void;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
@@ -47,15 +47,13 @@ export function ToolPermissionButtons({ requestId, options, onRespond, className
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={cn("p-3 border-t border-warning-border/30 bg-warning-bg/50", className)}>
|
||||
<div className={cn('p-3 border-t border-warning-border/30 bg-warning-bg/50', className)}>
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<ShieldAlertIcon className="size-4 text-warning-text" />
|
||||
<span className="text-xs font-medium text-warning-text">
|
||||
Permission Required
|
||||
</span>
|
||||
<span className="text-xs font-medium text-warning-text">Permission Required</span>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{options.map((option) => (
|
||||
{options.map(option => (
|
||||
<Button
|
||||
key={option.optionId}
|
||||
variant={getButtonVariant(option.kind)}
|
||||
@@ -71,4 +69,3 @@ export function ToolPermissionButtons({ requestId, options, onRespond, className
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,16 +1,12 @@
|
||||
"use client";
|
||||
'use client';
|
||||
|
||||
import { useControllableState } from "@radix-ui/react-use-controllable-state";
|
||||
import {
|
||||
Collapsible,
|
||||
CollapsibleContent,
|
||||
CollapsibleTrigger,
|
||||
} from "../ui/collapsible";
|
||||
import { cn } from "../../src/lib/utils";
|
||||
import { BrainIcon, ChevronDownIcon } from "lucide-react";
|
||||
import type { ComponentProps, ReactNode } from "react";
|
||||
import { createContext, memo, useCallback, useContext, useEffect, useState } from "react";
|
||||
import { Shimmer } from "./shimmer";
|
||||
import { useControllableState } from '@radix-ui/react-use-controllable-state';
|
||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '../ui/collapsible';
|
||||
import { cn } from '../../src/lib/utils';
|
||||
import { BrainIcon, ChevronDownIcon } from 'lucide-react';
|
||||
import type { ComponentProps, ReactNode } from 'react';
|
||||
import { createContext, memo, useCallback, useContext, useEffect, useState } from 'react';
|
||||
import { Shimmer } from './shimmer';
|
||||
|
||||
interface ReasoningContextValue {
|
||||
isStreaming: boolean;
|
||||
@@ -24,7 +20,7 @@ const ReasoningContext = createContext<ReasoningContextValue | null>(null);
|
||||
export const useReasoning = () => {
|
||||
const context = useContext(ReasoningContext);
|
||||
if (!context) {
|
||||
throw new Error("Reasoning components must be used within Reasoning");
|
||||
throw new Error('Reasoning components must be used within Reasoning');
|
||||
}
|
||||
return context;
|
||||
};
|
||||
@@ -78,8 +74,8 @@ export const Reasoning = memo(
|
||||
|
||||
// Auto-open when streaming starts, auto-close when streaming ends (once only)
|
||||
// Respect prefers-reduced-motion: skip animation auto-close
|
||||
const prefersReducedMotion = typeof window !== "undefined"
|
||||
&& window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
||||
const prefersReducedMotion =
|
||||
typeof window !== 'undefined' && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||||
|
||||
useEffect(() => {
|
||||
if (!prefersReducedMotion && defaultOpen && !isStreaming && isOpen && !hasAutoClosed) {
|
||||
@@ -97,11 +93,9 @@ export const Reasoning = memo(
|
||||
};
|
||||
|
||||
return (
|
||||
<ReasoningContext.Provider
|
||||
value={{ isStreaming, isOpen: isOpen ?? false, setIsOpen, duration }}
|
||||
>
|
||||
<ReasoningContext.Provider value={{ isStreaming, isOpen: isOpen ?? false, setIsOpen, duration }}>
|
||||
<Collapsible
|
||||
className={cn("not-prose mb-4", className)}
|
||||
className={cn('not-prose mb-4', className)}
|
||||
onOpenChange={handleOpenChange}
|
||||
open={isOpen}
|
||||
{...props}
|
||||
@@ -110,12 +104,10 @@ export const Reasoning = memo(
|
||||
</Collapsible>
|
||||
</ReasoningContext.Provider>
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
export type ReasoningTriggerProps = ComponentProps<
|
||||
typeof CollapsibleTrigger
|
||||
> & {
|
||||
export type ReasoningTriggerProps = ComponentProps<typeof CollapsibleTrigger> & {
|
||||
getThinkingMessage?: (isStreaming: boolean, duration?: number) => ReactNode;
|
||||
};
|
||||
|
||||
@@ -130,19 +122,14 @@ const defaultGetThinkingMessage = (isStreaming: boolean, duration?: number) => {
|
||||
};
|
||||
|
||||
export const ReasoningTrigger = memo(
|
||||
({
|
||||
className,
|
||||
children,
|
||||
getThinkingMessage = defaultGetThinkingMessage,
|
||||
...props
|
||||
}: ReasoningTriggerProps) => {
|
||||
({ className, children, getThinkingMessage = defaultGetThinkingMessage, ...props }: ReasoningTriggerProps) => {
|
||||
const { isStreaming, isOpen, duration } = useReasoning();
|
||||
|
||||
return (
|
||||
<CollapsibleTrigger
|
||||
className={cn(
|
||||
"flex w-full items-center gap-2 text-muted-foreground text-sm transition-colors hover:text-foreground",
|
||||
className
|
||||
'flex w-full items-center gap-2 text-muted-foreground text-sm transition-colors hover:text-foreground',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
@@ -150,41 +137,31 @@ export const ReasoningTrigger = memo(
|
||||
<>
|
||||
<BrainIcon className="size-4" />
|
||||
{getThinkingMessage(isStreaming, duration)}
|
||||
<ChevronDownIcon
|
||||
className={cn(
|
||||
"size-4 transition-transform",
|
||||
isOpen ? "rotate-180" : "rotate-0"
|
||||
)}
|
||||
/>
|
||||
<ChevronDownIcon className={cn('size-4 transition-transform', isOpen ? 'rotate-180' : 'rotate-0')} />
|
||||
</>
|
||||
)}
|
||||
</CollapsibleTrigger>
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
export type ReasoningContentProps = ComponentProps<
|
||||
typeof CollapsibleContent
|
||||
> & {
|
||||
export type ReasoningContentProps = ComponentProps<typeof CollapsibleContent> & {
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
export const ReasoningContent = memo(
|
||||
({ className, children, ...props }: ReasoningContentProps) => (
|
||||
<CollapsibleContent
|
||||
className={cn(
|
||||
"mt-4 text-sm",
|
||||
"data-[state=closed]:fade-out-0 data-[state=closed]:slide-out-to-top-2 data-[state=open]:slide-in-from-top-2 text-muted-foreground outline-none data-[state=closed]:animate-out data-[state=open]:animate-in",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</CollapsibleContent>
|
||||
)
|
||||
);
|
||||
|
||||
Reasoning.displayName = "Reasoning";
|
||||
ReasoningTrigger.displayName = "ReasoningTrigger";
|
||||
ReasoningContent.displayName = "ReasoningContent";
|
||||
export const ReasoningContent = memo(({ className, children, ...props }: ReasoningContentProps) => (
|
||||
<CollapsibleContent
|
||||
className={cn(
|
||||
'mt-4 text-sm',
|
||||
'data-[state=closed]:fade-out-0 data-[state=closed]:slide-out-to-top-2 data-[state=open]:slide-in-from-top-2 text-muted-foreground outline-none data-[state=closed]:animate-out data-[state=open]:animate-in',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</CollapsibleContent>
|
||||
));
|
||||
|
||||
Reasoning.displayName = 'Reasoning';
|
||||
ReasoningTrigger.displayName = 'ReasoningTrigger';
|
||||
ReasoningContent.displayName = 'ReasoningContent';
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
"use client";
|
||||
'use client';
|
||||
|
||||
import { cn } from "../../src/lib/utils";
|
||||
import { motion } from "motion/react";
|
||||
import {
|
||||
type ElementType,
|
||||
type JSX,
|
||||
memo,
|
||||
} from "react";
|
||||
import { cn } from '../../src/lib/utils';
|
||||
import { motion } from 'motion/react';
|
||||
import { type ElementType, type JSX, memo } from 'react';
|
||||
|
||||
export interface TextShimmerProps {
|
||||
children: string;
|
||||
@@ -16,27 +12,17 @@ export interface TextShimmerProps {
|
||||
spread?: number;
|
||||
}
|
||||
|
||||
const ShimmerComponent = ({
|
||||
children,
|
||||
as: Component = "p",
|
||||
className,
|
||||
duration = 2,
|
||||
}: TextShimmerProps) => {
|
||||
const MotionComponent = motion.create(
|
||||
Component as keyof JSX.IntrinsicElements
|
||||
);
|
||||
const ShimmerComponent = ({ children, as: Component = 'p', className, duration = 2 }: TextShimmerProps) => {
|
||||
const MotionComponent = motion.create(Component as keyof JSX.IntrinsicElements);
|
||||
|
||||
return (
|
||||
<MotionComponent
|
||||
animate={{ opacity: [0.5, 1, 0.5] }}
|
||||
className={cn(
|
||||
"relative inline-block text-muted-foreground",
|
||||
className
|
||||
)}
|
||||
className={cn('relative inline-block text-muted-foreground', className)}
|
||||
transition={{
|
||||
repeat: Number.POSITIVE_INFINITY,
|
||||
duration,
|
||||
ease: "easeInOut",
|
||||
ease: 'easeInOut',
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -1,67 +1,56 @@
|
||||
"use client";
|
||||
'use client';
|
||||
|
||||
import { Badge } from "../ui/badge";
|
||||
import {
|
||||
Collapsible,
|
||||
CollapsibleContent,
|
||||
CollapsibleTrigger,
|
||||
} from "../ui/collapsible";
|
||||
import { cn } from "../../src/lib/utils";
|
||||
import type { ToolUIPart } from "ai";
|
||||
import {
|
||||
CheckCircleIcon,
|
||||
ChevronDownIcon,
|
||||
CircleIcon,
|
||||
ClockIcon,
|
||||
WrenchIcon,
|
||||
XCircleIcon,
|
||||
} from "lucide-react";
|
||||
import type { ComponentProps, ReactNode } from "react";
|
||||
import { isValidElement } from "react";
|
||||
import { CodeBlock } from "./code-block";
|
||||
import { Badge } from '../ui/badge';
|
||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '../ui/collapsible';
|
||||
import { cn } from '../../src/lib/utils';
|
||||
import type { ToolUIPart } from 'ai';
|
||||
import { CheckCircleIcon, ChevronDownIcon, CircleIcon, ClockIcon, WrenchIcon, XCircleIcon } from 'lucide-react';
|
||||
import type { ComponentProps, ReactNode } from 'react';
|
||||
import { isValidElement } from 'react';
|
||||
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 max-w-full overflow-hidden rounded-md border", className)}
|
||||
className={cn('not-prose mb-4 w-full max-w-full overflow-hidden rounded-md border', className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
||||
// Extended state type to include our custom states
|
||||
export type ExtendedToolState = ToolUIPart["state"] | "waiting-for-confirmation" | "rejected";
|
||||
export type ExtendedToolState = ToolUIPart['state'] | 'waiting-for-confirmation' | 'rejected';
|
||||
|
||||
export type ToolHeaderProps = {
|
||||
title?: string;
|
||||
type: ToolUIPart["type"];
|
||||
type: ToolUIPart['type'];
|
||||
state: ExtendedToolState;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
const getStatusBadge = (status: ExtendedToolState) => {
|
||||
const labels: Record<ExtendedToolState, string> = {
|
||||
"input-streaming": "Pending",
|
||||
"input-available": "Running",
|
||||
"approval-requested": "Awaiting Approval",
|
||||
"approval-responded": "Responded",
|
||||
"output-available": "Completed",
|
||||
"output-error": "Error",
|
||||
"output-denied": "Denied",
|
||||
"waiting-for-confirmation": "Awaiting Approval",
|
||||
"rejected": "Rejected",
|
||||
'input-streaming': 'Pending',
|
||||
'input-available': 'Running',
|
||||
'approval-requested': 'Awaiting Approval',
|
||||
'approval-responded': 'Responded',
|
||||
'output-available': 'Completed',
|
||||
'output-error': 'Error',
|
||||
'output-denied': 'Denied',
|
||||
'waiting-for-confirmation': 'Awaiting Approval',
|
||||
rejected: 'Rejected',
|
||||
};
|
||||
|
||||
const icons: Record<ExtendedToolState, 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" />,
|
||||
"waiting-for-confirmation": <ClockIcon className="size-4 text-yellow-600" />,
|
||||
"rejected": <XCircleIcon className="size-4 text-orange-600" />,
|
||||
'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" />,
|
||||
'waiting-for-confirmation': <ClockIcon className="size-4 text-yellow-600" />,
|
||||
rejected: <XCircleIcon className="size-4 text-orange-600" />,
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -72,25 +61,11 @@ const getStatusBadge = (status: ExtendedToolState) => {
|
||||
);
|
||||
};
|
||||
|
||||
export const ToolHeader = ({
|
||||
className,
|
||||
title,
|
||||
type,
|
||||
state,
|
||||
...props
|
||||
}: ToolHeaderProps) => (
|
||||
<CollapsibleTrigger
|
||||
className={cn(
|
||||
"flex w-full items-center justify-between gap-4 p-3",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
export const ToolHeader = ({ className, title, type, state, ...props }: ToolHeaderProps) => (
|
||||
<CollapsibleTrigger className={cn('flex w-full items-center justify-between gap-4 p-3', className)} {...props}>
|
||||
<div className="flex min-w-0 items-center gap-2">
|
||||
<WrenchIcon className="size-4 shrink-0 text-muted-foreground" />
|
||||
<span className="truncate font-medium text-sm">
|
||||
{title ?? type.split("-").slice(1).join("-")}
|
||||
</span>
|
||||
<span className="truncate font-medium text-sm">{title ?? type.split('-').slice(1).join('-')}</span>
|
||||
{getStatusBadge(state)}
|
||||
</div>
|
||||
<ChevronDownIcon className="size-4 shrink-0 text-muted-foreground transition-transform group-data-[state=open]:rotate-180" />
|
||||
@@ -102,64 +77,53 @@ 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",
|
||||
className
|
||||
'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',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
||||
export type ToolInputProps = ComponentProps<"div"> & {
|
||||
input: ToolUIPart["input"];
|
||||
export type ToolInputProps = ComponentProps<'div'> & {
|
||||
input: ToolUIPart['input'];
|
||||
};
|
||||
|
||||
export const ToolInput = ({ className, input, ...props }: ToolInputProps) => (
|
||||
<div className={cn("space-y-2 overflow-hidden p-4 max-w-full", className)} {...props}>
|
||||
<h4 className="font-medium text-muted-foreground text-xs uppercase tracking-wide">
|
||||
Parameters
|
||||
</h4>
|
||||
<div className={cn('space-y-2 overflow-hidden p-4 max-w-full', className)} {...props}>
|
||||
<h4 className="font-medium text-muted-foreground text-xs uppercase tracking-wide">Parameters</h4>
|
||||
<div className="rounded-md bg-muted/50 overflow-hidden">
|
||||
<CodeBlock code={JSON.stringify(input, null, 2)} language="json" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
export type ToolOutputProps = ComponentProps<"div"> & {
|
||||
output: ToolUIPart["output"];
|
||||
errorText: ToolUIPart["errorText"];
|
||||
export type ToolOutputProps = ComponentProps<'div'> & {
|
||||
output: ToolUIPart['output'];
|
||||
errorText: ToolUIPart['errorText'];
|
||||
};
|
||||
|
||||
export const ToolOutput = ({
|
||||
className,
|
||||
output,
|
||||
errorText,
|
||||
...props
|
||||
}: ToolOutputProps) => {
|
||||
export const ToolOutput = ({ className, output, errorText, ...props }: ToolOutputProps) => {
|
||||
if (!(output || errorText)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
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") {
|
||||
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 max-w-full overflow-hidden", className)} {...props}>
|
||||
<div className={cn('space-y-2 p-4 max-w-full overflow-hidden', className)} {...props}>
|
||||
<h4 className="font-medium text-muted-foreground text-xs uppercase tracking-wide">
|
||||
{errorText ? "Error" : "Result"}
|
||||
{errorText ? 'Error' : 'Result'}
|
||||
</h4>
|
||||
<div
|
||||
className={cn(
|
||||
"overflow-hidden rounded-md text-xs [&_table]:w-full",
|
||||
errorText
|
||||
? "bg-destructive/10 text-destructive"
|
||||
: "bg-muted/50 text-foreground"
|
||||
'overflow-hidden rounded-md text-xs [&_table]:w-full',
|
||||
errorText ? 'bg-destructive/10 text-destructive' : 'bg-muted/50 text-foreground',
|
||||
)}
|
||||
>
|
||||
{errorText && <div className="p-2">{errorText}</div>}
|
||||
@@ -168,4 +132,3 @@ export const ToolOutput = ({
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user