From 567b0ac3134018d807749f60f36b3f91ff29cec8 Mon Sep 17 00:00:00 2001 From: Jared Palmer Date: Tue, 13 Jun 2023 17:31:15 -0400 Subject: [PATCH 01/18] Move to clerk --- app/actions.ts | 22 +- app/api/auth/[...nextauth]/route.ts | 2 - app/api/chat/route.ts | 28 +- app/chat/[id]/page.tsx | 21 +- app/layout.tsx | 37 ++- app/page.tsx | 11 +- app/sign-in/[[...sign-in]]/page.tsx | 11 + app/sign-up/[[...sign-up]]/page.tsx | 11 + auth.ts | 27 -- components/chat.tsx | 2 +- components/header.tsx | 17 +- components/login-button.tsx | 5 +- components/sidebar-list.tsx | 13 +- components/sidebar.tsx | 14 +- components/ui/icons.tsx | 21 ++ middleware.ts | 8 +- package.json | 5 +- pnpm-lock.yaml | 473 ++++++++++++++++++++++++---- 18 files changed, 550 insertions(+), 178 deletions(-) delete mode 100644 app/api/auth/[...nextauth]/route.ts create mode 100644 app/sign-in/[[...sign-in]]/page.tsx create mode 100644 app/sign-up/[[...sign-up]]/page.tsx delete mode 100644 auth.ts diff --git a/app/actions.ts b/app/actions.ts index 3d40e88..075918f 100644 --- a/app/actions.ts +++ b/app/actions.ts @@ -4,6 +4,7 @@ import { revalidatePath } from 'next/cache' import { kv } from '@vercel/kv' import { type Chat } from '@/lib/types' +import { currentUser } from '@clerk/nextjs' export async function getChats(userId?: string | null) { if (!userId) { @@ -40,24 +41,19 @@ export async function getChat(id: string, userId: string) { return chat } -export async function removeChat({ - id, - path, - userId -}: { - id: string - userId: string - path: string -}) { - // @todo next-auth@v5 doesn't work in server actions yet - // const session = await auth(); +export async function removeChat({ id, path }: { id: string; path: string }) { + const user = await currentUser() + + if (!user) { + throw new Error('Unauthorized') + } const uid = await kv.hget(`chat:${id}`, 'userId') - if (uid !== userId) { + if (uid !== user.id) { throw new Error('Unauthorized') } await kv.del(`chat:${id}`) - await kv.zrem(`user:chat:${userId}`, `chat:${id}`) + await kv.zrem(`user:chat:${user.id}`, `chat:${id}`) revalidatePath('/') revalidatePath('/chat/[id]') diff --git a/app/api/auth/[...nextauth]/route.ts b/app/api/auth/[...nextauth]/route.ts deleted file mode 100644 index 4463a8e..0000000 --- a/app/api/auth/[...nextauth]/route.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { GET, POST } from '@/auth' -// export const runtime = 'edge' diff --git a/app/api/chat/route.ts b/app/api/chat/route.ts index 3a91d6e..6feb8e5 100644 --- a/app/api/chat/route.ts +++ b/app/api/chat/route.ts @@ -1,11 +1,11 @@ -import { auth } from '@/auth' import { kv } from '@vercel/kv' import { OpenAIStream, StreamingTextResponse } from 'ai-connector' import { Configuration, OpenAIApi } from 'openai-edge' import { nanoid } from '@/lib/utils' +import { currentUser } from '@clerk/nextjs' -// export const runtime = 'edge' +export const runtime = 'edge' const configuration = new Configuration({ apiKey: process.env.OPENAI_API_KEY @@ -17,13 +17,15 @@ if (!process.env.OPENAI_API_KEY) { throw new Error('Missing env var from OpenAI') } -export const POST = auth(async function POST(req: Request) { +export async function POST(req: Request) { + const user = await currentUser() + if (user == null) { + return new Response('Unauthorized', { status: 401 }) + } + const json = await req.json() - // @ts-ignore - const messages = json.messages.map((m: any) => ({ - content: m.content, - role: m.role - })) + const { messages } = json + const res = await openai.createChatCompletion({ model: 'gpt-3.5-turbo', messages, @@ -35,14 +37,10 @@ export const POST = auth(async function POST(req: Request) { stream: true }) - const stream = await OpenAIStream(res, { + const stream = OpenAIStream(res, { async onCompletion(completion) { - // @ts-ignore - if (req.auth?.user?.email == null) { - return - } const title = json.messages[0].content.substring(0, 100) - const userId = (req as any).auth?.user?.email + const userId = user.id const id = json.id ?? nanoid() const createdAt = Date.now() const payload = { @@ -67,4 +65,4 @@ export const POST = auth(async function POST(req: Request) { }) return new StreamingTextResponse(stream) -}) +} diff --git a/app/chat/[id]/page.tsx b/app/chat/[id]/page.tsx index 728ef16..a125141 100644 --- a/app/chat/[id]/page.tsx +++ b/app/chat/[id]/page.tsx @@ -1,8 +1,9 @@ import { type Metadata } from 'next' -import { auth } from '@/auth' import { Chat } from '@/components/chat' import { getChat } from '@/app/actions' +import { Header } from '@/components/header' +import { auth } from '@clerk/nextjs' // export const runtime = 'edge' export const preferredRegion = 'home' @@ -16,16 +17,24 @@ export interface ChatPageProps { export async function generateMetadata({ params }: ChatPageProps): Promise { - const session = await auth() - const chat = await getChat(params.id, session?.user?.email ?? '') + const { user } = await auth() + const chat = await getChat(params.id, user?.id ?? '') return { title: chat?.title.slice(0, 50) ?? 'Chat' } } export default async function ChatPage({ params }: ChatPageProps) { - const session = await auth() - const chat = await getChat(params.id, session?.user?.email ?? '') + const { user } = await auth() + const chat = await getChat(params.id, user?.id ?? '') - return + return ( +
+ {/* @ts-ignore */} +
+
+ +
+
+ ) } diff --git a/app/layout.tsx b/app/layout.tsx index 4d366d0..d92c416 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -6,6 +6,7 @@ import { cn } from '@/lib/utils' import { Header } from '@/components/header' import { TailwindIndicator } from '@/components/tailwind-indicator' import { ThemeProvider } from '@/components/theme-provider' +import { ClerkProvider } from '@clerk/nextjs' export const metadata: Metadata = { title: { @@ -30,24 +31,22 @@ interface RootLayoutProps { export default function RootLayout({ children }: RootLayoutProps) { return ( - - - - -
- {/* @ts-ignore */} -
-
{children}
-
- -
- - + + + + + + {children} + + + + + ) } diff --git a/app/page.tsx b/app/page.tsx index ee1ff4c..667918a 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,8 +1,17 @@ import { Chat } from '@/components/chat' +import { Header } from '@/components/header' // export const runtime = 'edge' export const preferredRegion = 'home' export default async function IndexPage() { - return + return ( +
+ {/* @ts-ignore */} +
+
+ +
+
+ ) } diff --git a/app/sign-in/[[...sign-in]]/page.tsx b/app/sign-in/[[...sign-in]]/page.tsx new file mode 100644 index 0000000..3b64227 --- /dev/null +++ b/app/sign-in/[[...sign-in]]/page.tsx @@ -0,0 +1,11 @@ +import { SignIn } from '@clerk/nextjs' + +export default function Page() { + return ( +
+
+ +
+
+ ) +} diff --git a/app/sign-up/[[...sign-up]]/page.tsx b/app/sign-up/[[...sign-up]]/page.tsx new file mode 100644 index 0000000..38e2669 --- /dev/null +++ b/app/sign-up/[[...sign-up]]/page.tsx @@ -0,0 +1,11 @@ +import { SignUp } from '@clerk/nextjs' + +export default function Page() { + return ( +
+
+ +
+
+ ) +} diff --git a/auth.ts b/auth.ts deleted file mode 100644 index aee24e3..0000000 --- a/auth.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { NextResponse } from 'next/server' -import NextAuth from '@auth/nextjs' -import GitHub from '@auth/nextjs/providers/github' - -export const { - handlers: { GET, POST }, - auth, - CSRF_experimental -} = NextAuth({ - // @ts-ignore - providers: [GitHub], - session: { strategy: 'jwt' }, - async authorized({ request, auth }: any) { - const url = request.nextUrl - - if (request.method === 'POST') { - const { authToken } = (await request.json()) ?? {} - // If the request has a valid auth token, it is authorized - const valid = true - if (valid) return true - return NextResponse.json('Invalid auth token', { status: 401 }) - } - - // Logged in users are authenticated, otherwise redirect to login page - return !!auth - } -}) diff --git a/components/chat.tsx b/components/chat.tsx index 4352cc6..0949dff 100644 --- a/components/chat.tsx +++ b/components/chat.tsx @@ -1,6 +1,6 @@ 'use client' -import { useChat, type Message } from 'ai-connector' +import { useChat, type Message } from 'ai-connector/react' import { cn } from '@/lib/utils' import { ChatList } from '@/components/chat-list' diff --git a/components/header.tsx b/components/header.tsx index d59f67a..56d03f8 100644 --- a/components/header.tsx +++ b/components/header.tsx @@ -1,29 +1,28 @@ import { Suspense } from 'react' -import { auth } from '@/auth' import { cn } from '@/lib/utils' import { buttonVariants } from '@/components/ui/button' import { Sidebar } from '@/components/sidebar' -import { UserMenu } from '@/components/user-menu' import { SidebarList } from '@/components/sidebar-list' import { IconGitHub, IconSeparator, IconVercel } from '@/components/ui/icons' +import { UserButton, currentUser } from '@clerk/nextjs' export async function Header() { - const session = await auth() + const user = await currentUser() return ( -
+
{/* @ts-ignore */} - + }> {/* @ts-ignore */} - +
- - + +
@@ -34,7 +33,7 @@ export async function Header() { className={cn(buttonVariants({ variant: 'outline' }))} > - GitHub + GitHub { setIsLoading(true) - signIn('github') + clerk.openSignIn({}) }} disabled={isLoading} className={cn(className)} diff --git a/components/sidebar-list.tsx b/components/sidebar-list.tsx index 1e2278b..ce603ca 100644 --- a/components/sidebar-list.tsx +++ b/components/sidebar-list.tsx @@ -1,17 +1,16 @@ import { getChats } from '@/app/actions' -import { Session } from '@auth/core/types' import { SidebarItem } from '@/components/sidebar-item' import { LoginButton } from '@/components/login-button' export interface SidebarListProps { - session?: Session + userId?: string } -export async function SidebarList({ session }: SidebarListProps) { - if (!session?.user?.email) { +export async function SidebarList({ userId }: SidebarListProps) { + if (!userId) { return ( -
+

You are not logged in!

@@ -23,7 +22,7 @@ export async function SidebarList({ session }: SidebarListProps) { ) } - const chats = await getChats(session.user.email) + const chats = await getChats(userId) return (

@@ -33,7 +32,7 @@ export async function SidebarList({ session }: SidebarListProps) { diff --git a/components/sidebar.tsx b/components/sidebar.tsx index a4cbbc5..9724235 100644 --- a/components/sidebar.tsx +++ b/components/sidebar.tsx @@ -1,8 +1,6 @@ 'use client' import * as React from 'react' -import { signOut } from '@auth/nextjs/client' -import { type Session } from '@auth/nextjs/types' import { Button } from '@/components/ui/button' import { @@ -14,18 +12,20 @@ import { } from '@/components/ui/sheet' import { ThemeToggle } from '@/components/theme-toggle' import { IconSidebar } from '@/components/ui/icons' +import { useClerk } from '@clerk/nextjs' export interface SidebarProps { - session?: Session + userId?: string children?: React.ReactNode } -export function Sidebar({ session, children }: SidebarProps) { +export function Sidebar({ userId, children }: SidebarProps) { + const { signOut } = useClerk() return ( - @@ -36,7 +36,7 @@ export function Sidebar({ session, children }: SidebarProps) { {children}
- {session?.user && ( + {userId && ( diff --git a/components/ui/icons.tsx b/components/ui/icons.tsx index 704fef9..60f1142 100644 --- a/components/ui/icons.tsx +++ b/components/ui/icons.tsx @@ -232,7 +232,7 @@ function IconSpinner({ className, ...props }: React.ComponentProps<'svg'>) { xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor" - className={cn('h-4 w-4', className)} + className={cn('h-4 w-4 animate-spin', className)} {...props} > From f2c71a0f9d50551294a6e4d001a1161f5263c684 Mon Sep 17 00:00:00 2001 From: shadcn Date: Wed, 14 Jun 2023 16:04:40 +0400 Subject: [PATCH 04/18] fix: remove unused user-menu --- components/user-menu.tsx | 24 ------------------------ 1 file changed, 24 deletions(-) delete mode 100644 components/user-menu.tsx diff --git a/components/user-menu.tsx b/components/user-menu.tsx deleted file mode 100644 index c5b96e2..0000000 --- a/components/user-menu.tsx +++ /dev/null @@ -1,24 +0,0 @@ -'use client' - -import * as React from 'react' -import { type Session } from '@auth/nextjs/types' - -import { LoginButton } from '@/components/login-button' - -export interface UserMenuProps { - session?: Session -} - -export function UserMenu({ session }: UserMenuProps) { - if (!session?.user) { - return ( - - ) - } - - return ( -

- {session.user.name} -

- ) -} From 5c01f8a637e1d7aa5c4c624d0fb3015b8eab5346 Mon Sep 17 00:00:00 2001 From: shadcn Date: Wed, 14 Jun 2023 16:05:03 +0400 Subject: [PATCH 05/18] fix: use ai/react --- app/chat/[id]/page.tsx | 4 ++-- components/chat-panel.tsx | 2 +- components/empty-screen.tsx | 4 ++-- components/prompt-form.tsx | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/chat/[id]/page.tsx b/app/chat/[id]/page.tsx index a125141..f0d4034 100644 --- a/app/chat/[id]/page.tsx +++ b/app/chat/[id]/page.tsx @@ -1,9 +1,9 @@ import { type Metadata } from 'next' +import { auth } from '@clerk/nextjs' import { Chat } from '@/components/chat' import { getChat } from '@/app/actions' import { Header } from '@/components/header' -import { auth } from '@clerk/nextjs' // export const runtime = 'edge' export const preferredRegion = 'home' @@ -29,7 +29,7 @@ export default async function ChatPage({ params }: ChatPageProps) { const chat = await getChat(params.id, user?.id ?? '') return ( -
+
{/* @ts-ignore */}
diff --git a/components/chat-panel.tsx b/components/chat-panel.tsx index 6bd0a40..3bd0d18 100644 --- a/components/chat-panel.tsx +++ b/components/chat-panel.tsx @@ -1,4 +1,4 @@ -import { type UseChatHelpers } from 'ai-connector' +import { type UseChatHelpers } from 'ai-connector/react' import { Button } from '@/components/ui/button' import { ExternalLink } from '@/components/external-link' diff --git a/components/empty-screen.tsx b/components/empty-screen.tsx index e739b31..f47d1b0 100644 --- a/components/empty-screen.tsx +++ b/components/empty-screen.tsx @@ -1,8 +1,8 @@ -import { cn } from '@/lib/utils' +import { UseChatHelpers } from 'ai-connector/react' + import { Button } from '@/components/ui/button' import { ExternalLink } from '@/components/external-link' import { IconArrowRight } from '@/components/ui/icons' -import { UseChatHelpers } from 'ai-connector' const exampleMessages = [ { diff --git a/components/prompt-form.tsx b/components/prompt-form.tsx index 1fbc7b1..2dec66a 100644 --- a/components/prompt-form.tsx +++ b/components/prompt-form.tsx @@ -1,6 +1,7 @@ import * as React from 'react' import Link from 'next/link' import Textarea from 'react-textarea-autosize' +import { UseChatHelpers } from 'ai-connector/react' import { useEnterSubmit } from '@/lib/hooks/use-enter-submit' import { cn } from '@/lib/utils' @@ -12,7 +13,6 @@ import { TooltipTrigger } from '@/components/ui/tooltip' import { IconArrowElbow, IconPlus } from '@/components/ui/icons' -import { UseChatHelpers } from 'ai-connector' export interface PromptProps extends Pick { From c0011ed15fa467c42716d80bd132638ac0bf2bcf Mon Sep 17 00:00:00 2001 From: shadcn Date: Wed, 14 Jun 2023 16:05:20 +0400 Subject: [PATCH 06/18] feat: add style for user button --- components/header.tsx | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/components/header.tsx b/components/header.tsx index 56d03f8..bc4619a 100644 --- a/components/header.tsx +++ b/components/header.tsx @@ -11,7 +11,7 @@ export async function Header() { const user = await currentUser() return ( -
+
{/* @ts-ignore */} @@ -21,8 +21,27 @@ export async function Header() {
- - + + *]:dark:text-zinc-600', + userPreview: 'p-4 border-b border-border m-0', + userButtonPopoverActionButton: 'px-1 gap-1', + userButtonPopoverActionButtonText: + 'text-sm tracking-normal dark:text-zinc-400', + userButtonPopoverActionButtonIcon: + 'h-4 w-4 text-muted-foreground' + } + }} + />
@@ -33,7 +52,7 @@ export async function Header() { className={cn(buttonVariants({ variant: 'outline' }))} > - GitHub + GitHub Date: Wed, 14 Jun 2023 16:39:55 +0400 Subject: [PATCH 07/18] fix: workaround for using currentUser in server actions --- app/actions.ts | 2 +- components/chat-list.tsx | 2 -- components/sidebar-item.tsx | 19 +++++++------------ components/sidebar-list.tsx | 21 ++------------------- components/sidebar.tsx | 1 + 5 files changed, 11 insertions(+), 34 deletions(-) diff --git a/app/actions.ts b/app/actions.ts index 075918f..94016a5 100644 --- a/app/actions.ts +++ b/app/actions.ts @@ -56,5 +56,5 @@ export async function removeChat({ id, path }: { id: string; path: string }) { await kv.zrem(`user:chat:${user.id}`, `chat:${id}`) revalidatePath('/') - revalidatePath('/chat/[id]') + revalidatePath(path) } diff --git a/components/chat-list.tsx b/components/chat-list.tsx index cf6b830..ef18c87 100644 --- a/components/chat-list.tsx +++ b/components/chat-list.tsx @@ -1,5 +1,3 @@ -'use client' - import { type Message } from 'ai-connector' import { Separator } from '@/components/ui/separator' diff --git a/components/sidebar-item.tsx b/components/sidebar-item.tsx index ea0295d..3fd627f 100644 --- a/components/sidebar-item.tsx +++ b/components/sidebar-item.tsx @@ -1,7 +1,6 @@ 'use client' import * as React from 'react' -import { useTransition } from 'react' import Link from 'next/link' import { usePathname, useRouter } from 'next/navigation' @@ -17,25 +16,21 @@ import { AlertDialogTitle } from '@/components/ui/alert-dialog' import { Button, buttonVariants } from '@/components/ui/button' -import { removeChat } from '@/app/actions' import { IconMessage, IconSpinner, IconTrash } from '@/components/ui/icons' -export function SidebarItem({ - title, - href, - id, - userId -}: { +interface SidebarItemProps { title: string href: string id: string - userId: string -}) { + removeChat: (args: { id: string; path: string }) => Promise +} + +export function SidebarItem({ title, href, id, removeChat }: SidebarItemProps) { const [open, setIsOpen] = React.useState(false) const pathname = usePathname() const router = useRouter() + const [isPending, startTransition] = React.useTransition() const isActive = pathname === href - const [isPending, startTransition] = useTransition() if (!id) return null @@ -85,7 +80,7 @@ export function SidebarItem({ onClick={event => { event.preventDefault() startTransition(async () => { - await removeChat({ id, userId, path: href }) + await removeChat({ id, path: href }) setIsOpen(false) router.push('/') }) diff --git a/components/sidebar-list.tsx b/components/sidebar-list.tsx index 95751b1..c729f20 100644 --- a/components/sidebar-list.tsx +++ b/components/sidebar-list.tsx @@ -1,27 +1,12 @@ -import { getChats } from '@/app/actions' +import { getChats, removeChat } from '@/app/actions' import { SidebarItem } from '@/components/sidebar-item' -import { LoginButton } from '@/components/login-button' export interface SidebarListProps { userId?: string } export async function SidebarList({ userId }: SidebarListProps) { - if (!userId) { - return ( -
-
-

You are not logged in!

-

- Please login for chat history. -

-
- -
- ) - } - const chats = await getChats(userId) return ( @@ -32,9 +17,9 @@ export async function SidebarList({ userId }: SidebarListProps) { ))}
@@ -46,5 +31,3 @@ export async function SidebarList({ userId }: SidebarListProps) {
) } - -SidebarList.displayName = 'SidebarList' diff --git a/components/sidebar.tsx b/components/sidebar.tsx index 0e8c628..c210dd1 100644 --- a/components/sidebar.tsx +++ b/components/sidebar.tsx @@ -21,6 +21,7 @@ export interface SidebarProps { export function Sidebar({ userId, children }: SidebarProps) { const { signOut } = useClerk() + return ( From fd99ff634f51ec6852fdd78d2d23d887d71e9356 Mon Sep 17 00:00:00 2001 From: shadcn Date: Wed, 14 Jun 2023 16:57:28 +0400 Subject: [PATCH 08/18] fix: yank colors from clerk elements --- app/sign-in/[[...sign-in]]/page.tsx | 7 +++---- app/sign-up/[[...sign-up]]/page.tsx | 12 ++++-------- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/app/sign-in/[[...sign-in]]/page.tsx b/app/sign-in/[[...sign-in]]/page.tsx index a06c0c8..e5fc141 100644 --- a/app/sign-in/[[...sign-in]]/page.tsx +++ b/app/sign-in/[[...sign-in]]/page.tsx @@ -20,11 +20,10 @@ export default function SignInPage() { elements: { card: 'shadow rounded-lg border border-border', formFieldInput: - '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', - formButtonPrimary: cn(buttonVariants(), 'normal-case'), + 'flex h-9 w-full rounded-md px-3 py-2 text-sm shadow-sm', + formButtonPrimary: 'normal-case', footerAction: 'text-sm', - footerActionLink: - 'text-primary font-medium hover:underline hover:text-primary' + footerActionLink: 'font-medium' } }} /> diff --git a/app/sign-up/[[...sign-up]]/page.tsx b/app/sign-up/[[...sign-up]]/page.tsx index 49f0245..aaadbcb 100644 --- a/app/sign-up/[[...sign-up]]/page.tsx +++ b/app/sign-up/[[...sign-up]]/page.tsx @@ -20,15 +20,11 @@ export default function Page() { elements: { card: 'shadow rounded-lg border border-border', formFieldInput: - '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', - formButtonPrimary: cn(buttonVariants(), 'normal-case'), + 'flex h-9 w-full rounded-md px-3 py-2 text-sm shadow-sm', + formButtonPrimary: 'normal-case', footerAction: 'text-sm', - footerActionLink: - 'text-primary font-medium hover:underline hover:text-primary', - identityPreview: 'rounded-md', - identityPreviewEditButton: 'text-primary', - otpCodeFieldInput: 'text-primary focus:border-b-primary', - formResendCodeLink: 'text-primary font-medium' + footerActionLink: 'font-medium hover:underline', + identityPreview: 'rounded-md' } }} /> From 92309d77ecf77871ec7c7f7cca6e4455418a3e8b Mon Sep 17 00:00:00 2001 From: shadcn Date: Thu, 15 Jun 2023 11:31:03 +0400 Subject: [PATCH 09/18] fix: pass down id to append --- app/page.tsx | 7 +++++-- components/chat-message-actions.tsx | 2 +- components/chat-panel.tsx | 6 ++++-- components/chat.tsx | 5 ++++- 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/app/page.tsx b/app/page.tsx index 7c5859a..16de2f9 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,16 +1,19 @@ +import { nanoid } from '@/lib/utils' import { Chat } from '@/components/chat' import { Header } from '@/components/header' // export const runtime = 'edge' export const preferredRegion = 'home' -export default async function IndexPage() { +export default function IndexPage() { + const id = nanoid() + return (
{/* @ts-ignore */}
- +
) diff --git a/components/chat-message-actions.tsx b/components/chat-message-actions.tsx index 0ed4564..732206a 100644 --- a/components/chat-message-actions.tsx +++ b/components/chat-message-actions.tsx @@ -20,7 +20,7 @@ export function ChatMessageActions({ return (
{ - append({ + onSubmit={async value => { + await append({ + id, content: value, role: 'user' }) diff --git a/components/chat.tsx b/components/chat.tsx index 0949dff..4e097c9 100644 --- a/components/chat.tsx +++ b/components/chat.tsx @@ -16,7 +16,10 @@ export function Chat({ id, initialMessages, className }: ChatProps) { const { messages, append, reload, stop, isLoading, input, setInput } = useChat({ initialMessages, - id + id, + body: { + id + } }) return ( From a9993640ff08314832e32679ebaa11e0d3d9f876 Mon Sep 17 00:00:00 2001 From: shadcn Date: Thu, 15 Jun 2023 11:31:20 +0400 Subject: [PATCH 10/18] fix: update codeblock display --- components/chat-message.tsx | 66 ++++++++++++++++++------------------- components/ui/codeblock.tsx | 48 ++++++++++++++++----------- 2 files changed, 61 insertions(+), 53 deletions(-) diff --git a/components/chat-message.tsx b/components/chat-message.tsx index 9b9e3d6..e4a1af8 100644 --- a/components/chat-message.tsx +++ b/components/chat-message.tsx @@ -29,47 +29,47 @@ export function ChatMessage({ message, ...props }: ChatMessageProps) { {message.role === 'user' ? : }
-
- {children}

- }, - code({ node, inline, className, children, ...props }) { - if (children.length) { - if (children[0] == '▍') { - return ( - - ▍ - - ) - } - - children[0] = (children[0] as string).replace('`▍`', '▍') + {children}

+ }, + code({ node, inline, className, children, ...props }) { + if (children.length) { + if (children[0] == '▍') { + return ( + + ) } - const match = /language-(\w+)/.exec(className || '') + children[0] = (children[0] as string).replace('`▍`', '▍') + } - return !inline ? ( - - ) : ( + const match = /language-(\w+)/.exec(className || '') + + if (inline) { + return ( {children} ) } - }} - > - {message.content} -
-
+ + return ( + + ) + } + }} + > + {message.content} +
diff --git a/components/ui/codeblock.tsx b/components/ui/codeblock.tsx index cfa98d0..d8f0a4a 100644 --- a/components/ui/codeblock.tsx +++ b/components/ui/codeblock.tsx @@ -6,6 +6,7 @@ import { coldarkDark } from 'react-syntax-highlighter/dist/cjs/styles/prism' import { useCopyToClipboard } from '@/lib/hooks/use-copy-to-clipboard' import { IconCheck, IconCopy, IconDownload } from '@/components/ui/icons' +import { Button } from '@/components/ui/button' interface Props { language: string @@ -51,8 +52,10 @@ export const generateRandomString = (length: number, lowercase = false) => { } return lowercase ? result.toLowerCase() : result } + const CodeBlock: FC = memo(({ language, value }) => { const { isCopied, copyToClipboard } = useCopyToClipboard({ timeout: 2000 }) + const downloadAsFile = () => { if (typeof window === 'undefined') { return @@ -65,7 +68,7 @@ const CodeBlock: FC = memo(({ language, value }) => { const fileName = window.prompt('Enter file name' || '', suggestedFileName) if (!fileName) { - // user pressed cancel on prompt + // User pressed cancel on prompt. return } @@ -80,37 +83,42 @@ const CodeBlock: FC = memo(({ language, value }) => { document.body.removeChild(link) URL.revokeObjectURL(url) } - return ( -
-
- {language} -
- + - + Copy code +
- Date: Thu, 15 Jun 2023 15:27:41 +0400 Subject: [PATCH 11/18] feat: implement auto scroll --- components/button-scroll-to-bottom.tsx | 18 ++-------------- components/chat-scroll-anchor.tsx | 29 ++++++++++++++++++++++++++ components/chat.tsx | 6 +++++- lib/hooks/use-at-bottom.tsx | 23 ++++++++++++++++++++ package.json | 1 + pnpm-lock.yaml | 11 ++++++++++ 6 files changed, 71 insertions(+), 17 deletions(-) create mode 100644 components/chat-scroll-anchor.tsx create mode 100644 lib/hooks/use-at-bottom.tsx diff --git a/components/button-scroll-to-bottom.tsx b/components/button-scroll-to-bottom.tsx index b94f34a..436a6c0 100644 --- a/components/button-scroll-to-bottom.tsx +++ b/components/button-scroll-to-bottom.tsx @@ -3,26 +3,12 @@ import * as React from 'react' import { cn } from '@/lib/utils' +import { useAtBottom } from '@/lib/hooks/use-at-bottom' import { Button, type ButtonProps } from '@/components/ui/button' import { IconArrowDown } from '@/components/ui/icons' export function ButtonScrollToBottom({ className, ...props }: ButtonProps) { - const [isAtBottom, setIsAtBottom] = React.useState(false) - - React.useEffect(() => { - const handleScroll = () => { - setIsAtBottom( - window.innerHeight + window.scrollY > document.body.offsetHeight - 100 - ) - } - - window.addEventListener('scroll', handleScroll, { passive: true }) - handleScroll() - - return () => { - window.removeEventListener('scroll', handleScroll) - } - }, []) + const isAtBottom = useAtBottom() return (