Skip to content

Commit 65312e6

Browse files
committed
feat: add chat page
1 parent 16ef078 commit 65312e6

File tree

15 files changed

+1480
-187
lines changed

15 files changed

+1480
-187
lines changed

app/_components/Chat.tsx

Lines changed: 0 additions & 40 deletions
This file was deleted.

app/chat/[id]/page.tsx

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
"use client"
2+
3+
import { useEffect, useRef } from "react"
4+
5+
import { MESSAGES } from "@/fixtures/chats"
6+
import Chat from "@/components/common/Chat"
7+
import ChatNote from "@/components/common/ChatNote"
8+
import Messages from "@/components/common/Messages"
9+
10+
export default function ChatPage() {
11+
const messagesEndRef = useRef<HTMLDivElement | null>(null)
12+
13+
useEffect(() => {
14+
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" })
15+
}, [])
16+
17+
return (
18+
<div
19+
style={{ height: "calc(100vh - 32px)" }}
20+
className="flex flex-col justify-between"
21+
>
22+
<div className="flex-1 overflow-y-auto">
23+
<Messages messages={MESSAGES} />
24+
<div ref={messagesEndRef} />
25+
</div>
26+
<div className="space-y-4 bg-neutral-100">
27+
<Chat />
28+
<ChatNote />
29+
</div>
30+
</div>
31+
)
32+
}

app/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import Chat from "./_components/Chat"
1+
import Chat from "../components/common/Chat"
22
import Header from "./_components/Header"
33
import Prompts from "./_components/Prompts"
44

components/common/Chat.tsx

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
"use client"
2+
3+
import clsx from "clsx"
4+
import { useState } from "react"
5+
import { usePathname, useRouter } from "next/navigation"
6+
7+
import { useChatStore } from "@/stores/chatStore"
8+
import { generateRandomId } from "@/utils/random"
9+
import Icon from "@/components/common/Icon"
10+
import Tooltip from "@/components/common/Tooltip"
11+
import AddAttachment from "@/app/_components/AddAttachment"
12+
13+
const maxLength = 1000
14+
15+
export default function Chat() {
16+
const router = useRouter()
17+
const { addMessage } = useChatStore()
18+
19+
const pathname = usePathname()
20+
const isHomePage = pathname === "/"
21+
22+
const [prompt, setPrompt] = useState<string>("")
23+
24+
const onSubmit = () => {
25+
const id = generateRandomId()
26+
27+
addMessage(id, {
28+
role: "user",
29+
content: prompt,
30+
})
31+
router.push(`/chat/${id}`)
32+
}
33+
34+
return (
35+
<section
36+
className={clsx(
37+
"w-full space-y-2 bg-white shadow-sm rounded-xl border p-4 border-neutral-150",
38+
isHomePage ? "sm:mt-16" : ""
39+
)}
40+
>
41+
<textarea
42+
rows={isHomePage ? 5 : 1}
43+
value={prompt}
44+
maxLength={maxLength}
45+
className="bg-transparent text-sm w-full resize-none placeholder:text-black focus:outline-none"
46+
placeholder="Ask whatever you want..."
47+
onChange={e => setPrompt(e.target.value)}
48+
/>
49+
50+
<footer className="flex text-sm items-end justify-between">
51+
<AddAttachment />
52+
53+
<div className="space-x-2 flex items-center">
54+
<span className="ml-auto text-neutral-500 font-medium">
55+
{prompt.length}/{maxLength}
56+
</span>
57+
58+
<Tooltip id="send" content="Send" place="top">
59+
<button
60+
onClick={onSubmit}
61+
className="rounded-xl p-1.5 text-white bg-gradient-to-t from-purple-900 to-purple-600"
62+
>
63+
<Icon name="arrowUp" size={16} strokeWidth={2} />
64+
</button>
65+
</Tooltip>
66+
</div>
67+
</footer>
68+
</section>
69+
)
70+
}

components/common/ChatNote.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export default function ChatNote() {
2+
return (
3+
<p className="text-xs text-center w-full">
4+
ChatAI can make mistakes. Check important info.
5+
</p>
6+
)
7+
}

components/common/Layout.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,13 @@ const Layout: FC<PropsWithChildren> = ({ children }) => {
1717

1818
<main
1919
className={clsx(
20-
"flex-1 p-4 md:py-16 max-h-screen overflow-y-auto",
20+
"flex-1 p-4 max-h-screen overflow-y-auto",
2121
isHomePage && "sm:grid sm:place-items-center"
2222
)}
2323
>
2424
<section
2525
className={clsx(
26-
"max-w-3xl min-w-3xl mx-auto",
26+
"max-w-3xl min-w-3xl mx-auto h-full relative",
2727
isHomePage &&
2828
"flex flex-col h-full items-start justify-between sm:h-auto"
2929
)}

components/common/Messages.tsx

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { Message } from "@/stores/chatStore"
2+
import UserMessage from "./UserMessage"
3+
import SystemMessage from "./SystemMessage"
4+
5+
interface MessagesProps {
6+
messages: Message[]
7+
}
8+
9+
export default function Messages({ messages }: MessagesProps) {
10+
return (
11+
<section className="flex flex-col flex-1 py-4 text-sm space-y-4 justify-end">
12+
{messages.map(({ role, content }, index) =>
13+
role === "user" ? (
14+
<UserMessage key={index}>{content}</UserMessage>
15+
) : (
16+
<SystemMessage key={index}>{content}</SystemMessage>
17+
)
18+
)}
19+
</section>
20+
)
21+
}

components/common/SystemMessage.tsx

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import Image from "next/image"
2+
import ReactMarkdown from "react-markdown"
3+
import rehypeHighlight from "rehype-highlight"
4+
import "highlight.js/styles/github.css"
5+
6+
export default function SystemMessage({ children }: { children: string }) {
7+
return (
8+
<div className="flex space-x-2 items-start max-w-[90%] sm:max-w-[80%]">
9+
<Image
10+
src="/logo.png"
11+
alt="Logo"
12+
width={34}
13+
height={34}
14+
className="rounded-full"
15+
/>
16+
17+
<div className="system-message overflow-x-auto bg-white border border-neutral-200 p-4 rounded-xl">
18+
<ReactMarkdown rehypePlugins={[rehypeHighlight]}>
19+
{children}
20+
</ReactMarkdown>
21+
</div>
22+
</div>
23+
)
24+
}

components/common/UserMessage.tsx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import Image from "next/image"
2+
3+
export default function UserMessage({ children }: { children: string }) {
4+
return (
5+
<div className="flex space-x-2 items-start max-w-[80%] ml-auto">
6+
<div
7+
className="bg-neutral-200 border border-neutral-300 p-3 rounded-xl"
8+
dangerouslySetInnerHTML={{ __html: children }}
9+
/>
10+
11+
<Image
12+
src="/profile.png"
13+
alt="Profile"
14+
width={34}
15+
height={34}
16+
className="hidden sm:block rounded-full"
17+
/>
18+
</div>
19+
)
20+
}

fixtures/chats.tsx

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { Message } from "@/stores/chatStore"
2+
3+
export const MESSAGES: Message[] = [
4+
{
5+
role: "user",
6+
content: "Can you show me how to implement a basic React component?",
7+
},
8+
{
9+
role: "system",
10+
content:
11+
"Sure! Here's a simple example of a React component that displays a greeting message.\n ```jsx\nimport React from 'react';\n\nconst Greeting = () => {\n return <h1>Hello, welcome to my site!</h1>;\n};\n\nexport default Greeting;\n```",
12+
},
13+
{
14+
role: "user",
15+
content:
16+
"That looks great! Can you add a button that changes the greeting message when clicked?",
17+
},
18+
{
19+
role: "system",
20+
content:
21+
"Absolutely! Here's the updated version with a button that changes the message.\n```jsx\nimport React, { useState } from 'react';\n\nconst Greeting = () => {\n const [message, setMessage] = useState('Hello, welcome to my site!');\n\n const changeMessage = () => {\n setMessage('Thanks for clicking the button!');\n };\n\n return (\n <div>\n <h1>{message}</h1>\n <button onClick={changeMessage}>Click me</button>\n </div>\n );\n};\n\nexport default Greeting;\n```",
22+
},
23+
{
24+
role: "user",
25+
content:
26+
"Looks good! Can you share a quick guide on how to use hooks in React?",
27+
},
28+
{
29+
role: "system",
30+
content:
31+
"Sure! Here's a quick list of common React hooks and how they are used:\n- `useState`: Allows you to add state to your functional components.\n- `useEffect`: Used for side effects such as fetching data, subscribing to events, etc.\n- `useContext`: Lets you subscribe to React context without needing to pass props manually.\n- `useReducer`: An alternative to `useState`, more suitable for complex state logic.\n- `useRef`: Allows you to access a DOM element or store a mutable value.",
32+
},
33+
{
34+
role: "user",
35+
content:
36+
"That's helpful! Can you show me an image of a React component lifecycle?",
37+
},
38+
{
39+
role: "system",
40+
content:
41+
"Here’s an image that illustrates the React component lifecycle.\n![React Component Lifecycle](https://cdn-media-1.freecodecamp.org/images/1*_drMYY_IEgboMS4RhvC-lQ.png)",
42+
},
43+
]

0 commit comments

Comments
 (0)