From a9930be700ebc1a84f379402058ca04422a316f9 Mon Sep 17 00:00:00 2001
From: Marcus Schiesser <mail@marcusschiesser.de>
Date: Tue, 5 Dec 2023 11:33:32 +0700
Subject: [PATCH] refactor: swapped html and shadcn

---
 .../components/ui/html/chat/chat-avatar.tsx   |  34 ++++++
 .../components/ui/html/chat/chat-input.tsx    |  43 ++++++++
 .../ui/html}/chat/chat-item.tsx               |   0
 .../components/ui/html/chat/chat-messages.tsx |  48 ++++++++
 .../ui/{shadcn => html}/chat/index.ts         |   3 +-
 .../components/ui/shadcn/chat/chat-avatar.tsx |  25 -----
 .../components/ui/shadcn/chat/chat-input.tsx  |  84 --------------
 .../ui/shadcn/chat/chat-messages.tsx          |  64 -----------
 templates/index.ts                            |  30 ++---
 .../app/components/ui}/README-template.md     |   0
 .../nextjs/app/components/ui}/button.tsx      |   0
 .../app/components/ui}/chat/chat-actions.tsx  |   0
 .../app/components/ui/chat/chat-avatar.tsx    |  21 +---
 .../app/components/ui/chat/chat-input.tsx     | 103 ++++++++++++------
 .../app/components/ui}/chat/chat-message.tsx  |   0
 .../app/components/ui/chat/chat-messages.tsx  |  62 +++++++----
 .../app/components/ui}/chat/chat.interface.ts |   0
 .../app/components/ui}/chat/codeblock.tsx     |   0
 .../nextjs/app/components/ui/chat/index.ts    |   3 +-
 .../app/components/ui}/chat/markdown.tsx      |   0
 .../ui}/chat/use-copy-to-clipboard.tsx        |   0
 .../app/components/ui}/file-uploader.tsx      |   0
 .../nextjs/app/components/ui}/input.tsx       |   0
 .../nextjs/app/components/ui}/lib/utils.ts    |   0
 .../components/ui}/upload-image-preview.tsx   |   0
 templates/types/streaming/nextjs/package.json |  19 +++-
 26 files changed, 275 insertions(+), 264 deletions(-)
 create mode 100644 templates/components/ui/html/chat/chat-avatar.tsx
 create mode 100644 templates/components/ui/html/chat/chat-input.tsx
 rename templates/{types/streaming/nextjs/app/components/ui => components/ui/html}/chat/chat-item.tsx (100%)
 create mode 100644 templates/components/ui/html/chat/chat-messages.tsx
 rename templates/components/ui/{shadcn => html}/chat/index.ts (54%)
 delete mode 100644 templates/components/ui/shadcn/chat/chat-avatar.tsx
 delete mode 100644 templates/components/ui/shadcn/chat/chat-input.tsx
 delete mode 100644 templates/components/ui/shadcn/chat/chat-messages.tsx
 rename templates/{components/ui/shadcn => types/streaming/nextjs/app/components/ui}/README-template.md (100%)
 rename templates/{components/ui/shadcn => types/streaming/nextjs/app/components/ui}/button.tsx (100%)
 rename templates/{components/ui/shadcn => types/streaming/nextjs/app/components/ui}/chat/chat-actions.tsx (100%)
 rename templates/{components/ui/shadcn => types/streaming/nextjs/app/components/ui}/chat/chat-message.tsx (100%)
 rename templates/{components/ui/shadcn => types/streaming/nextjs/app/components/ui}/chat/chat.interface.ts (100%)
 rename templates/{components/ui/shadcn => types/streaming/nextjs/app/components/ui}/chat/codeblock.tsx (100%)
 rename templates/{components/ui/shadcn => types/streaming/nextjs/app/components/ui}/chat/markdown.tsx (100%)
 rename templates/{components/ui/shadcn => types/streaming/nextjs/app/components/ui}/chat/use-copy-to-clipboard.tsx (100%)
 rename templates/{components/ui/shadcn => types/streaming/nextjs/app/components/ui}/file-uploader.tsx (100%)
 rename templates/{components/ui/shadcn => types/streaming/nextjs/app/components/ui}/input.tsx (100%)
 rename templates/{components/ui/shadcn => types/streaming/nextjs/app/components/ui}/lib/utils.ts (100%)
 rename templates/{components/ui/shadcn => types/streaming/nextjs/app/components/ui}/upload-image-preview.tsx (100%)

diff --git a/templates/components/ui/html/chat/chat-avatar.tsx b/templates/components/ui/html/chat/chat-avatar.tsx
new file mode 100644
index 00000000..cd241104
--- /dev/null
+++ b/templates/components/ui/html/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/components/ui/html/chat/chat-input.tsx b/templates/components/ui/html/chat/chat-input.tsx
new file mode 100644
index 00000000..7c3e8728
--- /dev/null
+++ b/templates/components/ui/html/chat/chat-input.tsx
@@ -0,0 +1,43 @@
+"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;
+  multiModal?: 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/components/ui/html/chat/chat-item.tsx
similarity index 100%
rename from templates/types/streaming/nextjs/app/components/ui/chat/chat-item.tsx
rename to templates/components/ui/html/chat/chat-item.tsx
diff --git a/templates/components/ui/html/chat/chat-messages.tsx b/templates/components/ui/html/chat/chat-messages.tsx
new file mode 100644
index 00000000..0e978394
--- /dev/null
+++ b/templates/components/ui/html/chat/chat-messages.tsx
@@ -0,0 +1,48 @@
+"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,
+  isLoading,
+  reload,
+  stop,
+}: {
+  messages: Message[];
+  isLoading?: boolean;
+  stop?: () => void;
+  reload?: () => void;
+}) {
+  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/components/ui/shadcn/chat/index.ts b/templates/components/ui/html/chat/index.ts
similarity index 54%
rename from templates/components/ui/shadcn/chat/index.ts
rename to templates/components/ui/html/chat/index.ts
index c7990f9c..5de7dce4 100644
--- a/templates/components/ui/shadcn/chat/index.ts
+++ b/templates/components/ui/html/chat/index.ts
@@ -1,5 +1,6 @@
 import ChatInput from "./chat-input";
 import ChatMessages from "./chat-messages";
 
-export { type ChatHandler, type Message } from "./chat.interface";
+export type { ChatInputProps } from "./chat-input";
+export type { Message } from "./chat-messages";
 export { ChatInput, ChatMessages };
diff --git a/templates/components/ui/shadcn/chat/chat-avatar.tsx b/templates/components/ui/shadcn/chat/chat-avatar.tsx
deleted file mode 100644
index ce04e306..00000000
--- a/templates/components/ui/shadcn/chat/chat-avatar.tsx
+++ /dev/null
@@ -1,25 +0,0 @@
-import { User2 } from "lucide-react";
-import Image from "next/image";
-
-export default function ChatAvatar({ role }: { role: string }) {
-  if (role === "user") {
-    return (
-      <div className="flex h-8 w-8 shrink-0 select-none items-center justify-center rounded-md border bg-background shadow">
-        <User2 className="h-4 w-4" />
-      </div>
-    );
-  }
-
-  return (
-    <div className="flex h-8 w-8 shrink-0 select-none items-center justify-center rounded-md border bg-black text-white shadow">
-      <Image
-        className="rounded-md"
-        src="/llama.png"
-        alt="Llama Logo"
-        width={24}
-        height={24}
-        priority
-      />
-    </div>
-  );
-}
diff --git a/templates/components/ui/shadcn/chat/chat-input.tsx b/templates/components/ui/shadcn/chat/chat-input.tsx
deleted file mode 100644
index 435637e5..00000000
--- a/templates/components/ui/shadcn/chat/chat-input.tsx
+++ /dev/null
@@ -1,84 +0,0 @@
-import { useState } from "react";
-import { Button } from "../button";
-import FileUploader from "../file-uploader";
-import { Input } from "../input";
-import UploadImagePreview from "../upload-image-preview";
-import { ChatHandler } from "./chat.interface";
-
-export default function ChatInput(
-  props: Pick<
-    ChatHandler,
-    | "isLoading"
-    | "input"
-    | "onFileUpload"
-    | "onFileError"
-    | "handleSubmit"
-    | "handleInputChange"
-  > & {
-    multiModal?: boolean;
-  },
-) {
-  const [imageUrl, setImageUrl] = useState<string | null>(null);
-
-  const onSubmit = (e: React.FormEvent<HTMLFormElement>) => {
-    if (imageUrl) {
-      props.handleSubmit(e, {
-        data: { imageUrl: imageUrl },
-      });
-      setImageUrl(null);
-      return;
-    }
-    props.handleSubmit(e);
-  };
-
-  const onRemovePreviewImage = () => setImageUrl(null);
-
-  const handleUploadImageFile = async (file: File) => {
-    const base64 = await new Promise<string>((resolve, reject) => {
-      const reader = new FileReader();
-      reader.readAsDataURL(file);
-      reader.onload = () => resolve(reader.result as string);
-      reader.onerror = (error) => reject(error);
-    });
-    setImageUrl(base64);
-  };
-
-  const handleUploadFile = async (file: File) => {
-    try {
-      if (props.multiModal && file.type.startsWith("image/")) {
-        return await handleUploadImageFile(file);
-      }
-      props.onFileUpload?.(file);
-    } catch (error: any) {
-      props.onFileError?.(error.message);
-    }
-  };
-
-  return (
-    <form
-      onSubmit={onSubmit}
-      className="rounded-xl bg-white p-4 shadow-xl space-y-4"
-    >
-      {imageUrl && (
-        <UploadImagePreview url={imageUrl} onRemove={onRemovePreviewImage} />
-      )}
-      <div className="flex w-full items-start justify-between gap-4 ">
-        <Input
-          autoFocus
-          name="message"
-          placeholder="Type a message"
-          className="flex-1"
-          value={props.input}
-          onChange={props.handleInputChange}
-        />
-        <FileUploader
-          onFileUpload={handleUploadFile}
-          onFileError={props.onFileError}
-        />
-        <Button type="submit" disabled={props.isLoading}>
-          Send message
-        </Button>
-      </div>
-    </form>
-  );
-}
diff --git a/templates/components/ui/shadcn/chat/chat-messages.tsx b/templates/components/ui/shadcn/chat/chat-messages.tsx
deleted file mode 100644
index ee40cfd6..00000000
--- a/templates/components/ui/shadcn/chat/chat-messages.tsx
+++ /dev/null
@@ -1,64 +0,0 @@
-import { useEffect, useRef } from "react";
-import { Loader2 } from "lucide-react";
-
-import ChatActions from "./chat-actions";
-import ChatMessage from "./chat-message";
-import { ChatHandler } from "./chat.interface";
-
-export default function ChatMessages(
-  props: Pick<ChatHandler, "messages" | "isLoading" | "reload" | "stop">,
-) {
-  const scrollableChatContainerRef = useRef<HTMLDivElement>(null);
-  const messageLength = props.messages.length;
-  const lastMessage = props.messages[messageLength - 1];
-
-  const scrollToBottom = () => {
-    if (scrollableChatContainerRef.current) {
-      scrollableChatContainerRef.current.scrollTop =
-        scrollableChatContainerRef.current.scrollHeight;
-    }
-  };
-
-  const isLastMessageFromAssistant =
-    messageLength > 0 && lastMessage?.role !== "user";
-  const showReload =
-    props.reload && !props.isLoading && isLastMessageFromAssistant;
-  const showStop = props.stop && props.isLoading;
-
-  // `isPending` indicate
-  // that stream response is not yet received from the server,
-  // so we show a loading indicator to give a better UX.
-  const isPending = props.isLoading && !isLastMessageFromAssistant;
-
-  useEffect(() => {
-    scrollToBottom();
-  }, [messageLength, lastMessage]);
-
-  return (
-    <div className="w-full rounded-xl bg-white p-4 shadow-xl pb-0">
-      <div
-        className="flex h-[50vh] flex-col gap-5 divide-y overflow-y-auto pb-4"
-        ref={scrollableChatContainerRef}
-      >
-        {props.messages.map((m) => (
-          <ChatMessage key={m.id} {...m} />
-        ))}
-        {isPending && (
-          <div
-            className='flex justify-center items-center pt-10'
-          >
-            <Loader2 className="h-4 w-4 animate-spin"/>
-          </div>
-        )}
-      </div>
-      <div className="flex justify-end py-4">
-        <ChatActions
-          reload={props.reload}
-          stop={props.stop}
-          showReload={showReload}
-          showStop={showStop}
-        />
-      </div>
-    </div>
-  );
-}
diff --git a/templates/index.ts b/templates/index.ts
index 6063e411..cd675448 100644
--- a/templates/index.ts
+++ b/templates/index.ts
@@ -162,7 +162,7 @@ const installTSTemplate = async ({
   /**
    * Copy the selected UI files to the target directory and reference it.
    */
-  if (framework === "nextjs" && ui !== "html") {
+  if (framework === "nextjs" && ui !== "shadcn") {
     console.log("\nUsing UI:", ui, "\n");
     const uiPath = path.join(compPath, "ui", ui);
     const destUiPath = path.join(root, "app", "components", "ui");
@@ -227,26 +227,26 @@ const installTSTemplate = async ({
     };
   }
 
-  if (framework === "nextjs" && ui === "shadcn") {
-    // add shadcn dependencies to package.json
+  if (framework === "nextjs" && ui === "html") {
+    // remove shadcn dependencies if html ui is selected
     packageJson.dependencies = {
       ...packageJson.dependencies,
-      "tailwind-merge": "^2",
-      "@radix-ui/react-slot": "^1",
-      "class-variance-authority": "^0.7",
-      clsx: "^1.2.1",
-      "lucide-react": "^0.291",
-      remark: "^14.0.3",
-      "remark-code-import": "^1.2.0",
-      "remark-gfm": "^3.0.1",
-      "remark-math": "^5.1.1",
-      "react-markdown": "^8.0.7",
-      "react-syntax-highlighter": "^15.5.0",
+      "tailwind-merge": undefined,
+      "@radix-ui/react-slot": undefined,
+      "class-variance-authority": undefined,
+      clsx: undefined,
+      "lucide-react": undefined,
+      remark: undefined,
+      "remark-code-import": undefined,
+      "remark-gfm": undefined,
+      "remark-math": undefined,
+      "react-markdown": undefined,
+      "react-syntax-highlighter": undefined,
     };
 
     packageJson.devDependencies = {
       ...packageJson.devDependencies,
-      "@types/react-syntax-highlighter": "^15.5.6",
+      "@types/react-syntax-highlighter": undefined,
     };
   }
 
diff --git a/templates/components/ui/shadcn/README-template.md b/templates/types/streaming/nextjs/app/components/ui/README-template.md
similarity index 100%
rename from templates/components/ui/shadcn/README-template.md
rename to templates/types/streaming/nextjs/app/components/ui/README-template.md
diff --git a/templates/components/ui/shadcn/button.tsx b/templates/types/streaming/nextjs/app/components/ui/button.tsx
similarity index 100%
rename from templates/components/ui/shadcn/button.tsx
rename to templates/types/streaming/nextjs/app/components/ui/button.tsx
diff --git a/templates/components/ui/shadcn/chat/chat-actions.tsx b/templates/types/streaming/nextjs/app/components/ui/chat/chat-actions.tsx
similarity index 100%
rename from templates/components/ui/shadcn/chat/chat-actions.tsx
rename to templates/types/streaming/nextjs/app/components/ui/chat/chat-actions.tsx
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
index cd241104..ce04e306 100644
--- a/templates/types/streaming/nextjs/app/components/ui/chat/chat-avatar.tsx
+++ b/templates/types/streaming/nextjs/app/components/ui/chat/chat-avatar.tsx
@@ -1,26 +1,17 @@
-"use client";
-
+import { User2 } from "lucide-react";
 import Image from "next/image";
-import { Message } from "./chat-messages";
 
-export default function ChatAvatar(message: Message) {
-  if (message.role === "user") {
+export default function ChatAvatar({ role }: { role: string }) {
+  if (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 className="flex h-8 w-8 shrink-0 select-none items-center justify-center rounded-md border bg-background shadow">
+        <User2 className="h-4 w-4" />
       </div>
     );
   }
 
   return (
-    <div className="flex h-8 w-8 shrink-0 select-none items-center justify-center rounded-md border  bg-black text-white">
+    <div className="flex h-8 w-8 shrink-0 select-none items-center justify-center rounded-md border bg-black text-white shadow">
       <Image
         className="rounded-md"
         src="/llama.png"
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
index 7c3e8728..435637e5 100644
--- a/templates/types/streaming/nextjs/app/components/ui/chat/chat-input.tsx
+++ b/templates/types/streaming/nextjs/app/components/ui/chat/chat-input.tsx
@@ -1,43 +1,84 @@
-"use client";
+import { useState } from "react";
+import { Button } from "../button";
+import FileUploader from "../file-uploader";
+import { Input } from "../input";
+import UploadImagePreview from "../upload-image-preview";
+import { ChatHandler } from "./chat.interface";
 
-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;
-  multiModal?: boolean;
-}
+export default function ChatInput(
+  props: Pick<
+    ChatHandler,
+    | "isLoading"
+    | "input"
+    | "onFileUpload"
+    | "onFileError"
+    | "handleSubmit"
+    | "handleInputChange"
+  > & {
+    multiModal?: boolean;
+  },
+) {
+  const [imageUrl, setImageUrl] = useState<string | null>(null);
+
+  const onSubmit = (e: React.FormEvent<HTMLFormElement>) => {
+    if (imageUrl) {
+      props.handleSubmit(e, {
+        data: { imageUrl: imageUrl },
+      });
+      setImageUrl(null);
+      return;
+    }
+    props.handleSubmit(e);
+  };
+
+  const onRemovePreviewImage = () => setImageUrl(null);
+
+  const handleUploadImageFile = async (file: File) => {
+    const base64 = await new Promise<string>((resolve, reject) => {
+      const reader = new FileReader();
+      reader.readAsDataURL(file);
+      reader.onload = () => resolve(reader.result as string);
+      reader.onerror = (error) => reject(error);
+    });
+    setImageUrl(base64);
+  };
+
+  const handleUploadFile = async (file: File) => {
+    try {
+      if (props.multiModal && file.type.startsWith("image/")) {
+        return await handleUploadImageFile(file);
+      }
+      props.onFileUpload?.(file);
+    } catch (error: any) {
+      props.onFileError?.(error.message);
+    }
+  };
 
-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
+    <form
+      onSubmit={onSubmit}
+      className="rounded-xl bg-white p-4 shadow-xl space-y-4"
+    >
+      {imageUrl && (
+        <UploadImagePreview url={imageUrl} onRemove={onRemovePreviewImage} />
+      )}
+      <div className="flex w-full items-start justify-between gap-4 ">
+        <Input
           autoFocus
           name="message"
           placeholder="Type a message"
-          className="w-full p-4 rounded-xl shadow-inner flex-1"
+          className="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"
-        >
+        <FileUploader
+          onFileUpload={handleUploadFile}
+          onFileError={props.onFileError}
+        />
+        <Button type="submit" disabled={props.isLoading}>
           Send message
-        </button>
-      </form>
-    </>
+        </Button>
+      </div>
+    </form>
   );
 }
diff --git a/templates/components/ui/shadcn/chat/chat-message.tsx b/templates/types/streaming/nextjs/app/components/ui/chat/chat-message.tsx
similarity index 100%
rename from templates/components/ui/shadcn/chat/chat-message.tsx
rename to templates/types/streaming/nextjs/app/components/ui/chat/chat-message.tsx
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
index 0e978394..abc3e52d 100644
--- a/templates/types/streaming/nextjs/app/components/ui/chat/chat-messages.tsx
+++ b/templates/types/streaming/nextjs/app/components/ui/chat/chat-messages.tsx
@@ -1,26 +1,16 @@
-"use client";
-
+import { Loader2 } from "lucide-react";
 import { useEffect, useRef } from "react";
-import ChatItem from "./chat-item";
 
-export interface Message {
-  id: string;
-  content: string;
-  role: string;
-}
+import ChatActions from "./chat-actions";
+import ChatMessage from "./chat-message";
+import { ChatHandler } from "./chat.interface";
 
-export default function ChatMessages({
-  messages,
-  isLoading,
-  reload,
-  stop,
-}: {
-  messages: Message[];
-  isLoading?: boolean;
-  stop?: () => void;
-  reload?: () => void;
-}) {
+export default function ChatMessages(
+  props: Pick<ChatHandler, "messages" | "isLoading" | "reload" | "stop">,
+) {
   const scrollableChatContainerRef = useRef<HTMLDivElement>(null);
+  const messageLength = props.messages.length;
+  const lastMessage = props.messages[messageLength - 1];
 
   const scrollToBottom = () => {
     if (scrollableChatContainerRef.current) {
@@ -29,19 +19,43 @@ export default function ChatMessages({
     }
   };
 
+  const isLastMessageFromAssistant =
+    messageLength > 0 && lastMessage?.role !== "user";
+  const showReload =
+    props.reload && !props.isLoading && isLastMessageFromAssistant;
+  const showStop = props.stop && props.isLoading;
+
+  // `isPending` indicate
+  // that stream response is not yet received from the server,
+  // so we show a loading indicator to give a better UX.
+  const isPending = props.isLoading && !isLastMessageFromAssistant;
+
   useEffect(() => {
     scrollToBottom();
-  }, [messages.length]);
+  }, [messageLength, lastMessage]);
 
   return (
-    <div className="w-full max-w-5xl p-4 bg-white rounded-xl shadow-xl">
+    <div className="w-full rounded-xl bg-white p-4 shadow-xl pb-0">
       <div
-        className="flex flex-col gap-5 divide-y h-[50vh] overflow-auto"
+        className="flex h-[50vh] flex-col gap-5 divide-y overflow-y-auto pb-4"
         ref={scrollableChatContainerRef}
       >
-        {messages.map((m: Message) => (
-          <ChatItem key={m.id} {...m} />
+        {props.messages.map((m) => (
+          <ChatMessage key={m.id} {...m} />
         ))}
+        {isPending && (
+          <div className="flex justify-center items-center pt-10">
+            <Loader2 className="h-4 w-4 animate-spin" />
+          </div>
+        )}
+      </div>
+      <div className="flex justify-end py-4">
+        <ChatActions
+          reload={props.reload}
+          stop={props.stop}
+          showReload={showReload}
+          showStop={showStop}
+        />
       </div>
     </div>
   );
diff --git a/templates/components/ui/shadcn/chat/chat.interface.ts b/templates/types/streaming/nextjs/app/components/ui/chat/chat.interface.ts
similarity index 100%
rename from templates/components/ui/shadcn/chat/chat.interface.ts
rename to templates/types/streaming/nextjs/app/components/ui/chat/chat.interface.ts
diff --git a/templates/components/ui/shadcn/chat/codeblock.tsx b/templates/types/streaming/nextjs/app/components/ui/chat/codeblock.tsx
similarity index 100%
rename from templates/components/ui/shadcn/chat/codeblock.tsx
rename to templates/types/streaming/nextjs/app/components/ui/chat/codeblock.tsx
diff --git a/templates/types/streaming/nextjs/app/components/ui/chat/index.ts b/templates/types/streaming/nextjs/app/components/ui/chat/index.ts
index 5de7dce4..c7990f9c 100644
--- a/templates/types/streaming/nextjs/app/components/ui/chat/index.ts
+++ b/templates/types/streaming/nextjs/app/components/ui/chat/index.ts
@@ -1,6 +1,5 @@
 import ChatInput from "./chat-input";
 import ChatMessages from "./chat-messages";
 
-export type { ChatInputProps } from "./chat-input";
-export type { Message } from "./chat-messages";
+export { type ChatHandler, type Message } from "./chat.interface";
 export { ChatInput, ChatMessages };
diff --git a/templates/components/ui/shadcn/chat/markdown.tsx b/templates/types/streaming/nextjs/app/components/ui/chat/markdown.tsx
similarity index 100%
rename from templates/components/ui/shadcn/chat/markdown.tsx
rename to templates/types/streaming/nextjs/app/components/ui/chat/markdown.tsx
diff --git a/templates/components/ui/shadcn/chat/use-copy-to-clipboard.tsx b/templates/types/streaming/nextjs/app/components/ui/chat/use-copy-to-clipboard.tsx
similarity index 100%
rename from templates/components/ui/shadcn/chat/use-copy-to-clipboard.tsx
rename to templates/types/streaming/nextjs/app/components/ui/chat/use-copy-to-clipboard.tsx
diff --git a/templates/components/ui/shadcn/file-uploader.tsx b/templates/types/streaming/nextjs/app/components/ui/file-uploader.tsx
similarity index 100%
rename from templates/components/ui/shadcn/file-uploader.tsx
rename to templates/types/streaming/nextjs/app/components/ui/file-uploader.tsx
diff --git a/templates/components/ui/shadcn/input.tsx b/templates/types/streaming/nextjs/app/components/ui/input.tsx
similarity index 100%
rename from templates/components/ui/shadcn/input.tsx
rename to templates/types/streaming/nextjs/app/components/ui/input.tsx
diff --git a/templates/components/ui/shadcn/lib/utils.ts b/templates/types/streaming/nextjs/app/components/ui/lib/utils.ts
similarity index 100%
rename from templates/components/ui/shadcn/lib/utils.ts
rename to templates/types/streaming/nextjs/app/components/ui/lib/utils.ts
diff --git a/templates/components/ui/shadcn/upload-image-preview.tsx b/templates/types/streaming/nextjs/app/components/ui/upload-image-preview.tsx
similarity index 100%
rename from templates/components/ui/shadcn/upload-image-preview.tsx
rename to templates/types/streaming/nextjs/app/components/ui/upload-image-preview.tsx
diff --git a/templates/types/streaming/nextjs/package.json b/templates/types/streaming/nextjs/package.json
index ea58702e..5cddd0f7 100644
--- a/templates/types/streaming/nextjs/package.json
+++ b/templates/types/streaming/nextjs/package.json
@@ -8,12 +8,24 @@
     "lint": "next lint"
   },
   "dependencies": {
+    "@radix-ui/react-slot": "^1.0.2",
     "ai": "^2.2.25",
-    "llamaindex": "0.0.31",
+    "class-variance-authority": "^0.7.0",
+    "clsx": "^1.2.1",
     "dotenv": "^16.3.1",
+    "llamaindex": "0.0.37",
+    "lucide-react": "^0.294.0",
     "next": "^13",
     "react": "^18",
-    "react-dom": "^18"
+    "react-dom": "^18",
+    "react-markdown": "^8.0.7",
+    "react-syntax-highlighter": "^15.5.0",
+    "remark": "^14.0.3",
+    "remark-code-import": "^1.2.0",
+    "remark-gfm": "^3.0.1",
+    "remark-math": "^5.1.1",
+    "supports-color": "^9.4.0",
+    "tailwind-merge": "^2.1.0"
   },
   "devDependencies": {
     "@types/node": "^20",
@@ -24,6 +36,7 @@
     "eslint-config-next": "^13",
     "postcss": "^8",
     "tailwindcss": "^3.3",
-    "typescript": "^5"
+    "typescript": "^5",
+    "@types/react-syntax-highlighter": "^15.5.6"
   }
 }
\ No newline at end of file
-- 
GitLab