Skip to content

Commit

Permalink
Fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
iipanda committed Oct 28, 2024
1 parent 5156b59 commit 6108635
Show file tree
Hide file tree
Showing 11 changed files with 84 additions and 10 deletions.
4 changes: 2 additions & 2 deletions apps/www/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ export const metadata: Metadata = {
],
authors: [
{
name: "shadcn",
url: "https://shadcn.com",
name: "Blazity",
url: "https://blazity.com",
},
],
creator: "shadcn",
Expand Down
2 changes: 1 addition & 1 deletion apps/www/components/copy-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
DropdownMenuTrigger,
} from "@/registry/new-york/ui/dropdown-menu"

interface CopyButtonProps extends ButtonProps {
interface CopyButtonProps extends Omit<ButtonProps, "onClick"> {
value: string
src?: string
event?: Event["name"]
Expand Down
9 changes: 9 additions & 0 deletions apps/www/components/site-footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,15 @@ export function SiteFooter() {
target="_blank"
rel="noreferrer"
className="font-medium underline underline-offset-4"
>
Blazity
</a>
, based on a project by{" "}
<a
href="https://x.com/shadcn"
target="_blank"
rel="noreferrer"
className="font-medium underline underline-offset-4"
>
shadcn
</a>
Expand Down
2 changes: 1 addition & 1 deletion apps/www/config/site.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ export const siteConfig = {
url: "https://shadcn-chatbot-kit.vercel.app",
ogImage: "https://shadcn-chatbot-kit.vercel.app/og.jpg",
description:
"Beautifully designed components that you can copy and paste into your apps. Accessible. Customizable. Open Source.",
"Beautifully designed chatbot components based on shadcn/ui. Fully customizable and owned by you.",
links: {
twitter: "https://twitter.com/blazity",
github: "https://github.com/Blazity/shadcn-chatbot-kit",
Expand Down
2 changes: 1 addition & 1 deletion apps/www/public/r/chat.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"files": [
{
"path": "ui/chat.tsx",
"content": "\"use client\"\n\nimport { forwardRef, useState, type ReactElement } from \"react\"\nimport { ArrowDown, ThumbsDown, ThumbsUp } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { useAutoScroll } from \"@/registry/default/hooks/use-auto-scroll\"\nimport { Button } from \"@/registry/default/ui/button\"\nimport { type Message } from \"@/registry/default/ui/chat-message\"\nimport { CopyButton } from \"@/registry/default/ui/copy-button\"\nimport { MessageInput } from \"@/registry/default/ui/message-input\"\nimport { MessageList } from \"@/registry/default/ui/message-list\"\nimport { PromptSuggestions } from \"@/registry/default/ui/prompt-suggestions\"\n\ninterface ChatPropsBase {\n handleSubmit: (\n event?: { preventDefault?: () => void },\n options?: { experimental_attachments?: FileList }\n ) => void\n messages: Array<Message>\n input: string\n className?: string\n handleInputChange: React.ChangeEventHandler<HTMLTextAreaElement>\n isGenerating: boolean\n stop?: () => void\n onRateResponse?: (\n messageId: string,\n rating: \"thumbs-up\" | \"thumbs-down\"\n ) => void\n}\n\ninterface ChatPropsWithoutSuggestions extends ChatPropsBase {\n append?: never\n suggestions?: never\n}\n\ninterface ChatPropsWithSuggestions extends ChatPropsBase {\n append: (message: { role: \"user\"; content: string }) => void\n suggestions: string[]\n}\n\ntype ChatProps = ChatPropsWithoutSuggestions | ChatPropsWithSuggestions\n\nexport function Chat({\n messages,\n handleSubmit,\n input,\n handleInputChange,\n stop,\n isGenerating,\n append,\n suggestions,\n className,\n onRateResponse,\n}: ChatProps) {\n const lastMessage = messages.at(-1)\n const isEmpty = messages.length === 0\n const isTyping = lastMessage?.role === \"user\"\n\n return (\n <ChatContainer className={className}>\n {isEmpty && append && suggestions ? (\n <PromptSuggestions append={append} suggestions={suggestions} />\n ) : null}\n\n {messages.length > 0 ? (\n <ChatMessages messages={messages}>\n <MessageList\n messages={messages}\n isTyping={isTyping}\n messageOptions={(message) => ({\n actions: onRateResponse ? (\n <>\n <div className=\"border-r pr-1\">\n <CopyButton\n content={message.content}\n copyMessage=\"Copied response to clipboard!\"\n />\n </div>\n <Button\n size=\"icon\"\n variant=\"ghost\"\n className=\"h-6 w-6\"\n onClick={() => onRateResponse(message.id, \"thumbs-up\")}\n >\n <ThumbsUp className=\"h-4 w-4\" />\n </Button>\n <Button\n size=\"icon\"\n variant=\"ghost\"\n className=\"h-6 w-6\"\n onClick={() => onRateResponse(message.id, \"thumbs-down\")}\n >\n <ThumbsDown className=\"h-4 w-4\" />\n </Button>\n </>\n ) : (\n <CopyButton\n content={message.content}\n copyMessage=\"Copied response to clipboard!\"\n />\n ),\n })}\n />\n </ChatMessages>\n ) : null}\n\n <ChatForm\n className=\"mt-auto\"\n isPending={isGenerating || isTyping}\n handleSubmit={handleSubmit}\n >\n {({ files, setFiles }) => (\n <MessageInput\n value={input}\n onChange={handleInputChange}\n allowAttachments\n files={files}\n setFiles={setFiles}\n stop={stop}\n isGenerating={isGenerating}\n />\n )}\n </ChatForm>\n </ChatContainer>\n )\n}\nChat.displayName = \"Chat\"\n\nexport function ChatMessages({\n messages,\n children,\n}: React.PropsWithChildren<{\n messages: Message[]\n}>) {\n const { containerRef, scrollToBottom, handleScroll, shouldAutoScroll } =\n useAutoScroll([messages])\n\n return (\n <div\n className=\"relative overflow-y-auto pb-4\"\n ref={containerRef}\n onScroll={handleScroll}\n >\n {children}\n\n {!shouldAutoScroll && (\n <div className=\"sticky bottom-0 left-0 flex w-full justify-end\">\n <Button\n onClick={scrollToBottom}\n className=\"h-8 w-8 rounded-full ease-in-out animate-in fade-in-0 slide-in-from-bottom-1\"\n size=\"icon\"\n variant=\"ghost\"\n >\n <ArrowDown className=\"h-4 w-4\" />\n </Button>\n </div>\n )}\n </div>\n )\n}\n\nexport const ChatContainer = forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => {\n return (\n <div\n ref={ref}\n className={cn(\"grid max-h-full w-full grid-rows-[1fr_auto]\", className)}\n {...props}\n />\n )\n})\nChatContainer.displayName = \"ChatContainer\"\n\ninterface ChatFormProps {\n className?: string\n isPending: boolean\n handleSubmit: (\n event?: { preventDefault?: () => void },\n options?: { experimental_attachments?: FileList }\n ) => void\n children: (props: {\n files: File[] | null\n setFiles: React.Dispatch<React.SetStateAction<File[] | null>>\n }) => ReactElement\n}\n\nexport const ChatForm = forwardRef<HTMLFormElement, ChatFormProps>(\n ({ children, handleSubmit, isPending, className }, ref) => {\n const [files, setFiles] = useState<File[] | null>(null)\n\n const onSubmit = (event: React.FormEvent) => {\n if (isPending) {\n event.preventDefault()\n return\n }\n\n if (!files) {\n handleSubmit(event)\n return\n }\n\n const fileList = createFileList(files)\n handleSubmit(event, { experimental_attachments: fileList })\n setFiles(null)\n }\n\n return (\n <form ref={ref} onSubmit={onSubmit} className={className}>\n {children({ files, setFiles })}\n </form>\n )\n }\n)\nChatForm.displayName = \"ChatForm\"\n\nfunction createFileList(files: File[] | FileList): FileList {\n const dataTransfer = new DataTransfer()\n for (const file of Array.from(files)) {\n dataTransfer.items.add(file)\n }\n return dataTransfer.files\n}\n",
"content": "\"use client\"\n\nimport { forwardRef, useState, type ReactElement } from \"react\"\nimport { ArrowDown, ThumbsDown, ThumbsUp } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { useAutoScroll } from \"@/registry/default/hooks/use-auto-scroll\"\nimport { Button } from \"@/registry/default/ui/button\"\nimport { type Message } from \"@/registry/default/ui/chat-message\"\nimport { CopyButton } from \"@/registry/default/ui/copy-button\"\nimport { MessageInput } from \"@/registry/default/ui/message-input\"\nimport { MessageList } from \"@/registry/default/ui/message-list\"\nimport { PromptSuggestions } from \"@/registry/default/ui/prompt-suggestions\"\n\ninterface ChatPropsBase {\n handleSubmit: (\n event?: { preventDefault?: () => void },\n options?: { experimental_attachments?: FileList }\n ) => void\n messages: Array<Message>\n input: string\n className?: string\n handleInputChange: React.ChangeEventHandler<HTMLTextAreaElement>\n isGenerating: boolean\n stop?: () => void\n onRateResponse?: (\n messageId: string,\n rating: \"thumbs-up\" | \"thumbs-down\"\n ) => void\n}\n\ninterface ChatPropsWithoutSuggestions extends ChatPropsBase {\n append?: never\n suggestions?: never\n}\n\ninterface ChatPropsWithSuggestions extends ChatPropsBase {\n append: (message: { role: \"user\"; content: string }) => void\n suggestions: string[]\n}\n\ntype ChatProps = ChatPropsWithoutSuggestions | ChatPropsWithSuggestions\n\nexport function Chat({\n messages,\n handleSubmit,\n input,\n handleInputChange,\n stop,\n isGenerating,\n append,\n suggestions,\n className,\n onRateResponse,\n}: ChatProps) {\n const lastMessage = messages.at(-1)\n const isEmpty = messages.length === 0\n const isTyping = lastMessage?.role === \"user\"\n\n return (\n <ChatContainer className={className}>\n {isEmpty && append && suggestions ? (\n <PromptSuggestions\n label=\"Try these prompts ✨\"\n append={append}\n suggestions={suggestions}\n />\n ) : null}\n\n {messages.length > 0 ? (\n <ChatMessages messages={messages}>\n <MessageList\n messages={messages}\n isTyping={isTyping}\n messageOptions={(message) => ({\n actions: onRateResponse ? (\n <>\n <div className=\"border-r pr-1\">\n <CopyButton\n content={message.content}\n copyMessage=\"Copied response to clipboard!\"\n />\n </div>\n <Button\n size=\"icon\"\n variant=\"ghost\"\n className=\"h-6 w-6\"\n onClick={() => onRateResponse(message.id, \"thumbs-up\")}\n >\n <ThumbsUp className=\"h-4 w-4\" />\n </Button>\n <Button\n size=\"icon\"\n variant=\"ghost\"\n className=\"h-6 w-6\"\n onClick={() => onRateResponse(message.id, \"thumbs-down\")}\n >\n <ThumbsDown className=\"h-4 w-4\" />\n </Button>\n </>\n ) : (\n <CopyButton\n content={message.content}\n copyMessage=\"Copied response to clipboard!\"\n />\n ),\n })}\n />\n </ChatMessages>\n ) : null}\n\n <ChatForm\n className=\"mt-auto\"\n isPending={isGenerating || isTyping}\n handleSubmit={handleSubmit}\n >\n {({ files, setFiles }) => (\n <MessageInput\n value={input}\n onChange={handleInputChange}\n allowAttachments\n files={files}\n setFiles={setFiles}\n stop={stop}\n isGenerating={isGenerating}\n />\n )}\n </ChatForm>\n </ChatContainer>\n )\n}\nChat.displayName = \"Chat\"\n\nexport function ChatMessages({\n messages,\n children,\n}: React.PropsWithChildren<{\n messages: Message[]\n}>) {\n const { containerRef, scrollToBottom, handleScroll, shouldAutoScroll } =\n useAutoScroll([messages])\n\n return (\n <div\n className=\"relative overflow-y-auto pb-4\"\n ref={containerRef}\n onScroll={handleScroll}\n >\n {children}\n\n {!shouldAutoScroll && (\n <div className=\"sticky bottom-0 left-0 flex w-full justify-end\">\n <Button\n onClick={scrollToBottom}\n className=\"h-8 w-8 rounded-full ease-in-out animate-in fade-in-0 slide-in-from-bottom-1\"\n size=\"icon\"\n variant=\"ghost\"\n >\n <ArrowDown className=\"h-4 w-4\" />\n </Button>\n </div>\n )}\n </div>\n )\n}\n\nexport const ChatContainer = forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => {\n return (\n <div\n ref={ref}\n className={cn(\"grid max-h-full w-full grid-rows-[1fr_auto]\", className)}\n {...props}\n />\n )\n})\nChatContainer.displayName = \"ChatContainer\"\n\ninterface ChatFormProps {\n className?: string\n isPending: boolean\n handleSubmit: (\n event?: { preventDefault?: () => void },\n options?: { experimental_attachments?: FileList }\n ) => void\n children: (props: {\n files: File[] | null\n setFiles: React.Dispatch<React.SetStateAction<File[] | null>>\n }) => ReactElement\n}\n\nexport const ChatForm = forwardRef<HTMLFormElement, ChatFormProps>(\n ({ children, handleSubmit, isPending, className }, ref) => {\n const [files, setFiles] = useState<File[] | null>(null)\n\n const onSubmit = (event: React.FormEvent) => {\n if (isPending) {\n event.preventDefault()\n return\n }\n\n if (!files) {\n handleSubmit(event)\n return\n }\n\n const fileList = createFileList(files)\n handleSubmit(event, { experimental_attachments: fileList })\n setFiles(null)\n }\n\n return (\n <form ref={ref} onSubmit={onSubmit} className={className}>\n {children({ files, setFiles })}\n </form>\n )\n }\n)\nChatForm.displayName = \"ChatForm\"\n\nfunction createFileList(files: File[] | FileList): FileList {\n const dataTransfer = new DataTransfer()\n for (const file of Array.from(files)) {\n dataTransfer.items.add(file)\n }\n return dataTransfer.files\n}\n",
"type": "registry:ui",
"target": ""
}
Expand Down
23 changes: 22 additions & 1 deletion apps/www/public/r/index.json
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,28 @@
"path": "ui/typing-indicator.tsx",
"type": "registry:ui"
}
]
],
"tailwind": {
"config": {
"theme": {
"extend": {
"keyframes": {
"typing-dot-bounce": {
"0%,40%": {
"transform": "translateY(0)"
},
"20%": {
"transform": "translateY(-0.25rem)"
}
}
},
"animation": {
"typing-dot-bounce": "typing-dot-bounce 1.25s ease-out infinite"
}
}
}
}
}
},
{
"name": "prompt-suggestions",
Expand Down
2 changes: 1 addition & 1 deletion apps/www/public/r/prompt-suggestions.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"files": [
{
"path": "ui/prompt-suggestions.tsx",
"content": "interface PromptSuggestionsProps {\n append: (message: { role: \"user\"; content: string }) => void\n suggestions: string[]\n}\n\nexport function PromptSuggestions({\n append,\n suggestions,\n}: PromptSuggestionsProps) {\n return (\n <div className=\"space-y-6\">\n <h2 className=\"text-center text-2xl font-bold\">Try these prompts ✨</h2>\n <div className=\"flex gap-6 text-sm\">\n {suggestions.map((suggestion) => (\n <button\n key={suggestion}\n onClick={() => append({ role: \"user\", content: suggestion })}\n className=\"h-max flex-1 rounded-xl border bg-background p-4 hover:bg-muted\"\n >\n <p>{suggestion}</p>\n </button>\n ))}\n </div>\n </div>\n )\n}\n",
"content": "interface PromptSuggestionsProps {\n label: string\n append: (message: { role: \"user\"; content: string }) => void\n suggestions: string[]\n}\n\nexport function PromptSuggestions({\n label,\n append,\n suggestions,\n}: PromptSuggestionsProps) {\n return (\n <div className=\"space-y-6\">\n <h2 className=\"text-center text-2xl font-bold\">{label}</h2>\n <div className=\"flex gap-6 text-sm\">\n {suggestions.map((suggestion) => (\n <button\n key={suggestion}\n onClick={() => append({ role: \"user\", content: suggestion })}\n className=\"h-max flex-1 rounded-xl border bg-background p-4 hover:bg-muted\"\n >\n <p>{suggestion}</p>\n </button>\n ))}\n </div>\n </div>\n )\n}\n",
"type": "registry:ui",
"target": ""
}
Expand Down
23 changes: 22 additions & 1 deletion apps/www/public/r/typing-indicator.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,26 @@
"type": "registry:ui",
"target": ""
}
]
],
"tailwind": {
"config": {
"theme": {
"extend": {
"keyframes": {
"typing-dot-bounce": {
"0%,40%": {
"transform": "translateY(0)"
},
"20%": {
"transform": "translateY(-0.25rem)"
}
}
},
"animation": {
"typing-dot-bounce": "typing-dot-bounce 1.25s ease-out infinite"
}
}
}
}
}
}
6 changes: 5 additions & 1 deletion apps/www/registry/default/ui/chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,11 @@ export function Chat({
return (
<ChatContainer className={className}>
{isEmpty && append && suggestions ? (
<PromptSuggestions append={append} suggestions={suggestions} />
<PromptSuggestions
label="Try these prompts ✨"
append={append}
suggestions={suggestions}
/>
) : null}

{messages.length > 0 ? (
Expand Down
4 changes: 3 additions & 1 deletion apps/www/registry/default/ui/prompt-suggestions.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
interface PromptSuggestionsProps {
label: string
append: (message: { role: "user"; content: string }) => void
suggestions: string[]
}

export function PromptSuggestions({
label,
append,
suggestions,
}: PromptSuggestionsProps) {
return (
<div className="space-y-6">
<h2 className="text-center text-2xl font-bold">Try these prompts ✨</h2>
<h2 className="text-center text-2xl font-bold">{label}</h2>
<div className="flex gap-6 text-sm">
{suggestions.map((suggestion) => (
<button
Expand Down
17 changes: 17 additions & 0 deletions apps/www/registry/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,23 @@ export const registry: Registry = [
name: "typing-indicator",
type: "registry:ui",
files: ["ui/typing-indicator.tsx"],
tailwind: {
config: {
theme: {
extend: {
keyframes: {
"typing-dot-bounce": {
"0%,40%": { transform: "translateY(0)" },
"20%": { transform: "translateY(-0.25rem)" },
},
},
animation: {
"typing-dot-bounce": "typing-dot-bounce 1.25s ease-out infinite",
},
},
},
},
},
},
{
name: "prompt-suggestions",
Expand Down

0 comments on commit 6108635

Please sign in to comment.