Restore Ultracite + fix sidebar (#1233)
This commit is contained in:
parent
8fbfc253fa
commit
947ed094a6
177 changed files with 6908 additions and 8306 deletions
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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}`}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -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>}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue