feat: update chat display and fix issue with regeneration
This commit is contained in:
parent
e06f34b0f1
commit
8f64c55896
17 changed files with 266 additions and 231 deletions
|
|
@ -5,68 +5,66 @@
|
|||
@layer base {
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 222.2 47.4% 11.2%;
|
||||
--foreground: 240 10% 3.9%;
|
||||
|
||||
--muted: 210 40% 96.1%;
|
||||
--muted-foreground: 215.4 16.3% 46.9%;
|
||||
--muted: 240 4.8% 95.9%;
|
||||
--muted-foreground: 240 3.8% 46.1%;
|
||||
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 222.2 47.4% 11.2%;
|
||||
--popover-foreground: 240 10% 3.9%;
|
||||
|
||||
--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%;
|
||||
--input: 214.3 31.8% 91.4%;
|
||||
--border: 240 5.9% 90%;
|
||||
--input: 240 5.9% 90%;
|
||||
|
||||
--primary: 222.2 47.4% 11.2%;
|
||||
--primary-foreground: 210 40% 98%;
|
||||
--primary: 240 5.9% 10%;
|
||||
--primary-foreground: 0 0% 98%;
|
||||
|
||||
--secondary: 210 40% 96.1%;
|
||||
--secondary-foreground: 222.2 47.4% 11.2%;
|
||||
--secondary: 240 4.8% 95.9%;
|
||||
--secondary-foreground: 240 5.9% 10%;
|
||||
|
||||
--accent: 210 40% 96.1%;
|
||||
--accent-foreground: 222.2 47.4% 11.2%;
|
||||
--accent: 240 4.8% 95.9%;
|
||||
--accent-foreground: ;
|
||||
|
||||
--destructive: 0 100% 50%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
|
||||
--ring: 215 20.2% 65.1%;
|
||||
--ring: 240 5% 64.9%;
|
||||
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: 224 71% 4%;
|
||||
--foreground: 213 31% 91%;
|
||||
--background: 240 10% 3.9%;
|
||||
--foreground: 0 0% 98%;
|
||||
|
||||
--muted: 223 47% 11%;
|
||||
--muted-foreground: 215.4 16.3% 56.9%;
|
||||
--muted: 240 3.7% 15.9%;
|
||||
--muted-foreground: 240 5% 64.9%;
|
||||
|
||||
--popover: 224 71% 4%;
|
||||
--popover-foreground: 215 20.2% 65.1%;
|
||||
--popover: 240 10% 3.9%;
|
||||
--popover-foreground: 0 0% 98%;
|
||||
|
||||
--card: 224 71% 4%;
|
||||
--card-foreground: 213 31% 91%;
|
||||
--card: 240 10% 3.9%;
|
||||
--card-foreground: 0 0% 98%;
|
||||
|
||||
--border: 216 34% 17%;
|
||||
--input: 216 34% 17%;
|
||||
--border: 240 3.7% 15.9%;
|
||||
--input: 240 3.7% 15.9%;
|
||||
|
||||
--primary: 210 40% 98%;
|
||||
--primary-foreground: 222.2 47.4% 1.2%;
|
||||
--primary: 0 0% 98%;
|
||||
--primary-foreground: 240 5.9% 10%;
|
||||
|
||||
--secondary: 222.2 47.4% 11.2%;
|
||||
--secondary-foreground: 210 40% 98%;
|
||||
--secondary: 240 3.7% 15.9%;
|
||||
--secondary-foreground: 0 0% 98%;
|
||||
|
||||
--accent: 216 34% 17%;
|
||||
--accent-foreground: 210 40% 98%;
|
||||
--accent: 240 3.7% 15.9%;
|
||||
--accent-foreground: ;
|
||||
|
||||
--destructive: 0 63% 31%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive-foreground: 0 85.7% 97.3%;
|
||||
|
||||
--ring: 216 34% 17%;
|
||||
|
||||
--radius: 0.5rem;
|
||||
--ring: 240 3.7% 15.9%;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -76,10 +74,5 @@
|
|||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
font-feature-settings: 'rlig' 1, 'calt' 1;
|
||||
}
|
||||
}
|
||||
|
||||
pre:has(div.codeblock) {
|
||||
padding: 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { type Message } from 'ai-connector'
|
|||
|
||||
import { ChatMessage } from '@/components/chat-message'
|
||||
import { EmptyScreen } from '@/components/empty-screen'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
|
||||
export interface ChatList {
|
||||
messages: Message[]
|
||||
|
|
@ -11,15 +12,16 @@ export interface ChatList {
|
|||
|
||||
export function ChatList({ messages }: ChatList) {
|
||||
return (
|
||||
<div className="relative max-w-2xl mx-auto">
|
||||
<div className="relative max-w-2xl px-4 mx-auto">
|
||||
{messages.length > 0 ? (
|
||||
messages.map(message => (
|
||||
<ChatMessage key={message.id || message.content} message={message} />
|
||||
messages.map((message, index) => (
|
||||
<div key={index}>
|
||||
<ChatMessage message={message} />
|
||||
{index < messages.length - 1 && <Separator className="my-8" />}
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<div className="pt-10">
|
||||
<EmptyScreen />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import remarkMath from 'remark-math'
|
|||
|
||||
import { cn } from '@/lib/utils'
|
||||
import { fontMessage } from '@/lib/fonts'
|
||||
import CodeBlock from '@/components/ui/codeblock'
|
||||
import { CodeBlock } from '@/components/ui/codeblock'
|
||||
import { MemoizedReactMarkdown } from '@/components/markdown'
|
||||
import { OpenAI } from '@/components/icons'
|
||||
|
||||
|
|
@ -17,16 +17,17 @@ export function ChatMessage({ message, ...props }: ChatMessageProps) {
|
|||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'flex items-start space-x-4 mb-4 relative -ml-12',
|
||||
fontMessage.className,
|
||||
message.role === 'user' && 'mt-12 first:mt-0'
|
||||
'flex items-start mb-4 relative md:-ml-12',
|
||||
fontMessage.className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
'flex h-8 w-8 shrink-0 items-center justify-center rounded-full select-none border',
|
||||
message.role === 'assistant' && 'bg-primary text-primary-foreground'
|
||||
'md:flex h-8 w-8 shrink-0 hidden items-center justify-center rounded-full select-none border',
|
||||
message.role === 'user'
|
||||
? 'bg-background'
|
||||
: 'bg-primary text-primary-foreground'
|
||||
)}
|
||||
>
|
||||
{message.role === 'user' ? (
|
||||
|
|
@ -35,14 +36,9 @@ export function ChatMessage({ message, ...props }: ChatMessageProps) {
|
|||
<OpenAI className="w-4 h-4" />
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
'border rounded-lg py-2 px-4',
|
||||
message.role === 'assistant' && 'bg-muted/30'
|
||||
)}
|
||||
>
|
||||
<div className="md:ml-4">
|
||||
<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]}
|
||||
components={{
|
||||
p({ children }) {
|
||||
|
|
@ -50,7 +46,7 @@ export function ChatMessage({ message, ...props }: ChatMessageProps) {
|
|||
<p
|
||||
className={cn(
|
||||
'mb-2 last:mb-0',
|
||||
message.role === 'user' && 'font-medium'
|
||||
message.role === 'user' && 'font-semibold'
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
|
|
|
|||
|
|
@ -1,25 +1,54 @@
|
|||
'use client'
|
||||
|
||||
import { ExternalLink } from '@/components/external-link'
|
||||
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
|
||||
extends Pick<UseChatHelpers, 'append' | 'isLoading' | 'reload' | 'messages'> {
|
||||
extends Pick<
|
||||
UseChatHelpers,
|
||||
'append' | 'isLoading' | 'reload' | 'messages' | 'stop'
|
||||
> {
|
||||
id?: string
|
||||
}
|
||||
|
||||
export function ChatPanel({
|
||||
id,
|
||||
append,
|
||||
isLoading,
|
||||
stop,
|
||||
append,
|
||||
reload,
|
||||
messages
|
||||
}: ChatPanelProps) {
|
||||
return (
|
||||
<div className="fixed bottom-0 left-0 right-0">
|
||||
<div className="max-w-2xl 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="fixed bottom-0 left-0 right-0 bg-gradient-to-b from-muted/10 from-10% to-muted/30 to-50%">
|
||||
<div className="sm:max-w-2xl sm:px-4 mx-auto">
|
||||
<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
|
||||
onSubmit={value => {
|
||||
append({
|
||||
|
|
@ -27,11 +56,10 @@ export function ChatPanel({
|
|||
role: 'user'
|
||||
})
|
||||
}}
|
||||
onRefresh={messages.length ? reload : undefined}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
<p className="text-muted-foreground text-xs leading-normal text-center pb-1">
|
||||
This is an open source AI chatbot app built with{' '}
|
||||
<p className="hidden sm:block text-muted-foreground text-xs leading-normal text-center px-2">
|
||||
Open source AI chatbot app built with{' '}
|
||||
<ExternalLink href="https://nextjs.org">Next.js</ExternalLink> and{' '}
|
||||
<ExternalLink href="https://vercel.com/storage/kv">
|
||||
Vercel KV
|
||||
|
|
|
|||
|
|
@ -11,23 +11,22 @@ export interface ChatProps {
|
|||
}
|
||||
|
||||
export function Chat({ id, initialMessages }: ChatProps) {
|
||||
const { messages, append, reload, isLoading } = useChat({
|
||||
const { messages, append, reload, stop, isLoading } = useChat({
|
||||
initialMessages,
|
||||
id
|
||||
})
|
||||
|
||||
return (
|
||||
<div className="h-full overflow-hidden">
|
||||
<div className="h-full w-full overflow-auto pb-[200px]">
|
||||
<div className="h-full w-full overflow-auto pt-4 md:pt-10 bg-muted/50 pb-[200px]">
|
||||
<ChatList messages={messages} />
|
||||
<ChatPanel
|
||||
id={id}
|
||||
append={append}
|
||||
isLoading={isLoading}
|
||||
stop={stop}
|
||||
append={append}
|
||||
reload={reload}
|
||||
messages={messages}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { ArrowRight } from 'lucide-react'
|
||||
|
||||
import { cn } from '@/lib/utils'
|
||||
import { fontMessage } from '@/lib/fonts'
|
||||
import { useChatStore } from '@/lib/hooks/use-chat-store'
|
||||
import { ExternalLink } from '@/components/external-link'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { OpenAI } from '@/components/icons'
|
||||
|
||||
const exampleMessages = [
|
||||
{
|
||||
|
|
@ -25,22 +25,17 @@ export function EmptyScreen({ className }: React.ComponentProps<'div'>) {
|
|||
const { setDefaultMessage } = useChatStore()
|
||||
|
||||
return (
|
||||
<div className="flex items-start space-x-4 mb-4 -ml-12">
|
||||
<div
|
||||
className={cn(
|
||||
'flex h-8 w-8 shrink-0 items-center justify-center rounded-full select-none border bg-primary text-primary-foreground'
|
||||
)}
|
||||
>
|
||||
<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',
|
||||
'border bg-background p-8 rounded-lg',
|
||||
fontMessage.className,
|
||||
className
|
||||
)}
|
||||
>
|
||||
<h1 className="font-semibold mb-2">Welcome to Next.js Chatbot!</h1>
|
||||
<p className="text-muted-foreground text-sm leading-normal">
|
||||
<h1 className="font-semibold text-lg mb-2">
|
||||
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{' '}
|
||||
<ExternalLink href="https://nextjs.org">Next.js</ExternalLink> and{' '}
|
||||
<ExternalLink href="https://vercel.com/storage/kv">
|
||||
|
|
@ -48,15 +43,15 @@ export function EmptyScreen({ className }: React.ComponentProps<'div'>) {
|
|||
</ExternalLink>
|
||||
.
|
||||
</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:
|
||||
</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) => (
|
||||
<Button
|
||||
key={index}
|
||||
variant="link"
|
||||
className="p-0 h-auto"
|
||||
className="p-0 h-auto text-base"
|
||||
onClick={() => setDefaultMessage(message.message)}
|
||||
>
|
||||
<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>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,14 +11,16 @@ export async function Header() {
|
|||
const chats = session?.user?.email ? await getChats(session.user.email) : []
|
||||
|
||||
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">
|
||||
<div className="flex items-center w-2/5">
|
||||
<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">
|
||||
{/* @ts-ignore */}
|
||||
<Sidebar chats={chats} session={session} />
|
||||
<div className="hidden md:flex items-center">
|
||||
<Separator className="h-6 w-6 text-muted-foreground/50" />
|
||||
<UserMenu session={session} />
|
||||
</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
|
||||
target="_blank"
|
||||
href="https://github.com/vercel/nextjs-ai-chatbot/"
|
||||
|
|
@ -34,7 +36,8 @@ export async function Header() {
|
|||
className={cn(buttonVariants())}
|
||||
>
|
||||
<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>
|
||||
</div>
|
||||
</header>
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
'use client'
|
||||
|
||||
import * as React from 'react'
|
||||
import { CornerDownLeft, Plus, RefreshCcw, StopCircle } from 'lucide-react'
|
||||
import { useState } from 'react'
|
||||
import { CornerDownLeft, Plus } from 'lucide-react'
|
||||
import Textarea from 'react-textarea-autosize'
|
||||
|
||||
import { Button, buttonVariants } from '@/components/ui/button'
|
||||
import { fontMessage } from '@/lib/fonts'
|
||||
import { useEnterSubmit } from '@/lib/hooks/use-enter-submit'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { useChatStore } from '@/lib/hooks/use-chat-store'
|
||||
|
|
@ -20,19 +18,12 @@ import Link from 'next/link'
|
|||
|
||||
export interface PromptProps {
|
||||
onSubmit: (value: string) => void
|
||||
onRefresh?: () => void
|
||||
onAbort?: () => void
|
||||
isLoading: boolean
|
||||
}
|
||||
|
||||
export function PromptForm({
|
||||
onSubmit,
|
||||
onRefresh,
|
||||
onAbort,
|
||||
isLoading
|
||||
}: PromptProps) {
|
||||
export function PromptForm({ onSubmit, isLoading }: PromptProps) {
|
||||
const { defaultMessage } = useChatStore()
|
||||
const [input, setInput] = useState(defaultMessage)
|
||||
const [input, setInput] = React.useState(defaultMessage)
|
||||
const { formRef, onKeyDown } = useEnterSubmit()
|
||||
const inputRef = React.useRef<HTMLTextAreaElement>(null)
|
||||
|
||||
|
|
@ -50,30 +41,16 @@ export function PromptForm({
|
|||
<form
|
||||
onSubmit={async e => {
|
||||
e.preventDefault()
|
||||
if (input === '') {
|
||||
return
|
||||
}
|
||||
setInput('')
|
||||
await onSubmit(input)
|
||||
}}
|
||||
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>
|
||||
<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>
|
||||
<TooltipTrigger asChild>
|
||||
<Link
|
||||
|
|
@ -98,40 +75,25 @@ export function PromptForm({
|
|||
onChange={e => setInput(e.target.value)}
|
||||
placeholder="Send a message."
|
||||
spellCheck={false}
|
||||
className={cn(
|
||||
'min-h-[60px] text-sm bg-transparent px-4 py-[1.4rem] focus-within:outline-none w-full resize-none',
|
||||
fontMessage.className
|
||||
)}
|
||||
className="min-h-[60px] sm:text-sm bg-transparent px-4 py-[1.4rem] focus-within:outline-none w-full resize-none"
|
||||
/>
|
||||
<div className="absolute top-4 right-4">
|
||||
{isLoading ? (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
type="button"
|
||||
onClick={onAbort}
|
||||
className="h-8 w-8 p-0"
|
||||
type="submit"
|
||||
size="icon"
|
||||
disabled={isLoading || input === ''}
|
||||
>
|
||||
<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" />
|
||||
<span className="sr-only">Send message</span>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Send message</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
'use client'
|
||||
|
||||
import * as React from 'react'
|
||||
import { useTransition } from 'react'
|
||||
import { Loader2Icon, MessageSquare, Trash2Icon } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
import { usePathname, useRouter } from 'next/navigation'
|
||||
import * as React from 'react'
|
||||
import { useTransition } from 'react'
|
||||
|
||||
import { cn } from '@/lib/utils'
|
||||
import { removeChat } from '@/app/actions'
|
||||
|
|
|
|||
|
|
@ -26,21 +26,21 @@ export async function Sidebar({ session, chats }: SidebarProps) {
|
|||
return (
|
||||
<Sheet>
|
||||
<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" />
|
||||
<span className="sr-only">Toggle Sidebar</span>
|
||||
</Button>
|
||||
</SheetTrigger>
|
||||
<SheetContent
|
||||
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>
|
||||
<SheetTitle>Chat History</SheetTitle>
|
||||
<SheetHeader className="px-4 py-4">
|
||||
<SheetTitle className="text-sm">Chat History</SheetTitle>
|
||||
</SheetHeader>
|
||||
<div className="flex-1">
|
||||
<div className="flex-1 overflow-auto">
|
||||
{chats?.length ? (
|
||||
<div className="space-y-2 -mx-2 py-4">
|
||||
<div className="space-y-2 px-2">
|
||||
{chats.map(chat => (
|
||||
<SidebarItem
|
||||
key={chat.id}
|
||||
|
|
@ -59,7 +59,7 @@ export async function Sidebar({ session, chats }: SidebarProps) {
|
|||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<div className="flex items-center p-4">
|
||||
<ThemeToggle />
|
||||
{session?.user && (
|
||||
<Button
|
||||
|
|
|
|||
|
|
@ -5,11 +5,12 @@ import { cva, type VariantProps } from 'class-variance-authority'
|
|||
import { cn } from '@/lib/utils'
|
||||
|
||||
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: {
|
||||
variant: {
|
||||
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
|
||||
default:
|
||||
'bg-primary shadow-md text-primary-foreground hover:bg-primary/90',
|
||||
destructive:
|
||||
'bg-destructive text-destructive-foreground hover:bg-destructive/90',
|
||||
outline:
|
||||
|
|
@ -20,10 +21,10 @@ const buttonVariants = cva(
|
|||
link: 'underline-offset-4 shadow-none hover:underline text-primary'
|
||||
},
|
||||
size: {
|
||||
default: 'h-9 py-2 px-4',
|
||||
sm: 'h-9 px-3 rounded-md',
|
||||
default: 'h-8 py-2 px-4',
|
||||
sm: 'h-8 px-3 rounded-md',
|
||||
lg: 'h-11 px-8 rounded-md',
|
||||
icon: 'h-9 w-9 p-0'
|
||||
icon: 'h-8 w-8 p-0'
|
||||
}
|
||||
},
|
||||
defaultVariants: {
|
||||
|
|
|
|||
|
|
@ -124,4 +124,4 @@ const CodeBlock: FC<Props> = memo(({ language, value }) => {
|
|||
})
|
||||
CodeBlock.displayName = 'CodeBlock'
|
||||
|
||||
export default CodeBlock
|
||||
export { CodeBlock }
|
||||
|
|
|
|||
31
components/ui/separator.tsx
Normal file
31
components/ui/separator.tsx
Normal 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 }
|
||||
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import {
|
||||
JetBrains_Mono as FontMono,
|
||||
IBM_Plex_Sans as FontMessage,
|
||||
Lora as FontMessage,
|
||||
Inter as FontSans
|
||||
} from 'next/font/google'
|
||||
|
||||
|
|
@ -11,7 +11,6 @@ export const fontSans = FontSans({
|
|||
|
||||
export const fontMessage = FontMessage({
|
||||
subsets: ['latin'],
|
||||
weight: ['400', '500', '600'],
|
||||
variable: '--font-message'
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
"@radix-ui/react-alert-dialog": "^1.0.4",
|
||||
"@radix-ui/react-dialog": "^1.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-tooltip": "^1.0.6",
|
||||
"@vercel/analytics": "^1.0.0",
|
||||
|
|
|
|||
24
pnpm-lock.yaml
generated
24
pnpm-lock.yaml
generated
|
|
@ -23,6 +23,9 @@ dependencies:
|
|||
'@radix-ui/react-dropdown-menu':
|
||||
specifier: ^2.0.4
|
||||
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':
|
||||
specifier: ^1.0.2
|
||||
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)
|
||||
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):
|
||||
resolution: {integrity: sha512-avutXAFL1ehGvAXtPquu0YK5oz6ctS474iM3vNGQIkswrVhdrS52e3uoMQBzZhNRAIE0jBnUyXWNmSjGHhCFcw==}
|
||||
peerDependencies:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue