├── .eslintrc.json ├── .gitignore ├── .prettierrc.json ├── README.md ├── jsconfig.json ├── next.config.js ├── package.json ├── postcss.config.js ├── public ├── favicon.ico ├── next.svg ├── thirteen.svg └── vercel.svg ├── src ├── components │ ├── Aside.jsx │ ├── Avatar.jsx │ ├── Banner.jsx │ ├── CreatePrompt.jsx │ ├── ErrorMessage.jsx │ ├── Icons.jsx │ ├── Layout.jsx │ ├── Message.jsx │ ├── ScrollToBottom.jsx │ ├── Twitch.jsx │ ├── TypingEffect.jsx │ ├── UserAvatar.jsx │ └── Welcome.jsx ├── pages │ ├── _app.js │ ├── _document.js │ ├── api │ │ └── chat.js │ └── index.js ├── store │ └── conversations.js └── styles │ ├── Home.module.css │ └── globals.css └── tailwind.config.js /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "next/core-web-vitals", 4 | "./node_modules/standard/eslintrc.json" 5 | ], 6 | "rules": { 7 | "space-before-function-paren": "off" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | .pnpm-debug.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "jsxSingleQuote": true, 5 | "trailingComma": "none" 6 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Clon de ChatGPT desde cero con React y Tailwind 2 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "paths": { 5 | "@/*": ["./src/*"] 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | reactStrictMode: true, 4 | } 5 | 6 | module.exports = nextConfig 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "midu-chat-gpt", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@formkit/auto-animate": "1.0.0-beta.6", 13 | "@next/font": "13.1.6", 14 | "eslint": "8.34.0", 15 | "eslint-config-next": "13.1.6", 16 | "highlight.js": "11.7.0", 17 | "lz-string": "1.4.4", 18 | "lz-ts": "1.1.2", 19 | "next": "13.1.6", 20 | "react": "18.2.0", 21 | "react-dom": "18.2.0", 22 | "snarkdown": "2.0.0", 23 | "zustand": "4.3.3" 24 | }, 25 | "devDependencies": { 26 | "autoprefixer": "10.4.13", 27 | "postcss": "8.4.21", 28 | "prettier": "2.8.4", 29 | "simple-zustand-devtools": "1.1.0", 30 | "standard": "17.0.0", 31 | "tailwindcss": "3.2.6" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {} 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/midudev/midu-chatgpt-clone/dba228f54eeef74a515a5c822c11993a9b2f80a0/public/favicon.ico -------------------------------------------------------------------------------- /public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/thirteen.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/Aside.jsx: -------------------------------------------------------------------------------- 1 | import { useConversationsStore } from '@/store/conversations' 2 | import { MenuIcon, MessageIcon, PencilIcon, PlusIcon, TrashIcon } from './Icons' 3 | import { useAutoAnimate } from '@formkit/auto-animate/react' 4 | import { useState } from 'react' 5 | 6 | export function Aside() { 7 | const [editConversationId, setEditConversationId] = useState(null) 8 | 9 | const addNewConversation = useConversationsStore( 10 | (state) => state.addNewConversation 11 | ) 12 | const conversationsInfo = useConversationsStore( 13 | (state) => state.conversationsInfo 14 | ) 15 | const removeConversation = useConversationsStore( 16 | (state) => state.removeConversation 17 | ) 18 | const clearConversations = useConversationsStore( 19 | (state) => state.clearConversations 20 | ) 21 | const selectConversation = useConversationsStore( 22 | (state) => state.selectConversation 23 | ) 24 | 25 | const [animationParent] = useAutoAnimate() 26 | 27 | return ( 28 | <> 29 |
30 | 37 |

New chat

38 | 41 |
42 | 43 | 100 | 101 | ) 102 | } 103 | -------------------------------------------------------------------------------- /src/components/Avatar.jsx: -------------------------------------------------------------------------------- 1 | export function Avatar({ children }) { 2 | return ( 3 |
4 | {children} 5 |
6 | ) 7 | } 8 | -------------------------------------------------------------------------------- /src/components/Banner.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react' 2 | import { useAutoAnimate } from '@formkit/auto-animate/react' 3 | 4 | import { CloseIcon } from './Icons' 5 | 6 | export function Banner () { 7 | const [showBanner, setShowBanner] = useState(false) 8 | const [animationParent] = useAutoAnimate() 9 | 10 | const handleClick = () => { 11 | setShowBanner(false) 12 | localStorage.setItem('bannerClosed', true) 13 | } 14 | 15 | useEffect(() => { 16 | // get from local storage is banner has been closed before 17 | const bannerClosed = localStorage.getItem('bannerClosed') 18 | if (bannerClosed) return 19 | 20 | // show banner after 5 seconds 21 | const timeoutId = setTimeout(() => { 22 | setShowBanner(true) 23 | } 24 | , 2000) 25 | 26 | return () => clearTimeout(timeoutId) 27 | }, []) 28 | 29 | return ( 30 |
31 | {showBanner && ( 32 |
33 |
34 | Esta página no es la oficial de ChatGPT. Es un clon creado con React y Tailwind para fines educativos. 35 | 38 |
39 |
40 | )} 41 |
42 | ) 43 | } 44 | -------------------------------------------------------------------------------- /src/components/CreatePrompt.jsx: -------------------------------------------------------------------------------- 1 | import { SendIcon } from '@/components/Icons.jsx' 2 | import { useEffect, useRef, useState } from 'react' 3 | import { useConversationsStore } from '@/store/conversations' 4 | 5 | const loadingStates = [ 6 | [true, false, false], 7 | [true, true, false], 8 | [true, true, true] 9 | ] 10 | 11 | function LoadingButton () { 12 | const [index, setIndex] = useState(0) 13 | 14 | useEffect(() => { 15 | const intervalId = setInterval(() => { 16 | setIndex(prevIndex => { 17 | const newIndex = prevIndex + 1 18 | return newIndex > 2 ? 0 : newIndex 19 | }) 20 | }, 400) 21 | 22 | return () => clearInterval(intervalId) 23 | }, []) 24 | 25 | const [, showSecond, showThird] = loadingStates[index] 26 | 27 | return ( 28 |
29 | · 30 | · 31 | · 32 |
33 | ) 34 | } 35 | 36 | export function ChatForm() { 37 | const sendPrompt = useConversationsStore((state) => state.sendPrompt) 38 | const isLoading = useConversationsStore(state => state.loading) 39 | const textAreaRef = useRef() 40 | 41 | const handleSubmit = (event) => { 42 | event?.preventDefault() 43 | if (isLoading) return 44 | 45 | const { value } = textAreaRef.current 46 | sendPrompt({ prompt: value }) 47 | textAreaRef.current.value = '' 48 | } 49 | 50 | const handleChange = () => { 51 | const el = textAreaRef.current 52 | 53 | el.style.height = '0px' 54 | const scrollHeight = el.scrollHeight 55 | el.style.height = scrollHeight + 'px' 56 | } 57 | 58 | const handleKeyDown = (e) => { 59 | if (e.key === 'Enter' && !e.shiftKey) { 60 | e.preventDefault() 61 | handleSubmit() 62 | } 63 | } 64 | 65 | useEffect(() => { 66 | textAreaRef.current.focus() 67 | }, []) 68 | 69 | return ( 70 |
71 |
77 |
78 |