MingruiZhang commited on
Commit
052672d
β€’
1 Parent(s): 41adc65

clean up code

Browse files
This view is limited to 50 files because it contains too many changes. Β  See raw diff
Files changed (50) hide show
  1. app/(chat)/chat/[id]/page.tsx +0 -47
  2. app/(chat)/layout.tsx +9 -11
  3. app/(chat)/page.tsx +2 -2
  4. app/actions.ts +0 -129
  5. app/layout.tsx +3 -3
  6. app/opengraph-image.png +0 -0
  7. app/sign-in/page.tsx +2 -2
  8. app/twitter-image.png +0 -0
  9. components/{login-button.tsx β†’ LoginButton.tsx} +2 -2
  10. components/TailwindIndicator.tsx +14 -0
  11. components/{theme-toggle.tsx β†’ ThemeToggle.tsx} +2 -2
  12. components/{user-menu.tsx β†’ UserMenu.tsx} +3 -3
  13. components/button-scroll-to-bottom.tsx +0 -34
  14. components/chat-history.tsx +0 -46
  15. components/chat-scroll-anchor.tsx +0 -29
  16. components/chat-share-dialog.tsx +0 -106
  17. components/chat/ButtonScrollToBottom.tsx +34 -0
  18. components/chat/ChatList.tsx +2 -2
  19. components/{chat-message.tsx β†’ chat/ChatMessage.tsx} +5 -5
  20. components/{chat-message-actions.tsx β†’ chat/ChatMessageActions.tsx} +4 -4
  21. components/{chat-panel.tsx β†’ chat/ChatPanel.tsx} +5 -29
  22. components/chat/ChatScrollAnchor.tsx +29 -0
  23. components/{empty-screen.tsx β†’ chat/EmptyScreen.tsx} +2 -2
  24. components/chat/MemoizedReactMarkdown.tsx +9 -0
  25. components/{prompt-form.tsx β†’ chat/PromptForm.tsx} +4 -4
  26. components/chat/index.tsx +2 -2
  27. components/clear-history.tsx +0 -77
  28. components/external-link.tsx +0 -29
  29. components/header.tsx +27 -66
  30. components/markdown.tsx +0 -9
  31. components/providers.tsx +10 -13
  32. components/sidebar-actions.tsx +0 -125
  33. components/sidebar-desktop.tsx +0 -21
  34. components/sidebar-footer.tsx +0 -16
  35. components/sidebar-item.tsx +0 -124
  36. components/sidebar-items.tsx +0 -42
  37. components/sidebar-list.tsx +0 -38
  38. components/sidebar-mobile.tsx +0 -28
  39. components/sidebar-toggle.tsx +0 -24
  40. components/sidebar.tsx +0 -21
  41. components/tailwind-indicator.tsx +0 -14
  42. components/ui/DropdownMenu.tsx +128 -0
  43. components/ui/alert-dialog.tsx +0 -141
  44. components/ui/badge.tsx +0 -36
  45. components/ui/button.tsx +48 -48
  46. components/ui/codeblock.tsx +125 -125
  47. components/ui/dialog.tsx +0 -122
  48. components/ui/dropdown-menu.tsx +0 -128
  49. components/ui/input.tsx +19 -19
  50. components/ui/label.tsx +0 -26
app/(chat)/chat/[id]/page.tsx DELETED
@@ -1,47 +0,0 @@
1
- import { type Metadata } from 'next';
2
- import { notFound, redirect } from 'next/navigation';
3
-
4
- import { auth } from '@/auth';
5
- import { getChat } from '@/app/actions';
6
- import { Chat } from '@/components/chat';
7
-
8
- export interface ChatPageProps {
9
- params: {
10
- id: string;
11
- };
12
- }
13
-
14
- export async function generateMetadata({
15
- params,
16
- }: ChatPageProps): Promise<Metadata> {
17
- const session = await auth();
18
-
19
- if (!session?.user) {
20
- return {};
21
- }
22
-
23
- const chat = await getChat(params.id, session.user.id);
24
- return {
25
- title: chat?.title.toString().slice(0, 50) ?? 'Chat',
26
- };
27
- }
28
-
29
- export default async function ChatPage({ params }: ChatPageProps) {
30
- const session = await auth();
31
-
32
- if (!session?.user) {
33
- redirect(`/sign-in?next=/chat/${params.id}`);
34
- }
35
-
36
- const chat = await getChat(params.id, session.user.id);
37
-
38
- if (!chat) {
39
- notFound();
40
- }
41
-
42
- if (chat?.userId !== session?.user?.id) {
43
- notFound();
44
- }
45
-
46
- return <Chat id={chat.id} />;
47
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/(chat)/layout.tsx CHANGED
@@ -1,16 +1,14 @@
1
- import { SidebarDesktop } from '@/components/sidebar-desktop'
2
-
3
  interface ChatLayoutProps {
4
- children: React.ReactNode
5
  }
6
 
7
  export default async function ChatLayout({ children }: ChatLayoutProps) {
8
- return (
9
- <div className="relative flex h-[calc(100vh_-_theme(spacing.16))] overflow-hidden">
10
- <SidebarDesktop />
11
- <div className="group w-full overflow-auto pl-0 animate-in duration-300 ease-in-out peer-[[data-state=open]]:lg:pl-[250px] peer-[[data-state=open]]:xl:pl-[300px]">
12
- {children}
13
- </div>
14
- </div>
15
- )
16
  }
 
 
 
1
  interface ChatLayoutProps {
2
+ children: React.ReactNode;
3
  }
4
 
5
  export default async function ChatLayout({ children }: ChatLayoutProps) {
6
+ return (
7
+ <div className="relative flex h-[calc(100vh_-_theme(spacing.16))] overflow-hidden">
8
+ {/* <SidebarDesktop /> */}
9
+ <div className="group w-full overflow-auto pl-0 animate-in duration-300 ease-in-out peer-[[data-state=open]]:lg:pl-[250px] peer-[[data-state=open]]:xl:pl-[300px]">
10
+ {children}
11
+ </div>
12
+ </div>
13
+ );
14
  }
app/(chat)/page.tsx CHANGED
@@ -2,10 +2,10 @@
2
 
3
  import { nanoid } from '@/lib/utils';
4
  import { Chat } from '@/components/chat';
5
- import { ThemeToggle } from '../../components/theme-toggle';
6
  import { useAtomValue } from 'jotai';
7
  import { datasetAtom } from '../../state';
8
- import { EmptyScreen } from '../../components/empty-screen';
9
 
10
  export default function IndexPage() {
11
  const id = nanoid();
 
2
 
3
  import { nanoid } from '@/lib/utils';
4
  import { Chat } from '@/components/chat';
5
+ import { ThemeToggle } from '../../components/ThemeToggle';
6
  import { useAtomValue } from 'jotai';
7
  import { datasetAtom } from '../../state';
8
+ import { EmptyScreen } from '../../components/chat/EmptyScreen';
9
 
10
  export default function IndexPage() {
11
  const id = nanoid();
app/actions.ts DELETED
@@ -1,129 +0,0 @@
1
- 'use server'
2
-
3
- import { revalidatePath } from 'next/cache'
4
- import { redirect } from 'next/navigation'
5
- import { kv } from '@vercel/kv'
6
-
7
- import { auth } from '@/auth'
8
- import { type Chat } from '@/lib/types'
9
-
10
- export async function getChats(userId?: string | null) {
11
- if (!userId) {
12
- return []
13
- }
14
-
15
- try {
16
- const pipeline = kv.pipeline()
17
- const chats: string[] = await kv.zrange(`user:chat:${userId}`, 0, -1, {
18
- rev: true,
19
- })
20
-
21
- for (const chat of chats) {
22
- pipeline.hgetall(chat)
23
- }
24
-
25
- const results = await pipeline.exec()
26
-
27
- return results as Chat[]
28
- } catch (error) {
29
- return []
30
- }
31
- }
32
-
33
- export async function getChat(id: string, userId: string) {
34
- const chat = await kv.hgetall<Chat>(`chat:${id}`)
35
-
36
- if (!chat || (userId && chat.userId !== userId)) {
37
- return null
38
- }
39
-
40
- return chat
41
- }
42
-
43
- export async function removeChat({ id, path }: { id: string; path: string }) {
44
- const session = await auth()
45
-
46
- if (!session) {
47
- return {
48
- error: 'Unauthorized',
49
- }
50
- }
51
-
52
- //Convert uid to string for consistent comparison with session.user.id
53
- const uid = String(await kv.hget(`chat:${id}`, 'userId'))
54
-
55
- if (uid !== session?.user?.id) {
56
- return {
57
- error: 'Unauthorized',
58
- }
59
- }
60
-
61
- await kv.del(`chat:${id}`)
62
- await kv.zrem(`user:chat:${session.user.id}`, `chat:${id}`)
63
-
64
- revalidatePath('/')
65
- return revalidatePath(path)
66
- }
67
-
68
- export async function clearChats() {
69
- const session = await auth()
70
-
71
- if (!session?.user?.id) {
72
- return {
73
- error: 'Unauthorized',
74
- }
75
- }
76
-
77
- const chats: string[] = await kv.zrange(`user:chat:${session.user.id}`, 0, -1)
78
- if (!chats.length) {
79
- return redirect('/')
80
- }
81
- const pipeline = kv.pipeline()
82
-
83
- for (const chat of chats) {
84
- pipeline.del(chat)
85
- pipeline.zrem(`user:chat:${session.user.id}`, chat)
86
- }
87
-
88
- await pipeline.exec()
89
-
90
- revalidatePath('/')
91
- return redirect('/')
92
- }
93
-
94
- export async function getSharedChat(id: string) {
95
- const chat = await kv.hgetall<Chat>(`chat:${id}`)
96
-
97
- if (!chat || !chat.sharePath) {
98
- return null
99
- }
100
-
101
- return chat
102
- }
103
-
104
- export async function shareChat(id: string) {
105
- const session = await auth()
106
-
107
- if (!session?.user?.id) {
108
- return {
109
- error: 'Unauthorized',
110
- }
111
- }
112
-
113
- const chat = await kv.hgetall<Chat>(`chat:${id}`)
114
-
115
- if (!chat || chat.userId !== session.user.id) {
116
- return {
117
- error: 'Something went wrong',
118
- }
119
- }
120
-
121
- const payload = {
122
- ...chat,
123
- sharePath: `/share/${chat.id}`,
124
- }
125
-
126
- await kv.hmset(`chat:${chat.id}`, payload)
127
-
128
- return payload
129
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/layout.tsx CHANGED
@@ -4,9 +4,9 @@ import { GeistMono } from 'geist/font/mono';
4
 
5
  import '@/app/globals.css';
6
  import { cn } from '@/lib/utils';
7
- import { TailwindIndicator } from '@/components/tailwind-indicator';
8
- import { Providers } from '@/components/providers';
9
- import { Header } from '@/components/header';
10
 
11
  export const metadata = {
12
  metadataBase: new URL(`https://${process.env.VERCEL_URL}`),
 
4
 
5
  import '@/app/globals.css';
6
  import { cn } from '@/lib/utils';
7
+ import { TailwindIndicator } from '@/components/TailwindIndicator';
8
+ import { Providers } from '@/components/Providers';
9
+ import { Header } from '@/components/Header';
10
 
11
  export const metadata = {
12
  metadataBase: new URL(`https://${process.env.VERCEL_URL}`),
app/opengraph-image.png DELETED
Binary file (434 kB)
 
app/sign-in/page.tsx CHANGED
@@ -1,7 +1,7 @@
1
  import { auth } from '@/auth';
2
- import { LoginButton } from '@/components/login-button';
3
  import { redirect } from 'next/navigation';
4
- import { ThemeToggle } from '../../components/theme-toggle';
5
 
6
  export default async function SignInPage() {
7
  const session = await auth();
 
1
  import { auth } from '@/auth';
2
+ import { LoginButton } from '@/components/LoginButton';
3
  import { redirect } from 'next/navigation';
4
+ import { ThemeToggle } from '../../components/ThemeToggle';
5
 
6
  export default async function SignInPage() {
7
  const session = await auth();
app/twitter-image.png DELETED
Binary file (434 kB)
 
components/{login-button.tsx β†’ LoginButton.tsx} RENAMED
@@ -4,8 +4,8 @@ import * as React from 'react';
4
  import { signIn } from 'next-auth/react';
5
 
6
  import { cn } from '@/lib/utils';
7
- import { Button, type ButtonProps } from '@/components/ui/button';
8
- import { IconGitHub, IconSpinner, IconGoogle } from '@/components/ui/icons';
9
 
10
  interface LoginButtonProps extends ButtonProps {
11
  oauth: 'github' | 'google';
 
4
  import { signIn } from 'next-auth/react';
5
 
6
  import { cn } from '@/lib/utils';
7
+ import { Button, type ButtonProps } from '@/components/ui/Button';
8
+ import { IconGitHub, IconSpinner, IconGoogle } from '@/components/ui/Icons';
9
 
10
  interface LoginButtonProps extends ButtonProps {
11
  oauth: 'github' | 'google';
components/TailwindIndicator.tsx ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export function TailwindIndicator() {
2
+ if (process.env.NODE_ENV === 'production') return null;
3
+
4
+ return (
5
+ <div className="fixed bottom-1 left-1 z-50 flex size-6 items-center justify-center rounded-full bg-gray-800 p-3 font-mono text-xs text-white">
6
+ <div className="block sm:hidden">xs</div>
7
+ <div className="hidden sm:block md:hidden">sm</div>
8
+ <div className="hidden md:block lg:hidden">md</div>
9
+ <div className="hidden lg:block xl:hidden">lg</div>
10
+ <div className="hidden xl:block 2xl:hidden">xl</div>
11
+ <div className="hidden 2xl:block">2xl</div>
12
+ </div>
13
+ );
14
+ }
components/{theme-toggle.tsx β†’ ThemeToggle.tsx} RENAMED
@@ -3,8 +3,8 @@
3
  import * as React from 'react';
4
  import { useTheme } from 'next-themes';
5
 
6
- import { Button } from '@/components/ui/button';
7
- import { IconMoon, IconSun } from '@/components/ui/icons';
8
 
9
  export function ThemeToggle() {
10
  const { setTheme, theme } = useTheme();
 
3
  import * as React from 'react';
4
  import { useTheme } from 'next-themes';
5
 
6
+ import { Button } from '@/components/ui/Button';
7
+ import { IconMoon, IconSun } from '@/components/ui/Icons';
8
 
9
  export function ThemeToggle() {
10
  const { setTheme, theme } = useTheme();
components/{user-menu.tsx β†’ UserMenu.tsx} RENAMED
@@ -4,15 +4,15 @@ import Image from 'next/image';
4
  import { type Session } from 'next-auth';
5
  import { signOut } from 'next-auth/react';
6
 
7
- import { Button } from '@/components/ui/button';
8
  import {
9
  DropdownMenu,
10
  DropdownMenuContent,
11
  DropdownMenuItem,
12
  DropdownMenuSeparator,
13
  DropdownMenuTrigger,
14
- } from '@/components/ui/dropdown-menu';
15
- import { IconExternalLink } from '@/components/ui/icons';
16
 
17
  export interface UserMenuProps {
18
  user: Session['user'];
 
4
  import { type Session } from 'next-auth';
5
  import { signOut } from 'next-auth/react';
6
 
7
+ import { Button } from '@/components/ui/Button';
8
  import {
9
  DropdownMenu,
10
  DropdownMenuContent,
11
  DropdownMenuItem,
12
  DropdownMenuSeparator,
13
  DropdownMenuTrigger,
14
+ } from '@/components/ui/DropdownMenu';
15
+ import { IconExternalLink } from '@/components/ui/Icons';
16
 
17
  export interface UserMenuProps {
18
  user: Session['user'];
components/button-scroll-to-bottom.tsx DELETED
@@ -1,34 +0,0 @@
1
- 'use client'
2
-
3
- import * as React from 'react'
4
-
5
- import { cn } from '@/lib/utils'
6
- import { useAtBottom } from '@/lib/hooks/use-at-bottom'
7
- import { Button, type ButtonProps } from '@/components/ui/button'
8
- import { IconArrowDown } from '@/components/ui/icons'
9
-
10
- export function ButtonScrollToBottom({ className, ...props }: ButtonProps) {
11
- const isAtBottom = useAtBottom()
12
-
13
- return (
14
- <Button
15
- variant="outline"
16
- size="icon"
17
- className={cn(
18
- 'absolute right-4 top-1 z-10 bg-background transition-opacity duration-300 sm:right-8 md:top-2',
19
- isAtBottom ? 'opacity-0' : 'opacity-100',
20
- className
21
- )}
22
- onClick={() =>
23
- window.scrollTo({
24
- top: document.body.offsetHeight,
25
- behavior: 'smooth'
26
- })
27
- }
28
- {...props}
29
- >
30
- <IconArrowDown />
31
- <span className="sr-only">Scroll to bottom</span>
32
- </Button>
33
- )
34
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
components/chat-history.tsx DELETED
@@ -1,46 +0,0 @@
1
- import * as React from 'react'
2
-
3
- import Link from 'next/link'
4
-
5
- import { cn } from '@/lib/utils'
6
- import { SidebarList } from '@/components/sidebar-list'
7
- import { buttonVariants } from '@/components/ui/button'
8
- import { IconPlus } from '@/components/ui/icons'
9
-
10
- interface ChatHistoryProps {
11
- userId?: string
12
- }
13
-
14
- export async function ChatHistory({ userId }: ChatHistoryProps) {
15
- return (
16
- <div className="flex flex-col h-full">
17
- <div className="px-2 my-4">
18
- <Link
19
- href="/"
20
- className={cn(
21
- buttonVariants({ variant: 'outline' }),
22
- 'h-10 w-full justify-start bg-zinc-50 px-4 shadow-none transition-colors hover:bg-zinc-200/40 dark:bg-zinc-900 dark:hover:bg-zinc-300/10'
23
- )}
24
- >
25
- <IconPlus className="-translate-x-2 stroke-2" />
26
- New Chat
27
- </Link>
28
- </div>
29
- <React.Suspense
30
- fallback={
31
- <div className="flex flex-col flex-1 px-4 space-y-4 overflow-auto">
32
- {Array.from({ length: 10 }).map((_, i) => (
33
- <div
34
- key={i}
35
- className="w-full h-6 rounded-md shrink-0 animate-pulse bg-zinc-200 dark:bg-zinc-800"
36
- />
37
- ))}
38
- </div>
39
- }
40
- >
41
- {/* @ts-ignore */}
42
- <SidebarList userId={userId} />
43
- </React.Suspense>
44
- </div>
45
- )
46
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
components/chat-scroll-anchor.tsx DELETED
@@ -1,29 +0,0 @@
1
- 'use client'
2
-
3
- import * as React from 'react'
4
- import { useInView } from 'react-intersection-observer'
5
-
6
- import { useAtBottom } from '@/lib/hooks/use-at-bottom'
7
-
8
- interface ChatScrollAnchorProps {
9
- trackVisibility?: boolean
10
- }
11
-
12
- export function ChatScrollAnchor({ trackVisibility }: ChatScrollAnchorProps) {
13
- const isAtBottom = useAtBottom()
14
- const { ref, entry, inView } = useInView({
15
- trackVisibility,
16
- delay: 100,
17
- rootMargin: '0px 0px -150px 0px'
18
- })
19
-
20
- React.useEffect(() => {
21
- if (isAtBottom && trackVisibility && !inView) {
22
- entry?.target.scrollIntoView({
23
- block: 'start'
24
- })
25
- }
26
- }, [inView, entry, isAtBottom, trackVisibility])
27
-
28
- return <div ref={ref} className="h-px w-full" />
29
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
components/chat-share-dialog.tsx DELETED
@@ -1,106 +0,0 @@
1
- 'use client';
2
-
3
- import * as React from 'react';
4
- import { type DialogProps } from '@radix-ui/react-dialog';
5
- import { toast } from 'react-hot-toast';
6
-
7
- import { ServerActionResult, type Chat } from '@/lib/types';
8
- import { Button } from '@/components/ui/button';
9
- import {
10
- Dialog,
11
- DialogContent,
12
- DialogDescription,
13
- DialogFooter,
14
- DialogHeader,
15
- DialogTitle,
16
- } from '@/components/ui/dialog';
17
- import { IconSpinner } from '@/components/ui/icons';
18
- import { useCopyToClipboard } from '@/lib/hooks/use-copy-to-clipboard';
19
-
20
- interface ChatShareDialogProps extends DialogProps {
21
- chat: Pick<Chat, 'id' | 'title' | 'messages'>;
22
- shareChat: (id: string) => ServerActionResult<Chat>;
23
- onCopy: () => void;
24
- }
25
-
26
- export function ChatShareDialog({
27
- chat,
28
- shareChat,
29
- onCopy,
30
- ...props
31
- }: ChatShareDialogProps) {
32
- const { copyToClipboard } = useCopyToClipboard({ timeout: 1000 });
33
- const [isSharePending, startShareTransition] = React.useTransition();
34
-
35
- const copyShareLink = React.useCallback(
36
- async (chat: Chat) => {
37
- if (!chat.sharePath) {
38
- return toast.error('Could not copy share link to clipboard');
39
- }
40
-
41
- const url = new URL(window.location.href);
42
- url.pathname = chat.sharePath;
43
- copyToClipboard(url.toString());
44
- onCopy();
45
- toast.success('Share link copied to clipboard', {
46
- style: {
47
- borderRadius: '10px',
48
- background: '#333',
49
- color: '#fff',
50
- fontSize: '14px',
51
- },
52
- iconTheme: {
53
- primary: 'white',
54
- secondary: 'black',
55
- },
56
- });
57
- },
58
- [copyToClipboard, onCopy],
59
- );
60
-
61
- return (
62
- <Dialog {...props}>
63
- <DialogContent>
64
- <DialogHeader>
65
- <DialogTitle>Share link to chat</DialogTitle>
66
- <DialogDescription>
67
- Anyone with the URL will be able to view the shared chat.
68
- </DialogDescription>
69
- </DialogHeader>
70
- <div className="p-4 space-y-1 text-sm border rounded-md">
71
- <div className="font-medium">{chat.title}</div>
72
- <div className="text-muted-foreground">
73
- {chat.messages.length} messages
74
- </div>
75
- </div>
76
- <DialogFooter className="items-center">
77
- <Button
78
- disabled={isSharePending}
79
- onClick={() => {
80
- // @ts-ignore
81
- startShareTransition(async () => {
82
- const result = await shareChat(chat.id);
83
-
84
- if (result && 'error' in result) {
85
- toast.error(result.error);
86
- return;
87
- }
88
-
89
- copyShareLink(result);
90
- });
91
- }}
92
- >
93
- {isSharePending ? (
94
- <>
95
- <IconSpinner className="mr-2 animate-spin" />
96
- Copying...
97
- </>
98
- ) : (
99
- <>Copy link</>
100
- )}
101
- </Button>
102
- </DialogFooter>
103
- </DialogContent>
104
- </Dialog>
105
- );
106
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
components/chat/ButtonScrollToBottom.tsx ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+
5
+ import { cn } from '@/lib/utils';
6
+ import { useAtBottom } from '@/lib/hooks/useAtBottom';
7
+ import { Button, type ButtonProps } from '@/components/ui/Button';
8
+ import { IconArrowDown } from '@/components/ui/Icons';
9
+
10
+ export function ButtonScrollToBottom({ className, ...props }: ButtonProps) {
11
+ const isAtBottom = useAtBottom();
12
+
13
+ return (
14
+ <Button
15
+ variant="outline"
16
+ size="icon"
17
+ className={cn(
18
+ 'absolute right-4 top-1 z-10 bg-background transition-opacity duration-300 sm:right-8 md:top-2',
19
+ isAtBottom ? 'opacity-0' : 'opacity-100',
20
+ className,
21
+ )}
22
+ onClick={() =>
23
+ window.scrollTo({
24
+ top: document.body.offsetHeight,
25
+ behavior: 'smooth',
26
+ })
27
+ }
28
+ {...props}
29
+ >
30
+ <IconArrowDown />
31
+ <span className="sr-only">Scroll to bottom</span>
32
+ </Button>
33
+ );
34
+ }
components/chat/ChatList.tsx CHANGED
@@ -1,7 +1,7 @@
1
  'use client';
2
 
3
- import { Separator } from '@/components/ui/separator';
4
- import { ChatMessage } from '@/components/chat-message';
5
  import { MessageWithSelectedDataset } from '../../lib/types';
6
 
7
  export interface ChatList {
 
1
  'use client';
2
 
3
+ import { Separator } from '@/components/ui/Separator';
4
+ import { ChatMessage } from '@/components/chat/ChatMessage';
5
  import { MessageWithSelectedDataset } from '../../lib/types';
6
 
7
  export interface ChatList {
components/{chat-message.tsx β†’ chat/ChatMessage.tsx} RENAMED
@@ -5,11 +5,11 @@ import remarkGfm from 'remark-gfm';
5
  import remarkMath from 'remark-math';
6
 
7
  import { cn } from '@/lib/utils';
8
- import { CodeBlock } from '@/components/ui/codeblock';
9
- import { MemoizedReactMarkdown } from '@/components/markdown';
10
- import { IconOpenAI, IconUser } from '@/components/ui/icons';
11
- import { ChatMessageActions } from '@/components/chat-message-actions';
12
- import { MessageWithSelectedDataset } from '../lib/types';
13
 
14
  export interface ChatMessageProps {
15
  message: MessageWithSelectedDataset;
 
5
  import remarkMath from 'remark-math';
6
 
7
  import { cn } from '@/lib/utils';
8
+ import { CodeBlock } from '@/components/ui/CodeBlock';
9
+ import { MemoizedReactMarkdown } from '@/components/chat/MemoizedReactMarkdown';
10
+ import { IconOpenAI, IconUser } from '@/components/ui/Icons';
11
+ import { ChatMessageActions } from '@/components/chat/ChatMessageActions';
12
+ import { MessageWithSelectedDataset } from '../../lib/types';
13
 
14
  export interface ChatMessageProps {
15
  message: MessageWithSelectedDataset;
components/{chat-message-actions.tsx β†’ chat/ChatMessageActions.tsx} RENAMED
@@ -2,11 +2,11 @@
2
 
3
  import { type Message } from 'ai';
4
 
5
- import { Button } from '@/components/ui/button';
6
- import { IconCheck, IconCopy } from '@/components/ui/icons';
7
- import { useCopyToClipboard } from '@/lib/hooks/use-copy-to-clipboard';
8
  import { cn } from '@/lib/utils';
9
- import { MessageWithSelectedDataset } from '../lib/types';
10
 
11
  interface ChatMessageActionsProps extends React.ComponentProps<'div'> {
12
  message: MessageWithSelectedDataset;
 
2
 
3
  import { type Message } from 'ai';
4
 
5
+ import { Button } from '@/components/ui/Button';
6
+ import { IconCheck, IconCopy } from '@/components/ui/Icons';
7
+ import { useCopyToClipboard } from '@/lib/hooks/useCopyToClipboard';
8
  import { cn } from '@/lib/utils';
9
+ import { MessageWithSelectedDataset } from '../../lib/types';
10
 
11
  interface ChatMessageActionsProps extends React.ComponentProps<'div'> {
12
  message: MessageWithSelectedDataset;
components/{chat-panel.tsx β†’ chat/ChatPanel.tsx} RENAMED
@@ -1,13 +1,11 @@
1
  import * as React from 'react';
2
  import { type UseChatHelpers } from 'ai/react';
3
 
4
- import { shareChat } from '@/app/actions';
5
- import { Button } from '@/components/ui/button';
6
- import { PromptForm } from '@/components/prompt-form';
7
- import { ButtonScrollToBottom } from '@/components/button-scroll-to-bottom';
8
- import { IconRefresh, IconShare, IconStop } from '@/components/ui/icons';
9
- import { ChatShareDialog } from '@/components/chat-share-dialog';
10
- import { MessageWithSelectedDataset } from '../lib/types';
11
 
12
  export interface ChatPanelProps
13
  extends Pick<
@@ -53,28 +51,6 @@ export function ChatPanel({
53
  <IconRefresh className="mr-2" />
54
  Regenerate response
55
  </Button>
56
- {/* {id && title ? (
57
- <>
58
- <Button
59
- variant="outline"
60
- onClick={() => setShareDialogOpen(true)}
61
- >
62
- <IconShare className="mr-2" />
63
- Share
64
- </Button>
65
- <ChatShareDialog
66
- open={shareDialogOpen}
67
- onOpenChange={setShareDialogOpen}
68
- onCopy={() => setShareDialogOpen(false)}
69
- shareChat={shareChat}
70
- chat={{
71
- id,
72
- title,
73
- messages,
74
- }}
75
- />
76
- </>
77
- ) : null} */}
78
  </div>
79
  )
80
  )}
 
1
  import * as React from 'react';
2
  import { type UseChatHelpers } from 'ai/react';
3
 
4
+ import { Button } from '@/components/ui/Button';
5
+ import { PromptForm } from '@/components/chat/PromptForm';
6
+ import { ButtonScrollToBottom } from '@/components/chat/ButtonScrollToBottom';
7
+ import { IconRefresh, IconShare, IconStop } from '@/components/ui/Icons';
8
+ import { MessageWithSelectedDataset } from '../../lib/types';
 
 
9
 
10
  export interface ChatPanelProps
11
  extends Pick<
 
51
  <IconRefresh className="mr-2" />
52
  Regenerate response
53
  </Button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
  </div>
55
  )
56
  )}
components/chat/ChatScrollAnchor.tsx ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+ import { useInView } from 'react-intersection-observer';
5
+
6
+ import { useAtBottom } from '@/lib/hooks/useAtBottom';
7
+
8
+ interface ChatScrollAnchorProps {
9
+ trackVisibility?: boolean;
10
+ }
11
+
12
+ export function ChatScrollAnchor({ trackVisibility }: ChatScrollAnchorProps) {
13
+ const isAtBottom = useAtBottom();
14
+ const { ref, entry, inView } = useInView({
15
+ trackVisibility,
16
+ delay: 100,
17
+ rootMargin: '0px 0px -150px 0px',
18
+ });
19
+
20
+ React.useEffect(() => {
21
+ if (isAtBottom && trackVisibility && !inView) {
22
+ entry?.target.scrollIntoView({
23
+ block: 'start',
24
+ });
25
+ }
26
+ }, [inView, entry, isAtBottom, trackVisibility]);
27
+
28
+ return <div ref={ref} className="h-px w-full" />;
29
+ }
components/{empty-screen.tsx β†’ chat/EmptyScreen.tsx} RENAMED
@@ -1,8 +1,8 @@
1
  import { useAtom } from 'jotai';
2
  import { useDropzone } from 'react-dropzone';
3
- import { datasetAtom } from '../state';
4
  import Image from 'next/image';
5
- import useImageUpload from '../lib/hooks/useImageUpload';
6
 
7
  const examples = [
8
  'https://landing-lens-support.s3.us-east-2.amazonaws.com/vision-agent-examples/cereal-example.jpg',
 
1
  import { useAtom } from 'jotai';
2
  import { useDropzone } from 'react-dropzone';
3
+ import { datasetAtom } from '../../state';
4
  import Image from 'next/image';
5
+ import useImageUpload from '../../lib/hooks/useImageUpload';
6
 
7
  const examples = [
8
  'https://landing-lens-support.s3.us-east-2.amazonaws.com/vision-agent-examples/cereal-example.jpg',
components/chat/MemoizedReactMarkdown.tsx ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ import { FC, memo } from 'react';
2
+ import ReactMarkdown, { Options } from 'react-markdown';
3
+
4
+ export const MemoizedReactMarkdown: FC<Options> = memo(
5
+ ReactMarkdown,
6
+ (prevProps, nextProps) =>
7
+ prevProps.children === nextProps.children &&
8
+ prevProps.className === nextProps.className,
9
+ );
components/{prompt-form.tsx β†’ chat/PromptForm.tsx} RENAMED
@@ -1,15 +1,15 @@
1
  import * as React from 'react';
2
  import Textarea from 'react-textarea-autosize';
3
  import { UseChatHelpers } from 'ai/react';
4
- import { useEnterSubmit } from '@/lib/hooks/use-enter-submit';
5
  import { cn } from '@/lib/utils';
6
- import { Button, buttonVariants } from '@/components/ui/button';
7
  import {
8
  Tooltip,
9
  TooltipContent,
10
  TooltipTrigger,
11
- } from '@/components/ui/tooltip';
12
- import { IconArrowElbow, IconPlus } from '@/components/ui/icons';
13
  import { useRouter } from 'next/navigation';
14
 
15
  export interface PromptProps
 
1
  import * as React from 'react';
2
  import Textarea from 'react-textarea-autosize';
3
  import { UseChatHelpers } from 'ai/react';
4
+ import { useEnterSubmit } from '@/lib/hooks/useEnterSubmit';
5
  import { cn } from '@/lib/utils';
6
+ import { Button, buttonVariants } from '@/components/ui/Button';
7
  import {
8
  Tooltip,
9
  TooltipContent,
10
  TooltipTrigger,
11
+ } from '@/components/ui/Tooltip';
12
+ import { IconArrowElbow, IconPlus } from '@/components/ui/Icons';
13
  import { useRouter } from 'next/navigation';
14
 
15
  export interface PromptProps
components/chat/index.tsx CHANGED
@@ -1,8 +1,8 @@
1
  'use client';
2
  import { cn } from '@/lib/utils';
3
  import { ChatList } from '@/components/chat/ChatList';
4
- import { ChatPanel } from '@/components/chat-panel';
5
- import { ChatScrollAnchor } from '@/components/chat-scroll-anchor';
6
  import ImageList from './ImageList';
7
  import useChatWithDataset from '../../lib/hooks/useChatWithDataset';
8
 
 
1
  'use client';
2
  import { cn } from '@/lib/utils';
3
  import { ChatList } from '@/components/chat/ChatList';
4
+ import { ChatPanel } from '@/components/chat/ChatPanel';
5
+ import { ChatScrollAnchor } from '@/components/chat/ChatScrollAnchor';
6
  import ImageList from './ImageList';
7
  import useChatWithDataset from '../../lib/hooks/useChatWithDataset';
8
 
components/clear-history.tsx DELETED
@@ -1,77 +0,0 @@
1
- 'use client'
2
-
3
- import * as React from 'react'
4
- import { useRouter } from 'next/navigation'
5
- import { toast } from 'react-hot-toast'
6
-
7
- import { ServerActionResult } from '@/lib/types'
8
- import { Button } from '@/components/ui/button'
9
- import {
10
- AlertDialog,
11
- AlertDialogAction,
12
- AlertDialogCancel,
13
- AlertDialogContent,
14
- AlertDialogDescription,
15
- AlertDialogFooter,
16
- AlertDialogHeader,
17
- AlertDialogTitle,
18
- AlertDialogTrigger
19
- } from '@/components/ui/alert-dialog'
20
- import { IconSpinner } from '@/components/ui/icons'
21
-
22
- interface ClearHistoryProps {
23
- isEnabled: boolean
24
- clearChats: () => ServerActionResult<void>
25
- }
26
-
27
- export function ClearHistory({
28
- isEnabled = false,
29
- clearChats
30
- }: ClearHistoryProps) {
31
- const [open, setOpen] = React.useState(false)
32
- const [isPending, startTransition] = React.useTransition()
33
- const router = useRouter()
34
-
35
- return (
36
- <AlertDialog open={open} onOpenChange={setOpen}>
37
- <AlertDialogTrigger asChild>
38
- <Button variant="ghost" disabled={!isEnabled || isPending}>
39
- {isPending && <IconSpinner className="mr-2" />}
40
- Clear history
41
- </Button>
42
- </AlertDialogTrigger>
43
- <AlertDialogContent>
44
- <AlertDialogHeader>
45
- <AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
46
- <AlertDialogDescription>
47
- This will permanently delete your chat history and remove your data
48
- from our servers.
49
- </AlertDialogDescription>
50
- </AlertDialogHeader>
51
- <AlertDialogFooter>
52
- <AlertDialogCancel disabled={isPending}>Cancel</AlertDialogCancel>
53
- <AlertDialogAction
54
- disabled={isPending}
55
- onClick={event => {
56
- event.preventDefault()
57
- startTransition(() => {
58
- clearChats().then(result => {
59
- if (result && 'error' in result) {
60
- toast.error(result.error)
61
- return
62
- }
63
-
64
- setOpen(false)
65
- router.push('/')
66
- })
67
- })
68
- }}
69
- >
70
- {isPending && <IconSpinner className="mr-2 animate-spin" />}
71
- Delete
72
- </AlertDialogAction>
73
- </AlertDialogFooter>
74
- </AlertDialogContent>
75
- </AlertDialog>
76
- )
77
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
components/external-link.tsx DELETED
@@ -1,29 +0,0 @@
1
- export function ExternalLink({
2
- href,
3
- children
4
- }: {
5
- href: string
6
- children: React.ReactNode
7
- }) {
8
- return (
9
- <a
10
- href={href}
11
- target="_blank"
12
- className="inline-flex flex-1 justify-center gap-1 leading-4 hover:underline"
13
- >
14
- <span>{children}</span>
15
- <svg
16
- aria-hidden="true"
17
- height="7"
18
- viewBox="0 0 6 6"
19
- width="7"
20
- className="opacity-70"
21
- >
22
- <path
23
- d="M1.25215 5.54731L0.622742 4.9179L3.78169 1.75597H1.3834L1.38936 0.890915H5.27615V4.78069H4.40513L4.41109 2.38538L1.25215 5.54731Z"
24
- fill="currentColor"
25
- ></path>
26
- </svg>
27
- </a>
28
- )
29
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
components/header.tsx CHANGED
@@ -1,73 +1,34 @@
1
- import * as React from 'react'
2
- import Link from 'next/link'
3
 
4
- import { cn } from '@/lib/utils'
5
- import { auth } from '@/auth'
6
- import { Button, buttonVariants } from '@/components/ui/button'
7
- import {
8
- IconGitHub,
9
- IconNextChat,
10
- IconSeparator,
11
- IconVercel
12
- } from '@/components/ui/icons'
13
- import { UserMenu } from '@/components/user-menu'
14
- import { SidebarMobile } from './sidebar-mobile'
15
- import { SidebarToggle } from './sidebar-toggle'
16
- // import { ChatHistory } from './chat-history'
17
 
18
  async function UserOrLogin() {
19
- const session = await auth()
20
- return (
21
- <>
22
- {/* {session?.user ? (
23
- <>
24
- <SidebarMobile>
25
- <ChatHistory userId={session.user.id} />
26
- </SidebarMobile>
27
- <SidebarToggle />
28
- </>
29
- ) : ( */}
30
- {/* <Link href="/" target="_blank" rel="nofollow">
31
- <IconNextChat className="size-6 mr-2 dark:hidden" inverted />
32
- <IconNextChat className="hidden size-6 mr-2 dark:block" />
33
- </Link> */}
34
- {/* )} */}
35
- <div className="flex items-center">
36
- {/* <IconSeparator className="size-6 text-muted-foreground/50" /> */}
37
- {session?.user ? (
38
- <UserMenu user={session.user} />
39
- ) : (
40
- <Button variant="link" asChild className="-ml-2">
41
- <Link href="/sign-in?callbackUrl=/">Login</Link>
42
- </Button>
43
- )}
44
- </div>
45
- </>
46
- )
47
  }
48
 
49
  export function Header() {
50
- return (
51
- <header className="sticky top-0 z-50 flex items-center justify-between w-full h-16 px-8 border-b shrink-0 bg-gradient-to-b from-background/10 via-background/50 to-background/80 backdrop-blur-xl">
52
- <div className="flex items-center">
53
- {/* <React.Suspense fallback={<div className="flex-1 overflow-auto" />}>
54
- <UserOrLogin />
55
- </React.Suspense> */}
56
- </div>
57
- <div className="flex items-center justify-end space-x-2">
58
- <React.Suspense fallback={<div className="flex-1 overflow-auto" />}>
59
- <UserOrLogin />
60
- </React.Suspense>
61
- {/* <a
62
- target="_blank"
63
- href="https://github.com/vercel/nextjs-ai-chatbot/"
64
- rel="noopener noreferrer"
65
- className={cn(buttonVariants({ variant: 'outline' }))}
66
- >
67
- <IconGitHub />
68
- <span className="hidden ml-2 md:flex">GitHub</span>
69
- </a> */}
70
- </div>
71
- </header>
72
- )
73
  }
 
1
+ import * as React from 'react';
2
+ import Link from 'next/link';
3
 
4
+ import { auth } from '@/auth';
5
+ import { Button } from '@/components/ui/Button';
6
+ import { UserMenu } from '@/components/UserMenu';
 
 
 
 
 
 
 
 
 
 
7
 
8
  async function UserOrLogin() {
9
+ const session = await auth();
10
+ return (
11
+ <>
12
+ <div className="flex items-center">
13
+ {/* <IconSeparator className="size-6 text-muted-foreground/50" /> */}
14
+ {session?.user ? (
15
+ <UserMenu user={session.user} />
16
+ ) : (
17
+ <Button variant="link" asChild className="-ml-2">
18
+ <Link href="/sign-in?callbackUrl=/">Login</Link>
19
+ </Button>
20
+ )}
21
+ </div>
22
+ </>
23
+ );
 
 
 
 
 
 
 
 
 
 
 
 
 
24
  }
25
 
26
  export function Header() {
27
+ return (
28
+ <header className="sticky top-0 z-50 flex items-center justify-end w-full h-16 px-8 border-b shrink-0 bg-gradient-to-b from-background/10 via-background/50 to-background/80 backdrop-blur-xl">
29
+ <React.Suspense fallback={<div className="flex-1 overflow-auto" />}>
30
+ <UserOrLogin />
31
+ </React.Suspense>
32
+ </header>
33
+ );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  }
components/markdown.tsx DELETED
@@ -1,9 +0,0 @@
1
- import { FC, memo } from 'react'
2
- import ReactMarkdown, { Options } from 'react-markdown'
3
-
4
- export const MemoizedReactMarkdown: FC<Options> = memo(
5
- ReactMarkdown,
6
- (prevProps, nextProps) =>
7
- prevProps.children === nextProps.children &&
8
- prevProps.className === nextProps.className
9
- )
 
 
 
 
 
 
 
 
 
 
components/providers.tsx CHANGED
@@ -1,17 +1,14 @@
1
- 'use client'
2
 
3
- import * as React from 'react'
4
- import { ThemeProvider as NextThemesProvider } from 'next-themes'
5
- import { ThemeProviderProps } from 'next-themes/dist/types'
6
- import { SidebarProvider } from '@/lib/hooks/use-sidebar'
7
- import { TooltipProvider } from '@/components/ui/tooltip'
8
 
9
  export function Providers({ children, ...props }: ThemeProviderProps) {
10
- return (
11
- <NextThemesProvider {...props}>
12
- <SidebarProvider>
13
- <TooltipProvider>{children}</TooltipProvider>
14
- </SidebarProvider>
15
- </NextThemesProvider>
16
- )
17
  }
 
1
+ 'use client';
2
 
3
+ import * as React from 'react';
4
+ import { ThemeProvider as NextThemesProvider } from 'next-themes';
5
+ import { ThemeProviderProps } from 'next-themes/dist/types';
6
+ import { TooltipProvider } from '@/components/ui/Tooltip';
 
7
 
8
  export function Providers({ children, ...props }: ThemeProviderProps) {
9
+ return (
10
+ <NextThemesProvider {...props}>
11
+ <TooltipProvider>{children}</TooltipProvider>
12
+ </NextThemesProvider>
13
+ );
 
 
14
  }
components/sidebar-actions.tsx DELETED
@@ -1,125 +0,0 @@
1
- 'use client'
2
-
3
- import { useRouter } from 'next/navigation'
4
- import * as React from 'react'
5
- import { toast } from 'react-hot-toast'
6
-
7
- import { ServerActionResult, type Chat } from '@/lib/types'
8
- import {
9
- AlertDialog,
10
- AlertDialogAction,
11
- AlertDialogCancel,
12
- AlertDialogContent,
13
- AlertDialogDescription,
14
- AlertDialogFooter,
15
- AlertDialogHeader,
16
- AlertDialogTitle
17
- } from '@/components/ui/alert-dialog'
18
- import { Button } from '@/components/ui/button'
19
- import { IconShare, IconSpinner, IconTrash } from '@/components/ui/icons'
20
- import { ChatShareDialog } from '@/components/chat-share-dialog'
21
- import {
22
- Tooltip,
23
- TooltipContent,
24
- TooltipTrigger
25
- } from '@/components/ui/tooltip'
26
-
27
- interface SidebarActionsProps {
28
- chat: Chat
29
- removeChat: (args: { id: string; path: string }) => ServerActionResult<void>
30
- shareChat: (id: string) => ServerActionResult<Chat>
31
- }
32
-
33
- export function SidebarActions({
34
- chat,
35
- removeChat,
36
- shareChat
37
- }: SidebarActionsProps) {
38
- const router = useRouter()
39
- const [deleteDialogOpen, setDeleteDialogOpen] = React.useState(false)
40
- const [shareDialogOpen, setShareDialogOpen] = React.useState(false)
41
- const [isRemovePending, startRemoveTransition] = React.useTransition()
42
-
43
- return (
44
- <>
45
- <div className="space-x-1">
46
- <Tooltip>
47
- <TooltipTrigger asChild>
48
- <Button
49
- variant="ghost"
50
- className="size-6 p-0 hover:bg-background"
51
- onClick={() => setShareDialogOpen(true)}
52
- >
53
- <IconShare />
54
- <span className="sr-only">Share</span>
55
- </Button>
56
- </TooltipTrigger>
57
- <TooltipContent>Share chat</TooltipContent>
58
- </Tooltip>
59
- <Tooltip>
60
- <TooltipTrigger asChild>
61
- <Button
62
- variant="ghost"
63
- className="size-6 p-0 hover:bg-background"
64
- disabled={isRemovePending}
65
- onClick={() => setDeleteDialogOpen(true)}
66
- >
67
- <IconTrash />
68
- <span className="sr-only">Delete</span>
69
- </Button>
70
- </TooltipTrigger>
71
- <TooltipContent>Delete chat</TooltipContent>
72
- </Tooltip>
73
- </div>
74
- <ChatShareDialog
75
- chat={chat}
76
- shareChat={shareChat}
77
- open={shareDialogOpen}
78
- onOpenChange={setShareDialogOpen}
79
- onCopy={() => setShareDialogOpen(false)}
80
- />
81
- <AlertDialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
82
- <AlertDialogContent>
83
- <AlertDialogHeader>
84
- <AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
85
- <AlertDialogDescription>
86
- This will permanently delete your chat message and remove your
87
- data from our servers.
88
- </AlertDialogDescription>
89
- </AlertDialogHeader>
90
- <AlertDialogFooter>
91
- <AlertDialogCancel disabled={isRemovePending}>
92
- Cancel
93
- </AlertDialogCancel>
94
- <AlertDialogAction
95
- disabled={isRemovePending}
96
- onClick={event => {
97
- event.preventDefault()
98
- // @ts-ignore
99
- startRemoveTransition(async () => {
100
- const result = await removeChat({
101
- id: chat.id,
102
- path: chat.path
103
- })
104
-
105
- if (result && 'error' in result) {
106
- toast.error(result.error)
107
- return
108
- }
109
-
110
- setDeleteDialogOpen(false)
111
- router.refresh()
112
- router.push('/')
113
- toast.success('Chat deleted')
114
- })
115
- }}
116
- >
117
- {isRemovePending && <IconSpinner className="mr-2 animate-spin" />}
118
- Delete
119
- </AlertDialogAction>
120
- </AlertDialogFooter>
121
- </AlertDialogContent>
122
- </AlertDialog>
123
- </>
124
- )
125
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
components/sidebar-desktop.tsx DELETED
@@ -1,21 +0,0 @@
1
- import { Sidebar } from '@/components/sidebar'
2
-
3
- import { auth } from '@/auth'
4
- import { ChatHistory } from '@/components/chat-history'
5
-
6
- export async function SidebarDesktop() {
7
- const session = await auth()
8
-
9
- if (!session?.user?.id) {
10
- return null
11
- }
12
-
13
- return null
14
-
15
- // return (
16
- // <Sidebar className="peer absolute inset-y-0 z-30 hidden -translate-x-full border-r bg-muted duration-300 ease-in-out data-[state=open]:translate-x-0 lg:flex lg:w-[250px] xl:w-[300px]">
17
- // {/* @ts-ignore */}
18
- // <ChatHistory userId={session.user.id} />
19
- // </Sidebar>
20
- // )
21
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
components/sidebar-footer.tsx DELETED
@@ -1,16 +0,0 @@
1
- import { cn } from '@/lib/utils'
2
-
3
- export function SidebarFooter({
4
- children,
5
- className,
6
- ...props
7
- }: React.ComponentProps<'div'>) {
8
- return (
9
- <div
10
- className={cn('flex items-center justify-between p-4', className)}
11
- {...props}
12
- >
13
- {children}
14
- </div>
15
- )
16
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
components/sidebar-item.tsx DELETED
@@ -1,124 +0,0 @@
1
- 'use client'
2
-
3
- import * as React from 'react'
4
-
5
- import Link from 'next/link'
6
- import { usePathname } from 'next/navigation'
7
-
8
- import { motion } from 'framer-motion'
9
-
10
- import { buttonVariants } from '@/components/ui/button'
11
- import { IconMessage, IconUsers } from '@/components/ui/icons'
12
- import {
13
- Tooltip,
14
- TooltipContent,
15
- TooltipTrigger
16
- } from '@/components/ui/tooltip'
17
- import { useLocalStorage } from '@/lib/hooks/use-local-storage'
18
- import { type Chat } from '@/lib/types'
19
- import { cn } from '@/lib/utils'
20
-
21
- interface SidebarItemProps {
22
- index: number
23
- chat: Chat
24
- children: React.ReactNode
25
- }
26
-
27
- export function SidebarItem({ index, chat, children }: SidebarItemProps) {
28
- const pathname = usePathname()
29
-
30
- const isActive = pathname === chat.path
31
- const [newChatId, setNewChatId] = useLocalStorage('newChatId', null)
32
- const shouldAnimate = index === 0 && isActive && newChatId
33
-
34
- if (!chat?.id) return null
35
-
36
- return (
37
- <motion.div
38
- className="relative h-8"
39
- variants={{
40
- initial: {
41
- height: 0,
42
- opacity: 0
43
- },
44
- animate: {
45
- height: 'auto',
46
- opacity: 1
47
- }
48
- }}
49
- initial={shouldAnimate ? 'initial' : undefined}
50
- animate={shouldAnimate ? 'animate' : undefined}
51
- transition={{
52
- duration: 0.25,
53
- ease: 'easeIn'
54
- }}
55
- >
56
- <div className="absolute left-2 top-1 flex size-6 items-center justify-center">
57
- {chat.sharePath ? (
58
- <Tooltip delayDuration={1000}>
59
- <TooltipTrigger
60
- tabIndex={-1}
61
- className="focus:bg-muted focus:ring-1 focus:ring-ring"
62
- >
63
- <IconUsers className="mr-2" />
64
- </TooltipTrigger>
65
- <TooltipContent>This is a shared chat.</TooltipContent>
66
- </Tooltip>
67
- ) : (
68
- <IconMessage className="mr-2" />
69
- )}
70
- </div>
71
- <Link
72
- href={chat.path}
73
- className={cn(
74
- buttonVariants({ variant: 'ghost' }),
75
- 'group w-full px-8 transition-colors hover:bg-zinc-200/40 dark:hover:bg-zinc-300/10',
76
- isActive && 'bg-zinc-200 pr-16 font-semibold dark:bg-zinc-800'
77
- )}
78
- >
79
- <div
80
- className="relative max-h-5 flex-1 select-none overflow-hidden text-ellipsis break-all"
81
- title={chat.title}
82
- >
83
- <span className="whitespace-nowrap">
84
- {shouldAnimate ? (
85
- chat.title.split('').map((character, index) => (
86
- <motion.span
87
- key={index}
88
- variants={{
89
- initial: {
90
- opacity: 0,
91
- x: -100
92
- },
93
- animate: {
94
- opacity: 1,
95
- x: 0
96
- }
97
- }}
98
- initial={shouldAnimate ? 'initial' : undefined}
99
- animate={shouldAnimate ? 'animate' : undefined}
100
- transition={{
101
- duration: 0.25,
102
- ease: 'easeIn',
103
- delay: index * 0.05,
104
- staggerChildren: 0.05
105
- }}
106
- onAnimationComplete={() => {
107
- if (index === chat.title.length - 1) {
108
- setNewChatId(null)
109
- }
110
- }}
111
- >
112
- {character}
113
- </motion.span>
114
- ))
115
- ) : (
116
- <span>{chat.title}</span>
117
- )}
118
- </span>
119
- </div>
120
- </Link>
121
- {isActive && <div className="absolute right-2 top-1">{children}</div>}
122
- </motion.div>
123
- )
124
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
components/sidebar-items.tsx DELETED
@@ -1,42 +0,0 @@
1
- 'use client'
2
-
3
- import { Chat } from '@/lib/types'
4
- import { AnimatePresence, motion } from 'framer-motion'
5
-
6
- import { removeChat, shareChat } from '@/app/actions'
7
-
8
- import { SidebarActions } from '@/components/sidebar-actions'
9
- import { SidebarItem } from '@/components/sidebar-item'
10
-
11
- interface SidebarItemsProps {
12
- chats?: Chat[]
13
- }
14
-
15
- export function SidebarItems({ chats }: SidebarItemsProps) {
16
- if (!chats?.length) return null
17
-
18
- return (
19
- <AnimatePresence>
20
- {chats.map(
21
- (chat, index) =>
22
- chat && (
23
- <motion.div
24
- key={chat?.id}
25
- exit={{
26
- opacity: 0,
27
- height: 0
28
- }}
29
- >
30
- <SidebarItem index={index} chat={chat}>
31
- <SidebarActions
32
- chat={chat}
33
- removeChat={removeChat}
34
- shareChat={shareChat}
35
- />
36
- </SidebarItem>
37
- </motion.div>
38
- )
39
- )}
40
- </AnimatePresence>
41
- )
42
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
components/sidebar-list.tsx DELETED
@@ -1,38 +0,0 @@
1
- import { clearChats, getChats } from '@/app/actions'
2
- import { ClearHistory } from '@/components/clear-history'
3
- import { SidebarItems } from '@/components/sidebar-items'
4
- import { ThemeToggle } from '@/components/theme-toggle'
5
- import { cache } from 'react'
6
-
7
- interface SidebarListProps {
8
- userId?: string
9
- children?: React.ReactNode
10
- }
11
-
12
- const loadChats = cache(async (userId?: string) => {
13
- return await getChats(userId)
14
- })
15
-
16
- export async function SidebarList({ userId }: SidebarListProps) {
17
- const chats = await loadChats(userId)
18
-
19
- return (
20
- <div className="flex flex-1 flex-col overflow-hidden">
21
- <div className="flex-1 overflow-auto">
22
- {chats?.length ? (
23
- <div className="space-y-2 px-2">
24
- <SidebarItems chats={chats} />
25
- </div>
26
- ) : (
27
- <div className="p-8 text-center">
28
- <p className="text-sm text-muted-foreground">No chat history</p>
29
- </div>
30
- )}
31
- </div>
32
- <div className="flex items-center justify-between p-4">
33
- <ThemeToggle />
34
- <ClearHistory clearChats={clearChats} isEnabled={chats?.length > 0} />
35
- </div>
36
- </div>
37
- )
38
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
components/sidebar-mobile.tsx DELETED
@@ -1,28 +0,0 @@
1
- 'use client'
2
-
3
- import { Sheet, SheetContent, SheetTrigger } from '@/components/ui/sheet'
4
-
5
- import { Sidebar } from '@/components/sidebar'
6
- import { Button } from '@/components/ui/button'
7
-
8
- import { IconSidebar } from '@/components/ui/icons'
9
-
10
- interface SidebarMobileProps {
11
- children: React.ReactNode
12
- }
13
-
14
- export function SidebarMobile({ children }: SidebarMobileProps) {
15
- return (
16
- <Sheet>
17
- <SheetTrigger asChild>
18
- <Button variant="ghost" className="-ml-2 flex size-9 p-0 lg:hidden">
19
- <IconSidebar className="size-6" />
20
- <span className="sr-only">Toggle Sidebar</span>
21
- </Button>
22
- </SheetTrigger>
23
- <SheetContent className="inset-y-0 flex h-auto w-[300px] flex-col p-0">
24
- <Sidebar className="flex">{children}</Sidebar>
25
- </SheetContent>
26
- </Sheet>
27
- )
28
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
components/sidebar-toggle.tsx DELETED
@@ -1,24 +0,0 @@
1
- 'use client'
2
-
3
- import * as React from 'react'
4
-
5
- import { useSidebar } from '@/lib/hooks/use-sidebar'
6
- import { Button } from '@/components/ui/button'
7
- import { IconSidebar } from '@/components/ui/icons'
8
-
9
- export function SidebarToggle() {
10
- const { toggleSidebar } = useSidebar()
11
-
12
- return (
13
- <Button
14
- variant="ghost"
15
- className="-ml-2 hidden size-9 p-0 lg:flex"
16
- onClick={() => {
17
- toggleSidebar()
18
- }}
19
- >
20
- <IconSidebar className="size-6" />
21
- <span className="sr-only">Toggle Sidebar</span>
22
- </Button>
23
- )
24
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
components/sidebar.tsx DELETED
@@ -1,21 +0,0 @@
1
- 'use client'
2
-
3
- import * as React from 'react'
4
-
5
- import { useSidebar } from '@/lib/hooks/use-sidebar'
6
- import { cn } from '@/lib/utils'
7
-
8
- export interface SidebarProps extends React.ComponentProps<'div'> {}
9
-
10
- export function Sidebar({ className, children }: SidebarProps) {
11
- const { isSidebarOpen, isLoading } = useSidebar()
12
-
13
- return (
14
- <div
15
- data-state={isSidebarOpen && !isLoading ? 'open' : 'closed'}
16
- className={cn(className, 'h-full flex-col dark:bg-zinc-950')}
17
- >
18
- {children}
19
- </div>
20
- )
21
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
components/tailwind-indicator.tsx DELETED
@@ -1,14 +0,0 @@
1
- export function TailwindIndicator() {
2
- if (process.env.NODE_ENV === 'production') return null
3
-
4
- return (
5
- <div className="fixed bottom-1 left-1 z-50 flex size-6 items-center justify-center rounded-full bg-gray-800 p-3 font-mono text-xs text-white">
6
- <div className="block sm:hidden">xs</div>
7
- <div className="hidden sm:block md:hidden">sm</div>
8
- <div className="hidden md:block lg:hidden">md</div>
9
- <div className="hidden lg:block xl:hidden">lg</div>
10
- <div className="hidden xl:block 2xl:hidden">xl</div>
11
- <div className="hidden 2xl:block">2xl</div>
12
- </div>
13
- )
14
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
components/ui/DropdownMenu.tsx ADDED
@@ -0,0 +1,128 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+ import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
5
+
6
+ import { cn } from '@/lib/utils';
7
+
8
+ const DropdownMenu = DropdownMenuPrimitive.Root;
9
+
10
+ const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
11
+
12
+ const DropdownMenuGroup = DropdownMenuPrimitive.Group;
13
+
14
+ const DropdownMenuPortal = DropdownMenuPrimitive.Portal;
15
+
16
+ const DropdownMenuSub = DropdownMenuPrimitive.Sub;
17
+
18
+ const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
19
+
20
+ const DropdownMenuSubContent = React.forwardRef<
21
+ React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
22
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
23
+ >(({ className, ...props }, ref) => (
24
+ <DropdownMenuPrimitive.SubContent
25
+ ref={ref}
26
+ className={cn(
27
+ 'z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md animate-in data-[side=bottom]:slide-in-from-top-1 data-[side=left]:slide-in-from-right-1 data-[side=right]:slide-in-from-left-1 data-[side=top]:slide-in-from-bottom-1',
28
+ className,
29
+ )}
30
+ {...props}
31
+ />
32
+ ));
33
+ DropdownMenuSubContent.displayName =
34
+ DropdownMenuPrimitive.SubContent.displayName;
35
+
36
+ const DropdownMenuContent = React.forwardRef<
37
+ React.ElementRef<typeof DropdownMenuPrimitive.Content>,
38
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
39
+ >(({ className, sideOffset = 4, ...props }, ref) => (
40
+ <DropdownMenuPrimitive.Portal>
41
+ <DropdownMenuPrimitive.Content
42
+ ref={ref}
43
+ sideOffset={sideOffset}
44
+ className={cn(
45
+ 'z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow animate-in 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',
46
+ className,
47
+ )}
48
+ {...props}
49
+ />
50
+ </DropdownMenuPrimitive.Portal>
51
+ ));
52
+ DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;
53
+
54
+ const DropdownMenuItem = React.forwardRef<
55
+ React.ElementRef<typeof DropdownMenuPrimitive.Item>,
56
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
57
+ inset?: boolean;
58
+ }
59
+ >(({ className, inset, ...props }, ref) => (
60
+ <DropdownMenuPrimitive.Item
61
+ ref={ref}
62
+ className={cn(
63
+ 'relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
64
+ inset && 'pl-8',
65
+ className,
66
+ )}
67
+ {...props}
68
+ />
69
+ ));
70
+ DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;
71
+
72
+ const DropdownMenuLabel = React.forwardRef<
73
+ React.ElementRef<typeof DropdownMenuPrimitive.Label>,
74
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
75
+ inset?: boolean;
76
+ }
77
+ >(({ className, inset, ...props }, ref) => (
78
+ <DropdownMenuPrimitive.Label
79
+ ref={ref}
80
+ className={cn(
81
+ 'px-2 py-1.5 text-sm font-semibold',
82
+ inset && 'pl-8',
83
+ className,
84
+ )}
85
+ {...props}
86
+ />
87
+ ));
88
+ DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName;
89
+
90
+ const DropdownMenuSeparator = React.forwardRef<
91
+ React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
92
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
93
+ >(({ className, ...props }, ref) => (
94
+ <DropdownMenuPrimitive.Separator
95
+ ref={ref}
96
+ className={cn('-mx-1 my-1 h-px bg-muted', className)}
97
+ {...props}
98
+ />
99
+ ));
100
+ DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;
101
+
102
+ const DropdownMenuShortcut = ({
103
+ className,
104
+ ...props
105
+ }: React.HTMLAttributes<HTMLSpanElement>) => {
106
+ return (
107
+ <span
108
+ className={cn('ml-auto text-xs tracking-widest opacity-60', className)}
109
+ {...props}
110
+ />
111
+ );
112
+ };
113
+ DropdownMenuShortcut.displayName = 'DropdownMenuShortcut';
114
+
115
+ export {
116
+ DropdownMenu,
117
+ DropdownMenuTrigger,
118
+ DropdownMenuContent,
119
+ DropdownMenuItem,
120
+ DropdownMenuLabel,
121
+ DropdownMenuSeparator,
122
+ DropdownMenuShortcut,
123
+ DropdownMenuGroup,
124
+ DropdownMenuPortal,
125
+ DropdownMenuSub,
126
+ DropdownMenuSubContent,
127
+ DropdownMenuRadioGroup,
128
+ };
components/ui/alert-dialog.tsx DELETED
@@ -1,141 +0,0 @@
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<typeof AlertDialogPrimitive.Overlay>,
17
- React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>
18
- >(({ className, ...props }, ref) => (
19
- <AlertDialogPrimitive.Overlay
20
- className={cn(
21
- 'fixed inset-0 z-50 bg-background/80 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
22
- className
23
- )}
24
- {...props}
25
- ref={ref}
26
- />
27
- ))
28
- AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName
29
-
30
- const AlertDialogContent = React.forwardRef<
31
- React.ElementRef<typeof AlertDialogPrimitive.Content>,
32
- React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>
33
- >(({ className, ...props }, ref) => (
34
- <AlertDialogPortal>
35
- <AlertDialogOverlay />
36
- <AlertDialogPrimitive.Content
37
- ref={ref}
38
- className={cn(
39
- 'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out 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-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg',
40
- className
41
- )}
42
- {...props}
43
- />
44
- </AlertDialogPortal>
45
- ))
46
- AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName
47
-
48
- const AlertDialogHeader = ({
49
- className,
50
- ...props
51
- }: React.HTMLAttributes<HTMLDivElement>) => (
52
- <div
53
- className={cn(
54
- 'flex flex-col space-y-2 text-center sm:text-left',
55
- className
56
- )}
57
- {...props}
58
- />
59
- )
60
- AlertDialogHeader.displayName = 'AlertDialogHeader'
61
-
62
- const AlertDialogFooter = ({
63
- className,
64
- ...props
65
- }: React.HTMLAttributes<HTMLDivElement>) => (
66
- <div
67
- className={cn(
68
- 'flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2',
69
- className
70
- )}
71
- {...props}
72
- />
73
- )
74
- AlertDialogFooter.displayName = 'AlertDialogFooter'
75
-
76
- const AlertDialogTitle = React.forwardRef<
77
- React.ElementRef<typeof AlertDialogPrimitive.Title>,
78
- React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>
79
- >(({ className, ...props }, ref) => (
80
- <AlertDialogPrimitive.Title
81
- ref={ref}
82
- className={cn('text-lg font-semibold', className)}
83
- {...props}
84
- />
85
- ))
86
- AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName
87
-
88
- const AlertDialogDescription = React.forwardRef<
89
- React.ElementRef<typeof AlertDialogPrimitive.Description>,
90
- React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>
91
- >(({ className, ...props }, ref) => (
92
- <AlertDialogPrimitive.Description
93
- ref={ref}
94
- className={cn('text-sm text-muted-foreground', className)}
95
- {...props}
96
- />
97
- ))
98
- AlertDialogDescription.displayName =
99
- AlertDialogPrimitive.Description.displayName
100
-
101
- const AlertDialogAction = React.forwardRef<
102
- React.ElementRef<typeof AlertDialogPrimitive.Action>,
103
- React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>
104
- >(({ className, ...props }, ref) => (
105
- <AlertDialogPrimitive.Action
106
- ref={ref}
107
- className={cn(buttonVariants(), className)}
108
- {...props}
109
- />
110
- ))
111
- AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName
112
-
113
- const AlertDialogCancel = React.forwardRef<
114
- React.ElementRef<typeof AlertDialogPrimitive.Cancel>,
115
- React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>
116
- >(({ className, ...props }, ref) => (
117
- <AlertDialogPrimitive.Cancel
118
- ref={ref}
119
- className={cn(
120
- buttonVariants({ variant: 'outline' }),
121
- 'mt-2 sm:mt-0',
122
- className
123
- )}
124
- {...props}
125
- />
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
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
components/ui/badge.tsx DELETED
@@ -1,36 +0,0 @@
1
- import * as React from 'react'
2
- import { cva, type VariantProps } from 'class-variance-authority'
3
-
4
- import { cn } from '@/lib/utils'
5
-
6
- const badgeVariants = cva(
7
- 'inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2',
8
- {
9
- variants: {
10
- variant: {
11
- default:
12
- 'border-transparent bg-primary text-primary-foreground hover:bg-primary/80',
13
- secondary:
14
- 'border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80',
15
- destructive:
16
- 'border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80',
17
- outline: 'text-foreground'
18
- }
19
- },
20
- defaultVariants: {
21
- variant: 'default'
22
- }
23
- }
24
- )
25
-
26
- export interface BadgeProps
27
- extends React.HTMLAttributes<HTMLDivElement>,
28
- VariantProps<typeof badgeVariants> {}
29
-
30
- function Badge({ className, variant, ...props }: BadgeProps) {
31
- return (
32
- <div className={cn(badgeVariants({ variant }), className)} {...props} />
33
- )
34
- }
35
-
36
- export { Badge, badgeVariants }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
components/ui/button.tsx CHANGED
@@ -1,57 +1,57 @@
1
- import * as React from 'react'
2
- import { Slot } from '@radix-ui/react-slot'
3
- import { cva, type VariantProps } from 'class-variance-authority'
4
 
5
- import { cn } from '@/lib/utils'
6
 
7
  const buttonVariants = cva(
8
- 'inline-flex items-center justify-center rounded-md text-sm font-medium shadow ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
9
- {
10
- variants: {
11
- variant: {
12
- default:
13
- 'bg-primary text-primary-foreground shadow-md hover:bg-primary/90',
14
- destructive:
15
- 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
16
- outline:
17
- 'border border-input hover:bg-accent hover:text-accent-foreground',
18
- secondary:
19
- 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
20
- ghost: 'shadow-none hover:bg-accent hover:text-accent-foreground',
21
- link: 'text-primary underline-offset-4 shadow-none hover:underline'
22
- },
23
- size: {
24
- default: 'h-8 px-4 py-2',
25
- sm: 'h-8 rounded-md px-3',
26
- lg: 'h-11 rounded-md px-8',
27
- icon: 'size-8 p-0'
28
- }
29
- },
30
- defaultVariants: {
31
- variant: 'default',
32
- size: 'default'
33
- }
34
- }
35
- )
36
 
37
  export interface ButtonProps
38
- extends React.ButtonHTMLAttributes<HTMLButtonElement>,
39
- VariantProps<typeof buttonVariants> {
40
- asChild?: boolean
41
  }
42
 
43
  const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
44
- ({ className, variant, size, asChild = false, ...props }, ref) => {
45
- const Comp = asChild ? Slot : 'button'
46
- return (
47
- <Comp
48
- className={cn(buttonVariants({ variant, size, className }))}
49
- ref={ref}
50
- {...props}
51
- />
52
- )
53
- }
54
- )
55
- Button.displayName = 'Button'
56
 
57
- export { Button, buttonVariants }
 
1
+ import * as React from 'react';
2
+ import { Slot } from '@radix-ui/react-slot';
3
+ import { cva, type VariantProps } from 'class-variance-authority';
4
 
5
+ import { cn } from '@/lib/utils';
6
 
7
  const buttonVariants = cva(
8
+ 'inline-flex items-center justify-center rounded-md text-sm font-medium shadow ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default:
13
+ 'bg-primary text-primary-foreground shadow-md hover:bg-primary/90',
14
+ destructive:
15
+ 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
16
+ outline:
17
+ 'border border-input hover:bg-accent hover:text-accent-foreground',
18
+ secondary:
19
+ 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
20
+ ghost: 'shadow-none hover:bg-accent hover:text-accent-foreground',
21
+ link: 'text-primary underline-offset-4 shadow-none hover:underline',
22
+ },
23
+ size: {
24
+ default: 'h-8 px-4 py-2',
25
+ sm: 'h-8 rounded-md px-3',
26
+ lg: 'h-11 rounded-md px-8',
27
+ icon: 'size-8 p-0',
28
+ },
29
+ },
30
+ defaultVariants: {
31
+ variant: 'default',
32
+ size: 'default',
33
+ },
34
+ },
35
+ );
36
 
37
  export interface ButtonProps
38
+ extends React.ButtonHTMLAttributes<HTMLButtonElement>,
39
+ VariantProps<typeof buttonVariants> {
40
+ asChild?: boolean;
41
  }
42
 
43
  const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
44
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
45
+ const Comp = asChild ? Slot : 'button';
46
+ return (
47
+ <Comp
48
+ className={cn(buttonVariants({ variant, size, className }))}
49
+ ref={ref}
50
+ {...props}
51
+ />
52
+ );
53
+ },
54
+ );
55
+ Button.displayName = 'Button';
56
 
57
+ export { Button, buttonVariants };
components/ui/codeblock.tsx CHANGED
@@ -1,148 +1,148 @@
1
  // Inspired by Chatbot-UI and modified to fit the needs of this project
2
  // @see https://github.com/mckaywrigley/chatbot-ui/blob/main/components/Markdown/CodeBlock.tsx
3
 
4
- 'use client'
5
 
6
- import { FC, memo } from 'react'
7
- import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'
8
- import { coldarkDark } from 'react-syntax-highlighter/dist/cjs/styles/prism'
9
 
10
- import { useCopyToClipboard } from '@/lib/hooks/use-copy-to-clipboard'
11
- import { IconCheck, IconCopy, IconDownload } from '@/components/ui/icons'
12
- import { Button } from '@/components/ui/button'
13
 
14
  interface Props {
15
- language: string
16
- value: string
17
  }
18
 
19
  interface languageMap {
20
- [key: string]: string | undefined
21
  }
22
 
23
  export const programmingLanguages: languageMap = {
24
- javascript: '.js',
25
- python: '.py',
26
- java: '.java',
27
- c: '.c',
28
- cpp: '.cpp',
29
- 'c++': '.cpp',
30
- 'c#': '.cs',
31
- ruby: '.rb',
32
- php: '.php',
33
- swift: '.swift',
34
- 'objective-c': '.m',
35
- kotlin: '.kt',
36
- typescript: '.ts',
37
- go: '.go',
38
- perl: '.pl',
39
- rust: '.rs',
40
- scala: '.scala',
41
- haskell: '.hs',
42
- lua: '.lua',
43
- shell: '.sh',
44
- sql: '.sql',
45
- html: '.html',
46
- css: '.css'
47
- // add more file extensions here, make sure the key is same as language prop in CodeBlock.tsx component
48
- }
49
 
50
  export const generateRandomString = (length: number, lowercase = false) => {
51
- const chars = 'ABCDEFGHJKLMNPQRSTUVWXY3456789' // excluding similar looking characters like Z, 2, I, 1, O, 0
52
- let result = ''
53
- for (let i = 0; i < length; i++) {
54
- result += chars.charAt(Math.floor(Math.random() * chars.length))
55
- }
56
- return lowercase ? result.toLowerCase() : result
57
- }
58
 
59
  const CodeBlock: FC<Props> = memo(({ language, value }) => {
60
- const { isCopied, copyToClipboard } = useCopyToClipboard({ timeout: 2000 })
61
 
62
- const downloadAsFile = () => {
63
- if (typeof window === 'undefined') {
64
- return
65
- }
66
- const fileExtension = programmingLanguages[language] || '.file'
67
- const suggestedFileName = `file-${generateRandomString(
68
- 3,
69
- true
70
- )}${fileExtension}`
71
- const fileName = window.prompt('Enter file name' || '', suggestedFileName)
72
 
73
- if (!fileName) {
74
- // User pressed cancel on prompt.
75
- return
76
- }
77
 
78
- const blob = new Blob([value], { type: 'text/plain' })
79
- const url = URL.createObjectURL(blob)
80
- const link = document.createElement('a')
81
- link.download = fileName
82
- link.href = url
83
- link.style.display = 'none'
84
- document.body.appendChild(link)
85
- link.click()
86
- document.body.removeChild(link)
87
- URL.revokeObjectURL(url)
88
- }
89
 
90
- const onCopy = () => {
91
- if (isCopied) return
92
- copyToClipboard(value)
93
- }
94
 
95
- return (
96
- <div className="relative w-full font-sans codeblock bg-zinc-950">
97
- <div className="flex items-center justify-between w-full px-6 py-2 pr-4 bg-zinc-800 text-zinc-100">
98
- <span className="text-xs lowercase">{language}</span>
99
- <div className="flex items-center space-x-1">
100
- <Button
101
- variant="ghost"
102
- className="hover:bg-zinc-800 focus-visible:ring-1 focus-visible:ring-slate-700 focus-visible:ring-offset-0"
103
- onClick={downloadAsFile}
104
- size="icon"
105
- >
106
- <IconDownload />
107
- <span className="sr-only">Download</span>
108
- </Button>
109
- <Button
110
- variant="ghost"
111
- size="icon"
112
- className="text-xs hover:bg-zinc-800 focus-visible:ring-1 focus-visible:ring-slate-700 focus-visible:ring-offset-0"
113
- onClick={onCopy}
114
- >
115
- {isCopied ? <IconCheck /> : <IconCopy />}
116
- <span className="sr-only">Copy code</span>
117
- </Button>
118
- </div>
119
- </div>
120
- <SyntaxHighlighter
121
- language={language}
122
- style={coldarkDark}
123
- PreTag="div"
124
- showLineNumbers
125
- customStyle={{
126
- margin: 0,
127
- width: '100%',
128
- background: 'transparent',
129
- padding: '1.5rem 1rem'
130
- }}
131
- lineNumberStyle={{
132
- userSelect: "none",
133
- }}
134
- codeTagProps={{
135
- style: {
136
- fontSize: '0.9rem',
137
- fontFamily: 'var(--font-mono)'
138
- }
139
- }}
140
- >
141
- {value}
142
- </SyntaxHighlighter>
143
- </div>
144
- )
145
- })
146
- CodeBlock.displayName = 'CodeBlock'
147
 
148
- export { CodeBlock }
 
1
  // Inspired by Chatbot-UI and modified to fit the needs of this project
2
  // @see https://github.com/mckaywrigley/chatbot-ui/blob/main/components/Markdown/CodeBlock.tsx
3
 
4
+ 'use client';
5
 
6
+ import { FC, memo } from 'react';
7
+ import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
8
+ import { coldarkDark } from 'react-syntax-highlighter/dist/cjs/styles/prism';
9
 
10
+ import { useCopyToClipboard } from '@/lib/hooks/useCopyToClipboard';
11
+ import { IconCheck, IconCopy, IconDownload } from '@/components/ui/Icons';
12
+ import { Button } from '@/components/ui/Button';
13
 
14
  interface Props {
15
+ language: string;
16
+ value: string;
17
  }
18
 
19
  interface languageMap {
20
+ [key: string]: string | undefined;
21
  }
22
 
23
  export const programmingLanguages: languageMap = {
24
+ javascript: '.js',
25
+ python: '.py',
26
+ java: '.java',
27
+ c: '.c',
28
+ cpp: '.cpp',
29
+ 'c++': '.cpp',
30
+ 'c#': '.cs',
31
+ ruby: '.rb',
32
+ php: '.php',
33
+ swift: '.swift',
34
+ 'objective-c': '.m',
35
+ kotlin: '.kt',
36
+ typescript: '.ts',
37
+ go: '.go',
38
+ perl: '.pl',
39
+ rust: '.rs',
40
+ scala: '.scala',
41
+ haskell: '.hs',
42
+ lua: '.lua',
43
+ shell: '.sh',
44
+ sql: '.sql',
45
+ html: '.html',
46
+ css: '.css',
47
+ // add more file extensions here, make sure the key is same as language prop in CodeBlock.tsx component
48
+ };
49
 
50
  export const generateRandomString = (length: number, lowercase = false) => {
51
+ const chars = 'ABCDEFGHJKLMNPQRSTUVWXY3456789'; // excluding similar looking characters like Z, 2, I, 1, O, 0
52
+ let result = '';
53
+ for (let i = 0; i < length; i++) {
54
+ result += chars.charAt(Math.floor(Math.random() * chars.length));
55
+ }
56
+ return lowercase ? result.toLowerCase() : result;
57
+ };
58
 
59
  const CodeBlock: FC<Props> = memo(({ language, value }) => {
60
+ const { isCopied, copyToClipboard } = useCopyToClipboard({ timeout: 2000 });
61
 
62
+ const downloadAsFile = () => {
63
+ if (typeof window === 'undefined') {
64
+ return;
65
+ }
66
+ const fileExtension = programmingLanguages[language] || '.file';
67
+ const suggestedFileName = `file-${generateRandomString(
68
+ 3,
69
+ true,
70
+ )}${fileExtension}`;
71
+ const fileName = window.prompt('Enter file name' || '', suggestedFileName);
72
 
73
+ if (!fileName) {
74
+ // User pressed cancel on prompt.
75
+ return;
76
+ }
77
 
78
+ const blob = new Blob([value], { type: 'text/plain' });
79
+ const url = URL.createObjectURL(blob);
80
+ const link = document.createElement('a');
81
+ link.download = fileName;
82
+ link.href = url;
83
+ link.style.display = 'none';
84
+ document.body.appendChild(link);
85
+ link.click();
86
+ document.body.removeChild(link);
87
+ URL.revokeObjectURL(url);
88
+ };
89
 
90
+ const onCopy = () => {
91
+ if (isCopied) return;
92
+ copyToClipboard(value);
93
+ };
94
 
95
+ return (
96
+ <div className="relative w-full font-sans codeblock bg-zinc-950">
97
+ <div className="flex items-center justify-between w-full px-6 py-2 pr-4 bg-zinc-800 text-zinc-100">
98
+ <span className="text-xs lowercase">{language}</span>
99
+ <div className="flex items-center space-x-1">
100
+ <Button
101
+ variant="ghost"
102
+ className="hover:bg-zinc-800 focus-visible:ring-1 focus-visible:ring-slate-700 focus-visible:ring-offset-0"
103
+ onClick={downloadAsFile}
104
+ size="icon"
105
+ >
106
+ <IconDownload />
107
+ <span className="sr-only">Download</span>
108
+ </Button>
109
+ <Button
110
+ variant="ghost"
111
+ size="icon"
112
+ className="text-xs hover:bg-zinc-800 focus-visible:ring-1 focus-visible:ring-slate-700 focus-visible:ring-offset-0"
113
+ onClick={onCopy}
114
+ >
115
+ {isCopied ? <IconCheck /> : <IconCopy />}
116
+ <span className="sr-only">Copy code</span>
117
+ </Button>
118
+ </div>
119
+ </div>
120
+ <SyntaxHighlighter
121
+ language={language}
122
+ style={coldarkDark}
123
+ PreTag="div"
124
+ showLineNumbers
125
+ customStyle={{
126
+ margin: 0,
127
+ width: '100%',
128
+ background: 'transparent',
129
+ padding: '1.5rem 1rem',
130
+ }}
131
+ lineNumberStyle={{
132
+ userSelect: 'none',
133
+ }}
134
+ codeTagProps={{
135
+ style: {
136
+ fontSize: '0.9rem',
137
+ fontFamily: 'var(--font-mono)',
138
+ },
139
+ }}
140
+ >
141
+ {value}
142
+ </SyntaxHighlighter>
143
+ </div>
144
+ );
145
+ });
146
+ CodeBlock.displayName = 'CodeBlock';
147
 
148
+ export { CodeBlock };
components/ui/dialog.tsx DELETED
@@ -1,122 +0,0 @@
1
- 'use client'
2
-
3
- import * as React from 'react'
4
- import * as DialogPrimitive from '@radix-ui/react-dialog'
5
-
6
- import { cn } from '@/lib/utils'
7
- import { IconClose } from '@/components/ui/icons'
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<typeof DialogPrimitive.Overlay>,
19
- React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
20
- >(({ className, ...props }, ref) => (
21
- <DialogPrimitive.Overlay
22
- ref={ref}
23
- className={cn(
24
- 'fixed inset-0 z-50 bg-background/80 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
25
- className
26
- )}
27
- {...props}
28
- />
29
- ))
30
- DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
31
-
32
- const DialogContent = React.forwardRef<
33
- React.ElementRef<typeof DialogPrimitive.Content>,
34
- React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
35
- >(({ className, children, ...props }, ref) => (
36
- <DialogPortal>
37
- <DialogOverlay />
38
- <DialogPrimitive.Content
39
- ref={ref}
40
- className={cn(
41
- 'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out 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-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg',
42
- className
43
- )}
44
- {...props}
45
- >
46
- {children}
47
- <DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
48
- <IconClose className="size-4" />
49
- <span className="sr-only">Close</span>
50
- </DialogPrimitive.Close>
51
- </DialogPrimitive.Content>
52
- </DialogPortal>
53
- ))
54
- DialogContent.displayName = DialogPrimitive.Content.displayName
55
-
56
- const DialogHeader = ({
57
- className,
58
- ...props
59
- }: React.HTMLAttributes<HTMLDivElement>) => (
60
- <div
61
- className={cn(
62
- 'flex flex-col space-y-1.5 text-center sm:text-left',
63
- className
64
- )}
65
- {...props}
66
- />
67
- )
68
- DialogHeader.displayName = 'DialogHeader'
69
-
70
- const DialogFooter = ({
71
- className,
72
- ...props
73
- }: React.HTMLAttributes<HTMLDivElement>) => (
74
- <div
75
- className={cn(
76
- 'flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2',
77
- className
78
- )}
79
- {...props}
80
- />
81
- )
82
- DialogFooter.displayName = 'DialogFooter'
83
-
84
- const DialogTitle = React.forwardRef<
85
- React.ElementRef<typeof DialogPrimitive.Title>,
86
- React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
87
- >(({ className, ...props }, ref) => (
88
- <DialogPrimitive.Title
89
- ref={ref}
90
- className={cn(
91
- 'text-lg font-semibold leading-none tracking-tight',
92
- className
93
- )}
94
- {...props}
95
- />
96
- ))
97
- DialogTitle.displayName = DialogPrimitive.Title.displayName
98
-
99
- const DialogDescription = React.forwardRef<
100
- React.ElementRef<typeof DialogPrimitive.Description>,
101
- React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
102
- >(({ className, ...props }, ref) => (
103
- <DialogPrimitive.Description
104
- ref={ref}
105
- className={cn('text-sm text-muted-foreground', className)}
106
- {...props}
107
- />
108
- ))
109
- DialogDescription.displayName = DialogPrimitive.Description.displayName
110
-
111
- export {
112
- Dialog,
113
- DialogPortal,
114
- DialogOverlay,
115
- DialogClose,
116
- DialogTrigger,
117
- DialogContent,
118
- DialogHeader,
119
- DialogFooter,
120
- DialogTitle,
121
- DialogDescription
122
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
components/ui/dropdown-menu.tsx DELETED
@@ -1,128 +0,0 @@
1
- 'use client'
2
-
3
- import * as React from 'react'
4
- import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu'
5
-
6
- import { cn } from '@/lib/utils'
7
-
8
- const DropdownMenu = DropdownMenuPrimitive.Root
9
-
10
- const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
11
-
12
- const DropdownMenuGroup = DropdownMenuPrimitive.Group
13
-
14
- const DropdownMenuPortal = DropdownMenuPrimitive.Portal
15
-
16
- const DropdownMenuSub = DropdownMenuPrimitive.Sub
17
-
18
- const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
19
-
20
- const DropdownMenuSubContent = React.forwardRef<
21
- React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
22
- React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
23
- >(({ className, ...props }, ref) => (
24
- <DropdownMenuPrimitive.SubContent
25
- ref={ref}
26
- className={cn(
27
- 'z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md animate-in data-[side=bottom]:slide-in-from-top-1 data-[side=left]:slide-in-from-right-1 data-[side=right]:slide-in-from-left-1 data-[side=top]:slide-in-from-bottom-1',
28
- className
29
- )}
30
- {...props}
31
- />
32
- ))
33
- DropdownMenuSubContent.displayName =
34
- DropdownMenuPrimitive.SubContent.displayName
35
-
36
- const DropdownMenuContent = React.forwardRef<
37
- React.ElementRef<typeof DropdownMenuPrimitive.Content>,
38
- React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
39
- >(({ className, sideOffset = 4, ...props }, ref) => (
40
- <DropdownMenuPrimitive.Portal>
41
- <DropdownMenuPrimitive.Content
42
- ref={ref}
43
- sideOffset={sideOffset}
44
- className={cn(
45
- 'z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow animate-in 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',
46
- className
47
- )}
48
- {...props}
49
- />
50
- </DropdownMenuPrimitive.Portal>
51
- ))
52
- DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
53
-
54
- const DropdownMenuItem = React.forwardRef<
55
- React.ElementRef<typeof DropdownMenuPrimitive.Item>,
56
- React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
57
- inset?: boolean
58
- }
59
- >(({ className, inset, ...props }, ref) => (
60
- <DropdownMenuPrimitive.Item
61
- ref={ref}
62
- className={cn(
63
- 'relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
64
- inset && 'pl-8',
65
- className
66
- )}
67
- {...props}
68
- />
69
- ))
70
- DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
71
-
72
- const DropdownMenuLabel = React.forwardRef<
73
- React.ElementRef<typeof DropdownMenuPrimitive.Label>,
74
- React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
75
- inset?: boolean
76
- }
77
- >(({ className, inset, ...props }, ref) => (
78
- <DropdownMenuPrimitive.Label
79
- ref={ref}
80
- className={cn(
81
- 'px-2 py-1.5 text-sm font-semibold',
82
- inset && 'pl-8',
83
- className
84
- )}
85
- {...props}
86
- />
87
- ))
88
- DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
89
-
90
- const DropdownMenuSeparator = React.forwardRef<
91
- React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
92
- React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
93
- >(({ className, ...props }, ref) => (
94
- <DropdownMenuPrimitive.Separator
95
- ref={ref}
96
- className={cn('-mx-1 my-1 h-px bg-muted', className)}
97
- {...props}
98
- />
99
- ))
100
- DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
101
-
102
- const DropdownMenuShortcut = ({
103
- className,
104
- ...props
105
- }: React.HTMLAttributes<HTMLSpanElement>) => {
106
- return (
107
- <span
108
- className={cn('ml-auto text-xs tracking-widest opacity-60', className)}
109
- {...props}
110
- />
111
- )
112
- }
113
- DropdownMenuShortcut.displayName = 'DropdownMenuShortcut'
114
-
115
- export {
116
- DropdownMenu,
117
- DropdownMenuTrigger,
118
- DropdownMenuContent,
119
- DropdownMenuItem,
120
- DropdownMenuLabel,
121
- DropdownMenuSeparator,
122
- DropdownMenuShortcut,
123
- DropdownMenuGroup,
124
- DropdownMenuPortal,
125
- DropdownMenuSub,
126
- DropdownMenuSubContent,
127
- DropdownMenuRadioGroup
128
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
components/ui/input.tsx CHANGED
@@ -1,25 +1,25 @@
1
- import * as React from 'react'
2
 
3
- import { cn } from '@/lib/utils'
4
 
5
  export interface InputProps
6
- extends React.InputHTMLAttributes<HTMLInputElement> {}
7
 
8
  const Input = React.forwardRef<HTMLInputElement, InputProps>(
9
- ({ className, type, ...props }, ref) => {
10
- return (
11
- <input
12
- type={type}
13
- className={cn(
14
- 'flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
15
- className
16
- )}
17
- ref={ref}
18
- {...props}
19
- />
20
- )
21
- }
22
- )
23
- Input.displayName = 'Input'
24
 
25
- export { Input }
 
1
+ import * as React from 'react';
2
 
3
+ import { cn } from '@/lib/utils';
4
 
5
  export interface InputProps
6
+ extends React.InputHTMLAttributes<HTMLInputElement> {}
7
 
8
  const Input = React.forwardRef<HTMLInputElement, InputProps>(
9
+ ({ className, type, ...props }, ref) => {
10
+ return (
11
+ <input
12
+ type={type}
13
+ className={cn(
14
+ 'flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
15
+ className,
16
+ )}
17
+ ref={ref}
18
+ {...props}
19
+ />
20
+ );
21
+ },
22
+ );
23
+ Input.displayName = 'Input';
24
 
25
+ export { Input };
components/ui/label.tsx DELETED
@@ -1,26 +0,0 @@
1
- "use client"
2
-
3
- import * as React from "react"
4
- import * as LabelPrimitive from "@radix-ui/react-label"
5
- import { cva, type VariantProps } from "class-variance-authority"
6
-
7
- import { cn } from "@/lib/utils"
8
-
9
- const labelVariants = cva(
10
- "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
11
- )
12
-
13
- const Label = React.forwardRef<
14
- React.ElementRef<typeof LabelPrimitive.Root>,
15
- React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
16
- VariantProps<typeof labelVariants>
17
- >(({ className, ...props }, ref) => (
18
- <LabelPrimitive.Root
19
- ref={ref}
20
- className={cn(labelVariants(), className)}
21
- {...props}
22
- />
23
- ))
24
- Label.displayName = LabelPrimitive.Root.displayName
25
-
26
- export { Label }