87 | >(({ className, ...props }, ref) => (
88 | [role=checkbox]]:translate-y-[2px]",
92 | className
93 | )}
94 | {...props}
95 | />
96 | ))
97 | TableCell.displayName = "TableCell"
98 |
99 | const TableCaption = React.forwardRef<
100 | HTMLTableCaptionElement,
101 | React.HTMLAttributes
102 | >(({ className, ...props }, ref) => (
103 |
108 | ))
109 | TableCaption.displayName = "TableCaption"
110 |
111 | export {
112 | Table,
113 | TableHeader,
114 | TableBody,
115 | TableFooter,
116 | TableHead,
117 | TableRow,
118 | TableCell,
119 | TableCaption,
120 | }
121 |
--------------------------------------------------------------------------------
/components/navbar/user.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import Link from "next/link";
3 | import { Settings, LogOut, SquareChevronUp } from "lucide-react";
4 | import { signOut } from "next-auth/react";
5 | import {
6 | DropdownMenu,
7 | DropdownMenuContent,
8 | DropdownMenuItem,
9 | DropdownMenuLabel,
10 | DropdownMenuSeparator,
11 | DropdownMenuTrigger,
12 | } from "~/components/ui/dropdown-menu";
13 | import { Button } from "~/components/ui/button";
14 | import ModeToggle from "~/components/navbar/toggle-mode";
15 | import { Avatar, AvatarFallback, AvatarImage } from "~/components/ui/avatar";
16 | import { Session } from "next-auth";
17 | import { use } from "react";
18 |
19 | interface Props {
20 | sessionPromise: Promise;
21 | }
22 |
23 | export default function UserButton({ sessionPromise }: Props) {
24 | const session = use(sessionPromise);
25 | return (
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | {session?.user?.name
34 | ?.split(" ")
35 | .map((n) => n[0])
36 | .join("")
37 | .toUpperCase()}
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | {session?.user?.name}
46 |
47 |
48 | {session?.user?.email}
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 | {session?.user?.name}
61 |
62 |
63 | {session?.user?.email}
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 | Settings
72 |
73 |
74 |
75 | Preferences
76 |
77 | Toggle theme
78 |
79 |
80 |
81 |
82 | signOut()}
87 | >
88 |
89 | Log out
90 |
91 |
92 |
93 |
94 | );
95 | }
96 |
--------------------------------------------------------------------------------
/components/navbar/nav-items.tsx:
--------------------------------------------------------------------------------
1 | import NavItem from "~/components/navbar/nav-item";
2 | import {
3 | SidebarGroup,
4 | SidebarGroupContent,
5 | SidebarGroupLabel,
6 | SidebarMenuButton,
7 | SidebarMenuItem,
8 | } from "../ui/sidebar";
9 | import useSwr from "swr";
10 | import { Chat } from "~/lib/drizzle";
11 | import { fetcher, groupChats } from "~/lib/utils";
12 | import Spinner from "../ai/spinner";
13 |
14 | import { MessageSquarePlus } from "lucide-react";
15 | import { ChatsSkeleton } from "../skeletons";
16 | export default function NavItems() {
17 | const {
18 | data: chats,
19 | isLoading,
20 | isValidating,
21 | } = useSwr>("/api/chats", fetcher, {
22 | suspense: true,
23 | fallbackData: [],
24 | revalidateOnFocus: false,
25 | });
26 | const groupedChats = groupChats(chats || []);
27 |
28 | return (
29 | <>
30 | {isLoading || isValidating ? (
31 |
32 |
33 | Loading chats
34 |
35 |
36 |
37 |
38 |
39 | ) : chats && chats.length > 0 ? (
40 | <>
41 | {groupedChats.today.length > 0 && (
42 |
43 | Today
44 |
45 | {groupedChats.today.map((chat) => (
46 |
47 | ))}
48 |
49 |
50 | )}
51 | {groupedChats.yesterday.length > 0 && (
52 |
53 | Yesterday
54 |
55 | {groupedChats.yesterday.map((chat) => (
56 |
57 | ))}
58 |
59 |
60 | )}
61 | {groupedChats.lastWeek.length > 0 && (
62 |
63 | Previous 7 Days
64 |
65 | {groupedChats.lastWeek.map((chat) => (
66 |
67 | ))}
68 |
69 |
70 | )}
71 | {groupedChats.lastMonth.length > 0 && (
72 |
73 | Last Month
74 |
75 | {groupedChats.lastMonth.map((chat) => (
76 |
77 | ))}
78 |
79 |
80 | )}
81 | {groupedChats.older.length > 0 && (
82 |
83 | Older Chats
84 |
85 | {groupedChats.older.map((chat) => (
86 |
87 | ))}
88 |
89 |
90 | )}
91 | >
92 | ) : (
93 |
94 |
95 |
96 |
97 |
98 | No recent chats
99 |
100 |
101 |
102 |
103 | )}
104 | >
105 | );
106 | }
107 |
--------------------------------------------------------------------------------
/components/ui/dialog.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as DialogPrimitive from "@radix-ui/react-dialog"
5 | import { Cross2Icon } from "@radix-ui/react-icons"
6 |
7 | import { cn } from "~/lib/utils"
8 |
9 | const Dialog = DialogPrimitive.Root
10 |
11 | const DialogTrigger = DialogPrimitive.Trigger
12 |
13 | const DialogPortal = DialogPrimitive.Portal
14 |
15 | const DialogClose = DialogPrimitive.Close
16 |
17 | const DialogOverlay = React.forwardRef<
18 | React.ElementRef,
19 | React.ComponentPropsWithoutRef
20 | >(({ className, ...props }, ref) => (
21 |
29 | ))
30 | DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
31 |
32 | const DialogContent = React.forwardRef<
33 | React.ElementRef,
34 | React.ComponentPropsWithoutRef
35 | >(({ className, children, ...props }, ref) => (
36 |
37 |
38 |
46 | {children}
47 |
48 |
49 | Close
50 |
51 |
52 |
53 | ))
54 | DialogContent.displayName = DialogPrimitive.Content.displayName
55 |
56 | const DialogHeader = ({
57 | className,
58 | ...props
59 | }: React.HTMLAttributes) => (
60 |
67 | )
68 | DialogHeader.displayName = "DialogHeader"
69 |
70 | const DialogFooter = ({
71 | className,
72 | ...props
73 | }: React.HTMLAttributes) => (
74 |
81 | )
82 | DialogFooter.displayName = "DialogFooter"
83 |
84 | const DialogTitle = React.forwardRef<
85 | React.ElementRef,
86 | React.ComponentPropsWithoutRef
87 | >(({ className, ...props }, ref) => (
88 |
96 | ))
97 | DialogTitle.displayName = DialogPrimitive.Title.displayName
98 |
99 | const DialogDescription = React.forwardRef<
100 | React.ElementRef,
101 | React.ComponentPropsWithoutRef
102 | >(({ className, ...props }, ref) => (
103 |
108 | ))
109 | DialogDescription.displayName = DialogPrimitive.Description.displayName
110 |
111 | export {
112 | Dialog,
113 | DialogPortal,
114 | DialogOverlay,
115 | DialogTrigger,
116 | DialogClose,
117 | DialogContent,
118 | DialogHeader,
119 | DialogFooter,
120 | DialogTitle,
121 | DialogDescription,
122 | }
123 |
--------------------------------------------------------------------------------
/components/ui/sheet.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as React from "react";
4 | import * as SheetPrimitive from "@radix-ui/react-dialog";
5 | import { cva, type VariantProps } from "class-variance-authority";
6 | import { cn } from "~/lib/utils";
7 | import { Cross2Icon } from "@radix-ui/react-icons";
8 |
9 | const Sheet = SheetPrimitive.Root;
10 |
11 | const SheetTrigger = SheetPrimitive.Trigger;
12 |
13 | const SheetClose = SheetPrimitive.Close;
14 |
15 | const SheetPortal = SheetPrimitive.Portal;
16 |
17 | const SheetOverlay = React.forwardRef<
18 | React.ElementRef,
19 | React.ComponentPropsWithoutRef
20 | >(({ className, ...props }, ref) => (
21 |
29 | ));
30 | SheetOverlay.displayName = SheetPrimitive.Overlay.displayName;
31 |
32 | const sheetVariants = cva(
33 | "fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500 data-[state=open]:animate-in data-[state=closed]:animate-out",
34 | {
35 | variants: {
36 | side: {
37 | top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
38 | bottom:
39 | "inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
40 | left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
41 | right:
42 | "inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
43 | },
44 | },
45 | defaultVariants: {
46 | side: "right",
47 | },
48 | }
49 | );
50 |
51 | interface SheetContentProps
52 | extends React.ComponentPropsWithoutRef,
53 | VariantProps {}
54 |
55 | const SheetContent = React.forwardRef<
56 | React.ElementRef,
57 | SheetContentProps
58 | >(({ side = "right", className, children, ...props }, ref) => (
59 |
60 |
61 |
66 |
67 |
68 | Close
69 |
70 | {children}
71 |
72 |
73 | ));
74 | SheetContent.displayName = SheetPrimitive.Content.displayName;
75 |
76 | const SheetHeader = ({
77 | className,
78 | ...props
79 | }: React.HTMLAttributes) => (
80 |
87 | );
88 | SheetHeader.displayName = "SheetHeader";
89 |
90 | const SheetFooter = ({
91 | className,
92 | ...props
93 | }: React.HTMLAttributes) => (
94 |
101 | );
102 | SheetFooter.displayName = "SheetFooter";
103 |
104 | const SheetTitle = React.forwardRef<
105 | React.ElementRef,
106 | React.ComponentPropsWithoutRef
107 | >(({ className, ...props }, ref) => (
108 |
113 | ));
114 | SheetTitle.displayName = SheetPrimitive.Title.displayName;
115 |
116 | const SheetDescription = React.forwardRef<
117 | React.ElementRef,
118 | React.ComponentPropsWithoutRef
119 | >(({ className, ...props }, ref) => (
120 |
125 | ));
126 | SheetDescription.displayName = SheetPrimitive.Description.displayName;
127 |
128 | export {
129 | Sheet,
130 | SheetPortal,
131 | SheetOverlay,
132 | SheetTrigger,
133 | SheetClose,
134 | SheetContent,
135 | SheetHeader,
136 | SheetFooter,
137 | SheetTitle,
138 | SheetDescription,
139 | };
140 |
--------------------------------------------------------------------------------
/components/chat/model-select.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { models, type Model } from "~/lib/ai/models";
3 | import React, { useState, useMemo } from "react";
4 | import { Search } from "lucide-react";
5 | import {
6 | Popover,
7 | PopoverContent,
8 | PopoverTrigger,
9 | } from "~/components/ui/popover";
10 | import { Button } from "~/components/ui/button";
11 | import { ScrollArea } from "~/components/ui/scroll-area";
12 |
13 | interface ModelSelectorProps {
14 | selectedModel: Model | null;
15 | onModelSelect: React.Dispatch>;
16 | }
17 |
18 | export function ModelSelector({
19 | selectedModel,
20 | onModelSelect,
21 | }: ModelSelectorProps) {
22 | const [searchQuery, setSearchQuery] = useState("");
23 | const [open, setOpen] = useState(false);
24 |
25 | const filteredModels = useMemo(() => {
26 | return models.filter((model) => {
27 | const matchesSearch =
28 | model.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
29 | model.provider.name.toLowerCase().includes(searchQuery.toLowerCase());
30 | return matchesSearch;
31 | });
32 | }, [searchQuery]);
33 |
34 | const handleModelSelect = (model: Model) => {
35 | onModelSelect(model);
36 | setOpen(false);
37 | setSearchQuery("");
38 | };
39 |
40 | return (
41 |
42 |
43 | setOpen(!open)}
47 | >
48 | {selectedModel && (
49 |
50 |
51 |
52 |
53 |
54 |
{selectedModel.name}
55 |
56 |
57 | {selectedModel.provider.name}
58 |
59 |
60 |
61 |
62 | )}
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 | setSearchQuery(e.target.value)}
74 | className="pl-10 h-10 outline-0 border-0 shadow-none focus:border-0 focus:outline-0 focus-within:border-0 focus-within:outline-none "
75 | />
76 |
77 |
78 |
79 |
80 |
81 | {filteredModels.length > 0 ? (
82 | filteredModels.map((model) => (
83 |
handleModelSelect(model)}
89 | role="button"
90 | tabIndex={0}
91 | aria-label={`Select ${model.name} from ${model.provider.name}`}
92 | onKeyDown={(e) => {
93 | if (e.key === "Enter" || e.key === " ") {
94 | handleModelSelect(model);
95 | }
96 | }}
97 | >
98 |
99 |
100 |
101 |
102 |
{model.name}
103 |
104 |
105 |
{model.provider.name}
106 |
107 |
108 |
109 | ))
110 | ) : (
111 |
112 |
No models found matching your search.
113 |
114 | )}
115 |
116 |
117 |
118 |
119 | );
120 | }
121 |
--------------------------------------------------------------------------------
/components/ui/alert-dialog.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"
5 |
6 | import { cn } from "~/lib/utils"
7 | import { buttonVariants } from "~/components/ui/button"
8 |
9 | const AlertDialog = AlertDialogPrimitive.Root
10 |
11 | const AlertDialogTrigger = AlertDialogPrimitive.Trigger
12 |
13 | const AlertDialogPortal = AlertDialogPrimitive.Portal
14 |
15 | const AlertDialogOverlay = React.forwardRef<
16 | React.ElementRef,
17 | React.ComponentPropsWithoutRef
18 | >(({ className, ...props }, ref) => (
19 |
27 | ))
28 | AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName
29 |
30 | const AlertDialogContent = React.forwardRef<
31 | React.ElementRef,
32 | React.ComponentPropsWithoutRef
33 | >(({ className, ...props }, ref) => (
34 |
35 |
36 |
44 |
45 | ))
46 | AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName
47 |
48 | const AlertDialogHeader = ({
49 | className,
50 | ...props
51 | }: React.HTMLAttributes) => (
52 |
59 | )
60 | AlertDialogHeader.displayName = "AlertDialogHeader"
61 |
62 | const AlertDialogFooter = ({
63 | className,
64 | ...props
65 | }: React.HTMLAttributes) => (
66 |
73 | )
74 | AlertDialogFooter.displayName = "AlertDialogFooter"
75 |
76 | const AlertDialogTitle = React.forwardRef<
77 | React.ElementRef,
78 | React.ComponentPropsWithoutRef
79 | >(({ className, ...props }, ref) => (
80 |
85 | ))
86 | AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName
87 |
88 | const AlertDialogDescription = React.forwardRef<
89 | React.ElementRef,
90 | React.ComponentPropsWithoutRef
91 | >(({ className, ...props }, ref) => (
92 |
97 | ))
98 | AlertDialogDescription.displayName =
99 | AlertDialogPrimitive.Description.displayName
100 |
101 | const AlertDialogAction = React.forwardRef<
102 | React.ElementRef,
103 | React.ComponentPropsWithoutRef
104 | >(({ className, ...props }, ref) => (
105 |
110 | ))
111 | AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName
112 |
113 | const AlertDialogCancel = React.forwardRef<
114 | React.ElementRef,
115 | React.ComponentPropsWithoutRef
116 | >(({ className, ...props }, ref) => (
117 |
126 | ))
127 | AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName
128 |
129 | export {
130 | AlertDialog,
131 | AlertDialogPortal,
132 | AlertDialogOverlay,
133 | AlertDialogTrigger,
134 | AlertDialogContent,
135 | AlertDialogHeader,
136 | AlertDialogFooter,
137 | AlertDialogTitle,
138 | AlertDialogDescription,
139 | AlertDialogAction,
140 | AlertDialogCancel,
141 | }
142 |
--------------------------------------------------------------------------------
/components/ai/markdown.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { memo, useMemo } from "react";
3 | import ReactMarkdown, { type Components } from "react-markdown";
4 | import remarkGfm from "remark-gfm";
5 | import Link from "next/link";
6 | import {
7 | Table,
8 | TableCell,
9 | TableHead,
10 | TableHeader,
11 | TableRow,
12 | } from "../ui/table";
13 | import Code from "./code";
14 | import { cn } from "~/lib/utils";
15 | import { marked } from "marked";
16 |
17 | function parseMarkdown(markdown: string): string[] {
18 | const content = marked.lexer(markdown);
19 | return content.map((c) => c.raw);
20 | }
21 | function MarkdownComponent({ children }: { children: string }) {
22 | const components = useMemo(
23 | () =>
24 | ({
25 | table: ({ node, className, ...props }) => (
26 |
35 | ),
36 | thead: ({ node, className, ...props }) => (
37 |
38 | ),
39 | th: ({ node, ...props }: any) => (
40 |
44 | ),
45 | tr: ({ node, ...props }: any) => (
46 |
47 | ),
48 | td: ({ node, ...props }: any) => (
49 |
50 | ),
51 | ul: ({ children, className, ...props }) => (
52 |
55 | ),
56 | ol: ({ children, className, ...props }) => (
57 |
58 | {children}
59 |
60 | ),
61 | li: ({ children, className, ...props }) => (
62 |
63 | {children}
64 |
65 | ),
66 | strong: ({ children, className, ...props }) => (
67 |
68 | {children}
69 |
70 | ),
71 | a: ({ node, children, className, ...props }) => {
72 | return (
73 |
79 | {children}
80 |
81 | );
82 | },
83 | code: ({ node, inline, className, children, ...props }) => {
84 | const match = /language-(\w+)/.exec(className || "");
85 | return !inline && match ? (
86 |
91 | ) : (
92 |
99 | {children}
100 |
101 | );
102 | },
103 | p: ({ children, className, ...props }) => (
104 |
105 | {children}
106 |
107 | ),
108 | h1: ({ children, className, ...props }) => (
109 |
110 | {children}
111 |
112 | ),
113 | h2: ({ children, className, ...props }) => (
114 |
115 | {children}
116 |
117 | ),
118 | h3: ({ children, className, ...props }) => (
119 |
120 | {children}
121 |
122 | ),
123 | }) satisfies Components,
124 | [],
125 | );
126 |
127 | return (
128 |
129 |
134 | {children}
135 |
136 |
137 | );
138 | }
139 |
140 | const MarkdownBlock = memo(
141 | MarkdownComponent,
142 | (prev, next) => prev.children === next.children,
143 | );
144 |
145 | export const Markdown = memo(({ children }: { children: string }) => {
146 | const blocks = useMemo(() => parseMarkdown(children), [children]);
147 | return blocks.map((block, index) => (
148 | {block}
149 | ));
150 | });
151 |
152 | Markdown.displayName = "Markdown";
153 |
--------------------------------------------------------------------------------
/components/ui/command.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import { type DialogProps } from "@radix-ui/react-dialog"
5 | import { Command as CommandPrimitive } from "cmdk"
6 | import { cn } from "~/lib/utils"
7 | import { Dialog, DialogContent } from "~/components/ui/dialog"
8 | import { MagnifyingGlassIcon } from "@radix-ui/react-icons"
9 |
10 | const Command = React.forwardRef<
11 | React.ElementRef,
12 | React.ComponentPropsWithoutRef
13 | >(({ className, ...props }, ref) => (
14 |
22 | ))
23 | Command.displayName = CommandPrimitive.displayName
24 |
25 | const CommandDialog = ({ children, ...props }: DialogProps) => {
26 | return (
27 |
28 |
29 |
30 | {children}
31 |
32 |
33 |
34 | )
35 | }
36 |
37 | const CommandInput = React.forwardRef<
38 | React.ElementRef,
39 | React.ComponentPropsWithoutRef
40 | >(({ className, ...props }, ref) => (
41 |
42 |
43 |
51 |
52 | ))
53 |
54 | CommandInput.displayName = CommandPrimitive.Input.displayName
55 |
56 | const CommandList = React.forwardRef<
57 | React.ElementRef,
58 | React.ComponentPropsWithoutRef
59 | >(({ className, ...props }, ref) => (
60 |
65 | ))
66 |
67 | CommandList.displayName = CommandPrimitive.List.displayName
68 |
69 | const CommandEmpty = React.forwardRef<
70 | React.ElementRef,
71 | React.ComponentPropsWithoutRef
72 | >((props, ref) => (
73 |
78 | ))
79 |
80 | CommandEmpty.displayName = CommandPrimitive.Empty.displayName
81 |
82 | const CommandGroup = React.forwardRef<
83 | React.ElementRef,
84 | React.ComponentPropsWithoutRef
85 | >(({ className, ...props }, ref) => (
86 |
94 | ))
95 |
96 | CommandGroup.displayName = CommandPrimitive.Group.displayName
97 |
98 | const CommandSeparator = React.forwardRef<
99 | React.ElementRef,
100 | React.ComponentPropsWithoutRef
101 | >(({ className, ...props }, ref) => (
102 |
107 | ))
108 | CommandSeparator.displayName = CommandPrimitive.Separator.displayName
109 |
110 | const CommandItem = React.forwardRef<
111 | React.ElementRef,
112 | React.ComponentPropsWithoutRef
113 | >(({ className, ...props }, ref) => (
114 |
122 | ))
123 |
124 | CommandItem.displayName = CommandPrimitive.Item.displayName
125 |
126 | const CommandShortcut = ({
127 | className,
128 | ...props
129 | }: React.HTMLAttributes) => {
130 | return (
131 |
138 | )
139 | }
140 | CommandShortcut.displayName = "CommandShortcut"
141 |
142 | export {
143 | Command,
144 | CommandDialog,
145 | CommandInput,
146 | CommandList,
147 | CommandEmpty,
148 | CommandGroup,
149 | CommandItem,
150 | CommandShortcut,
151 | CommandSeparator,
152 | }
153 |
--------------------------------------------------------------------------------
/components/dialogs.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useActionState } from "react";
3 | import {
4 | Dialog,
5 | DialogClose,
6 | DialogContent,
7 | DialogDescription,
8 | DialogFooter,
9 | DialogHeader,
10 | DialogTitle,
11 | DialogTrigger,
12 | } from "~/components/ui/dialog";
13 | import { deleteChat, editChat } from "~/lib/server/actions";
14 | import { Input } from "~/components/ui/input";
15 | import { Button } from "~/components/ui/button";
16 | import { useClipBoard } from "~/lib/hooks";
17 | import {
18 | AlertCircle,
19 | Check,
20 | Copy,
21 | Edit3,
22 | Link,
23 | Loader2,
24 | Save,
25 | Share2,
26 | Trash2,
27 | } from "lucide-react";
28 | import AlertMessage from "~/components/auth/alert";
29 | import { Chat } from "~/lib/drizzle";
30 |
31 | interface Props {
32 | chat: Chat;
33 | }
34 |
35 | export function DeleteDialog({ chat }: Props) {
36 | const [state, action, isPending] = useActionState(deleteChat, undefined);
37 |
38 | return (
39 |
40 |
41 |
45 |
46 | Delete
47 |
48 |
49 |
50 |
51 |
52 |
53 | Confirm Deletion
54 |
55 |
56 | Are you sure you want to delete this chat? This action cannot be
57 | undone.
58 |
59 |
60 |
85 |
86 |
87 | );
88 | }
89 |
90 | export function RenameDialog({ chat }: Props) {
91 | const [state, action, isPending] = useActionState(editChat, undefined);
92 |
93 | return (
94 |
95 |
96 |
97 |
98 | Rename
99 |
100 |
101 |
102 |
103 |
104 |
105 | Rename Chat
106 |
107 | Enter a new name for your chat.
108 |
109 |
145 |
146 |
147 | );
148 | }
149 |
150 | export function ShareDialog({ chat }: Props) {
151 | const [isCopied, copyText] = useClipBoard();
152 | const link = `${process.env.NEXT_PUBLIC_BASE_URL}/chat/${chat.id}`;
153 |
154 | return (
155 |
156 |
157 |
158 |
159 | Share
160 |
161 |
162 |
163 |
164 |
165 |
166 | Share Chat
167 |
168 |
169 | Copy the link below to share this chat with others.
170 |
171 |
172 |
173 |
174 | copyText(link)} className="shrink-0">
175 | {isCopied ? (
176 | <>
177 |
178 | Copied!
179 | >
180 | ) : (
181 | <>
182 |
183 | Copy
184 | >
185 | )}
186 |
187 |
188 |
189 |
190 | );
191 | }
192 |
--------------------------------------------------------------------------------
/components/ui/select.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import {
5 | CaretSortIcon,
6 | CheckIcon,
7 | ChevronDownIcon,
8 | ChevronUpIcon,
9 | } from "@radix-ui/react-icons"
10 | import * as SelectPrimitive from "@radix-ui/react-select"
11 |
12 | import { cn } from "~/lib/utils"
13 |
14 | const Select = SelectPrimitive.Root
15 |
16 | const SelectGroup = SelectPrimitive.Group
17 |
18 | const SelectValue = SelectPrimitive.Value
19 |
20 | const SelectTrigger = React.forwardRef<
21 | React.ElementRef,
22 | React.ComponentPropsWithoutRef
23 | >(({ className, children, ...props }, ref) => (
24 | span]:line-clamp-1",
28 | className
29 | )}
30 | {...props}
31 | >
32 | {children}
33 |
34 |
35 |
36 |
37 | ))
38 | SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
39 |
40 | const SelectScrollUpButton = React.forwardRef<
41 | React.ElementRef,
42 | React.ComponentPropsWithoutRef
43 | >(({ className, ...props }, ref) => (
44 |
52 |
53 |
54 | ))
55 | SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
56 |
57 | const SelectScrollDownButton = React.forwardRef<
58 | React.ElementRef,
59 | React.ComponentPropsWithoutRef
60 | >(({ className, ...props }, ref) => (
61 |
69 |
70 |
71 | ))
72 | SelectScrollDownButton.displayName =
73 | SelectPrimitive.ScrollDownButton.displayName
74 |
75 | const SelectContent = React.forwardRef<
76 | React.ElementRef,
77 | React.ComponentPropsWithoutRef
78 | >(({ className, children, position = "popper", ...props }, ref) => (
79 |
80 |
91 |
92 |
99 | {children}
100 |
101 |
102 |
103 |
104 | ))
105 | SelectContent.displayName = SelectPrimitive.Content.displayName
106 |
107 | const SelectLabel = React.forwardRef<
108 | React.ElementRef,
109 | React.ComponentPropsWithoutRef
110 | >(({ className, ...props }, ref) => (
111 |
116 | ))
117 | SelectLabel.displayName = SelectPrimitive.Label.displayName
118 |
119 | const SelectItem = React.forwardRef<
120 | React.ElementRef,
121 | React.ComponentPropsWithoutRef
122 | >(({ className, children, ...props }, ref) => (
123 |
131 |
132 |
133 |
134 |
135 |
136 | {children}
137 |
138 | ))
139 | SelectItem.displayName = SelectPrimitive.Item.displayName
140 |
141 | const SelectSeparator = React.forwardRef<
142 | React.ElementRef,
143 | React.ComponentPropsWithoutRef
144 | >(({ className, ...props }, ref) => (
145 |
150 | ))
151 | SelectSeparator.displayName = SelectPrimitive.Separator.displayName
152 |
153 | export {
154 | Select,
155 | SelectGroup,
156 | SelectValue,
157 | SelectTrigger,
158 | SelectContent,
159 | SelectLabel,
160 | SelectItem,
161 | SelectSeparator,
162 | SelectScrollUpButton,
163 | SelectScrollDownButton,
164 | }
165 |
--------------------------------------------------------------------------------
/components/chat/index.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import React, { useEffect, useMemo, useOptimistic, useState } from "react";
3 | import { ScrollArea } from "~/components/ui/scroll-area";
4 | import { UIMessage, useChat } from "@ai-sdk/react";
5 | import InputField from "~/components/chat/input";
6 | import Messages from "~/components/chat/messages";
7 | import ScrollAnchor from "~/components/chat/scroll-anchor";
8 | import EmptyScreen from "~/components/chat/empty-messages";
9 | import { useLocalStorage, useScroll } from "~/lib/hooks";
10 | import { cn } from "~/lib/utils";
11 | import { useSWRConfig } from "swr";
12 | import { usePathname } from "next/navigation";
13 | import { Button } from "~/components/ui/button";
14 | import Link from "next/link";
15 | import { useIsMobile } from "~/lib/hooks/use-mobile";
16 | import { useSession } from "next-auth/react";
17 | import { Github } from "lucide-react";
18 | import { AutoScroller } from "./auto-scoller";
19 | import { Model, models } from "~/lib/ai/models";
20 | import { DefaultChatTransport, ChatTransport, FileUIPart } from "ai";
21 | import { generateMessageId } from "~/lib/ai/utis";
22 | import cookies from "js-cookie";
23 |
24 | interface ChatProps {
25 | initialMessages: UIMessage[];
26 | chatId: string;
27 | chatTitle?: string;
28 | }
29 | export default function Chat({
30 | chatId,
31 | initialMessages,
32 | chatTitle,
33 | }: ChatProps) {
34 | const [_new, setChatId] = useLocalStorage("chatId", null);
35 | const [input, setInput] = useState("");
36 | const session = useSession();
37 | const isLoggedIn = session.status === "loading" ? true : !!session.data?.user;
38 | const { mutate } = useSWRConfig();
39 | const path = usePathname();
40 | const [selectedModel, setSelectedModel] = useState(() => {
41 | return models.find((model) => model.isDefault) || models[0];
42 | });
43 | const [attachments, setAttachments] = useState>([]);
44 | const [optimisticAttachments, setOptimisticAttachments] =
45 | useOptimistic>(attachments);
46 |
47 | const { messages, status, error, sendMessage, regenerate, stop } = useChat({
48 | messages: initialMessages,
49 | id: chatId,
50 | transport: new DefaultChatTransport({
51 | api: "/api/chat",
52 | }),
53 | generateId: generateMessageId,
54 | onFinish: (data) => {
55 | setChatId(chatId);
56 | mutate("/api/chats");
57 | },
58 | });
59 |
60 | async function handleSubmit(e: React.FormEvent) {
61 | e.preventDefault();
62 | if (!input) return;
63 | sendMessage({ text: input });
64 | setInput("");
65 | }
66 | const loading = ["streaming", "submitted"].includes(status);
67 | const isEmpty = messages.length === 0;
68 | const {
69 | isAtBottom,
70 | scrollToBottom,
71 | messagesRef,
72 | visibilityRef,
73 | handleScroll,
74 | } = useScroll();
75 | const isMobile = useIsMobile();
76 |
77 | useEffect(() => {
78 | cookies.set("model.id", selectedModel.id.toString(), {
79 | expires: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000),
80 | });
81 | }, [selectedModel]);
82 |
83 | return (
84 |
85 | {!isLoggedIn ? (
86 |
87 |
88 | Login
89 |
90 |
91 | Register
92 |
93 |
94 | ) : (
95 | isMobile &&
96 | !isEmpty &&
97 | !path.includes(chatId) && (
98 |
99 |
100 | {chatTitle
101 | ? chatTitle?.length > 35
102 | ? chatTitle?.slice(0, 30) + "..."
103 | : chatTitle
104 | : "Unititled Chat"}
105 |
106 |
107 | New Chat
108 |
109 |
110 | )
111 | )}
112 | {isEmpty ? (
113 |
sendMessage({ text: msg })} />
114 | ) : (
115 | <>
116 |
120 |
124 |
131 |
132 |
133 |
134 |
138 |
139 | >
140 | )}
141 |
142 |
143 |
144 | setInput(e.target.value)}
155 | />
156 |
157 |
158 |
159 |
160 |
165 | view Project On Github
166 |
167 |
168 |
169 |
170 |
171 | );
172 | }
173 |
--------------------------------------------------------------------------------
/components/chat/input.tsx:
--------------------------------------------------------------------------------
1 | import { Button } from "~/components/ui/button";
2 | import Textarea from "react-textarea-autosize";
3 | import {
4 | CloudUpload,
5 | MoveUp,
6 | Paperclip,
7 | Send,
8 | TriangleAlert,
9 | } from "lucide-react";
10 | import React, { ChangeEvent, useRef, useTransition } from "react";
11 | import { cn, sleep } from "~/lib/utils";
12 | import { LoadingButton } from "~/components/ai/spinner-message";
13 | import AttachmentPreview, {
14 | Loading,
15 | } from "~/components/chat/attachment-preview";
16 | import { FileUIPart } from "ai";
17 | import { useUploadThing } from "~/lib/uploadthing";
18 | import { toast } from "sonner";
19 | import { deleteAttachment } from "~/lib/server/actions";
20 | import { Separator } from "../ui/separator";
21 | import { ModelSelector } from "./model-select";
22 | import { Model } from "~/lib/ai/models";
23 |
24 | interface InputFieldProps {
25 | handleSubmit: (e: React.FormEvent) => void;
26 | handleChange: (e: ChangeEvent) => void;
27 | input: string;
28 | isLoading: boolean;
29 | stop: () => void;
30 | setAttachments: React.Dispatch>;
31 | setOPtimisticAttachments: React.Dispatch>;
32 | optimisticAttachments: Array;
33 | selectedModel: Model;
34 | setSelectedModel: React.Dispatch>;
35 | }
36 | function InputField({
37 | handleChange,
38 | handleSubmit,
39 | input,
40 | isLoading,
41 | stop,
42 | setAttachments,
43 | setOPtimisticAttachments,
44 | optimisticAttachments,
45 | selectedModel,
46 | setSelectedModel,
47 | }: InputFieldProps) {
48 | const inputRef = useRef(null);
49 | const attachementRef = useRef(null);
50 | const [isPending, startTransition] = useTransition();
51 | function onKeyDown(e: React.KeyboardEvent) {
52 | if (e.key === "Enter" && !e.shiftKey && !e.nativeEvent.isComposing) {
53 | e.currentTarget.form?.requestSubmit();
54 | e.preventDefault();
55 | }
56 | }
57 |
58 | const { startUpload } = useUploadThing("imageUploader", {
59 | onUploadError: (error) => {
60 | toast.error("Error", {
61 | description: "Attachment upload failed",
62 | icon: ,
63 | position: "top-center",
64 | action: {
65 | label: "Retry",
66 | onClick: () => handleOnClick(),
67 | },
68 | });
69 | },
70 | onClientUploadComplete: (files) => {
71 | files.forEach((file) => {
72 | setAttachments((prev) => [
73 | ...prev,
74 | {
75 | url: file.ufsUrl,
76 | contentType: file.type,
77 | name: file.name,
78 | type: "file",
79 | mediaType: file.type,
80 | },
81 | ]);
82 | });
83 | },
84 | });
85 | async function removeAttachement(key: string | undefined) {
86 | if (!key) return;
87 | const deleted = await deleteAttachment(key);
88 | if (!deleted) return;
89 | setAttachments((current) => {
90 | return current.filter((a) => a.filename != key);
91 | });
92 | }
93 | function handleOnClick() {
94 | if (!attachementRef.current) return;
95 | attachementRef.current?.click();
96 | }
97 | async function handleFileChange(e: ChangeEvent) {
98 | const files = Array.from(e.target.files || []);
99 | if (!files) return;
100 | startTransition(async () => {
101 | files.forEach((file) => {
102 | setOPtimisticAttachments((prev) => [
103 | ...prev,
104 | {
105 | name: file.name,
106 | contentType: file.type,
107 | url: URL.createObjectURL(file),
108 | isUploading: true,
109 | key: file.name,
110 | type: "file",
111 | mediaType: file.type,
112 | },
113 | ]);
114 | });
115 | await sleep(2000);
116 | await startUpload(files);
117 | });
118 | setAttachments([]);
119 | }
120 |
121 | return (
122 |
216 | );
217 | }
218 |
219 | export default InputField;
220 |
--------------------------------------------------------------------------------
/lib/ai/models.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Gemini,
3 | DeepSeek,
4 | Qwen,
5 | Kimi,
6 | Meta,
7 | OpenAI,
8 | Anthropic,
9 | XAIGrok,
10 | Google,
11 | Groq,
12 | OpenRouter,
13 | Vercel,
14 | } from "~/components/ui/icons";
15 | import { google } from "@ai-sdk/google";
16 | import { groq } from "@ai-sdk/groq";
17 | import { openrouter } from "@openrouter/ai-sdk-provider";
18 | import {
19 | LanguageModel,
20 | extractReasoningMiddleware,
21 | wrapLanguageModel,
22 | } from "ai";
23 | import React from "react";
24 |
25 | export interface Model {
26 | id: string;
27 | name: string;
28 | icon: React.ComponentType<{ size: number; className?: string }>;
29 | model: LanguageModel;
30 | isDefault?: boolean;
31 | provider: {
32 | name: string;
33 | icon: React.ComponentType<{ size: number; className?: string }>;
34 | };
35 | }
36 |
37 | function withMiddleware>(model: T) {
38 | return wrapLanguageModel({
39 | model: model,
40 | middleware: [extractReasoningMiddleware({ tagName: "think" })],
41 | });
42 | }
43 |
44 | export const models: Model[] = [
45 | {
46 | id: "DeepSeek: DeepSeek V3",
47 | name: "DeepSeek V3",
48 | model: withMiddleware(openrouter.chat("DeepSeek: DeepSeek V3")),
49 | icon: DeepSeek,
50 | provider: {
51 | name: "OpenRouter",
52 | icon: OpenRouter,
53 | },
54 | },
55 | {
56 | id: "deepseek/deepseek-r1:free",
57 | name: "DeepSeek R1",
58 | model: withMiddleware(openrouter.chat("deepseek/deepseek-r1:free")),
59 | icon: DeepSeek,
60 | provider: {
61 | name: "OpenRouter",
62 | icon: OpenRouter,
63 | },
64 | },
65 | {
66 | id: "deepseek/deepseek-r1-distill-llama-70b:free",
67 | name: "DeepSeek R1 Llama Distilled",
68 | model: withMiddleware(
69 | openrouter.chat("deepseek/deepseek-r1-distill-llama-70b:free"),
70 | ),
71 | icon: DeepSeek,
72 | provider: {
73 | name: "OpenRouter",
74 | icon: OpenRouter,
75 | },
76 | },
77 | {
78 | id: "deepseek/deepseek-r1-distill-qwen-14b:free",
79 | name: "DeepSeek R1 Qwen Distilled",
80 | model: withMiddleware(
81 | openrouter.chat("deepseek/deepseek-r1-distill-qwen-14b:free"),
82 | ),
83 | icon: DeepSeek,
84 | provider: {
85 | name: "OpenRouter",
86 | icon: OpenRouter,
87 | },
88 | },
89 | {
90 | id: "qwen/qwen3-4b:free",
91 | name: "Qwen 2.5 32B",
92 | model: withMiddleware(openrouter.chat("qwen/qwen3-4b:free")),
93 | icon: Qwen,
94 | provider: {
95 | name: "OpenRouter",
96 | icon: OpenRouter,
97 | },
98 | },
99 | {
100 | id: "moonshotai/kimi-k2:free",
101 | name: "Kimi K2",
102 | model: withMiddleware(openrouter.chat("moonshotai/kimi-k2:free")),
103 | icon: Kimi,
104 | provider: {
105 | name: "OpenRouter",
106 | icon: OpenRouter,
107 | },
108 | },
109 | {
110 | id: "meta-llama/llama-3.1-405b-instruct:free",
111 | name: "Llama 3.1 405B",
112 | model: withMiddleware(
113 | openrouter.chat("meta-llama/llama-3.1-405b-instruct:free"),
114 | ),
115 | icon: Meta,
116 | provider: {
117 | name: "OpenRouter",
118 | icon: OpenRouter,
119 | },
120 | },
121 | {
122 | id: "x-ai/grok-4-fast:free",
123 | icon: XAIGrok,
124 | model: openrouter.chat("x-ai/grok-4-fast:free"),
125 | name: "Grok 4 Fast Non-Reasoning",
126 | isDefault: true,
127 | provider: {
128 | name: "OpenRouter",
129 | icon: OpenRouter,
130 | },
131 | },
132 | {
133 | id: "openai/gpt-oss-20b:free",
134 | name: "GPT OSS 20B 128k",
135 | model: openrouter.chat("openai/gpt-oss-20b:free"),
136 | icon: OpenAI,
137 | provider: {
138 | name: "OpenRouter",
139 | icon: OpenRouter,
140 | },
141 | },
142 | {
143 | id: "openai/gpt-oss-120b:free",
144 | name: "GPT OSS 120B 128k",
145 | model: openrouter.chat("openai/gpt-oss-120b:free"),
146 | icon: OpenAI,
147 | provider: {
148 | name: "OpenRouter",
149 | icon: OpenRouter,
150 | },
151 | },
152 | {
153 | id: "anthropic/claude-sonnet-4",
154 | icon: Anthropic,
155 | model: "anthropic/claude-sonnet-4",
156 | name: "Claude Sonnet 4",
157 | provider: {
158 | name: "Vercel",
159 | icon: Vercel,
160 | },
161 | },
162 | {
163 | id: "anthropic/claude-3.5-sonnet",
164 | icon: Anthropic,
165 | model: "anthropic/claude-3.5-sonnet",
166 | name: "Claude 3.5 Sonnet",
167 | provider: {
168 | name: "Vercel",
169 | icon: Vercel,
170 | },
171 | },
172 | {
173 | id: "anthropic/claude-3.7-sonnet",
174 | icon: Anthropic,
175 | model: "anthropic/claude-3.7-sonnet",
176 | name: "Claude 3.7 Sonnet",
177 | provider: {
178 | name: "Vercel",
179 | icon: Vercel,
180 | },
181 | },
182 | {
183 | id: "anthropic/claude-3.5-haiku",
184 | icon: Anthropic,
185 | model: "anthropic/claude-3.5-haiku",
186 | name: "Claude 3.5 haiku",
187 | provider: {
188 | name: "Vercel",
189 | icon: Vercel,
190 | },
191 | },
192 | {
193 | id: "openai/gpt-5",
194 | icon: OpenAI,
195 | model: "openai/gpt-5",
196 | name: "GPT-5",
197 | provider: {
198 | name: "Vercel",
199 | icon: Vercel,
200 | },
201 | },
202 | {
203 | id: "google/gemini-2.5-pro",
204 | icon: Google,
205 | model: "google/gemini-2.5-pro",
206 | name: "GEMINI 2.5 pro",
207 | provider: {
208 | name: "Vercel",
209 | icon: Vercel,
210 | },
211 | },
212 | {
213 | id: "xai/grok-4-fast-non-reasoning",
214 | icon: XAIGrok,
215 | model: "xai/grok-4-fast-non-reasoning",
216 | name: "Grok 4 Fast Non-Reasoning",
217 | provider: {
218 | name: "Vercel",
219 | icon: Vercel,
220 | },
221 | },
222 | {
223 | id: "xai/grok-4-fast-reasoning",
224 | icon: XAIGrok,
225 | model: "xai/grok-4-fast-reasoning",
226 | name: "Grok 4 Fast Reasoning",
227 | provider: {
228 | name: "Vercel",
229 | icon: Vercel,
230 | },
231 | },
232 | {
233 | id: "openai/gpt-4o",
234 | icon: OpenAI,
235 | model: "openai/gpt-4o",
236 | name: "GPT-4o",
237 | provider: {
238 | name: "Vercel",
239 | icon: Vercel,
240 | },
241 | },
242 | {
243 | id: "meta-llama/llama-4-scout-17b-16e-instruct",
244 | name: "Llama 4 Maverick 17B",
245 | model: withMiddleware(groq("meta-llama/llama-4-scout-17b-16e-instruct")),
246 | icon: Meta,
247 | provider: {
248 | name: "Groq",
249 | icon: Groq,
250 | },
251 | },
252 | {
253 | id: "deepseek-r1-distill-llama-70b",
254 | name: "DeepSeek R1 Llama Distilled",
255 | model: withMiddleware(groq("deepseek-r1-distill-llama-70b")),
256 | icon: DeepSeek,
257 | provider: {
258 | name: "Groq",
259 | icon: Groq,
260 | },
261 | },
262 | {
263 | id: "deepseek-r1-distill-qwen-14b",
264 | name: "DeepSeek R1 Qwen Distilled",
265 | model: withMiddleware(groq("deepseek-r1-distill-qwen-14b")),
266 | icon: DeepSeek,
267 | provider: {
268 | name: "Groq",
269 | icon: Groq,
270 | },
271 | },
272 | {
273 | id: "openai/gpt-oss-20b",
274 | name: "GPT OSS 20B 128k",
275 | model: withMiddleware(groq("openai/gpt-oss-20b")),
276 | icon: OpenAI,
277 | provider: {
278 | name: "Groq",
279 | icon: Groq,
280 | },
281 | },
282 | {
283 | id: "openai/gpt-oss-120b",
284 | name: "GPT OSS 120B 128k",
285 | model: withMiddleware(groq("openai/gpt-oss-120b")),
286 | icon: OpenAI,
287 | provider: {
288 | name: "Groq",
289 | icon: Groq,
290 | },
291 | },
292 | {
293 | id: "gemini-1.5-flash",
294 | name: "Gemini 1.5 Flash",
295 | model: withMiddleware(google("gemini-1.5-flash")),
296 | icon: Gemini,
297 | provider: {
298 | name: "Google",
299 | icon: Google,
300 | },
301 | },
302 | ];
303 |
--------------------------------------------------------------------------------
/components/ui/dropdown-menu.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
5 | import {
6 | CheckIcon,
7 | ChevronRightIcon,
8 | DotFilledIcon,
9 | } from "@radix-ui/react-icons"
10 |
11 | import { cn } from "~/lib/utils"
12 |
13 | const DropdownMenu = DropdownMenuPrimitive.Root
14 |
15 | const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
16 |
17 | const DropdownMenuGroup = DropdownMenuPrimitive.Group
18 |
19 | const DropdownMenuPortal = DropdownMenuPrimitive.Portal
20 |
21 | const DropdownMenuSub = DropdownMenuPrimitive.Sub
22 |
23 | const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
24 |
25 | const DropdownMenuSubTrigger = React.forwardRef<
26 | React.ElementRef,
27 | React.ComponentPropsWithoutRef & {
28 | inset?: boolean
29 | }
30 | >(({ className, inset, children, ...props }, ref) => (
31 |
40 | {children}
41 |
42 |
43 | ))
44 | DropdownMenuSubTrigger.displayName =
45 | DropdownMenuPrimitive.SubTrigger.displayName
46 |
47 | const DropdownMenuSubContent = React.forwardRef<
48 | React.ElementRef,
49 | React.ComponentPropsWithoutRef
50 | >(({ className, ...props }, ref) => (
51 |
59 | ))
60 | DropdownMenuSubContent.displayName =
61 | DropdownMenuPrimitive.SubContent.displayName
62 |
63 | const DropdownMenuContent = React.forwardRef<
64 | React.ElementRef,
65 | React.ComponentPropsWithoutRef
66 | >(({ className, sideOffset = 4, ...props }, ref) => (
67 |
68 |
78 |
79 | ))
80 | DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
81 |
82 | const DropdownMenuItem = React.forwardRef<
83 | React.ElementRef,
84 | React.ComponentPropsWithoutRef & {
85 | inset?: boolean
86 | }
87 | >(({ className, inset, ...props }, ref) => (
88 |
97 | ))
98 | DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
99 |
100 | const DropdownMenuCheckboxItem = React.forwardRef<
101 | React.ElementRef,
102 | React.ComponentPropsWithoutRef
103 | >(({ className, children, checked, ...props }, ref) => (
104 |
113 |
114 |
115 |
116 |
117 |
118 | {children}
119 |
120 | ))
121 | DropdownMenuCheckboxItem.displayName =
122 | DropdownMenuPrimitive.CheckboxItem.displayName
123 |
124 | const DropdownMenuRadioItem = React.forwardRef<
125 | React.ElementRef,
126 | React.ComponentPropsWithoutRef
127 | >(({ className, children, ...props }, ref) => (
128 |
136 |
137 |
138 |
139 |
140 |
141 | {children}
142 |
143 | ))
144 | DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
145 |
146 | const DropdownMenuLabel = React.forwardRef<
147 | React.ElementRef,
148 | React.ComponentPropsWithoutRef & {
149 | inset?: boolean
150 | }
151 | >(({ className, inset, ...props }, ref) => (
152 |
161 | ))
162 | DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
163 |
164 | const DropdownMenuSeparator = React.forwardRef<
165 | React.ElementRef,
166 | React.ComponentPropsWithoutRef
167 | >(({ className, ...props }, ref) => (
168 |
173 | ))
174 | DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
175 |
176 | const DropdownMenuShortcut = ({
177 | className,
178 | ...props
179 | }: React.HTMLAttributes) => {
180 | return (
181 |
185 | )
186 | }
187 | DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
188 |
189 | export {
190 | DropdownMenu,
191 | DropdownMenuTrigger,
192 | DropdownMenuContent,
193 | DropdownMenuItem,
194 | DropdownMenuCheckboxItem,
195 | DropdownMenuRadioItem,
196 | DropdownMenuLabel,
197 | DropdownMenuSeparator,
198 | DropdownMenuShortcut,
199 | DropdownMenuGroup,
200 | DropdownMenuPortal,
201 | DropdownMenuSub,
202 | DropdownMenuSubContent,
203 | DropdownMenuSubTrigger,
204 | DropdownMenuRadioGroup,
205 | }
206 |
--------------------------------------------------------------------------------