feat: update chat display and fix issue with regeneration

This commit is contained in:
shadcn 2023-06-11 15:08:59 +04:00
parent e06f34b0f1
commit 8f64c55896
17 changed files with 266 additions and 231 deletions

View file

@ -5,68 +5,66 @@
@layer base { @layer base {
:root { :root {
--background: 0 0% 100%; --background: 0 0% 100%;
--foreground: 222.2 47.4% 11.2%; --foreground: 240 10% 3.9%;
--muted: 210 40% 96.1%; --muted: 240 4.8% 95.9%;
--muted-foreground: 215.4 16.3% 46.9%; --muted-foreground: 240 3.8% 46.1%;
--popover: 0 0% 100%; --popover: 0 0% 100%;
--popover-foreground: 222.2 47.4% 11.2%; --popover-foreground: 240 10% 3.9%;
--card: 0 0% 100%; --card: 0 0% 100%;
--card-foreground: 222.2 47.4% 11.2%; --card-foreground: 240 10% 3.9%;
--border: 214.3 31.8% 91.4%; --border: 240 5.9% 90%;
--input: 214.3 31.8% 91.4%; --input: 240 5.9% 90%;
--primary: 222.2 47.4% 11.2%; --primary: 240 5.9% 10%;
--primary-foreground: 210 40% 98%; --primary-foreground: 0 0% 98%;
--secondary: 210 40% 96.1%; --secondary: 240 4.8% 95.9%;
--secondary-foreground: 222.2 47.4% 11.2%; --secondary-foreground: 240 5.9% 10%;
--accent: 210 40% 96.1%; --accent: 240 4.8% 95.9%;
--accent-foreground: 222.2 47.4% 11.2%; --accent-foreground: ;
--destructive: 0 100% 50%; --destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 40% 98%; --destructive-foreground: 0 0% 98%;
--ring: 215 20.2% 65.1%; --ring: 240 5% 64.9%;
--radius: 0.5rem; --radius: 0.5rem;
} }
.dark { .dark {
--background: 224 71% 4%; --background: 240 10% 3.9%;
--foreground: 213 31% 91%; --foreground: 0 0% 98%;
--muted: 223 47% 11%; --muted: 240 3.7% 15.9%;
--muted-foreground: 215.4 16.3% 56.9%; --muted-foreground: 240 5% 64.9%;
--popover: 224 71% 4%; --popover: 240 10% 3.9%;
--popover-foreground: 215 20.2% 65.1%; --popover-foreground: 0 0% 98%;
--card: 224 71% 4%; --card: 240 10% 3.9%;
--card-foreground: 213 31% 91%; --card-foreground: 0 0% 98%;
--border: 216 34% 17%; --border: 240 3.7% 15.9%;
--input: 216 34% 17%; --input: 240 3.7% 15.9%;
--primary: 210 40% 98%; --primary: 0 0% 98%;
--primary-foreground: 222.2 47.4% 1.2%; --primary-foreground: 240 5.9% 10%;
--secondary: 222.2 47.4% 11.2%; --secondary: 240 3.7% 15.9%;
--secondary-foreground: 210 40% 98%; --secondary-foreground: 0 0% 98%;
--accent: 216 34% 17%; --accent: 240 3.7% 15.9%;
--accent-foreground: 210 40% 98%; --accent-foreground: ;
--destructive: 0 63% 31%; --destructive: 0 62.8% 30.6%;
--destructive-foreground: 210 40% 98%; --destructive-foreground: 0 85.7% 97.3%;
--ring: 216 34% 17%; --ring: 240 3.7% 15.9%;
--radius: 0.5rem;
} }
} }
@ -76,10 +74,5 @@
} }
body { body {
@apply bg-background text-foreground; @apply bg-background text-foreground;
font-feature-settings: 'rlig' 1, 'calt' 1;
} }
} }
pre:has(div.codeblock) {
padding: 0;
}

View file

