diff --git a/templates/index.ts b/templates/index.ts index f1b5b2ef561d0bc00db2a941d1b8733b1b59e9e9..7fb46e45c624d8f232559ad91d020d1777fd6236 100644 --- a/templates/index.ts +++ b/templates/index.ts @@ -84,13 +84,6 @@ export const installTemplate = async ({ parents: true, cwd: uiPath, }); - const chatSectionFile = path.join(root, componentsPath, "chat-section.tsx"); - const chatSectionFileContent = await fs.readFile(chatSectionFile, "utf8"); - const newContent = chatSectionFileContent.replace( - /^import { ChatInput, ChatMessages, Message }.*$/m, - 'import { ChatInput, ChatMessages, Message } from "./ui/chat"\n', - ); - await fs.writeFile(chatSectionFile, newContent); } /** @@ -116,7 +109,7 @@ export const installTemplate = async ({ ); // remove the default api folder const apiPath = path.join(root, "app", "api"); - await fs.rmdir(apiPath, { recursive: true }); + await fs.rm(apiPath, { recursive: true }); // modify the dev script to use the custom api path packageJson.scripts = { ...packageJson.scripts, diff --git a/templates/types/simple/nextjs/app/components/chat-section.tsx b/templates/types/simple/nextjs/app/components/chat-section.tsx index 70740429b763214291a48963f2578756664ec05a..246d46e08caca8b09be62c195b9dfb3af4a81718 100644 --- a/templates/types/simple/nextjs/app/components/chat-section.tsx +++ b/templates/types/simple/nextjs/app/components/chat-section.tsx @@ -2,7 +2,7 @@ import { nanoid } from "nanoid"; import { useState } from "react"; -import { ChatInput, ChatMessages, Message } from "../../../../../ui/html/chat"; +import { ChatInput, ChatMessages, Message } from "./ui/chat"; export default function ChatSection() { const [messages, setMessages] = useState<Message[]>([]); diff --git a/templates/components/ui/html/chat/chat-avatar.tsx b/templates/types/simple/nextjs/app/components/ui/chat/chat-avatar.tsx similarity index 100% rename from templates/components/ui/html/chat/chat-avatar.tsx rename to templates/types/simple/nextjs/app/components/ui/chat/chat-avatar.tsx diff --git a/templates/components/ui/html/chat/chat-input.tsx b/templates/types/simple/nextjs/app/components/ui/chat/chat-input.tsx similarity index 100% rename from templates/components/ui/html/chat/chat-input.tsx rename to templates/types/simple/nextjs/app/components/ui/chat/chat-input.tsx diff --git a/templates/components/ui/html/chat/chat-item.tsx b/templates/types/simple/nextjs/app/components/ui/chat/chat-item.tsx similarity index 100% rename from templates/components/ui/html/chat/chat-item.tsx rename to templates/types/simple/nextjs/app/components/ui/chat/chat-item.tsx diff --git a/templates/components/ui/html/chat/chat-messages.tsx b/templates/types/simple/nextjs/app/components/ui/chat/chat-messages.tsx similarity index 100% rename from templates/components/ui/html/chat/chat-messages.tsx rename to templates/types/simple/nextjs/app/components/ui/chat/chat-messages.tsx diff --git a/templates/components/ui/html/chat/index.ts b/templates/types/simple/nextjs/app/components/ui/chat/index.ts similarity index 100% rename from templates/components/ui/html/chat/index.ts rename to templates/types/simple/nextjs/app/components/ui/chat/index.ts diff --git a/templates/types/streaming/nextjs/app/components/chat-section.tsx b/templates/types/streaming/nextjs/app/components/chat-section.tsx index a36d2f2067c9a3332dfdf2d0e827d5b9492d767d..fd771fb3dd5221e3c63259ffcf154691cd13190e 100644 --- a/templates/types/streaming/nextjs/app/components/chat-section.tsx +++ b/templates/types/streaming/nextjs/app/components/chat-section.tsx @@ -1,7 +1,7 @@ "use client"; import { useChat } from "ai/react"; -import { ChatInput, ChatMessages, Message } from "../../../../../ui/html/chat"; +import { ChatInput, ChatMessages } from "./ui/chat"; export default function ChatSection() { const { messages, input, isLoading, handleSubmit, handleInputChange } = diff --git a/templates/types/streaming/nextjs/app/components/ui/chat/chat-avatar.tsx b/templates/types/streaming/nextjs/app/components/ui/chat/chat-avatar.tsx new file mode 100644 index 0000000000000000000000000000000000000000..cd241104e4ef210c728aec47a1ab8b0161ad6538 --- /dev/null +++ b/templates/types/streaming/nextjs/app/components/ui/chat/chat-avatar.tsx @@ -0,0 +1,34 @@ +"use client"; + +import Image from "next/image"; +import { Message } from "./chat-messages"; + +export default function ChatAvatar(message: Message) { + if (message.role === "user") { + return ( + <div className="flex h-8 w-8 shrink-0 select-none items-center justify-center rounded-md border shadow bg-background"> + <svg + xmlns="http://www.w3.org/2000/svg" + viewBox="0 0 256 256" + fill="currentColor" + className="h-4 w-4" + > + <path d="M230.92 212c-15.23-26.33-38.7-45.21-66.09-54.16a72 72 0 1 0-73.66 0c-27.39 8.94-50.86 27.82-66.09 54.16a8 8 0 1 0 13.85 8c18.84-32.56 52.14-52 89.07-52s70.23 19.44 89.07 52a8 8 0 1 0 13.85-8ZM72 96a56 56 0 1 1 56 56 56.06 56.06 0 0 1-56-56Z"></path> + </svg> + </div> + ); + } + + return ( + <div className="flex h-8 w-8 shrink-0 select-none items-center justify-center rounded-md border bg-black text-white"> + <Image + className="rounded-md" + src="/llama.png" + alt="Llama Logo" + width={24} + height={24} + priority + /> + </div> + ); +} diff --git a/templates/types/streaming/nextjs/app/components/ui/chat/chat-input.tsx b/templates/types/streaming/nextjs/app/components/ui/chat/chat-input.tsx new file mode 100644 index 0000000000000000000000000000000000000000..3eb979b02735943f3f11290c78b84f0e37709438 --- /dev/null +++ b/templates/types/streaming/nextjs/app/components/ui/chat/chat-input.tsx @@ -0,0 +1,42 @@ +"use client"; + +export interface ChatInputProps { + /** The current value of the input */ + input?: string; + /** An input/textarea-ready onChange handler to control the value of the input */ + handleInputChange?: ( + e: + | React.ChangeEvent<HTMLInputElement> + | React.ChangeEvent<HTMLTextAreaElement>, + ) => void; + /** Form submission handler to automatically reset input and append a user message */ + handleSubmit: (e: React.FormEvent<HTMLFormElement>) => void; + isLoading: boolean; +} + +export default function ChatInput(props: ChatInputProps) { + return ( + <> + <form + onSubmit={props.handleSubmit} + className="flex items-start justify-between w-full max-w-5xl p-4 bg-white rounded-xl shadow-xl gap-4" + > + <input + autoFocus + name="message" + placeholder="Type a message" + className="w-full p-4 rounded-xl shadow-inner flex-1" + value={props.input} + onChange={props.handleInputChange} + /> + <button + disabled={props.isLoading} + type="submit" + className="p-4 text-white rounded-xl shadow-xl bg-gradient-to-r from-cyan-500 to-sky-500 disabled:opacity-50 disabled:cursor-not-allowed" + > + Send message + </button> + </form> + </> + ); +} diff --git a/templates/types/streaming/nextjs/app/components/ui/chat/chat-item.tsx b/templates/types/streaming/nextjs/app/components/ui/chat/chat-item.tsx new file mode 100644 index 0000000000000000000000000000000000000000..2244f729a8ba668121ab5ec0842963d22153ef92 --- /dev/null +++ b/templates/types/streaming/nextjs/app/components/ui/chat/chat-item.tsx @@ -0,0 +1,13 @@ +"use client"; + +import ChatAvatar from "./chat-avatar"; +import { Message } from "./chat-messages"; + +export default function ChatItem(message: Message) { + return ( + <div className="flex items-start gap-4 pt-5"> + <ChatAvatar {...message} /> + <p className="break-words">{message.content}</p> + </div> + ); +} diff --git a/templates/types/streaming/nextjs/app/components/ui/chat/chat-messages.tsx b/templates/types/streaming/nextjs/app/components/ui/chat/chat-messages.tsx new file mode 100644 index 0000000000000000000000000000000000000000..65eacabbfb1b7bc99950b43e7fd07ba56b3ecdb0 --- /dev/null +++ b/templates/types/streaming/nextjs/app/components/ui/chat/chat-messages.tsx @@ -0,0 +1,38 @@ +"use client"; + +import { useEffect, useRef } from "react"; +import ChatItem from "./chat-item"; + +export interface Message { + id: string; + content: string; + role: string; +} + +export default function ChatMessages({ messages }: { messages: Message[] }) { + const scrollableChatContainerRef = useRef<HTMLDivElement>(null); + + const scrollToBottom = () => { + if (scrollableChatContainerRef.current) { + scrollableChatContainerRef.current.scrollTop = + scrollableChatContainerRef.current.scrollHeight; + } + }; + + useEffect(() => { + scrollToBottom(); + }, [messages.length]); + + return ( + <div className="w-full max-w-5xl p-4 bg-white rounded-xl shadow-xl"> + <div + className="flex flex-col gap-5 divide-y h-[50vh] overflow-auto" + ref={scrollableChatContainerRef} + > + {messages.map((m: Message) => ( + <ChatItem key={m.id} {...m} /> + ))} + </div> + </div> + ); +} diff --git a/templates/types/streaming/nextjs/app/components/ui/chat/index.ts b/templates/types/streaming/nextjs/app/components/ui/chat/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..4ccc54926fbd450313f952747bc1ea720100f2df --- /dev/null +++ b/templates/types/streaming/nextjs/app/components/ui/chat/index.ts @@ -0,0 +1,6 @@ +import ChatInput from "./chat-input"; +import ChatMessages from "./chat-messages"; + +export type { ChatInputProps } from "./chat-input"; +export type { Message } from "./chat-messages"; +export { ChatMessages, ChatInput };