Upgrade linter and formatter to Ultracite (#1224)

This commit is contained in:
Hayden Bleasel 2025-09-20 12:47:10 -07:00 committed by GitHub
parent b1d254283b
commit 0e320b391d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
177 changed files with 6951 additions and 8342 deletions

View file

@ -1,19 +1,19 @@
'use client';
"use client";
import { Button } from '@/components/ui/button';
import type { ComponentProps } from "react";
import { Button } from "@/components/ui/button";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from '@/components/ui/tooltip';
import { cn } from '@/lib/utils';
import type { ComponentProps } from 'react';
} from "@/components/ui/tooltip";
import { cn } from "@/lib/utils";
export type ActionsProps = ComponentProps<'div'>;
export type ActionsProps = ComponentProps<"div">;
export const Actions = ({ className, children, ...props }: ActionsProps) => (
<div className={cn('flex items-center gap-1', className)} {...props}>
<div className={cn("flex items-center gap-1", className)} {...props}>
{children}
</div>
);
@ -28,15 +28,15 @@ export const Action = ({
children,
label,
className,
variant = 'ghost',
size = 'sm',
variant = "ghost",
size = "sm",
...props
}: ActionProps) => {
const button = (
<Button
className={cn(
'relative size-9 p-1.5 text-muted-foreground hover:text-foreground',
className,
"relative size-9 p-1.5 text-muted-foreground hover:text-foreground",
className
)}
size={size}
type="button"

View file

@ -1,11 +1,11 @@
'use client';
"use client";
import { Button } from '@/components/ui/button';
import { cn } from '@/lib/utils';
import type { UIMessage } from 'ai';
import { ChevronLeftIcon, ChevronRightIcon } from 'lucide-react';
import type { ComponentProps, HTMLAttributes, ReactElement } from 'react';
import { createContext, useContext, useEffect, useState, useMemo } from 'react';
import type { UIMessage } from "ai";
import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react";
import type { ComponentProps, HTMLAttributes, ReactElement } from "react";
import { createContext, useContext, useEffect, useMemo, useState } from "react";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
type BranchContextType = {
currentBranch: number;
@ -22,7 +22,7 @@ const useBranch = () => {
const context = useContext(BranchContext);
if (!context) {
throw new Error('Branch components must be used within Branch');
throw new Error("Branch components must be used within Branch");
}
return context;
@ -71,7 +71,7 @@ export const Branch = ({
return (
<BranchContext.Provider value={contextValue}>
<div
className={cn('grid w-full gap-2 [&>div]:pb-0', className)}
className={cn("grid w-full gap-2 [&>div]:pb-0", className)}
{...props}
/>
</BranchContext.Provider>
@ -84,7 +84,7 @@ export const BranchMessages = ({ children, ...props }: BranchMessagesProps) => {
const { currentBranch, setBranches, branches } = useBranch();
const childrenArray = useMemo(
() => (Array.isArray(children) ? children : [children]),
[children],
[children]
);
// Use useEffect to update branches when they change
@ -97,8 +97,8 @@ export const BranchMessages = ({ children, ...props }: BranchMessagesProps) => {
return childrenArray.map((branch, index) => (
<div
className={cn(
'grid gap-2 overflow-hidden [&>div]:pb-0',
index === currentBranch ? 'block' : 'hidden',
"grid gap-2 overflow-hidden [&>div]:pb-0",
index === currentBranch ? "block" : "hidden"
)}
key={branch.key}
{...props}
@ -109,7 +109,7 @@ export const BranchMessages = ({ children, ...props }: BranchMessagesProps) => {
};
export type BranchSelectorProps = HTMLAttributes<HTMLDivElement> & {
from: UIMessage['role'];
from: UIMessage["role"];
};
export const BranchSelector = ({
@ -127,9 +127,9 @@ export const BranchSelector = ({
return (
<div
className={cn(
'flex items-center gap-2 self-end px-10',
from === 'assistant' ? 'justify-start' : 'justify-end',
className,
"flex items-center gap-2 self-end px-10",
from === "assistant" ? "justify-start" : "justify-end",
className
)}
{...props}
/>
@ -149,10 +149,10 @@ export const BranchPrevious = ({
<Button
aria-label="Previous branch"
className={cn(
'size-7 shrink-0 rounded-full text-muted-foreground transition-colors',
'hover:bg-accent hover:text-foreground',
'disabled:pointer-events-none disabled:opacity-50',
className,
"size-7 shrink-0 rounded-full text-muted-foreground transition-colors",
"hover:bg-accent hover:text-foreground",
"disabled:pointer-events-none disabled:opacity-50",
className
)}
disabled={totalBranches <= 1}
onClick={goToPrevious}
@ -179,10 +179,10 @@ export const BranchNext = ({
<Button
aria-label="Next branch"
className={cn(
'size-7 shrink-0 rounded-full text-muted-foreground transition-colors',
'hover:bg-accent hover:text-foreground',
'disabled:pointer-events-none disabled:opacity-50',
className,
"size-7 shrink-0 rounded-full text-muted-foreground transition-colors",
"hover:bg-accent hover:text-foreground",
"disabled:pointer-events-none disabled:opacity-50",
className
)}
disabled={totalBranches <= 1}
onClick={goToNext}
@ -204,8 +204,8 @@ export const BranchPage = ({ className, ...props }: BranchPageProps) => {
return (
<span
className={cn(
'font-medium text-muted-foreground text-xs tabular-nums',
className,
"font-medium text-muted-foreground text-xs tabular-nums",
className
)}
{...props}
>

View file

@ -1,22 +1,22 @@
'use client';
"use client";
import { Button } from '@/components/ui/button';
import { cn } from '@/lib/utils';
import { CheckIcon, CopyIcon } from 'lucide-react';
import type { ComponentProps, HTMLAttributes, ReactNode } from 'react';
import { createContext, useContext, useState } from 'react';
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { CheckIcon, CopyIcon } from "lucide-react";
import type { ComponentProps, HTMLAttributes, ReactNode } from "react";
import { createContext, useContext, useState } from "react";
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import {
oneDark,
oneLight,
} from 'react-syntax-highlighter/dist/esm/styles/prism';
} from "react-syntax-highlighter/dist/esm/styles/prism";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
type CodeBlockContextType = {
code: string;
};
const CodeBlockContext = createContext<CodeBlockContextType>({
code: '',
code: "",
});
export type CodeBlockProps = HTMLAttributes<HTMLDivElement> & {
@ -37,8 +37,8 @@ export const CodeBlock = ({
<CodeBlockContext.Provider value={{ code }}>
<div
className={cn(
'relative w-full overflow-hidden rounded-md border bg-background text-foreground',
className,
"relative w-full overflow-hidden rounded-md border bg-background text-foreground",
className
)}
{...props}
>
@ -46,23 +46,23 @@ export const CodeBlock = ({
<SyntaxHighlighter
className="overflow-hidden dark:hidden"
codeTagProps={{
className: 'font-mono text-sm',
className: "font-mono text-sm",
}}
customStyle={{
margin: 0,
padding: '1rem',
fontSize: '0.875rem',
background: 'hsl(var(--background))',
color: 'hsl(var(--foreground))',
overflowX: 'auto',
overflowWrap: 'break-word',
wordBreak: 'break-all',
padding: "1rem",
fontSize: "0.875rem",
background: "hsl(var(--background))",
color: "hsl(var(--foreground))",
overflowX: "auto",
overflowWrap: "break-word",
wordBreak: "break-all",
}}
language={language}
lineNumberStyle={{
color: 'hsl(var(--muted-foreground))',
paddingRight: '1rem',
minWidth: '2.5rem',
color: "hsl(var(--muted-foreground))",
paddingRight: "1rem",
minWidth: "2.5rem",
}}
showLineNumbers={showLineNumbers}
style={oneLight}
@ -72,23 +72,23 @@ export const CodeBlock = ({
<SyntaxHighlighter
className="hidden overflow-hidden dark:block"
codeTagProps={{
className: 'font-mono text-sm',
className: "font-mono text-sm",
}}
customStyle={{
margin: 0,
padding: '1rem',
fontSize: '0.875rem',
background: 'hsl(var(--background))',
color: 'hsl(var(--foreground))',
overflowX: 'auto',
overflowWrap: 'break-word',
wordBreak: 'break-all',
padding: "1rem",
fontSize: "0.875rem",
background: "hsl(var(--background))",
color: "hsl(var(--foreground))",
overflowX: "auto",
overflowWrap: "break-word",
wordBreak: "break-all",
}}
language={language}
lineNumberStyle={{
color: 'hsl(var(--muted-foreground))',
paddingRight: '1rem',
minWidth: '2.5rem',
color: "hsl(var(--muted-foreground))",
paddingRight: "1rem",
minWidth: "2.5rem",
}}
showLineNumbers={showLineNumbers}
style={oneDark}
@ -123,8 +123,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;
}
@ -142,7 +142,7 @@ export const CodeBlockCopyButton = ({
return (
<Button
className={cn('shrink-0', className)}
className={cn("shrink-0", className)}
onClick={copyToClipboard}
size="icon"
variant="ghost"

View file

@ -1,24 +1,24 @@
'use client';
"use client";
import type { ComponentProps } from "react";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { cn } from '@/lib/utils';
import type { ComponentProps } from 'react';
import { Separator } from '@/components/ui/separator';
import { Progress } from '@/components/ui/progress';
import type { AppUsage } from '@/lib/usage';
} from "@/components/ui/dropdown-menu";
import { Progress } from "@/components/ui/progress";
import { Separator } from "@/components/ui/separator";
import type { AppUsage } from "@/lib/usage";
import { cn } from "@/lib/utils";
export type ContextProps = ComponentProps<'button'> & {
export type ContextProps = ComponentProps<"button"> & {
/** Optional full usage payload to enable breakdown view */
usage?: AppUsage;
};
const THOUSAND = 1000;
const MILLION = 1_000_000;
const BILLION = 1_000_000_000;
const _THOUSAND = 1000;
const _MILLION = 1_000_000;
const _BILLION = 1_000_000_000;
const PERCENT_MAX = 100;
// Lucide CircleIcon geometry
@ -41,7 +41,7 @@ export const ContextIcon = ({ percent }: ContextIconProps) => {
aria-label={`${percent.toFixed(2)}% of model context used`}
height="28"
role="img"
style={{ color: 'currentcolor' }}
style={{ color: "currentcolor" }}
viewBox={`0 0 ${ICON_VIEWBOX} ${ICON_VIEWBOX}`}
width="28"
>
@ -71,7 +71,6 @@ export const ContextIcon = ({ percent }: ContextIconProps) => {
);
};
function InfoRow({
label,
tokens,
@ -85,14 +84,16 @@ function InfoRow({
<div className="flex items-center justify-between text-xs">
<span className="text-muted-foreground">{label}</span>
<div className="flex items-center gap-2 font-mono">
<span className="text-right min-w-[4ch]">
{tokens === undefined ? '—' : tokens.toLocaleString()}
<span className="min-w-[4ch] text-right">
{tokens === undefined ? "—" : tokens.toLocaleString()}
</span>
{costText !== undefined && costText !== null && !isNaN(parseFloat(costText)) && (
<span className="text-muted-foreground">
${parseFloat(costText).toFixed(6)}
</span>
)}
{costText !== undefined &&
costText !== null &&
!Number.isNaN(Number.parseFloat(costText)) && (
<span className="text-muted-foreground">
${Number.parseFloat(costText).toFixed(6)}
</span>
)}
</div>
</div>
);
@ -104,16 +105,16 @@ export const Context = ({ className, usage, ...props }: ContextProps) => {
usage?.context?.totalMax ??
usage?.context?.combinedMax ??
usage?.context?.inputMax;
const hasMax = typeof max === 'number' && Number.isFinite(max) && max > 0;
const hasMax = typeof max === "number" && Number.isFinite(max) && max > 0;
const usedPercent = hasMax ? Math.min(100, (used / max) * 100) : 0;
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<button
className={cn(
'inline-flex items-center gap-1 select-none rounded-md text-sm',
'cursor-pointer bg-background text-foreground',
className,
"inline-flex select-none items-center gap-1 rounded-md text-sm",
"cursor-pointer bg-background text-foreground",
className
)}
type="button"
{...props}
@ -124,7 +125,7 @@ export const Context = ({ className, usage, ...props }: ContextProps) => {
<ContextIcon percent={usedPercent} />
</button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" side="top" className="w-fit p-3">
<DropdownMenuContent align="end" className="w-fit p-3" side="top">
<div className="min-w-[240px] space-y-2">
<div className="flex items-start justify-between text-sm">
<span>{usedPercent.toFixed(1)}%</span>
@ -138,42 +139,43 @@ export const Context = ({ className, usage, ...props }: ContextProps) => {
<div className="mt-1 space-y-1">
{usage?.cachedInputTokens && usage.cachedInputTokens > 0 && (
<InfoRow
costText={usage?.costUSD?.cacheReadUSD?.toString()}
label="Cache Hits"
tokens={usage?.cachedInputTokens}
costText={usage?.costUSD?.cacheReadUSD?.toString()}
/>
)}
<InfoRow
costText={usage?.costUSD?.inputUSD?.toString()}
label="Input"
tokens={usage?.inputTokens}
costText={usage?.costUSD?.inputUSD?.toString()}
/>
<InfoRow
costText={usage?.costUSD?.outputUSD?.toString()}
label="Output"
tokens={usage?.outputTokens}
costText={usage?.costUSD?.outputUSD?.toString()}
/>
<InfoRow
costText={usage?.costUSD?.reasoningUSD?.toString()}
label="Reasoning"
tokens={
usage?.reasoningTokens && usage.reasoningTokens > 0
? usage.reasoningTokens
: undefined
}
costText={usage?.costUSD?.reasoningUSD?.toString()}
/>
{usage?.costUSD?.totalUSD !== undefined && (
<>
<Separator className="mt-1" />
<div className="flex justify-between items-center pt-1 text-xs">
<div className="flex items-center justify-between pt-1 text-xs">
<span className="text-muted-foreground">Total cost</span>
<div className="flex items-center gap-2 font-mono">
<span className="text-right min-w-[4ch]"></span>
<span className="min-w-[4ch] text-right" />
<span>
{!isNaN(parseFloat(usage.costUSD.totalUSD.toString()))
? `$${parseFloat(usage.costUSD.totalUSD.toString()).toFixed(6)}`
: '—'
}
{Number.isNaN(
Number.parseFloat(usage.costUSD.totalUSD.toString())
)
? "—"
: `$${Number.parseFloat(usage.costUSD.totalUSD.toString()).toFixed(6)}`}
</span>
</div>
</div>

View file

@ -1,19 +1,19 @@
'use client';
"use client";
import { Button } from '@/components/ui/button';
import { cn } from '@/lib/utils';
import { ArrowDownIcon } from 'lucide-react';
import type { ComponentProps } from 'react';
import { useCallback } from 'react';
import { StickToBottom, useStickToBottomContext } from 'use-stick-to-bottom';
import { ArrowDownIcon } from "lucide-react";
import type { ComponentProps } from "react";
import { useCallback } from "react";
import { StickToBottom, useStickToBottomContext } from "use-stick-to-bottom";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
export type ConversationProps = ComponentProps<typeof StickToBottom>;
export const Conversation = ({ className, ...props }: ConversationProps) => (
<StickToBottom
className={cn(
'relative flex-1 touch-pan-y overflow-y-auto will-change-scroll',
className,
"relative flex-1 touch-pan-y overflow-y-auto will-change-scroll",
className
)}
initial="smooth"
resize="smooth"
@ -30,7 +30,7 @@ export const ConversationContent = ({
className,
...props
}: ConversationContentProps) => (
<StickToBottom.Content className={cn('p-4', className)} {...props} />
<StickToBottom.Content className={cn("p-4", className)} {...props} />
);
export type ConversationScrollButtonProps = ComponentProps<typeof Button>;
@ -49,8 +49,8 @@ export const ConversationScrollButton = ({
!isAtBottom && (
<Button
className={cn(
'-translate-x-1/2 absolute bottom-4 left-1/2 z-10 rounded-full shadow-lg',
className,
"-translate-x-1/2 absolute bottom-4 left-1/2 z-10 rounded-full shadow-lg",
className
)}
onClick={handleScrollToBottom}
size="icon"

View file

@ -1,5 +1,5 @@
import { cn } from '@/lib/utils';
import type { Experimental_GeneratedImage } from 'ai';
import type { Experimental_GeneratedImage } from "ai";
import { cn } from "@/lib/utils";
export type ImageProps = Experimental_GeneratedImage & {
className?: string;
@ -12,13 +12,14 @@ export const Image = ({
mediaType,
...props
}: ImageProps) => (
// eslint-disable-next-line @next/next/no-img-element
// biome-ignore lint/nursery/useImageSize: "Generated image without explicit size"
// biome-ignore lint/performance/noImgElement: "Generated image without explicit size"
<img
{...props}
alt={props.alt}
className={cn(
'h-auto max-w-full overflow-hidden rounded-md',
props.className,
"h-auto max-w-full overflow-hidden rounded-md",
props.className
)}
src={`data:${mediaType};base64,${base64}`}
/>

View file

@ -1,19 +1,6 @@
'use client';
"use client";
import { Badge } from '@/components/ui/badge';
import {
Carousel,
CarouselContent,
CarouselItem,
type CarouselApi,
} from '@/components/ui/carousel';
import {
HoverCard,
HoverCardContent,
HoverCardTrigger,
} from '@/components/ui/hover-card';
import { cn } from '@/lib/utils';
import { ArrowLeftIcon, ArrowRightIcon } from 'lucide-react';
import { ArrowLeftIcon, ArrowRightIcon } from "lucide-react";
import {
type ComponentProps,
createContext,
@ -21,28 +8,41 @@ import {
useContext,
useEffect,
useState,
} from 'react';
} from "react";
import { Badge } from "@/components/ui/badge";
import {
Carousel,
type CarouselApi,
CarouselContent,
CarouselItem,
} from "@/components/ui/carousel";
import {
HoverCard,
HoverCardContent,
HoverCardTrigger,
} from "@/components/ui/hover-card";
import { cn } from "@/lib/utils";
export type InlineCitationProps = ComponentProps<'span'>;
export type InlineCitationProps = ComponentProps<"span">;
export const InlineCitation = ({
className,
...props
}: InlineCitationProps) => (
<span
className={cn('group inline items-center gap-1', className)}
className={cn("group inline items-center gap-1", className)}
{...props}
/>
);
export type InlineCitationTextProps = ComponentProps<'span'>;
export type InlineCitationTextProps = ComponentProps<"span">;
export const InlineCitationText = ({
className,
...props
}: InlineCitationTextProps) => (
<span
className={cn('transition-colors group-hover:bg-accent', className)}
className={cn("transition-colors group-hover:bg-accent", className)}
{...props}
/>
);
@ -64,29 +64,29 @@ export const InlineCitationCardTrigger = ({
}: InlineCitationCardTriggerProps) => (
<HoverCardTrigger asChild>
<Badge
className={cn('ml-1 rounded-full', className)}
className={cn("ml-1 rounded-full", className)}
variant="secondary"
{...props}
>
{sources.length ? (
<>
{new URL(sources[0]).hostname}{' '}
{new URL(sources[0]).hostname}{" "}
{sources.length > 1 && `+${sources.length - 1}`}
</>
) : (
'unknown'
"unknown"
)}
</Badge>
</HoverCardTrigger>
);
export type InlineCitationCardBodyProps = ComponentProps<'div'>;
export type InlineCitationCardBodyProps = ComponentProps<"div">;
export const InlineCitationCardBody = ({
className,
...props
}: InlineCitationCardBodyProps) => (
<HoverCardContent className={cn('relative w-80 p-0', className)} {...props} />
<HoverCardContent className={cn("relative w-80 p-0", className)} {...props} />
);
const CarouselApiContext = createContext<CarouselApi | undefined>(undefined);
@ -107,32 +107,32 @@ export const InlineCitationCarousel = ({
return (
<CarouselApiContext.Provider value={api}>
<Carousel className={cn('w-full', className)} setApi={setApi} {...props}>
<Carousel className={cn("w-full", className)} setApi={setApi} {...props}>
{children}
</Carousel>
</CarouselApiContext.Provider>
);
};
export type InlineCitationCarouselContentProps = ComponentProps<'div'>;
export type InlineCitationCarouselContentProps = ComponentProps<"div">;
export const InlineCitationCarouselContent = (
props: InlineCitationCarouselContentProps,
props: InlineCitationCarouselContentProps
) => <CarouselContent {...props} />;
export type InlineCitationCarouselItemProps = ComponentProps<'div'>;
export type InlineCitationCarouselItemProps = ComponentProps<"div">;
export const InlineCitationCarouselItem = ({
className,
...props
}: InlineCitationCarouselItemProps) => (
<CarouselItem
className={cn('w-full space-y-2 p-4 pl-8', className)}
className={cn("w-full space-y-2 p-4 pl-8", className)}
{...props}
/>
);
export type InlineCitationCarouselHeaderProps = ComponentProps<'div'>;
export type InlineCitationCarouselHeaderProps = ComponentProps<"div">;
export const InlineCitationCarouselHeader = ({
className,
@ -140,14 +140,14 @@ export const InlineCitationCarouselHeader = ({
}: InlineCitationCarouselHeaderProps) => (
<div
className={cn(
'flex items-center justify-between gap-2 rounded-t-md bg-secondary p-2',
className,
"flex items-center justify-between gap-2 rounded-t-md bg-secondary p-2",
className
)}
{...props}
/>
);
export type InlineCitationCarouselIndexProps = ComponentProps<'div'>;
export type InlineCitationCarouselIndexProps = ComponentProps<"div">;
export const InlineCitationCarouselIndex = ({
children,
@ -166,7 +166,7 @@ export const InlineCitationCarouselIndex = ({
setCount(api.scrollSnapList().length);
setCurrent(api.selectedScrollSnap() + 1);
api.on('select', () => {
api.on("select", () => {
setCurrent(api.selectedScrollSnap() + 1);
});
}, [api]);
@ -174,8 +174,8 @@ export const InlineCitationCarouselIndex = ({
return (
<div
className={cn(
'flex flex-1 items-center justify-end px-3 py-1 text-muted-foreground text-xs',
className,
"flex flex-1 items-center justify-end px-3 py-1 text-muted-foreground text-xs",
className
)}
{...props}
>
@ -184,7 +184,7 @@ export const InlineCitationCarouselIndex = ({
);
};
export type InlineCitationCarouselPrevProps = ComponentProps<'button'>;
export type InlineCitationCarouselPrevProps = ComponentProps<"button">;
export const InlineCitationCarouselPrev = ({
className,
@ -201,7 +201,7 @@ export const InlineCitationCarouselPrev = ({
return (
<button
aria-label="Previous"
className={cn('shrink-0', className)}
className={cn("shrink-0", className)}
onClick={handleClick}
type="button"
{...props}
@ -211,7 +211,7 @@ export const InlineCitationCarouselPrev = ({
);
};
export type InlineCitationCarouselNextProps = ComponentProps<'button'>;
export type InlineCitationCarouselNextProps = ComponentProps<"button">;
export const InlineCitationCarouselNext = ({
className,
@ -228,7 +228,7 @@ export const InlineCitationCarouselNext = ({
return (
<button
aria-label="Next"
className={cn('shrink-0', className)}
className={cn("shrink-0", className)}
onClick={handleClick}
type="button"
{...props}
@ -238,7 +238,7 @@ export const InlineCitationCarouselNext = ({
);
};
export type InlineCitationSourceProps = ComponentProps<'div'> & {
export type InlineCitationSourceProps = ComponentProps<"div"> & {
title?: string;
url?: string;
description?: string;
@ -252,7 +252,7 @@ export const InlineCitationSource = ({
children,
...props
}: InlineCitationSourceProps) => (
<div className={cn('space-y-1', className)} {...props}>
<div className={cn("space-y-1", className)} {...props}>
{title && (
<h4 className="truncate font-medium text-sm leading-tight">{title}</h4>
)}
@ -268,7 +268,7 @@ export const InlineCitationSource = ({
</div>
);
export type InlineCitationQuoteProps = ComponentProps<'blockquote'>;
export type InlineCitationQuoteProps = ComponentProps<"blockquote">;
export const InlineCitationQuote = ({
children,
@ -277,8 +277,8 @@ export const InlineCitationQuote = ({
}: InlineCitationQuoteProps) => (
<blockquote
className={cn(
'border-muted border-l-2 pl-3 text-muted-foreground text-sm italic',
className,
"border-muted border-l-2 pl-3 text-muted-foreground text-sm italic",
className
)}
{...props}
>

View file

@ -1,5 +1,5 @@
import { cn } from '@/lib/utils';
import type { HTMLAttributes } from 'react';
import type { HTMLAttributes } from "react";
import { cn } from "@/lib/utils";
type LoaderIconProps = {
size?: number;
@ -9,7 +9,7 @@ const LoaderIcon = ({ size = 16 }: LoaderIconProps) => (
<svg
height={size}
strokeLinejoin="round"
style={{ color: 'currentcolor' }}
style={{ color: "currentcolor" }}
viewBox="0 0 16 16"
width={size}
>
@ -86,8 +86,8 @@ export type LoaderProps = HTMLAttributes<HTMLDivElement> & {
export const Loader = ({ className, size = 16, ...props }: LoaderProps) => (
<div
className={cn(
'inline-flex animate-spin items-center justify-center',
className,
"inline-flex animate-spin items-center justify-center",
className
)}
{...props}
>

View file

@ -1,19 +1,19 @@
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import { cn } from '@/lib/utils';
import type { UIMessage } from 'ai';
import type { ComponentProps, HTMLAttributes } from 'react';
import type { UIMessage } from "ai";
import type { ComponentProps, HTMLAttributes } from "react";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { cn } from "@/lib/utils";
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 items-end justify-end gap-2 py-4',
from === 'user' ? 'is-user' : 'is-assistant flex-row-reverse justify-end',
'[&>div]:max-w-[80%]',
className,
"group flex w-full items-end justify-end gap-2 py-4",
from === "user" ? "is-user" : "is-assistant flex-row-reverse justify-end",
"[&>div]:max-w-[80%]",
className
)}
{...props}
/>
@ -28,11 +28,11 @@ export const MessageContent = ({
}: MessageContentProps) => (
<div
className={cn(
'flex flex-col gap-2 overflow-hidden rounded-lg px-4 py-3 text-foreground text-sm',
'group-[.is-user]:bg-primary group-[.is-user]:text-primary-foreground',
'group-[.is-assistant]:bg-secondary group-[.is-assistant]:text-foreground',
'is-user:dark',
className,
"flex flex-col gap-2 overflow-hidden rounded-lg px-4 py-3 text-foreground text-sm",
"group-[.is-user]:bg-primary group-[.is-user]:text-primary-foreground",
"group-[.is-assistant]:bg-secondary group-[.is-assistant]:text-foreground",
"is-user:dark",
className
)}
{...props}
>
@ -51,8 +51,8 @@ export const MessageAvatar = ({
className,
...props
}: MessageAvatarProps) => (
<Avatar className={cn('size-8 ring-1 ring-border', className)} {...props}>
<Avatar className={cn("size-8 ring-1 ring-border", className)} {...props}>
<AvatarImage alt="" className="my-0" src={src} />
<AvatarFallback>{name?.slice(0, 2) || 'ME'}</AvatarFallback>
<AvatarFallback>{name?.slice(0, 2) || "ME"}</AvatarFallback>
</Avatar>
);

View file

@ -1,31 +1,31 @@
'use client';
"use client";
import { Button } from '@/components/ui/button';
import type { ChatStatus } from "ai";
import { Loader2Icon, SendIcon, SquareIcon, XIcon } from "lucide-react";
import type {
ComponentProps,
HTMLAttributes,
KeyboardEventHandler,
} from "react";
import { Children } from "react";
import { Button } from "@/components/ui/button";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import { Textarea } from '@/components/ui/textarea';
import { cn } from '@/lib/utils';
import type { ChatStatus } from 'ai';
import { Loader2Icon, SendIcon, SquareIcon, XIcon } from 'lucide-react';
import type {
ComponentProps,
HTMLAttributes,
KeyboardEventHandler,
} from 'react';
import { Children } from 'react';
} from "@/components/ui/select";
import { Textarea } from "@/components/ui/textarea";
import { cn } from "@/lib/utils";
export type PromptInputProps = HTMLAttributes<HTMLFormElement>;
export const PromptInput = ({ className, ...props }: PromptInputProps) => (
<form
className={cn(
'w-full overflow-hidden rounded-xl border bg-background shadow-xs',
className,
"w-full overflow-hidden rounded-xl border bg-background shadow-xs",
className
)}
{...props}
/>
@ -41,7 +41,7 @@ export type PromptInputTextareaProps = ComponentProps<typeof Textarea> & {
export const PromptInputTextarea = ({
onChange,
className,
placeholder = 'What would you like to know?',
placeholder = "What would you like to know?",
minHeight = 48,
maxHeight = 164,
disableAutoResize = false,
@ -49,7 +49,7 @@ export const PromptInputTextarea = ({
...props
}: PromptInputTextareaProps) => {
const handleKeyDown: KeyboardEventHandler<HTMLTextAreaElement> = (e) => {
if (e.key === 'Enter') {
if (e.key === "Enter") {
// Don't submit if IME composition is in progress
if (e.nativeEvent.isComposing) {
return;
@ -72,15 +72,15 @@ export const PromptInputTextarea = ({
return (
<Textarea
className={cn(
'w-full resize-none rounded-none border-none p-3 shadow-none outline-hidden ring-0',
"w-full resize-none rounded-none border-none p-3 shadow-none outline-hidden ring-0",
disableAutoResize
? 'field-sizing-fixed'
? "field-sizing-fixed"
: resizeOnNewLinesOnly
? 'field-sizing-fixed'
: 'field-sizing-content max-h-[6lh]',
'bg-transparent dark:bg-transparent',
'focus-visible:ring-0',
className,
? "field-sizing-fixed"
: "field-sizing-content max-h-[6lh]",
"bg-transparent dark:bg-transparent",
"focus-visible:ring-0",
className
)}
name="message"
onChange={(e) => {
@ -100,7 +100,7 @@ export const PromptInputToolbar = ({
...props
}: PromptInputToolbarProps) => (
<div
className={cn('flex items-center justify-between p-1', className)}
className={cn("flex items-center justify-between p-1", className)}
{...props}
/>
);
@ -113,9 +113,9 @@ export const PromptInputTools = ({
}: PromptInputToolsProps) => (
<div
className={cn(
'flex items-center gap-1',
'[&_button:first-child]:rounded-bl-xl',
className,
"flex items-center gap-1",
"[&_button:first-child]:rounded-bl-xl",
className
)}
{...props}
/>
@ -124,21 +124,21 @@ export const PromptInputTools = ({
export type PromptInputButtonProps = ComponentProps<typeof Button>;
export const PromptInputButton = ({
variant = 'ghost',
variant = "ghost",
className,
size,
...props
}: PromptInputButtonProps) => {
const newSize =
(size ?? Children.count(props.children) > 1) ? 'default' : 'icon';
(size ?? Children.count(props.children) > 1) ? "default" : "icon";
return (
<Button
className={cn(
'shrink-0 gap-1.5 rounded-lg',
variant === 'ghost' && 'text-muted-foreground',
newSize === 'default' && 'px-3',
className,
"shrink-0 gap-1.5 rounded-lg",
variant === "ghost" && "text-muted-foreground",
newSize === "default" && "px-3",
className
)}
size={newSize}
type="button"
@ -154,25 +154,25 @@ export type PromptInputSubmitProps = ComponentProps<typeof Button> & {
export const PromptInputSubmit = ({
className,
variant = 'default',
size = 'icon',
variant = "default",
size = "icon",
status,
children,
...props
}: PromptInputSubmitProps) => {
let Icon = <SendIcon className="size-4" />;
if (status === 'submitted') {
if (status === "submitted") {
Icon = <Loader2Icon className="size-4 animate-spin" />;
} else if (status === 'streaming') {
} else if (status === "streaming") {
Icon = <SquareIcon className="size-4" />;
} else if (status === 'error') {
} else if (status === "error") {
Icon = <XIcon className="size-4" />;
}
return (
<Button
className={cn('gap-1.5 rounded-lg', className)}
className={cn("gap-1.5 rounded-lg", className)}
size={size}
type="submit"
variant={variant}
@ -199,10 +199,10 @@ export const PromptInputModelSelectTrigger = ({
}: PromptInputModelSelectTriggerProps) => (
<SelectTrigger
className={cn(
'border-none bg-transparent font-medium text-muted-foreground shadow-none transition-colors',
'hover:bg-accent hover:text-foreground aria-expanded:bg-accent aria-expanded:text-foreground',
'h-auto px-2 py-1.5',
className,
"border-none bg-transparent font-medium text-muted-foreground shadow-none transition-colors",
"hover:bg-accent hover:text-foreground aria-expanded:bg-accent aria-expanded:text-foreground",
"h-auto px-2 py-1.5",
className
)}
{...props}
/>

View file

@ -1,16 +1,16 @@
'use client';
"use client";
import { useControllableState } from '@radix-ui/react-use-controllable-state';
import { useControllableState } from "@radix-ui/react-use-controllable-state";
import { BrainIcon, ChevronDownIcon } from "lucide-react";
import type { ComponentProps } from "react";
import { createContext, memo, useContext, useEffect, useState } from "react";
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from '@/components/ui/collapsible';
import { cn } from '@/lib/utils';
import { BrainIcon, ChevronDownIcon } from 'lucide-react';
import type { ComponentProps } from 'react';
import { createContext, memo, useContext, useEffect, useState } from 'react';
import { Response } from './response';
} from "@/components/ui/collapsible";
import { cn } from "@/lib/utils";
import { Response } from "./response";
type ReasoningContextValue = {
isStreaming: boolean;
@ -24,7 +24,7 @@ const ReasoningContext = createContext<ReasoningContextValue | null>(null);
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;
};
@ -98,7 +98,7 @@ export const Reasoning = memo(
value={{ isStreaming, isOpen, setIsOpen, duration }}
>
<Collapsible
className={cn('not-prose', className)}
className={cn("not-prose", className)}
onOpenChange={handleOpenChange}
open={isOpen}
{...props}
@ -107,7 +107,7 @@ export const Reasoning = memo(
</Collapsible>
</ReasoningContext.Provider>
);
},
}
);
export type ReasoningTriggerProps = ComponentProps<typeof CollapsibleTrigger>;
@ -119,8 +119,8 @@ export const ReasoningTrigger = memo(
return (
<CollapsibleTrigger
className={cn(
'flex items-center gap-1.5 text-muted-foreground text-xs transition-colors hover:text-foreground',
className,
"flex items-center gap-1.5 text-muted-foreground text-xs transition-colors hover:text-foreground",
className
)}
{...props}
>
@ -134,15 +134,15 @@ export const ReasoningTrigger = memo(
)}
<ChevronDownIcon
className={cn(
'size-3 text-muted-foreground transition-transform',
isOpen ? 'rotate-180' : 'rotate-0',
"size-3 text-muted-foreground transition-transform",
isOpen ? "rotate-180" : "rotate-0"
)}
/>
</>
)}
</CollapsibleTrigger>
);
},
}
);
export type ReasoningContentProps = ComponentProps<
@ -155,17 +155,17 @@ export const ReasoningContent = memo(
({ className, children, ...props }: ReasoningContentProps) => (
<CollapsibleContent
className={cn(
'mt-2 text-muted-foreground text-xs',
'data-[state=closed]:fade-out-0 data-[state=closed]:slide-out-to-top-2 data-[state=open]:slide-in-from-top-2 outline-hidden data-[state=closed]:animate-out data-[state=open]:animate-in',
className,
"mt-2 text-muted-foreground text-xs",
"data-[state=closed]:fade-out-0 data-[state=closed]:slide-out-to-top-2 data-[state=open]:slide-in-from-top-2 outline-hidden data-[state=closed]:animate-out data-[state=open]:animate-in",
className
)}
{...props}
>
<Response className="grid gap-2">{children}</Response>
</CollapsibleContent>
),
)
);
Reasoning.displayName = 'Reasoning';
ReasoningTrigger.displayName = 'ReasoningTrigger';
ReasoningContent.displayName = 'ReasoningContent';
Reasoning.displayName = "Reasoning";
ReasoningTrigger.displayName = "ReasoningTrigger";
ReasoningContent.displayName = "ReasoningContent";

View file

@ -1,8 +1,8 @@
'use client';
"use client";
import { cn } from '@/lib/utils';
import { type ComponentProps, memo } from 'react';
import { Streamdown } from 'streamdown';
import { type ComponentProps, memo } from "react";
import { Streamdown } from "streamdown";
import { cn } from "@/lib/utils";
type ResponseProps = ComponentProps<typeof Streamdown>;
@ -10,13 +10,13 @@ export const Response = memo(
({ className, ...props }: ResponseProps) => (
<Streamdown
className={cn(
'size-full [&>*:first-child]:mt-0 [&>*:last-child]:mb-0 [&_code]:whitespace-pre-wrap [&_code]:break-words [&_pre]:max-w-full [&_pre]:overflow-x-auto',
className,
"size-full [&>*:first-child]:mt-0 [&>*:last-child]:mb-0 [&_code]:whitespace-pre-wrap [&_code]:break-words [&_pre]:max-w-full [&_pre]:overflow-x-auto",
className
)}
{...props}
/>
),
(prevProps, nextProps) => prevProps.children === nextProps.children,
(prevProps, nextProps) => prevProps.children === nextProps.children
);
Response.displayName = 'Response';
Response.displayName = "Response";

View file

@ -1,19 +1,19 @@
'use client';
"use client";
import { BookIcon, ChevronDownIcon } from "lucide-react";
import type { ComponentProps } from "react";
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from '@/components/ui/collapsible';
import { cn } from '@/lib/utils';
import { BookIcon, ChevronDownIcon } from 'lucide-react';
import type { ComponentProps } from 'react';
} from "@/components/ui/collapsible";
import { cn } from "@/lib/utils";
export type SourcesProps = ComponentProps<'div'>;
export type SourcesProps = ComponentProps<"div">;
export const Sources = ({ className, ...props }: SourcesProps) => (
<Collapsible
className={cn('not-prose mb-4 text-primary text-xs', className)}
className={cn("not-prose mb-4 text-primary text-xs", className)}
{...props}
/>
);
@ -46,15 +46,15 @@ export const SourcesContent = ({
}: SourcesContentProps) => (
<CollapsibleContent
className={cn(
'mt-3 flex w-fit flex-col gap-2',
'data-[state=closed]:fade-out-0 data-[state=closed]:slide-out-to-top-2 data-[state=open]:slide-in-from-top-2 outline-hidden data-[state=closed]:animate-out data-[state=open]:animate-in',
className,
"mt-3 flex w-fit flex-col gap-2",
"data-[state=closed]:fade-out-0 data-[state=closed]:slide-out-to-top-2 data-[state=open]:slide-in-from-top-2 outline-hidden data-[state=closed]:animate-out data-[state=open]:animate-in",
className
)}
{...props}
/>
);
export type SourceProps = ComponentProps<'a'>;
export type SourceProps = ComponentProps<"a">;
export const Source = ({ href, title, children, ...props }: SourceProps) => (
<a

View file

@ -1,9 +1,9 @@
'use client';
"use client";
import { Button } from '@/components/ui/button';
import { ScrollArea, ScrollBar } from '@/components/ui/scroll-area';
import { cn } from '@/lib/utils';
import type { ComponentProps } from 'react';
import type { ComponentProps } from "react";
import { Button } from "@/components/ui/button";
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
import { cn } from "@/lib/utils";
export type SuggestionsProps = ComponentProps<typeof ScrollArea>;
@ -13,14 +13,14 @@ export const Suggestions = ({
...props
}: SuggestionsProps) => (
<ScrollArea className="w-full overflow-x-auto whitespace-nowrap" {...props}>
<div className={cn('flex w-max flex-nowrap items-center gap-2', className)}>
<div className={cn("flex w-max flex-nowrap items-center gap-2", className)}>
{children}
</div>
<ScrollBar className="hidden" orientation="horizontal" />
</ScrollArea>
);
export type SuggestionProps = Omit<ComponentProps<typeof Button>, 'onClick'> & {
export type SuggestionProps = Omit<ComponentProps<typeof Button>, "onClick"> & {
suggestion: string;
onClick?: (suggestion: string) => void;
};
@ -29,8 +29,8 @@ export const Suggestion = ({
suggestion,
onClick,
className,
variant = 'outline',
size = 'sm',
variant = "outline",
size = "sm",
children,
...props
}: SuggestionProps) => {
@ -40,7 +40,7 @@ export const Suggestion = ({
return (
<Button
className={cn('cursor-pointer rounded-full px-4', className)}
className={cn("cursor-pointer rounded-full px-4", className)}
onClick={handleClick}
size={size}
type="button"

View file

@ -1,15 +1,15 @@
'use client';
"use client";
import { ChevronDownIcon, SearchIcon } from "lucide-react";
import type { ComponentProps } from "react";
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from '@/components/ui/collapsible';
import { cn } from '@/lib/utils';
import { ChevronDownIcon, SearchIcon } from 'lucide-react';
import type { ComponentProps } from 'react';
} from "@/components/ui/collapsible";
import { cn } from "@/lib/utils";
export type TaskItemFileProps = ComponentProps<'div'>;
export type TaskItemFileProps = ComponentProps<"div">;
export const TaskItemFile = ({
children,
@ -18,8 +18,8 @@ export const TaskItemFile = ({
}: TaskItemFileProps) => (
<div
className={cn(
'inline-flex items-center gap-1 rounded-md border bg-secondary px-1.5 py-0.5 text-foreground text-xs',
className,
"inline-flex items-center gap-1 rounded-md border bg-secondary px-1.5 py-0.5 text-foreground text-xs",
className
)}
{...props}
>
@ -27,10 +27,10 @@ export const TaskItemFile = ({
</div>
);
export type TaskItemProps = ComponentProps<'div'>;
export type TaskItemProps = ComponentProps<"div">;
export const TaskItem = ({ children, className, ...props }: TaskItemProps) => (
<div className={cn('text-muted-foreground text-sm', className)} {...props}>
<div className={cn("text-muted-foreground text-sm", className)} {...props}>
{children}
</div>
);
@ -44,8 +44,8 @@ export const Task = ({
}: TaskProps) => (
<Collapsible
className={cn(
'data-[state=closed]:fade-out-0 data-[state=closed]:slide-out-to-top-2 data-[state=open]:slide-in-from-top-2 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 data-[state=closed]:animate-out data-[state=open]:animate-in",
className
)}
defaultOpen={defaultOpen}
{...props}
@ -62,7 +62,7 @@ export const TaskTrigger = ({
title,
...props
}: TaskTriggerProps) => (
<CollapsibleTrigger asChild className={cn('group', className)} {...props}>
<CollapsibleTrigger asChild className={cn("group", className)} {...props}>
{children ?? (
<div className="flex cursor-pointer items-center gap-2 text-muted-foreground hover:text-foreground">
<SearchIcon className="size-4" />
@ -82,8 +82,8 @@ export const TaskContent = ({
}: TaskContentProps) => (
<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-hidden 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-hidden data-[state=closed]:animate-out data-[state=open]:animate-in",
className
)}
{...props}
>

View file

@ -1,13 +1,6 @@
'use client';
"use client";
import { Badge } from '@/components/ui/badge';
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from '@/components/ui/collapsible';
import { cn } from '@/lib/utils';
import type { ToolUIPart } from 'ai';
import type { ToolUIPart } from "ai";
import {
CheckCircleIcon,
ChevronDownIcon,
@ -15,38 +8,45 @@ import {
ClockIcon,
WrenchIcon,
XCircleIcon,
} from 'lucide-react';
import type { ComponentProps, ReactNode } from 'react';
import { CodeBlock } from './code-block';
} from "lucide-react";
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 { 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("not-prose mb-4 w-full rounded-md border", className)}
{...props}
/>
);
export type ToolHeaderProps = {
type: ToolUIPart['type'];
state: ToolUIPart['state'];
type: ToolUIPart["type"];
state: ToolUIPart["state"];
className?: string;
};
const getStatusBadge = (status: ToolUIPart['state']) => {
const getStatusBadge = (status: ToolUIPart["state"]) => {
const labels = {
'input-streaming': 'Pending',
'input-available': 'Running',
'output-available': 'Completed',
'output-error': 'Error',
"input-streaming": "Pending",
"input-available": "Running",
"output-available": "Completed",
"output-error": "Error",
} as const;
const icons = {
'input-streaming': <CircleIcon className="size-4" />,
'input-available': <ClockIcon className="size-4 animate-pulse" />,
'output-available': <CheckCircleIcon className="size-4 text-green-600" />,
'output-error': <XCircleIcon className="size-4 text-red-600" />,
"input-streaming": <CircleIcon className="size-4" />,
"input-available": <ClockIcon className="size-4 animate-pulse" />,
"output-available": <CheckCircleIcon className="size-4 text-green-600" />,
"output-error": <XCircleIcon className="size-4 text-red-600" />,
} as const;
return (
@ -68,8 +68,8 @@ export const ToolHeader = ({
}: ToolHeaderProps) => (
<CollapsibleTrigger
className={cn(
'flex w-full min-w-0 items-center justify-between gap-2 p-3',
className,
"flex w-full min-w-0 items-center justify-between gap-2 p-3",
className
)}
{...props}
>
@ -89,19 +89,19 @@ 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-hidden 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-hidden 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', className)} {...props}>
<div className={cn("space-y-2 overflow-hidden p-4", className)} {...props}>
<h4 className="font-medium text-muted-foreground text-xs uppercase tracking-wide">
Parameters
</h4>
@ -111,9 +111,9 @@ export const ToolInput = ({ className, input, ...props }: ToolInputProps) => (
</div>
);
export type ToolOutputProps = ComponentProps<'div'> & {
export type ToolOutputProps = ComponentProps<"div"> & {
output: ReactNode;
errorText: ToolUIPart['errorText'];
errorText: ToolUIPart["errorText"];
};
export const ToolOutput = ({
@ -127,16 +127,16 @@ export const ToolOutput = ({
}
return (
<div className={cn('space-y-2 p-4', className)} {...props}>
<div className={cn("space-y-2 p-4", 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-x-auto rounded-md text-xs [&_table]:w-full',
"overflow-x-auto rounded-md text-xs [&_table]:w-full",
errorText
? 'bg-destructive/10 text-destructive'
: 'bg-muted/50 text-foreground',
? "bg-destructive/10 text-destructive"
: "bg-muted/50 text-foreground"
)}
>
{errorText && <div>{errorText}</div>}

View file

@ -1,22 +1,22 @@
'use client';
"use client";
import { Button } from '@/components/ui/button';
import { ChevronDownIcon } from "lucide-react";
import type { ComponentProps, ReactNode } from "react";
import { createContext, useContext, useState } from "react";
import { Button } from "@/components/ui/button";
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from '@/components/ui/collapsible';
import { Input } from '@/components/ui/input';
} from "@/components/ui/collapsible";
import { Input } from "@/components/ui/input";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from '@/components/ui/tooltip';
import { cn } from '@/lib/utils';
import { ChevronDownIcon } from 'lucide-react';
import type { ComponentProps, ReactNode } from 'react';
import { createContext, useContext, useState } from 'react';
} from "@/components/ui/tooltip";
import { cn } from "@/lib/utils";
export type WebPreviewContextValue = {
url: string;
@ -30,12 +30,12 @@ const WebPreviewContext = createContext<WebPreviewContextValue | null>(null);
const useWebPreview = () => {
const context = useContext(WebPreviewContext);
if (!context) {
throw new Error('WebPreview components must be used within a WebPreview');
throw new Error("WebPreview components must be used within a WebPreview");
}
return context;
};
export type WebPreviewProps = ComponentProps<'div'> & {
export type WebPreviewProps = ComponentProps<"div"> & {
defaultUrl?: string;
onUrlChange?: (url: string) => void;
};
@ -43,7 +43,7 @@ export type WebPreviewProps = ComponentProps<'div'> & {
export const WebPreview = ({
className,
children,
defaultUrl = '',
defaultUrl = "",
onUrlChange,
...props
}: WebPreviewProps) => {
@ -66,8 +66,8 @@ export const WebPreview = ({
<WebPreviewContext.Provider value={contextValue}>
<div
className={cn(
'flex size-full flex-col rounded-lg border bg-card',
className,
"flex size-full flex-col rounded-lg border bg-card",
className
)}
{...props}
>
@ -77,7 +77,7 @@ export const WebPreview = ({
);
};
export type WebPreviewNavigationProps = ComponentProps<'div'>;
export type WebPreviewNavigationProps = ComponentProps<"div">;
export const WebPreviewNavigation = ({
className,
@ -85,7 +85,7 @@ export const WebPreviewNavigation = ({
...props
}: WebPreviewNavigationProps) => (
<div
className={cn('flex items-center gap-1 border-b p-2', className)}
className={cn("flex items-center gap-1 border-b p-2", className)}
{...props}
>
{children}
@ -135,7 +135,7 @@ export const WebPreviewUrl = ({
const { url, setUrl } = useWebPreview();
const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
if (event.key === 'Enter') {
if (event.key === "Enter") {
const target = event.target as HTMLInputElement;
setUrl(target.value);
}
@ -154,7 +154,7 @@ export const WebPreviewUrl = ({
);
};
export type WebPreviewBodyProps = ComponentProps<'iframe'> & {
export type WebPreviewBodyProps = ComponentProps<"iframe"> & {
loading?: ReactNode;
};
@ -169,7 +169,7 @@ export const WebPreviewBody = ({
return (
<div className="flex-1">
<iframe
className={cn('size-full', className)}
className={cn("size-full", className)}
sandbox="allow-scripts allow-same-origin allow-forms allow-popups allow-presentation"
src={(src ?? url) || undefined}
title="Preview"
@ -180,9 +180,9 @@ export const WebPreviewBody = ({
);
};
export type WebPreviewConsoleProps = ComponentProps<'div'> & {
export type WebPreviewConsoleProps = ComponentProps<"div"> & {
logs?: Array<{
level: 'log' | 'warn' | 'error';
level: "log" | "warn" | "error";
message: string;
timestamp: Date;
}>;
@ -198,7 +198,7 @@ export const WebPreviewConsole = ({
return (
<Collapsible
className={cn('border-t bg-muted/50 font-mono text-sm', className)}
className={cn("border-t bg-muted/50 font-mono text-sm", className)}
onOpenChange={setConsoleOpen}
open={consoleOpen}
{...props}
@ -211,16 +211,16 @@ export const WebPreviewConsole = ({
Console
<ChevronDownIcon
className={cn(
'h-4 w-4 transition-transform duration-200',
consoleOpen && 'rotate-180',
"h-4 w-4 transition-transform duration-200",
consoleOpen && "rotate-180"
)}
/>
</Button>
</CollapsibleTrigger>
<CollapsibleContent
className={cn(
'px-4 pb-4',
'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 outline-hidden data-[state=closed]:animate-out data-[state=open]:animate-in',
"px-4 pb-4",
"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 outline-hidden data-[state=closed]:animate-out data-[state=open]:animate-in"
)}
>
<div className="max-h-48 space-y-1 overflow-y-auto">
@ -230,16 +230,16 @@ export const WebPreviewConsole = ({
logs.map((log, index) => (
<div
className={cn(
'text-xs',
log.level === 'error' && 'text-destructive',
log.level === 'warn' && 'text-yellow-600',
log.level === 'log' && 'text-foreground',
"text-xs",
log.level === "error" && "text-destructive",
log.level === "warn" && "text-yellow-600",
log.level === "log" && "text-foreground"
)}
key={`${log.timestamp.getTime()}-${index}`}
>
<span className="text-muted-foreground">
{log.timestamp.toLocaleTimeString()}
</span>{' '}
</span>{" "}
{log.message}
</div>
))