From 282ef825cb932b33f528eebc7bb042f1b06152ac Mon Sep 17 00:00:00 2001 From: shadcn Date: Tue, 13 Jun 2023 18:14:06 +0400 Subject: [PATCH 1/4] fix: update login button and sidebar --- components/header.tsx | 8 ++++---- components/login-button.tsx | 40 +++++++++++++++++++++++++++++++++++++ components/sidebar-list.tsx | 33 ++++++++++++++++++++---------- components/ui/sheet.tsx | 8 +------- components/user-menu.tsx | 23 +++++---------------- 5 files changed, 72 insertions(+), 40 deletions(-) create mode 100644 components/login-button.tsx diff --git a/components/header.tsx b/components/header.tsx index b2ffc03..d59f67a 100644 --- a/components/header.tsx +++ b/components/header.tsx @@ -21,7 +21,7 @@ export async function Header() { -
+
@@ -33,15 +33,15 @@ export async function Header() { rel="noopener noreferrer" className={cn(buttonVariants({ variant: 'outline' }))} > - - GitHub + + GitHub - + Deploy to Vercel Deploy diff --git a/components/login-button.tsx b/components/login-button.tsx new file mode 100644 index 0000000..3aef62d --- /dev/null +++ b/components/login-button.tsx @@ -0,0 +1,40 @@ +'use client' + +import * as React from 'react' +import { signIn } from '@auth/nextjs/client' + +import { cn } from '@/lib/utils' +import { Button, type ButtonProps } from '@/components/ui/button' +import { IconGitHub, IconSpinner } from '@/components/ui/icons' + +interface LoginButtonProps extends ButtonProps { + text?: string +} + +export function LoginButton({ + className, + text = 'Login with GitHub', + ...props +}: LoginButtonProps) { + const [isLoading, setIsLoading] = React.useState(false) + + return ( + + ) +} diff --git a/components/sidebar-list.tsx b/components/sidebar-list.tsx index 22c405e..1e2278b 100644 --- a/components/sidebar-list.tsx +++ b/components/sidebar-list.tsx @@ -1,13 +1,30 @@ import { getChats } from '@/app/actions' import { Session } from '@auth/core/types' -import { SidebarItem } from './sidebar-item' + +import { SidebarItem } from '@/components/sidebar-item' +import { LoginButton } from '@/components/login-button' export interface SidebarListProps { session?: Session } -export async function SidebarList(props: SidebarListProps) { - const chats = await getChats(props.session?.user?.email) +export async function SidebarList({ session }: SidebarListProps) { + if (!session?.user?.email) { + return ( +
+
+

You are not logged in!

+

+ Please login for chat history. +

+
+ +
+ ) + } + + const chats = await getChats(session.user.email) + return (
{chats?.length ? ( @@ -16,7 +33,7 @@ export async function SidebarList(props: SidebarListProps) { @@ -24,13 +41,7 @@ export async function SidebarList(props: SidebarListProps) {
) : (
-

- {props?.session?.user ? ( - <>No chat history - ) : ( - <>Login for history - )} -

+

No chat history

)}
diff --git a/components/ui/sheet.tsx b/components/ui/sheet.tsx index 6d12ff7..d333799 100644 --- a/components/ui/sheet.tsx +++ b/components/ui/sheet.tsx @@ -85,13 +85,7 @@ const SheetHeader = ({ className, ...props }: React.HTMLAttributes) => ( -
+
) SheetHeader.displayName = 'SheetHeader' diff --git a/components/user-menu.tsx b/components/user-menu.tsx index 280fa3c..c5b96e2 100644 --- a/components/user-menu.tsx +++ b/components/user-menu.tsx @@ -1,37 +1,24 @@ 'use client' import * as React from 'react' -import { signIn } from '@auth/nextjs/client' import { type Session } from '@auth/nextjs/types' -import { Button } from '@/components/ui/button' -import { IconSpinner } from '@/components/ui/icons' +import { LoginButton } from '@/components/login-button' export interface UserMenuProps { session?: Session } export function UserMenu({ session }: UserMenuProps) { - const [isLoading, setIsLoading] = React.useState(false) - if (!session?.user) { return ( - + ) } return ( -

Logged in as {session.user.name}

+

+ {session.user.name} +

) } From 9158bfe82c7b1f98e8846172e6f3348209d68a5e Mon Sep 17 00:00:00 2001 From: shadcn Date: Tue, 13 Jun 2023 18:14:20 +0400 Subject: [PATCH 2/4] fix: update sheet display and sidebar items --- app/api/chat/route.ts | 2 +- components/sidebar.tsx | 5 +---- components/ui/sheet.tsx | 29 ++++++----------------------- 3 files changed, 8 insertions(+), 28 deletions(-) diff --git a/app/api/chat/route.ts b/app/api/chat/route.ts index 515d947..7fc3ec8 100644 --- a/app/api/chat/route.ts +++ b/app/api/chat/route.ts @@ -42,7 +42,7 @@ export const POST = auth(async function POST(req: Request) { if (req.auth?.user?.email == null) { return } - const title = json.messages[0].content.substring(0, 20) + const title = json.messages[0].content.substring(0, 100) const userId = (req as any).auth?.user?.email const id = json.id ?? nanoid() const createdAt = Date.now() diff --git a/components/sidebar.tsx b/components/sidebar.tsx index 75554e4..a4cbbc5 100644 --- a/components/sidebar.tsx +++ b/components/sidebar.tsx @@ -29,10 +29,7 @@ export function Sidebar({ session, children }: SidebarProps) { Toggle Sidebar - + Chat History diff --git a/components/ui/sheet.tsx b/components/ui/sheet.tsx index d333799..97deab9 100644 --- a/components/ui/sheet.tsx +++ b/components/ui/sheet.tsx @@ -2,7 +2,6 @@ import * as React from 'react' import * as SheetPrimitive from '@radix-ui/react-dialog' -import { cva, type VariantProps } from 'class-variance-authority' import { cn } from '@/lib/utils' import { IconClose } from '@/components/ui/icons' @@ -42,33 +41,17 @@ const SheetOverlay = React.forwardRef< )) SheetOverlay.displayName = SheetPrimitive.Overlay.displayName -const sheetVariants = cva( - 'fixed z-50 h-full scale-100 gap-4 border bg-background p-6 opacity-100 shadow-lg', - { - variants: { - position: { - left: 'data-[state=closed]:animate-slide-to-left data-[state=open]:animate-slide-from-left', - right: 'h-full animate-in slide-in-from-right duration-300' - } - }, - defaultVariants: { - position: 'left' - } - } -) - -export interface DialogContentProps - extends React.ComponentPropsWithoutRef, - VariantProps {} - const SheetContent = React.forwardRef< React.ElementRef, - DialogContentProps ->(({ position, className, children, ...props }, ref) => ( + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( {children} From 6f69b878d546e05db5e571df081be67369bad414 Mon Sep 17 00:00:00 2001 From: shadcn Date: Tue, 13 Jun 2023 22:01:50 +0400 Subject: [PATCH 3/4] fix: remove use client boundary --- components/chat-panel.tsx | 2 -- components/prompt-form.tsx | 2 -- 2 files changed, 4 deletions(-) diff --git a/components/chat-panel.tsx b/components/chat-panel.tsx index c3c0f6b..3559b71 100644 --- a/components/chat-panel.tsx +++ b/components/chat-panel.tsx @@ -1,5 +1,3 @@ -'use client' - import { UseChatHelpers } from 'ai-connector' import { Button } from '@/components/ui/button' diff --git a/components/prompt-form.tsx b/components/prompt-form.tsx index c250dcb..f42cd17 100644 --- a/components/prompt-form.tsx +++ b/components/prompt-form.tsx @@ -1,5 +1,3 @@ -'use client' - import * as React from 'react' import Link from 'next/link' import Textarea from 'react-textarea-autosize' From f476c903ef1572980b450e1c417368241b7e84aa Mon Sep 17 00:00:00 2001 From: shadcn Date: Tue, 13 Jun 2023 22:16:54 +0400 Subject: [PATCH 4/4] fix: remove zustand and use input and setInput --- components/chat-list.tsx | 21 ++++++------- components/chat-panel.tsx | 14 +++++++-- components/chat.tsx | 30 ++++++++++++------ components/empty-screen.tsx | 62 ++++++++++++++++++------------------- components/prompt-form.tsx | 18 +++++------ lib/hooks/use-chat-store.ts | 11 ------- package.json | 3 +- pnpm-lock.yaml | 19 ------------ 8 files changed, 83 insertions(+), 95 deletions(-) delete mode 100644 lib/hooks/use-chat-store.ts diff --git a/components/chat-list.tsx b/components/chat-list.tsx index 808fd90..cf6b830 100644 --- a/components/chat-list.tsx +++ b/components/chat-list.tsx @@ -4,25 +4,24 @@ import { type Message } from 'ai-connector' import { Separator } from '@/components/ui/separator' import { ChatMessage } from '@/components/chat-message' -import { EmptyScreen } from '@/components/empty-screen' export interface ChatList { messages: Message[] } export function ChatList({ messages }: ChatList) { + if (!messages.length) { + return null + } + return (
- {messages.length > 0 ? ( - messages.map((message, index) => ( -
- - {index < messages.length - 1 && } -
- )) - ) : ( - - )} + {messages.map((message, index) => ( +
+ + {index < messages.length - 1 && } +
+ ))}
) } diff --git a/components/chat-panel.tsx b/components/chat-panel.tsx index 3559b71..6bd0a40 100644 --- a/components/chat-panel.tsx +++ b/components/chat-panel.tsx @@ -1,4 +1,4 @@ -import { UseChatHelpers } from 'ai-connector' +import { type UseChatHelpers } from 'ai-connector' import { Button } from '@/components/ui/button' import { ExternalLink } from '@/components/external-link' @@ -9,7 +9,13 @@ import { IconRefresh, IconStop } from '@/components/ui/icons' export interface ChatPanelProps extends Pick< UseChatHelpers, - 'append' | 'isLoading' | 'reload' | 'messages' | 'stop' + | 'append' + | 'isLoading' + | 'reload' + | 'messages' + | 'stop' + | 'input' + | 'setInput' > { id?: string } @@ -19,6 +25,8 @@ export function ChatPanel({ stop, append, reload, + input, + setInput, messages }: ChatPanelProps) { return ( @@ -56,6 +64,8 @@ export function ChatPanel({ role: 'user' }) }} + input={input} + setInput={setInput} isLoading={isLoading} />

diff --git a/components/chat.tsx b/components/chat.tsx index c7fff5d..4352cc6 100644 --- a/components/chat.tsx +++ b/components/chat.tsx @@ -2,24 +2,32 @@ import { useChat, type Message } from 'ai-connector' +import { cn } from '@/lib/utils' import { ChatList } from '@/components/chat-list' import { ChatPanel } from '@/components/chat-panel' +import { EmptyScreen } from '@/components/empty-screen' -export interface ChatProps { - // create?: (input: string) => Chat | undefined; +export interface ChatProps extends React.ComponentProps<'div'> { initialMessages?: Message[] id?: string } -export function Chat({ id, initialMessages }: ChatProps) { - const { messages, append, reload, stop, isLoading } = useChat({ - initialMessages, - id - }) +export function Chat({ id, initialMessages, className }: ChatProps) { + const { messages, append, reload, stop, isLoading, input, setInput } = + useChat({ + initialMessages, + id + }) return ( -

- + <> +
+ {messages.length ? ( + + ) : ( + + )} +
-
+ ) } diff --git a/components/empty-screen.tsx b/components/empty-screen.tsx index b2804c4..e739b31 100644 --- a/components/empty-screen.tsx +++ b/components/empty-screen.tsx @@ -1,8 +1,8 @@ -import { useChatStore } from '@/lib/hooks/use-chat-store' import { cn } from '@/lib/utils' 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 = [ { @@ -19,37 +19,37 @@ const exampleMessages = [ } ] -export function EmptyScreen({ className }: React.ComponentProps<'div'>) { - const { setDefaultMessage } = useChatStore() - +export function EmptyScreen({ setInput }: Pick) { return ( -
-

- Welcome to Next.js Chatbot! -

-

- This is an open source AI chatbot app built with{' '} - Next.js and{' '} - - Vercel KV - - . -

-

- You can start a conversation here or try the following examples: -

-
- {exampleMessages.map((message, index) => ( - - ))} +
+
+

+ Welcome to Next.js Chatbot! +

+

+ This is an open source AI chatbot app built with{' '} + Next.js and{' '} + + Vercel KV + + . +

+

+ You can start a conversation here or try the following examples: +

+
+ {exampleMessages.map((message, index) => ( + + ))} +
) diff --git a/components/prompt-form.tsx b/components/prompt-form.tsx index f42cd17..1fbc7b1 100644 --- a/components/prompt-form.tsx +++ b/components/prompt-form.tsx @@ -2,7 +2,6 @@ import * as React from 'react' import Link from 'next/link' import Textarea from 'react-textarea-autosize' -import { useChatStore } from '@/lib/hooks/use-chat-store' import { useEnterSubmit } from '@/lib/hooks/use-enter-submit' import { cn } from '@/lib/utils' import { Button, buttonVariants } from '@/components/ui/button' @@ -13,15 +12,20 @@ import { TooltipTrigger } from '@/components/ui/tooltip' import { IconArrowElbow, IconPlus } from '@/components/ui/icons' +import { UseChatHelpers } from 'ai-connector' -export interface PromptProps { +export interface PromptProps + extends Pick { onSubmit: (value: string) => void isLoading: boolean } -export function PromptForm({ onSubmit, isLoading }: PromptProps) { - const { defaultMessage } = useChatStore() - const [input, setInput] = React.useState(defaultMessage) +export function PromptForm({ + onSubmit, + input, + setInput, + isLoading +}: PromptProps) { const { formRef, onKeyDown } = useEnterSubmit() const inputRef = React.useRef(null) @@ -31,10 +35,6 @@ export function PromptForm({ onSubmit, isLoading }: PromptProps) { } }, []) - React.useEffect(() => { - setInput(defaultMessage) - }, [defaultMessage]) - return (
{ diff --git a/lib/hooks/use-chat-store.ts b/lib/hooks/use-chat-store.ts deleted file mode 100644 index e6e913c..0000000 --- a/lib/hooks/use-chat-store.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { create } from 'zustand' - -interface UseChatStore { - defaultMessage: string - setDefaultMessage: (message: string) => void -} - -export const useChatStore = create()(set => ({ - defaultMessage: '', - setDefaultMessage: message => set({ defaultMessage: message }) -})) diff --git a/package.json b/package.json index 646b9b7..96a74ed 100644 --- a/package.json +++ b/package.json @@ -39,8 +39,7 @@ "react-syntax-highlighter": "^15.5.0", "react-textarea-autosize": "^8.4.1", "remark-gfm": "^3.0.1", - "remark-math": "^5.1.1", - "zustand": "^4.3.8" + "remark-math": "^5.1.1" }, "devDependencies": { "@tailwindcss/typography": "^0.5.9", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a5f796b..a765a30 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -86,9 +86,6 @@ dependencies: remark-math: specifier: ^5.1.1 version: 5.1.1 - zustand: - specifier: ^4.3.8 - version: 4.3.8(react@18.2.0) devDependencies: '@tailwindcss/typography': @@ -5070,22 +5067,6 @@ packages: /zod@3.21.4: resolution: {integrity: sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==} - /zustand@4.3.8(react@18.2.0): - resolution: {integrity: sha512-4h28KCkHg5ii/wcFFJ5Fp+k1J3gJoasaIbppdgZFO4BPJnsNxL0mQXBSFgOgAdCdBj35aDTPvdAJReTMntFPGg==} - engines: {node: '>=12.7.0'} - peerDependencies: - immer: '>=9.0' - react: '>=16.8' - peerDependenciesMeta: - immer: - optional: true - react: - optional: true - dependencies: - react: 18.2.0 - use-sync-external-store: 1.2.0(react@18.2.0) - dev: false - /zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} dev: false