@ -4,6 +4,7 @@ import { type Message } from 'ai-connector'
import { ChatMessage } from '@/components/chat-message' import { ChatMessage } from '@/components/chat-message'
import { EmptyScreen } from '@/components/empty-screen' import { EmptyScreen } from '@/components/empty-screen'
import { Separator } from '@/components/ui/separator'
export interface ChatList { export interface ChatList {
messages: Message[] messages: Message[]
@ -11,15 +12,16 @@ export interface ChatList {
export function ChatList({ messages }: ChatList) { export function ChatList({ messages }: ChatList) {
return ( return (
<div className="relative max-w-2xl mx-auto"> <div className="relative max-w-2xl px-4 mx-auto">
{messages.length > 0 ? ( {messages.length > 0 ? (
messages.map(message => ( messages.map((message, index) => (
<ChatMessage key={message.id || message.content} message={message} /> <div key={index}>
<ChatMessage message={message} />
{index < messages.length - 1 && <Separator className="my-8" />}
</div>
)) ))
) : ( ) : (
<div className="pt-10">
<EmptyScreen /> <EmptyScreen />
</div>
)} )}
</div> </div>
) )

View file

@ -5,7 +5,7 @@ import remarkMath from 'remark-math'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
import { fontMessage } from '@/lib/fonts' import { fontMessage } from '@/lib/fonts'
import CodeBlock from '@/components/ui/codeblock' import { CodeBlock } from '@/components/ui/codeblock'
import { MemoizedReactMarkdown } from '@/components/markdown' import { MemoizedReactMarkdown } from '@/components/markdown'
import { OpenAI } from '@/components/icons' import { OpenAI } from '@/components/icons'
@ -17,16 +17,17 @@ export function ChatMessage({ message, ...props }: ChatMessageProps) {
return ( return (
<div <div
className={cn( className={cn(
'flex items-start space-x-4 mb-4 relative -ml-12', 'flex items-start mb-4 relative md:-ml-12',
fontMessage.className, fontMessage.className
message.role === 'user' && 'mt-12 first:mt-0'
)} )}
{...props} {...props}
> >
<div <div
className={cn( className={cn(
'flex h-8 w-8 shrink-0 items-center justify-center rounded-full select-none border', 'md:flex h-8 w-8 shrink-0 hidden items-center justify-center rounded-full select-none border',
message.role === 'assistant' && 'bg-primary text-primary-foreground' message.role === 'user'
? 'bg-background'
: 'bg-primary text-primary-foreground'
)} )}
> >
{message.role === 'user' ? ( {message.role === 'user' ? (
@ -35,14 +36,9 @@ export function ChatMessage({ message, ...props }: ChatMessageProps) {
<OpenAI className="w-4 h-4" /> <OpenAI className="w-4 h-4" />
)} )}
</div> </div>
<div <div className="md:ml-4">
className={cn(
'border rounded-lg py-2 px-4',
message.role === 'assistant' && 'bg-muted/30'
)}
>
<MemoizedReactMarkdown <MemoizedReactMarkdown
className="prose dark:prose-invert prose-sm prose-pre:rounded-md w-full flex-1 leading-6 prose-p:leading-[1.8rem] prose-pre:bg-[#282c34] max-w-full" className="prose dark:prose-invert prose-pre:rounded-md leading-6 prose-p:leading-[1.8rem] prose-pre:bg-[#282c34]"
remarkPlugins={[remarkGfm, remarkMath]} remarkPlugins={[remarkGfm, remarkMath]}
components={{ components={{
p({ children }) { p({ children }) {
@ -50,7 +46,7 @@ export function ChatMessage({ message, ...props }: ChatMessageProps) {
<p <p
className={cn( className={cn(
'mb-2 last:mb-0', 'mb-2 last:mb-0',
message.role === 'user' && 'font-medium' message.role === 'user' && 'font-semibold'
)} )}
> >
{children} {children}

View file

@ -1,25 +1,54 @@
'use client' 'use client'
import { ExternalLink } from '@/components/external-link'
import { UseChatHelpers } from 'ai-connector' import { UseChatHelpers } from 'ai-connector'
import { PromptForm } from './prompt-form' import { RefreshCcw, StopCircle } from 'lucide-react'
import { ExternalLink } from '@/components/external-link'
import { PromptForm } from '@/components/prompt-form'
import { Button } from '@/components/ui/button'
export interface ChatPanelProps export interface ChatPanelProps
extends Pick<UseChatHelpers, 'append' | 'isLoading' | 'reload' | 'messages'> { extends Pick<
UseChatHelpers,
'append' | 'isLoading' | 'reload' | 'messages' | 'stop'
> {
id?: string id?: string
} }
export function ChatPanel({ export function ChatPanel({
id,
append,
isLoading, isLoading,
stop,
append,
reload, reload,
messages messages
}: ChatPanelProps) { }: ChatPanelProps) {
return ( return (
<div className="fixed bottom-0 left-0 right-0"> <div className="fixed bottom-0 left-0 right-0 bg-gradient-to-b from-muted/10 from-10% to-muted/30 to-50%">
<div className="max-w-2xl mx-auto"> <div className="sm:max-w-2xl sm:px-4 mx-auto">
<div className="px-4 py-2 border space-y-4 shadow-lg bg-gradient-to-b from-background/10 via-background/50 to-background/80 backdrop-blur-lg rounded-t-xl"> <div className="flex items-center justify-center py-2">
{isLoading ? (
<Button
variant="outline"
onClick={() => stop()}
className="bg-background"
>
<StopCircle className="h-4 w-4 mr-2" />
Stop generating
</Button>
) : (
messages?.length > 0 && (
<Button
variant="outline"
onClick={() => reload()}
className="bg-background"
>
<RefreshCcw className="h-4 w-4 mr-2" />
Regenerate response
</Button>
)
)}
</div>
<div className="sm:p-4 border-t sm:border bg-background space-y-4 shadow-lg sm:rounded-t-xl">
<PromptForm <PromptForm
onSubmit={value => { onSubmit={value => {
append({ append({
@ -27,11 +56,10 @@ export function ChatPanel({
role: 'user' role: 'user'
}) })
}} }}
onRefresh={messages.length ? reload : undefined}
isLoading={isLoading} isLoading={isLoading}
/> />
<p className="text-muted-foreground text-xs leading-normal text-center pb-1"> <p className="hidden sm:block text-muted-foreground text-xs leading-normal text-center px-2">
This is an open source AI chatbot app built with{' '} Open source AI chatbot app built with{' '}
<ExternalLink href="https://nextjs.org">Next.js</ExternalLink> and{' '} <ExternalLink href="https://nextjs.org">Next.js</ExternalLink> and{' '}
<ExternalLink href="https://vercel.com/storage/kv"> <ExternalLink href="https://vercel.com/storage/kv">
Vercel KV Vercel KV

View file

@ -11,23 +11,22 @@ export interface ChatProps {
} }
export function Chat({ id, initialMessages }: ChatProps) { export function Chat({ id, initialMessages }: ChatProps) {
const { messages, append, reload, isLoading } = useChat({ const { messages, append, reload, stop, isLoading } = useChat({
initialMessages, initialMessages,
id id
}) })
return ( return (
<div className="h-full overflow-hidden"> <div className="h-full w-full overflow-auto pt-4 md:pt-10 bg-muted/50 pb-[200px]">
<div className="h-full w-full overflow-auto pb-[200px]">
<ChatList messages={messages} /> <ChatList messages={messages} />
<ChatPanel <ChatPanel
id={id} id={id}
append={append}
isLoading={isLoading} isLoading={isLoading}
stop={stop}
append={append}
reload={reload} reload={reload}
messages={messages} messages={messages}
/> />
</div> </div>
</div>
) )
} }

View file

@ -1,10 +1,10 @@
import { ArrowRight } from 'lucide-react' import { ArrowRight } from 'lucide-react'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
import { fontMessage } from '@/lib/fonts'
import { useChatStore } from '@/lib/hooks/use-chat-store' import { useChatStore } from '@/lib/hooks/use-chat-store'
import { ExternalLink } from '@/components/external-link' import { ExternalLink } from '@/components/external-link'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { OpenAI } from '@/components/icons'
const exampleMessages = [ const exampleMessages = [
{ {
@ -25,22 +25,17 @@ export function EmptyScreen({ className }: React.ComponentProps<'div'>) {
const { setDefaultMessage } = useChatStore() const { setDefaultMessage } = useChatStore()
return ( return (
<div className="flex items-start space-x-4 mb-4 -ml-12">
<div <div
className={cn( className={cn(
'flex h-8 w-8 shrink-0 items-center justify-center rounded-full select-none border bg-primary text-primary-foreground' 'border bg-background p-8 rounded-lg',
)} fontMessage.className,
>
<OpenAI className="w-4 h-4" />
</div>
<div
className={cn(
'border rounded-lg flex-1 p-10 bg-gradient-to-b from-background via-muted/10 to-muted/50',
className className
)} )}
> >
<h1 className="font-semibold mb-2">Welcome to Next.js Chatbot!</h1> <h1 className="font-semibold text-lg mb-2">
<p className="text-muted-foreground text-sm leading-normal"> Welcome to Next.js Chatbot!
</h1>
<p className="text-muted-foreground leading-normal mb-2">
This is an open source AI chatbot app built with{' '} This is an open source AI chatbot app built with{' '}
<ExternalLink href="https://nextjs.org">Next.js</ExternalLink> and{' '} <ExternalLink href="https://nextjs.org">Next.js</ExternalLink> and{' '}
<ExternalLink href="https://vercel.com/storage/kv"> <ExternalLink href="https://vercel.com/storage/kv">
@ -48,15 +43,15 @@ export function EmptyScreen({ className }: React.ComponentProps<'div'>) {
</ExternalLink> </ExternalLink>
. .
</p> </p>
<p className="text-muted-foreground text-sm leading-normal"> <p className="text-muted-foreground leading-normal">
You can start a conversation here or try the following examples: You can start a conversation here or try the following examples:
</p> </p>
<div className="text-sm mt-4 flex flex-col items-start space-y-2"> <div className="mt-4 flex flex-col items-start space-y-2">
{exampleMessages.map((message, index) => ( {exampleMessages.map((message, index) => (
<Button <Button
key={index} key={index}
variant="link" variant="link"
className="p-0 h-auto" className="p-0 h-auto text-base"
onClick={() => setDefaultMessage(message.message)} onClick={() => setDefaultMessage(message.message)}
> >
<ArrowRight className="w-4 h-4 mr-2 text-muted-foreground" /> <ArrowRight className="w-4 h-4 mr-2 text-muted-foreground" />
@ -65,6 +60,5 @@ export function EmptyScreen({ className }: React.ComponentProps<'div'>) {
))} ))}
</div> </div>
</div> </div>
</div>
) )
} }

View file

@ -11,14 +11,16 @@ export async function Header() {
const chats = session?.user?.email ? await getChats(session.user.email) : [] const chats = session?.user?.email ? await getChats(session.user.email) : []
return ( return (
<header className="h-16 shrink-0 z-50 bg-gradient-to-b from-background/10 via-background/50 to-background/80 backdrop-blur-lg px-4 flex sticky top-0 w-full items-center justify-between"> <header className="h-16 shrink-0 z-50 border-b bg-gradient-to-b from-background/10 via-background/50 backdrop-blur-xl to-background/80 px-4 flex sticky top-0 w-full items-center justify-between">
<div className="flex items-center w-2/5"> <div className="flex items-center">
{/* @ts-ignore */} {/* @ts-ignore */}
<Sidebar chats={chats} session={session} /> <Sidebar chats={chats} session={session} />
<div className="hidden md:flex items-center">
<Separator className="h-6 w-6 text-muted-foreground/50" /> <Separator className="h-6 w-6 text-muted-foreground/50" />
<UserMenu session={session} /> <UserMenu session={session} />
</div> </div>
<div className="flex space-x-2 items-center justify-end w-2/5"> </div>
<div className="flex space-x-2 items-center justify-end">
<a <a
target="_blank" target="_blank"
href="https://github.com/vercel/nextjs-ai-chatbot/" href="https://github.com/vercel/nextjs-ai-chatbot/"
@ -34,7 +36,8 @@ export async function Header() {
className={cn(buttonVariants())} className={cn(buttonVariants())}
> >
<Vercel className="w-4 h-4 mr-2" /> <Vercel className="w-4 h-4 mr-2" />
Deploy to Vercel <span className="hidden sm:block">Deploy to Vercel</span>
<span className="sm:hidden">Vercel</span>
</a> </a>
</div> </div>
</header> </header>

View file

@ -1,12 +1,10 @@
'use client' 'use client'
import * as React from 'react' import * as React from 'react'
import { CornerDownLeft, Plus, RefreshCcw, StopCircle } from 'lucide-react' import { CornerDownLeft, Plus } from 'lucide-react'
import { useState } from 'react'
import Textarea from 'react-textarea-autosize' import Textarea from 'react-textarea-autosize'
import { Button, buttonVariants } from '@/components/ui/button' import { Button, buttonVariants } from '@/components/ui/button'
import { fontMessage } from '@/lib/fonts'
import { useEnterSubmit } from '@/lib/hooks/use-enter-submit' import { useEnterSubmit } from '@/lib/hooks/use-enter-submit'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
import { useChatStore } from '@/lib/hooks/use-chat-store' import { useChatStore } from '@/lib/hooks/use-chat-store'
@ -20,19 +18,12 @@ import Link from 'next/link'
export interface PromptProps { export interface PromptProps {
onSubmit: (value: string) => void onSubmit: (value: string) => void
onRefresh?: () => void
onAbort?: () => void
isLoading: boolean isLoading: boolean
} }
export function PromptForm({ export function PromptForm({ onSubmit, isLoading }: PromptProps) {
onSubmit,
onRefresh,
onAbort,
isLoading
}: PromptProps) {
const { defaultMessage } = useChatStore() const { defaultMessage } = useChatStore()
const [input, setInput] = useState(defaultMessage) const [input, setInput] = React.useState(defaultMessage)
const { formRef, onKeyDown } = useEnterSubmit() const { formRef, onKeyDown } = useEnterSubmit()
const inputRef = React.useRef<HTMLTextAreaElement>(null) const inputRef = React.useRef<HTMLTextAreaElement>(null)
@ -50,30 +41,16 @@ export function PromptForm({
<form <form
onSubmit={async e => { onSubmit={async e => {
e.preventDefault() e.preventDefault()
if (input === '') {
return
}
setInput('') setInput('')
await onSubmit(input) await onSubmit(input)
}} }}
ref={formRef} ref={formRef}
> >
<div className="relative flex h-full flex-1 flex-row-reverse items-stretch md:flex-col">
<div>
<div className="ml-1 flex h-full justify-center gap-0 md:m-auto md:mb-2 md:w-full md:gap-2">
{onRefresh ? (
<Button
variant="ghost"
className="relative border-0 h-full md:h-auto px-3 md:border bg-white dark:bg-zinc-900 dark:border-zinc-800 dark:text-zinc-400"
onClick={onRefresh}
>
<div className="flex h-gull w-full items-center justify-center gap-2">
<RefreshCcw className="h-4 w-4 text-zinc-500 md:h-3 md:w-3" />
<span className="hidden md:block">Regenerate response</span>
</div>
</Button>
) : null}
</div>
</div>
<TooltipProvider> <TooltipProvider>
<div className="relative flex w-full grow flex-col rounded-md border bg-background overflow-hidden px-12"> <div className="relative flex w-full grow flex-col sm:rounded-md sm:border bg-background overflow-hidden px-12">
<Tooltip> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<Link <Link
@ -98,40 +75,25 @@ export function PromptForm({
onChange={e => setInput(e.target.value)} onChange={e => setInput(e.target.value)}
placeholder="Send a message." placeholder="Send a message."
spellCheck={false} spellCheck={false}
className={cn( className="min-h-[60px] sm:text-sm bg-transparent px-4 py-[1.4rem] focus-within:outline-none w-full resize-none"
'min-h-[60px] text-sm bg-transparent px-4 py-[1.4rem] focus-within:outline-none w-full resize-none',
fontMessage.className
)}
/> />
<div className="absolute top-4 right-4"> <div className="absolute top-4 right-4">
{isLoading ? (
<Tooltip> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<Button <Button
variant="ghost" type="submit"
type="button" size="icon"
onClick={onAbort} disabled={isLoading || input === ''}
className="h-8 w-8 p-0"
> >
<StopCircle className="h-4 w-4" />
</Button>
</TooltipTrigger>
<TooltipContent>Stop generating</TooltipContent>
</Tooltip>
) : (
<Tooltip>
<TooltipTrigger asChild>
<Button type="submit" className="h-8 w-8 p-0">
<CornerDownLeft className="h-4 w-4" /> <CornerDownLeft className="h-4 w-4" />
<span className="sr-only">Send message</span>
</Button> </Button>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent>Send message</TooltipContent> <TooltipContent>Send message</TooltipContent>
</Tooltip> </Tooltip>
)}
</div> </div>
</div> </div>
</TooltipProvider> </TooltipProvider>
</div>
</form> </form>
) )
} }

View file

@ -1,10 +1,10 @@
'use client' 'use client'
import * as React from 'react'
import { useTransition } from 'react'
import { Loader2Icon, MessageSquare, Trash2Icon } from 'lucide-react' import { Loader2Icon, MessageSquare, Trash2Icon } from 'lucide-react'
import Link from 'next/link' import Link from 'next/link'
import { usePathname, useRouter } from 'next/navigation' import { usePathname, useRouter } from 'next/navigation'
import * as React from 'react'
import { useTransition } from 'react'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
import { removeChat } from '@/app/actions' import { removeChat } from '@/app/actions'

View file

@ -26,21 +26,21 @@ export async function Sidebar({ session, chats }: SidebarProps) {
return ( return (
<Sheet> <Sheet>
<SheetTrigger asChild> <SheetTrigger asChild>
<Button variant="ghost" className="w-9 h-9 p-0"> <Button variant="ghost" className="w-9 h-9 p-0 -ml-2">
<SidebarIcon className="w-6 h-6" /> <SidebarIcon className="w-6 h-6" />
<span className="sr-only">Toggle Sidebar</span> <span className="sr-only">Toggle Sidebar</span>
</Button> </Button>
</SheetTrigger> </SheetTrigger>
<SheetContent <SheetContent
position="left" position="left"
className="w-[300px] gap-0 top-2 px-4 rounded-r-lg bottom-2 h-auto flex flex-col" className="w-[300px] gap-0 top-0 bottom-0 lg:top-2 p-0 rounded-r-lg lg:bottom-2 h-auto flex flex-col"
> >
<SheetHeader> <SheetHeader className="px-4 py-4">
<SheetTitle>Chat History</SheetTitle> <SheetTitle className="text-sm">Chat History</SheetTitle>
</SheetHeader> </SheetHeader>
<div className="flex-1"> <div className="flex-1 overflow-auto">
{chats?.length ? ( {chats?.length ? (
<div className="space-y-2 -mx-2 py-4"> <div className="space-y-2 px-2">
{chats.map(chat => ( {chats.map(chat => (
<SidebarItem <SidebarItem
key={chat.id} key={chat.id}
@ -59,7 +59,7 @@ export async function Sidebar({ session, chats }: SidebarProps) {
</div> </div>
)} )}
</div> </div>
<div className="flex items-center"> <div className="flex items-center p-4">
<ThemeToggle /> <ThemeToggle />
{session?.user && ( {session?.user && (
<Button <Button

View file

@ -5,11 +5,12 @@ import { cva, type VariantProps } from 'class-variance-authority'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
const buttonVariants = cva( const buttonVariants = cva(
'inline-flex items-center justify-center shadow-sm rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none ring-offset-background', 'inline-flex items-center justify-center shadow rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none ring-offset-background',
{ {
variants: { variants: {
variant: { variant: {
default: 'bg-primary text-primary-foreground hover:bg-primary/90', default:
'bg-primary shadow-md text-primary-foreground hover:bg-primary/90',
destructive: destructive:
'bg-destructive text-destructive-foreground hover:bg-destructive/90', 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
outline: outline:
@ -20,10 +21,10 @@ const buttonVariants = cva(
link: 'underline-offset-4 shadow-none hover:underline text-primary' link: 'underline-offset-4 shadow-none hover:underline text-primary'
}, },
size: { size: {
default: 'h-9 py-2 px-4', default: 'h-8 py-2 px-4',
sm: 'h-9 px-3 rounded-md', sm: 'h-8 px-3 rounded-md',
lg: 'h-11 px-8 rounded-md', lg: 'h-11 px-8 rounded-md',
icon: 'h-9 w-9 p-0' icon: 'h-8 w-8 p-0'
} }
}, },
defaultVariants: { defaultVariants: {

View file

@ -124,4 +124,4 @@ const CodeBlock: FC<Props> = memo(({ language, value }) => {
}) })
CodeBlock.displayName = 'CodeBlock' CodeBlock.displayName = 'CodeBlock'
export default CodeBlock export { CodeBlock }

View file

@ -0,0 +1,31 @@
"use client"
import * as React from "react"
import * as SeparatorPrimitive from "@radix-ui/react-separator"
import { cn } from "@/lib/utils"
const Separator = React.forwardRef<
React.ElementRef<typeof SeparatorPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
>(
(
{ className, orientation = "horizontal", decorative = true, ...props },
ref
) => (
<SeparatorPrimitive.Root
ref={ref}
decorative={decorative}
orientation={orientation}
className={cn(
"shrink-0 bg-border",
orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
className
)}
{...props}
/>
)
)
Separator.displayName = SeparatorPrimitive.Root.displayName
export { Separator }

View file

@ -32,5 +32,7 @@ export function UserMenu({ session }: UserMenuProps) {
) )
} }
return <p className="text-sm font-medium">Logged in as {session.user.name}</p> return (
<p className="text-sm font-medium px-2">Logged in as {session.user.name}</p>
)
} }

View file

@ -1,6 +1,6 @@
import { import {
JetBrains_Mono as FontMono, JetBrains_Mono as FontMono,
IBM_Plex_Sans as FontMessage, Lora as FontMessage,
Inter as FontSans Inter as FontSans
} from 'next/font/google' } from 'next/font/google'
@ -11,7 +11,6 @@ export const fontSans = FontSans({
export const fontMessage = FontMessage({ export const fontMessage = FontMessage({
subsets: ['latin'], subsets: ['latin'],
weight: ['400', '500', '600'],
variable: '--font-message' variable: '--font-message'
}) })

View file

@ -19,6 +19,7 @@
"@radix-ui/react-alert-dialog": "^1.0.4", "@radix-ui/react-alert-dialog": "^1.0.4",
"@radix-ui/react-dialog": "^1.0.4", "@radix-ui/react-dialog": "^1.0.4",
"@radix-ui/react-dropdown-menu": "^2.0.4", "@radix-ui/react-dropdown-menu": "^2.0.4",
"@radix-ui/react-separator": "^1.0.3",
"@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-tooltip": "^1.0.6", "@radix-ui/react-tooltip": "^1.0.6",
"@vercel/analytics": "^1.0.0", "@vercel/analytics": "^1.0.0",

24
pnpm-lock.yaml generated
View file

@ -23,6 +23,9 @@ dependencies:
'@radix-ui/react-dropdown-menu': '@radix-ui/react-dropdown-menu':
specifier: ^2.0.4 specifier: ^2.0.4
version: 2.0.4(@types/react@18.2.6)(react-dom@18.2.0)(react@18.2.0) version: 2.0.4(@types/react@18.2.6)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-separator':
specifier: ^1.0.3
version: 1.0.3(@types/react-dom@18.2.4)(@types/react@18.2.6)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-slot': '@radix-ui/react-slot':
specifier: ^1.0.2 specifier: ^1.0.2
version: 1.0.2(@types/react@18.2.6)(react@18.2.0) version: 1.0.2(@types/react@18.2.6)(react@18.2.0)
@ -996,6 +999,27 @@ packages:
react-dom: 18.2.0(react@18.2.0) react-dom: 18.2.0(react@18.2.0)
dev: false dev: false
/@radix-ui/react-separator@1.0.3(@types/react-dom@18.2.4)(@types/react@18.2.6)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-itYmTy/kokS21aiV5+Z56MZB54KrhPgn6eHDKkFeOLR34HMN2s8PaN47qZZAGnvupcjxHaFZnW4pQEh0BvvVuw==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0
react-dom: ^16.8 || ^17.0 || ^18.0
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
dependencies:
'@babel/runtime': 7.21.5
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.4)(@types/react@18.2.6)(react-dom@18.2.0)(react@18.2.0)
'@types/react': 18.2.6
'@types/react-dom': 18.2.4
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
/@radix-ui/react-slot@1.0.1(react@18.2.0): /@radix-ui/react-slot@1.0.1(react@18.2.0):
resolution: {integrity: sha512-avutXAFL1ehGvAXtPquu0YK5oz6ctS474iM3vNGQIkswrVhdrS52e3uoMQBzZhNRAIE0jBnUyXWNmSjGHhCFcw==} resolution: {integrity: sha512-avutXAFL1ehGvAXtPquu0YK5oz6ctS474iM3vNGQIkswrVhdrS52e3uoMQBzZhNRAIE0jBnUyXWNmSjGHhCFcw==}
peerDependencies: peerDependencies: