diff --git a/templates/components/ui/shadcn/chat/chat-actions.tsx b/templates/components/ui/shadcn/chat/chat-actions.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..151ef61a945c49cacfd44c77cbdf7287b5967861
--- /dev/null
+++ b/templates/components/ui/shadcn/chat/chat-actions.tsx
@@ -0,0 +1,28 @@
+import { PauseCircle, RefreshCw } from "lucide-react";
+
+import { Button } from "../button";
+import { ChatHandler } from "./chat.interface";
+
+export default function ChatActions(
+  props: Pick<ChatHandler, "stop" | "reload"> & {
+    showReload?: boolean;
+    showStop?: boolean;
+  },
+) {
+  return (
+    <div className="space-x-4">
+      {props.showStop && (
+        <Button variant="outline" size="sm" onClick={props.stop}>
+          <PauseCircle className="mr-2 h-4 w-4" />
+          Stop generating
+        </Button>
+      )}
+      {props.showReload && (
+        <Button variant="outline" size="sm" onClick={props.reload}>
+          <RefreshCw className="mr-2 h-4 w-4" />
+          Regenerate
+        </Button>
+      )}
+    </div>
+  );
+}
diff --git a/templates/components/ui/shadcn/chat/chat-input.tsx b/templates/components/ui/shadcn/chat/chat-input.tsx
index 86b079e0bc4b14d5c72d3f24c3a7641fddd2e153..1a0cc3e0cc6d92178bafce5e8d573586e024e3f6 100644
--- a/templates/components/ui/shadcn/chat/chat-input.tsx
+++ b/templates/components/ui/shadcn/chat/chat-input.tsx
@@ -1,20 +1,17 @@
-import * as React from "react";
-
 import { Button } from "../button";
 import { Input } from "../input";
+import { ChatHandler } from "./chat.interface";
 
-export interface ChatInputProps {
-  handleSubmit: (e: React.FormEvent<HTMLFormElement>) => void;
-  handleInputChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
-  input: string;
-  isLoading: boolean;
-}
-
-export default function ChatInput(props: ChatInputProps) {
+export default function ChatInput(
+  props: Pick<
+    ChatHandler,
+    "isLoading" | "handleSubmit" | "handleInputChange" | "input"
+  >,
+) {
   return (
     <form
       onSubmit={props.handleSubmit}
-      className="mx-auto flex w-full max-w-5xl items-start justify-between gap-4 rounded-xl bg-white p-4 shadow-xl"
+      className="flex w-full items-start justify-between gap-4 rounded-xl bg-white p-4 shadow-xl"
     >
       <Input
         autoFocus
@@ -24,7 +21,7 @@ export default function ChatInput(props: ChatInputProps) {
         value={props.input}
         onChange={props.handleInputChange}
       />
-      <Button disabled={props.isLoading} type="submit">
+      <Button type="submit" disabled={props.isLoading}>
         Send message
       </Button>
     </form>
diff --git a/templates/components/ui/shadcn/chat/chat-message.tsx b/templates/components/ui/shadcn/chat/chat-message.tsx
index 62f7e75dd64f8f95b753a69e6934a72e7ab64bca..9ada08a3d7498a204ef3403b2148007f302936bf 100644
--- a/templates/components/ui/shadcn/chat/chat-message.tsx
+++ b/templates/components/ui/shadcn/chat/chat-message.tsx
@@ -1,36 +1,25 @@
 import { Check, Copy } from "lucide-react";
-import { useState } from "react";
 
 import { Button } from "../button";
 import ChatAvatar from "./chat-avatar";
-
-export interface Message {
-  id: string;
-  content: string;
-  role: string;
-}
+import { Message } from "./chat.interface";
+import Markdown from "./markdown";
+import { useCopyToClipboard } from "./use-copy-to-clipboard";
 
 export default function ChatMessage(chatMessage: Message) {
-  const [isCopied, setIsCopied] = useState(false);
-
-  const copyToClipboard = () => {
-    navigator.clipboard.writeText(chatMessage.content);
-    setIsCopied(true);
-    setTimeout(() => {
-      setIsCopied(false);
-    }, 2000);
-  };
-
+  const { isCopied, copyToClipboard } = useCopyToClipboard({ timeout: 2000 });
   return (
-    <div className="flex items-start gap-4 pt-5">
+    <div className="flex items-start gap-4 pr-5 pt-5">
       <ChatAvatar role={chatMessage.role} />
       <div className="group flex flex-1 justify-between gap-2">
-        <p className="break-words">{chatMessage.content}</p>
+        <div className="flex-1">
+          <Markdown content={chatMessage.content} />
+        </div>
         <Button
-          onClick={copyToClipboard}
+          onClick={() => copyToClipboard(chatMessage.content)}
           size="icon"
           variant="ghost"
-          className="hidden h-8 w-8 group-hover:flex"
+          className="h-8 w-8 opacity-0 group-hover:opacity-100"
         >
           {isCopied ? (
             <Check className="h-4 w-4" />
diff --git a/templates/components/ui/shadcn/chat/chat-messages.tsx b/templates/components/ui/shadcn/chat/chat-messages.tsx
index d5162768c7e1db4d9963a6a6e50131e1700d1a1e..dd0a442b6cd567094f6393bf2ccf221ee1eb178d 100644
--- a/templates/components/ui/shadcn/chat/chat-messages.tsx
+++ b/templates/components/ui/shadcn/chat/chat-messages.tsx
@@ -1,9 +1,15 @@
 import { useEffect, useRef } from "react";
 
-import ChatMessage, { Message } from "./chat-message";
+import ChatActions from "./chat-actions";
+import ChatMessage from "./chat-message";
+import { ChatHandler } from "./chat.interface";
 
-export default function ChatMessages({ messages }: { messages: Message[] }) {
+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) {
@@ -12,20 +18,34 @@ export default function ChatMessages({ messages }: { messages: Message[] }) {
     }
   };
 
+  const isLastMessageFromAssistant =
+    messageLength > 0 && lastMessage?.role !== "user";
+  const showReload =
+    props.reload && !props.isLoading && isLastMessageFromAssistant;
+  const showStop = props.stop && props.isLoading;
+
   useEffect(() => {
     scrollToBottom();
-  }, [messages.length]);
+  }, [messageLength, lastMessage]);
 
   return (
-    <div className="mx-auto w-full max-w-5xl rounded-xl bg-white p-4 shadow-xl">
+    <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-auto"
+        className="flex h-[50vh] flex-col gap-5 divide-y overflow-y-auto pb-4"
         ref={scrollableChatContainerRef}
       >
-        {messages.map((m) => (
+        {props.messages.map((m) => (
           <ChatMessage key={m.id} {...m} />
         ))}
       </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/components/ui/shadcn/chat/chat.interface.ts
new file mode 100644
index 0000000000000000000000000000000000000000..3256f7f031b42114f192f3375632654dc21f78d8
--- /dev/null
+++ b/templates/components/ui/shadcn/chat/chat.interface.ts
@@ -0,0 +1,15 @@
+export interface Message {
+  id: string;
+  content: string;
+  role: string;
+}
+
+export interface ChatHandler {
+  messages: Message[];
+  input: string;
+  isLoading: boolean;
+  handleSubmit: (e: React.FormEvent<HTMLFormElement>) => void;
+  handleInputChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
+  reload?: () => void;
+  stop?: () => void;
+}
diff --git a/templates/components/ui/shadcn/chat/codeblock.tsx b/templates/components/ui/shadcn/chat/codeblock.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..10598223b811a77c09a53a27857cd433a71dcfef
--- /dev/null
+++ b/templates/components/ui/shadcn/chat/codeblock.tsx
@@ -0,0 +1,139 @@
+"use client"
+
+import React, { FC, memo } from "react"
+import { Check, Copy, Download } from "lucide-react"
+import { Prism, SyntaxHighlighterProps } from "react-syntax-highlighter"
+import { coldarkDark } from "react-syntax-highlighter/dist/cjs/styles/prism"
+
+import { Button } from "../button"
+import { useCopyToClipboard } from "./use-copy-to-clipboard"
+
+// TODO: Remove this when @type/react-syntax-highlighter is updated
+const SyntaxHighlighter = Prism as unknown as FC<SyntaxHighlighterProps>
+
+interface Props {
+  language: string
+  value: string
+}
+
+interface languageMap {
+  [key: string]: string | undefined
+}
+
+export const programmingLanguages: languageMap = {
+  javascript: ".js",
+  python: ".py",
+  java: ".java",
+  c: ".c",
+  cpp: ".cpp",
+  "c++": ".cpp",
+  "c#": ".cs",
+  ruby: ".rb",
+  php: ".php",
+  swift: ".swift",
+  "objective-c": ".m",
+  kotlin: ".kt",
+  typescript: ".ts",
+  go: ".go",
+  perl: ".pl",
+  rust: ".rs",
+  scala: ".scala",
+  haskell: ".hs",
+  lua: ".lua",
+  shell: ".sh",
+  sql: ".sql",
+  html: ".html",
+  css: ".css",
+  // add more file extensions here, make sure the key is same as language prop in CodeBlock.tsx component
+}
+
+export const generateRandomString = (length: number, lowercase = false) => {
+  const chars = "ABCDEFGHJKLMNPQRSTUVWXY3456789" // excluding similar looking characters like Z, 2, I, 1, O, 0
+  let result = ""
+  for (let i = 0; i < length; i++) {
+    result += chars.charAt(Math.floor(Math.random() * chars.length))
+  }
+  return lowercase ? result.toLowerCase() : result
+}
+
+const CodeBlock: FC<Props> = memo(({ language, value }) => {
+  const { isCopied, copyToClipboard } = useCopyToClipboard({ timeout: 2000 })
+
+  const downloadAsFile = () => {
+    if (typeof window === "undefined") {
+      return
+    }
+    const fileExtension = programmingLanguages[language] || ".file"
+    const suggestedFileName = `file-${generateRandomString(
+      3,
+      true
+    )}${fileExtension}`
+    const fileName = window.prompt("Enter file name" || "", suggestedFileName)
+
+    if (!fileName) {
+      // User pressed cancel on prompt.
+      return
+    }
+
+    const blob = new Blob([value], { type: "text/plain" })
+    const url = URL.createObjectURL(blob)
+    const link = document.createElement("a")
+    link.download = fileName
+    link.href = url
+    link.style.display = "none"
+    document.body.appendChild(link)
+    link.click()
+    document.body.removeChild(link)
+    URL.revokeObjectURL(url)
+  }
+
+  const onCopy = () => {
+    if (isCopied) return
+    copyToClipboard(value)
+  }
+
+  return (
+    <div className="codeblock relative w-full bg-zinc-950 font-sans">
+      <div className="flex w-full items-center justify-between bg-zinc-800 px-6 py-2 pr-4 text-zinc-100">
+        <span className="text-xs lowercase">{language}</span>
+        <div className="flex items-center space-x-1">
+          <Button variant="ghost" onClick={downloadAsFile} size="icon">
+            <Download />
+            <span className="sr-only">Download</span>
+          </Button>
+          <Button variant="ghost" size="icon" onClick={onCopy}>
+            {isCopied ? (
+              <Check className="h-4 w-4" />
+            ) : (
+              <Copy className="h-4 w-4" />
+            )}
+            <span className="sr-only">Copy code</span>
+          </Button>
+        </div>
+      </div>
+      <SyntaxHighlighter
+        language={language}
+        style={coldarkDark}
+        PreTag="div"
+        showLineNumbers
+        customStyle={{
+          width: "100%",
+          background: "transparent",
+          padding: "1.5rem 1rem",
+          borderRadius: "0.5rem",
+        }}
+        codeTagProps={{
+          style: {
+            fontSize: "0.9rem",
+            fontFamily: "var(--font-mono)",
+          },
+        }}
+      >
+        {value}
+      </SyntaxHighlighter>
+    </div>
+  )
+})
+CodeBlock.displayName = "CodeBlock"
+
+export { CodeBlock }
diff --git a/templates/components/ui/shadcn/chat/index.ts b/templates/components/ui/shadcn/chat/index.ts
index d50251ffd6184259c6916a74951d88e0f598c6c5..0b8104960cff17adb4bb2068d0cb7dcd1f8a93ae 100644
--- a/templates/components/ui/shadcn/chat/index.ts
+++ b/templates/components/ui/shadcn/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-message";
+export { type ChatHandler, type Message } from "./chat.interface";
 export { ChatMessages, ChatInput };
diff --git a/templates/components/ui/shadcn/chat/markdown.tsx b/templates/components/ui/shadcn/chat/markdown.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..31b78242d257db674676d4c69a076b26785d0033
--- /dev/null
+++ b/templates/components/ui/shadcn/chat/markdown.tsx
@@ -0,0 +1,59 @@
+import { FC, memo } from "react"
+import ReactMarkdown, { Options } from "react-markdown"
+import remarkGfm from "remark-gfm"
+import remarkMath from "remark-math"
+
+import { CodeBlock } from "./codeblock"
+
+const MemoizedReactMarkdown: FC<Options> = memo(
+  ReactMarkdown,
+  (prevProps, nextProps) =>
+    prevProps.children === nextProps.children &&
+    prevProps.className === nextProps.className
+)
+
+export default function Markdown({ content }: { content: string }) {
+  return (
+    <MemoizedReactMarkdown
+      className="prose dark:prose-invert prose-p:leading-relaxed prose-pre:p-0 break-words"
+      remarkPlugins={[remarkGfm, remarkMath]}
+      components={{
+        p({ children }) {
+          return <p className="mb-2 last:mb-0">{children}</p>
+        },
+        code({ node, inline, className, children, ...props }) {
+          if (children.length) {
+            if (children[0] == "▍") {
+              return (
+                <span className="mt-1 animate-pulse cursor-default">▍</span>
+              )
+            }
+
+            children[0] = (children[0] as string).replace("`▍`", "▍")
+          }
+
+          const match = /language-(\w+)/.exec(className || "")
+
+          if (inline) {
+            return (
+              <code className={className} {...props}>
+                {children}
+              </code>
+            )
+          }
+
+          return (
+            <CodeBlock
+              key={Math.random()}
+              language={(match && match[1]) || ""}
+              value={String(children).replace(/\n$/, "")}
+              {...props}
+            />
+          )
+        },
+      }}
+    >
+      {content}
+    </MemoizedReactMarkdown>
+  )
+}
diff --git a/templates/components/ui/shadcn/chat/use-copy-to-clipboard.tsx b/templates/components/ui/shadcn/chat/use-copy-to-clipboard.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..62f7156dca246c46b213151af003a3a177977ccf
--- /dev/null
+++ b/templates/components/ui/shadcn/chat/use-copy-to-clipboard.tsx
@@ -0,0 +1,33 @@
+'use client'
+
+import * as React from 'react'
+
+export interface useCopyToClipboardProps {
+  timeout?: number
+}
+
+export function useCopyToClipboard({
+  timeout = 2000
+}: useCopyToClipboardProps) {
+  const [isCopied, setIsCopied] = React.useState<Boolean>(false)
+
+  const copyToClipboard = (value: string) => {
+    if (typeof window === 'undefined' || !navigator.clipboard?.writeText) {
+      return
+    }
+
+    if (!value) {
+      return
+    }
+
+    navigator.clipboard.writeText(value).then(() => {
+      setIsCopied(true)
+
+      setTimeout(() => {
+        setIsCopied(false)
+      }, timeout)
+    })
+  }
+
+  return { isCopied, copyToClipboard }
+}
diff --git a/templates/index.ts b/templates/index.ts
index 3c6f63b1e7896a9ecae9b0bcce804575bafdc08a..6cc8b14e38219b8fcd7a95077ef39e92079d7521 100644
--- a/templates/index.ts
+++ b/templates/index.ts
@@ -137,6 +137,17 @@ const installTSTemplate = async ({
       "@radix-ui/react-slot": "^1",
       "class-variance-authority": "^0.7",
       "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",
+    };
+
+    packageJson.devDependencies = {
+      ...packageJson.devDependencies,
+      "@types/react-syntax-highlighter": "^15.5.6",
     };
   }
 
diff --git a/templates/types/simple/nextjs/app/components/chat-section.tsx b/templates/types/simple/nextjs/app/components/chat-section.tsx
index 246d46e08caca8b09be62c195b9dfb3af4a81718..133a0a884bb82fb1ed3c78553852b8ae9b169cd5 100644
--- a/templates/types/simple/nextjs/app/components/chat-section.tsx
+++ b/templates/types/simple/nextjs/app/components/chat-section.tsx
@@ -2,11 +2,11 @@
 
 import { nanoid } from "nanoid";
 import { useState } from "react";
-import { ChatInput, ChatMessages, Message } from "./ui/chat";
+import { ChatInput, ChatInputProps, ChatMessages, Message } from "./ui/chat";
 
-export default function ChatSection() {
+function useChat(): ChatInputProps & { messages: Message[] } {
   const [messages, setMessages] = useState<Message[]>([]);
-  const [loading, setLoading] = useState(false);
+  const [isLoading, setIsLoading] = useState(false);
   const [input, setInput] = useState("");
 
   const getAssistantMessage = async (messages: Message[]) => {
@@ -30,8 +30,10 @@ export default function ChatSection() {
   const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
     e.preventDefault();
     if (!input) return;
+
+    setIsLoading(true);
+
     try {
-      setLoading(true);
       const newMessages = [
         ...messages,
         { id: nanoid(), content: input, role: "user" },
@@ -40,9 +42,11 @@ export default function ChatSection() {
       setInput("");
       const assistantMessage = await getAssistantMessage(newMessages);
       setMessages([...newMessages, { ...assistantMessage, id: nanoid() }]);
-      setLoading(false);
     } catch (error: any) {
-      alert(JSON.stringify(error));
+      console.log(error);
+      alert(error.message);
+    } finally {
+      setIsLoading(false);
     }
   };
 
@@ -50,15 +54,27 @@ export default function ChatSection() {
     setInput(e.target.value);
   };
 
+  return {
+    messages,
+    isLoading,
+    input,
+    handleSubmit,
+    handleInputChange,
+  };
+}
+
+export default function ChatSection() {
+  const { messages, isLoading, input, handleSubmit, handleInputChange } =
+    useChat();
   return (
-    <>
+    <div className="space-y-4 max-w-5xl w-full">
       <ChatMessages messages={messages} />
       <ChatInput
         handleSubmit={handleSubmit}
-        isLoading={loading}
+        isLoading={isLoading}
         input={input}
         handleInputChange={handleInputChange}
       />
-    </>
+    </div>
   );
 }
diff --git a/templates/types/streaming/nextjs/app/components/chat-section.tsx b/templates/types/streaming/nextjs/app/components/chat-section.tsx
index fd771fb3dd5221e3c63259ffcf154691cd13190e..04098fcdf41a4e686459f0ce0d6f3ca311895a9e 100644
--- a/templates/types/streaming/nextjs/app/components/chat-section.tsx
+++ b/templates/types/streaming/nextjs/app/components/chat-section.tsx
@@ -4,18 +4,30 @@ import { useChat } from "ai/react";
 import { ChatInput, ChatMessages } from "./ui/chat";
 
 export default function ChatSection() {
-  const { messages, input, isLoading, handleSubmit, handleInputChange } =
-    useChat({ api: process.env.NEXT_PUBLIC_CHAT_API });
+  const {
+    messages,
+    input,
+    isLoading,
+    handleSubmit,
+    handleInputChange,
+    reload,
+    stop,
+  } = useChat({ api: process.env.NEXT_PUBLIC_CHAT_API });
 
   return (
-    <>
-      <ChatMessages messages={messages} />
+    <div className="space-y-4 max-w-5xl w-full">
+      <ChatMessages
+        messages={messages}
+        isLoading={isLoading}
+        reload={reload}
+        stop={stop}
+      />
       <ChatInput
         input={input}
         handleSubmit={handleSubmit}
         handleInputChange={handleInputChange}
         isLoading={isLoading}
       />
-    </>
+    </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
index 65eacabbfb1b7bc99950b43e7fd07ba56b3ecdb0..0e978394015bd985af40646e87fa6620e9001a2f 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
@@ -9,7 +9,17 @@ export interface Message {
   role: string;
 }
 
-export default function ChatMessages({ messages }: { messages: Message[] }) {
+export default function ChatMessages({
+  messages,
+  isLoading,
+  reload,
+  stop,
+}: {
+  messages: Message[];
+  isLoading?: boolean;
+  stop?: () => void;
+  reload?: () => void;
+}) {
   const scrollableChatContainerRef = useRef<HTMLDivElement>(null);
 
   const scrollToBottom = () => {