Skip to content

Commit

Permalink
Merge pull request #49 from jakobhoeg/nextra
Browse files Browse the repository at this point in the history
docs: refactor + update documentation
  • Loading branch information
jakobhoeg authored Sep 16, 2024
2 parents 416b21d + 4a67c7a commit d23c131
Show file tree
Hide file tree
Showing 54 changed files with 1,226 additions and 1,129 deletions.
111 changes: 2 additions & 109 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@

<p align="center">Package for adding customizable and re-usable chat components to your applications. Build beautiful chat interfaces in minutes.</p>

# Installation & Vercel AI SDK example
# Vercel AI SDK chatbot example

https://github.com/user-attachments/assets/b137f6a2-4565-4b4a-8a58-a8dfe0b66a7a

Build an AI support chatbot from scratch in less than five minutes! Find the code from the video [here](https://github.com/jakobhoeg/shadcn-chat/tree/master/examples/shadcn-chat-example-vercel-ai).

# Installation

For full documentation check out the [npm documentation](https://www.npmjs.com/package/shadcn-chat-cli)
Check out the full documentation and API reference [here](https://docsshadcn-chat.vercel.app/).

> [!NOTE]
> Some of the components rely on [shadcn-ui](https://ui.shadcn.com/docs/installation), so make sure to have it installed.
Expand All @@ -43,110 +43,3 @@ Otherwise, install individual components by running:
```
npx shadcn-chat-cli add [component]
```

# Usage & Examples

All of the below primitives are unstyled and you can add styling in any way you'd like - for instance with `className`.

## Chat components

This is an example of how to quickly build stunning Chat UIs.

```
import { ChatMessageList } from "@/components/ui/chat/chat-message-list";
import { ChatBubble, ChatBubbleAvatar, ChatBubbleMessage } from "@/components/ui/chat/chat-bubble";
import { ChatInput } from "@/components/ui/chat/chat-input";
<>
<ChatMessageList>
<ChatBubble>
<ChatBubbleAvatar />
<ChatBubbleMessage>
Message and other content here
</ChatBubbleMessage>
</ChatBubble>
</ChatMessageList>
<div className="flex-1" />
<ChatInput
placeholder="Type your message here..."
/>
<Button
size="sm" className="ml-auto gap-1.5">
Send Message
<CornerDownLeft className="size-3.5" />
</Button>
</>
```

For more comprehensive examples, check out [this](https://github.com/jakobhoeg/shadcn-chat/blob/master/apps/www/src/app/chatbot/page.tsx#L121-L220), [this](https://github.com/jakobhoeg/shadcn-chat/blob/master/apps/www/src/app/chatbot2/page.tsx#L122-L221) & [this](https://github.com/jakobhoeg/shadcn-chat/blob/master/apps/www/src/components/chat/chat-list.tsx) from the source code.

## Expandable Chat component

**v.0.2.0** adds additional functionality by adding an **expandable** chat component that you can use in conjunction with the other components to quickly build a chat-support type feature in your application.

`ChatSupport.tsx`:

```
import { ChatBubble, ChatBubbleAvatar, ChatBubbleMessage } from '@/components/ui/chat/chat-bubble'
import { ChatInput } from '@/components/ui/chat/chat-input'
import { ExpandableChat, ExpandableChatHeader, ExpandableChatBody, ExpandableChatFooter } from '@/components/ui/chat/expandable-chat'
import { ChatMessageList } from '@/components/ui/chat/chat-message-list'
export default function ChatSupport() {
return (
<ExpandableChat
size='lg'
position='bottom-right'>
<ExpandableChatHeader className='flex-col text-center justify-center'>
<h1 className='text-xl font-semibold'>Chat with our AI ✨</h1>
<p>Ask any question for our AI to answer</p>
<div className='flex gap-2 items-center pt-2'>
<Button
variant='secondary'
>
New Chat
</Button>
<Button
variant='secondary'
>
See FAQ
</Button>
</div>
</ExpandableChatHeader>
<ExpandableChatBody>
<ChatMessageList>
<ChatBubble>
<ChatBubbleAvatar/>
<ChatBubbleMessage>
{message.content}
</ChatBubbleMessage>
</ChatBubble>
</ChatMessageList>
</ExpandableChatBody>
<ExpandableChatFooter>
<ChatInput />
<Button
type="submit" size="icon">
<Send className="size-4" />
</Button>
</ExpandableChatFooter>
</ExpandableChat>
)
}
```

And then use that component on all your sites by placing it in `layout.tsx` (Next.js)

`layout.tsx`:

```
<html lang="en">
<body>
</main>
{children}
<ChatSupport />
</main>
</body>
```

Check out [this](https://github.com/jakobhoeg/shadcn-chat/blob/master/apps/www/src/components/chat/chat-support.tsx#L73-L156) for a better overview of the api.
32 changes: 23 additions & 9 deletions apps/docs/components/component-preview.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,38 @@
import * as React from "react"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "../components/ui/tabs"
import { CopyButton } from "./copy-button"
import * as React from "react";
import {
Tabs,
TabsContent,
TabsList,
TabsTrigger,
} from "../components/ui/tabs";
import { CopyButton } from "./copy-button";

interface ComponentPreviewProps {
component?: React.ReactNode
code: string
component?: React.ReactNode;
code: string;
}

export default function ComponentPreview({ component, code }: ComponentPreviewProps) {
export default function ComponentPreview({
component,
code,
}: ComponentPreviewProps) {
return (
<div className="relative my-4 flex flex-col space-y-2">
<Tabs defaultValue="preview" className="relative w-full">
<TabsList className=" justify-start rounded-md ">
<TabsTrigger value="preview">Preview</TabsTrigger>
<TabsTrigger value="code">Code</TabsTrigger>
</TabsList>
<TabsContent value="preview" className="relative rounded-md border dark:border-neutral-600 p-4">
<TabsContent
value="preview"
className="relative rounded-md border dark:border-neutral-600 p-4"
>
{component}
</TabsContent>
<TabsContent value="code" className="relative max-h-[400px] overflow-y-auto w-full rounded-md border dark:border-neutral-600 bg-foreground text-background p-4">
<TabsContent
value="code"
className="relative max-h-[400px] overflow-y-auto w-full rounded-md border dark:border-neutral-600 bg-foreground text-background p-4"
>
<div className="relative w-full rounded-md text-sm ">
<CopyButton
value={code}
Expand All @@ -31,4 +45,4 @@ export default function ComponentPreview({ component, code }: ComponentPreviewPr
</Tabs>
</div>
);
}
}
56 changes: 28 additions & 28 deletions apps/docs/components/copy-button.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
"use client"
"use client";

import * as React from "react"
import { CheckIcon, ClipboardIcon } from "lucide-react"
import * as React from "react";
import { CheckIcon, ClipboardIcon } from "lucide-react";

import { cn } from "@/lib/utils"
import { Button, ButtonProps } from "./ui/button"
import { cn } from "@/lib/utils";
import { Button, ButtonProps } from "./ui/button";

interface CopyButtonProps extends ButtonProps {
value: string
src?: string
value: string;
src?: string;
}

export async function copyToClipboardWithMeta(value: string) {
navigator.clipboard.writeText(value)
navigator.clipboard.writeText(value);
}

export function CopyButton({
Expand All @@ -22,37 +22,37 @@ export function CopyButton({
variant = "ghost",
...props
}: CopyButtonProps) {
const [hasCopied, setHasCopied] = React.useState(false)
const [hasCopied, setHasCopied] = React.useState(false);

React.useEffect(() => {
setTimeout(() => {
setHasCopied(false)
}, 2000)
}, [hasCopied])
setHasCopied(false);
}, 2000);
}, [hasCopied]);

return (
<Button
size="icon"
variant={variant}
className={cn(
"relative z-10 h-6 w-6 text-zinc-50 hover:bg-zinc-700 hover:text-zinc-50 [&_svg]:h-3 [&_svg]:w-3",
className
className,
)}
onClick={() => {
copyToClipboardWithMeta(value)
setHasCopied(true)
copyToClipboardWithMeta(value);
setHasCopied(true);
}}
{...props}
>
<span className="sr-only">Copy</span>
{hasCopied ? <CheckIcon /> : <ClipboardIcon />}
</Button>
)
);
}

interface CopyWithClassNamesProps extends ButtonProps {
value: string
classNames: string
value: string;
classNames: string;
}

export function CopyWithClassNames({
Expand All @@ -61,26 +61,26 @@ export function CopyWithClassNames({
className,
...props
}: CopyWithClassNamesProps) {
const [hasCopied, setHasCopied] = React.useState(false)
const [hasCopied, setHasCopied] = React.useState(false);

React.useEffect(() => {
setTimeout(() => {
setHasCopied(false)
}, 2000)
}, [hasCopied])
setHasCopied(false);
}, 2000);
}, [hasCopied]);

const copyToClipboard = React.useCallback((value: string) => {
copyToClipboardWithMeta(value)
setHasCopied(true)
}, [])
copyToClipboardWithMeta(value);
setHasCopied(true);
}, []);

return (
<Button
size="icon"
variant="ghost"
className={cn(
"relative z-10 h-6 w-6 text-zinc-50 hover:bg-zinc-700 hover:text-zinc-50",
className
className,
)}
onClick={() => copyToClipboard(value)}
{...props}
Expand All @@ -92,5 +92,5 @@ export function CopyWithClassNames({
)}
<span className="sr-only">Copy</span>
</Button>
)
}
);
}
4 changes: 2 additions & 2 deletions apps/docs/components/logo.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export default function Logo() {
return (
<p className="text-2xl md:text-3xl text-gradient font-bold">shadcn-chat</p>
)
}
);
}
45 changes: 27 additions & 18 deletions apps/docs/components/preview/chat-bubble-ai-layout-example.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { Copy, RefreshCcw } from 'lucide-react'
import { ChatBubble, ChatBubbleAvatar, ChatBubbleMessage, ChatBubbleAction } from '../../components/ui/chat/chat-bubble'
import { ChatMessageList } from '../../components/ui/chat/chat-message-list'
import { Copy, RefreshCcw } from "lucide-react";
import {
ChatBubble,
ChatBubbleAvatar,
ChatBubbleMessage,
ChatBubbleAction,
} from "../../components/ui/chat/chat-bubble";
import { ChatMessageList } from "../../components/ui/chat/chat-message-list";

