Spaces:
Sleeping
Sleeping
MingruiZhang
commited on
Commit
Β·
052672d
1
Parent(s):
41adc65
clean up code
Browse filesThis view is limited to 50 files because it contains too many changes. Β
See raw diff
- app/(chat)/chat/[id]/page.tsx +0 -47
- app/(chat)/layout.tsx +9 -11
- app/(chat)/page.tsx +2 -2
- app/actions.ts +0 -129
- app/layout.tsx +3 -3
- app/opengraph-image.png +0 -0
- app/sign-in/page.tsx +2 -2
- app/twitter-image.png +0 -0
- components/{login-button.tsx β LoginButton.tsx} +2 -2
- components/TailwindIndicator.tsx +14 -0
- components/{theme-toggle.tsx β ThemeToggle.tsx} +2 -2
- components/{user-menu.tsx β UserMenu.tsx} +3 -3
- components/button-scroll-to-bottom.tsx +0 -34
- components/chat-history.tsx +0 -46
- components/chat-scroll-anchor.tsx +0 -29
- components/chat-share-dialog.tsx +0 -106
- components/chat/ButtonScrollToBottom.tsx +34 -0
- components/chat/ChatList.tsx +2 -2
- components/{chat-message.tsx β chat/ChatMessage.tsx} +5 -5
- components/{chat-message-actions.tsx β chat/ChatMessageActions.tsx} +4 -4
- components/{chat-panel.tsx β chat/ChatPanel.tsx} +5 -29
- components/chat/ChatScrollAnchor.tsx +29 -0
- components/{empty-screen.tsx β chat/EmptyScreen.tsx} +2 -2
- components/chat/MemoizedReactMarkdown.tsx +9 -0
- components/{prompt-form.tsx β chat/PromptForm.tsx} +4 -4
- components/chat/index.tsx +2 -2
- components/clear-history.tsx +0 -77
- components/external-link.tsx +0 -29
- components/header.tsx +27 -66
- components/markdown.tsx +0 -9
- components/providers.tsx +10 -13
- components/sidebar-actions.tsx +0 -125
- components/sidebar-desktop.tsx +0 -21
- components/sidebar-footer.tsx +0 -16
- components/sidebar-item.tsx +0 -124
- components/sidebar-items.tsx +0 -42
- components/sidebar-list.tsx +0 -38
- components/sidebar-mobile.tsx +0 -28
- components/sidebar-toggle.tsx +0 -24
- components/sidebar.tsx +0 -21
- components/tailwind-indicator.tsx +0 -14
- components/ui/DropdownMenu.tsx +128 -0
- components/ui/alert-dialog.tsx +0 -141
- components/ui/badge.tsx +0 -36
- components/ui/button.tsx +48 -48
- components/ui/codeblock.tsx +125 -125
- components/ui/dialog.tsx +0 -122
- components/ui/dropdown-menu.tsx +0 -128
- components/ui/input.tsx +19 -19
- 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 |
-
|
5 |
}
|
6 |
|
7 |
export default async function ChatLayout({ children }: ChatLayoutProps) {
|
8 |
-
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
|
14 |
-
|
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/
|
6 |
import { useAtomValue } from 'jotai';
|
7 |
import { datasetAtom } from '../../state';
|
8 |
-
import { EmptyScreen } from '../../components/
|
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/
|
8 |
-
import { Providers } from '@/components/
|
9 |
-
import { Header } from '@/components/
|
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/
|
3 |
import { redirect } from 'next/navigation';
|
4 |
-
import { ThemeToggle } from '../../components/
|
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/
|
8 |
-
import { IconGitHub, IconSpinner, IconGoogle } from '@/components/ui/
|
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/
|
7 |
-
import { IconMoon, IconSun } from '@/components/ui/
|
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/
|
8 |
import {
|
9 |
DropdownMenu,
|
10 |
DropdownMenuContent,
|
11 |
DropdownMenuItem,
|
12 |
DropdownMenuSeparator,
|
13 |
DropdownMenuTrigger,
|
14 |
-
} from '@/components/ui/
|
15 |
-
import { IconExternalLink } from '@/components/ui/
|
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/
|
4 |
-
import { ChatMessage } from '@/components/chat
|
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/
|
9 |
-
import { MemoizedReactMarkdown } from '@/components/
|
10 |
-
import { IconOpenAI, IconUser } from '@/components/ui/
|
11 |
-
import { ChatMessageActions } from '@/components/chat
|
12 |
-
import { MessageWithSelectedDataset } from '
|
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/
|
6 |
-
import { IconCheck, IconCopy } from '@/components/ui/
|
7 |
-
import { useCopyToClipboard } from '@/lib/hooks/
|
8 |
import { cn } from '@/lib/utils';
|
9 |
-
import { MessageWithSelectedDataset } from '
|
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 {
|
5 |
-
import {
|
6 |
-
import {
|
7 |
-
import {
|
8 |
-
import {
|
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 '
|
4 |
import Image from 'next/image';
|
5 |
-
import useImageUpload from '
|
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/
|
5 |
import { cn } from '@/lib/utils';
|
6 |
-
import { Button, buttonVariants } from '@/components/ui/
|
7 |
import {
|
8 |
Tooltip,
|
9 |
TooltipContent,
|
10 |
TooltipTrigger,
|
11 |
-
} from '@/components/ui/
|
12 |
-
import { IconArrowElbow, IconPlus } from '@/components/ui/
|
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
|
5 |
-
import { ChatScrollAnchor } from '@/components/chat
|
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 {
|
5 |
-
import {
|
6 |
-
import {
|
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 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
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 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
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 {
|
7 |
-
import { TooltipProvider } from '@/components/ui/tooltip'
|
8 |
|
9 |
export function Providers({ children, ...props }: ThemeProviderProps) {
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
|
14 |
-
|
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 |
-
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
|
14 |
-
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
)
|
36 |
|
37 |
export interface ButtonProps
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
}
|
42 |
|
43 |
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
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/
|
11 |
-
import { IconCheck, IconCopy, IconDownload } from '@/components/ui/
|
12 |
-
import { Button } from '@/components/ui/
|
13 |
|
14 |
interface Props {
|
15 |
-
|
16 |
-
|
17 |
}
|
18 |
|
19 |
interface languageMap {
|
20 |
-
|
21 |
}
|
22 |
|
23 |
export const programmingLanguages: languageMap = {
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
}
|
49 |
|
50 |
export const generateRandomString = (length: number, lowercase = false) => {
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
}
|
58 |
|
59 |
const CodeBlock: FC<Props> = memo(({ language, value }) => {
|
60 |
-
|
61 |
|
62 |
-
|
63 |
-
|
64 |
-
|
65 |
-
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
|
71 |
-
|
72 |
|
73 |
-
|
74 |
-
|
75 |
-
|
76 |
-
|
77 |
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
|
90 |
-
|
91 |
-
|
92 |
-
|
93 |
-
|
94 |
|
95 |
-
|
96 |
-
|
97 |
-
|
98 |
-
|
99 |
-
|
100 |
-
|
101 |
-
|
102 |
-
|
103 |
-
|
104 |
-
|
105 |
-
|
106 |
-
|
107 |
-
|
108 |
-
|
109 |
-
|
110 |
-
|
111 |
-
|
112 |
-
|
113 |
-
|
114 |
-
|
115 |
-
|
116 |
-
|
117 |
-
|
118 |
-
|
119 |
-
|
120 |
-
|
121 |
-
|
122 |
-
|
123 |
-
|
124 |
-
|
125 |
-
|
126 |
-
|
127 |
-
|
128 |
-
|
129 |
-
|
130 |
-
|
131 |
-
|
132 |
-
|
133 |
-
|
134 |
-
|
135 |
-
|
136 |
-
|
137 |
-
|
138 |
-
|
139 |
-
|
140 |
-
|
141 |
-
|
142 |
-
|
143 |
-
|
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 |
-
|
7 |
|
8 |
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
|
14 |
-
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
|
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 }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|