export const ChatBubbleAILayoutExampleCode = `import { ChatBubble, ChatBubbleAvatar, ChatBubbleMessage, ChatBubbleAction } from '@/components/ui/chat/chat-bubble'
import { ChatMessageList } from '@/components/ui/chat/chat-message-list'
Expand Down Expand Up @@ -49,53 +54,57 @@ const actionIcons = [
)
})}
</ChatMessageList>
`
`;

export default function ChatBubbleAILayoutExample() {
const messages = [
{
id: 1,
message: 'Help me with my essay.',
sender: 'user',
message: "Help me with my essay.",
sender: "user",
},
{
id: 2,
message: 'I can help you with that. What do you need help with?',
sender: 'bot',
message: "I can help you with that. What do you need help with?",
sender: "bot",
},
];

const actionIcons = [
{ icon: Copy, type: 'Copy' },
{ icon: RefreshCcw, type: 'Regenerate' },
{ icon: Copy, type: "Copy" },
{ icon: RefreshCcw, type: "Regenerate" },
];

return (
<ChatMessageList>
{messages.map((message, index) => {
const variant = message.sender === 'user' ? 'sent' : 'received';
const variant = message.sender === "user" ? "sent" : "received";
return (
<ChatBubble key={message.id} layout='ai' >
<ChatBubbleAvatar fallback={variant === 'sent' ? 'US' : 'AI'} />
<ChatBubble key={message.id} layout="ai">
<ChatBubbleAvatar fallback={variant === "sent" ? "US" : "AI"} />
<ChatBubbleMessage>
{message.message}

{message.sender === 'bot' && (
{message.sender === "bot" && (
<div>
{actionIcons.map(({ icon: Icon, type }) => (
<ChatBubbleAction
className="size-6"
key={type}
icon={<Icon className="size-3" />}
onClick={() => console.log('Action ' + type + ' clicked for message ' + index)}
onClick={() =>
console.log(
"Action " + type + " clicked for message " + index,
)
}
/>
))}
</div>
)}
</ChatBubbleMessage>
</ChatBubble>
)
);
})}
</ChatMessageList>
)
}
);
}
Loading

0 comments on commit d23c131

Please sign in to comment.