{"id":84,"date":"2026-06-26T13:32:24","date_gmt":"2026-06-26T13:32:24","guid":{"rendered":"https:\/\/aabbee.cafe24.com\/?p=84"},"modified":"2026-06-26T14:02:47","modified_gmt":"2026-06-26T14:02:47","slug":"chatbot","status":"publish","type":"post","link":"https:\/\/coalacoding.com\/chatbot\/","title":{"rendered":"AI \ucc57\ubd07 \uc5f0\ub3d9\ud558\uae30"},"content":{"rendered":"<blockquote>\n<p><strong>Tip<\/strong>: <strong>Gemini CLI\ub85c \uad6c\ud604\ud558\uae30 \u2014 AI \ucc57\ubd07 \ucef4\ud3ec\ub10c\ud2b8<\/strong><\/p>\n<ul>\n<li><strong>\ud504\ub86c\ud504\ud2b8:<\/strong> <code>gemini &quot;src\/components\/Chatbot.jsx\ub97c \uc791\uc131\ud574\uc918. \ud654\uba74 \uc6b0\ud558\ub2e8\uc5d0 \uace0\uc815\ub41c \ud50c\ub85c\ud305 \ubc84\ud2bc\uc744 \ud074\ub9ad\ud558\uba74 \ucc44\ud305\ucc3d\uc774 \uc5f4\ub9ac\ub294 \ucef4\ud3ec\ub10c\ud2b8\uc57c. useState\ub85c \uc5f4\ub9bc\/\ub2eb\ud798, \uba54\uc2dc\uc9c0 \ubaa9\ub85d, \uc785\ub825\uac12, \ub85c\ub529 \uc0c1\ud0dc\ub97c \uad00\ub9ac\ud558\uace0, useRef\ub85c \uc0c8 \uba54\uc2dc\uc9c0\uac00 \uc624\uba74 \uc790\ub3d9 \uc2a4\ud06c\ub864\ud574\uc918. \uba54\uc2dc\uc9c0 \uc804\uc1a1 \uc2dc [\ubc31\uc5d4\ub4dc URL]\/chat \uc5d4\ub4dc\ud3ec\uc778\ud2b8\uc5d0 fetch\ub85c POST \uc694\uccad\uc744 \ubcf4\ub0b4\uc918. Tailwind CSS\ub97c \uc0ac\uc6a9\ud574\uc918.&quot;<\/code>\n<ul>\n<li><strong>\uc0ac\uc6a9 \uac00\uc774\ub4dc:<\/strong> <code>[\ubc31\uc5d4\ub4dc URL]<\/code>\uc744 \ubc30\ud3ec\ub41c \uc11c\ubc84 \uc8fc\uc18c\ub85c \ubc14\uafbc\ub2e4(\uc608: <code>https:\/\/my-api.onrender.com<\/code>). \ub85c\uceec \ud14c\uc2a4\ud2b8 \uc2dc\uc5d0\ub294 <code>http:\/\/localhost:8000<\/code>\uc73c\ub85c \ubc14\uafbc\ub2e4.<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<\/blockquote>\n<h2>1. \ucc57\ubd07 \uc5f0\ub3d9 \uac1c\uc694<\/h2>\n<p>\uc774\ubc88 \ud3b8\uc5d0\uc11c\ub294 GOFLEX \ud504\ub85c\uc81d\ud2b8\uc5d0 AI \ucc57\ubd07\uc744 \uc5f0\ub3d9\ud55c\ub2e4. \ud50c\ub85c\ud305 \ubc84\ud2bc\uacfc \ucc44\ud305\ucc3d\uc774 \ud558\ub098\uc758 <code>Chatbot.jsx<\/code> \ucef4\ud3ec\ub10c\ud2b8\uc5d0 \ub4e4\uc5b4 \uc788\uace0, <code>App.jsx<\/code>\uc5d0\uc11c \ubc14\ub85c \uc0ac\uc6a9\ud55c\ub2e4.<\/p>\n<table>\n<thead>\n<tr>\n<th align=\"left\">\uc21c\uc11c<\/th>\n<th align=\"left\">\ub9cc\ub4e4 \uae30\ub2a5<\/th>\n<th align=\"left\">\ud575\uc2ec \uac1c\ub150<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td align=\"left\">1\ub2e8\uacc4<\/td>\n<td align=\"left\">Chatbot.jsx \u2014 import + \uc0c1\ud0dc<\/td>\n<td align=\"left\"><code>useState<\/code>, <code>useRef<\/code><\/td>\n<\/tr>\n<tr>\n<td align=\"left\">2\ub2e8\uacc4<\/td>\n<td align=\"left\">\uc790\ub3d9 \uc2a4\ud06c\ub864<\/td>\n<td align=\"left\"><code>useRef<\/code>, <code>scrollIntoView<\/code><\/td>\n<\/tr>\n<tr>\n<td align=\"left\">3\ub2e8\uacc4<\/td>\n<td align=\"left\">\uba54\uc2dc\uc9c0 \uc804\uc1a1 \ud568\uc218<\/td>\n<td align=\"left\"><code>fetch<\/code>, <code>try\/catch\/finally<\/code><\/td>\n<\/tr>\n<tr>\n<td align=\"left\">4\ub2e8\uacc4<\/td>\n<td align=\"left\">JSX \u2014 \ud50c\ub85c\ud305 \ubc84\ud2bc + \ucc44\ud305\ucc3d<\/td>\n<td align=\"left\">Tailwind \ub9d0\ud48d\uc120, \uc870\uac74\ubd80 \ub80c\ub354\ub9c1<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">5\ub2e8\uacc4<\/td>\n<td align=\"left\">App.jsx\uc5d0 \uc5f0\uacb0<\/td>\n<td align=\"left\"><code>import<\/code>, <code>&lt;Chatbot \/&gt;<\/code><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<blockquote>\n<p><strong>Info<\/strong>: \ucc57\ubd07 \ubc31\uc5d4\ub4dc \uc11c\ubc84\ub294 \ubcc4\ub3c4 \ud504\ub85c\uc81d\ud2b8\ub85c Python + FastAPI + HuggingFace\ub85c \uad6c\ucd95\ud558\uc5ec Render\uc5d0 \ubc30\ud3ec\ud55c \uc0c1\ud0dc\uc5ec\uc57c \ud55c\ub2e4.<\/p>\n<\/blockquote>\n<hr>\n<h2>2. 1\ub2e8\uacc4 \u2014 import + \uc0c1\ud0dc \ubcc0\uc218<\/h2>\n<p><code>src\/components\/<\/code> \ud3f4\ub354 \uc548\uc5d0 <code>Chatbot.jsx<\/code> \ud30c\uc77c\uc744 \uc0c8\ub85c \ub9cc\ub4e4\uace0 \uc544\ub798 \ucf54\ub4dc\ub97c \uc785\ub825\ud55c\ub2e4. \uc774 \ud30c\uc77c \ud558\ub098\uc5d0 \ud50c\ub85c\ud305 \ubc84\ud2bc(\ud654\uba74 \uc6b0\ud558\ub2e8\uc5d0 \ub5a0 \uc788\ub294 \ub3d9\uadf8\ub780 \ubc84\ud2bc)\uacfc \ucc44\ud305\ucc3d\uc774 \ubaa8\ub450 \ub4e4\uc5b4 \uc788\ub2e4.<\/p>\n<pre><code class=\"language-jsx\">import { useState, useRef, useEffect } from &quot;react&quot;;\nimport { FontAwesomeIcon } from &quot;@fortawesome\/react-fontawesome&quot;;\nimport { faComment, faXmark } from &quot;@fortawesome\/free-solid-svg-icons&quot;;\nimport { Button } from &quot;.\/UI.jsx&quot;;\n\nconst BACKEND = &quot;https:\/\/cbot-rfcl.onrender.com\/chat&quot;;\n\nexport default function Chatbot() {\n  const [open, setOpen] = useState(false);\n  const [messages, setMessages] = useState([\n    { role: &quot;bot&quot;, text: &quot;\uc548\ub155\ud558\uc138\uc694! \uc601\ud654\uc5d0 \ub300\ud574 \ubb34\uc5c7\uc774\ub4e0 \ubb3c\uc5b4\ubcf4\uc138\uc694&quot; },\n  ]);\n  const [input, setInput] = useState(&quot;&quot;);\n  const [loading, setLoading] = useState(false);\n  const bottomRef = useRef(null);\n<\/code><\/pre>\n<table>\n<thead>\n<tr>\n<th align=\"left\">\uc904<\/th>\n<th align=\"left\">\uc124\uba85<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td align=\"left\">1<\/td>\n<td align=\"left\"><code>useRef<\/code>(\uc720\uc988\ub808\ud504) \u2014 DOM \uc694\uc18c\ub97c \uc9c1\uc811 \ucc38\uc870\ud558\ub294 \ud6c5\uc774\ub2e4. \uc5ec\uae30\uc11c\ub294 \uba54\uc2dc\uc9c0 \ubaa9\ub85d\uc758 \ub9e8 \uc544\ub798\ub97c \uac00\ub9ac\ud0a4\ub294 \ub370 \uc0ac\uc6a9\ud55c\ub2e4.<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">3<\/td>\n<td align=\"left\"><code>faComment<\/code>(\ub9d0\ud48d\uc120), <code>faXmark<\/code>(X) \uc544\uc774\ucf58\uc774\ub2e4.<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">5<\/td>\n<td align=\"left\">\ucc57\ubd07 \ubc31\uc5d4\ub4dc \uc11c\ubc84 \uc8fc\uc18c\uc774\ub2e4. \ubcf8\uc778\uc758 Render \ubc30\ud3ec \uc8fc\uc18c\ub85c \ubcc0\uacbd\ud55c\ub2e4.<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">7<\/td>\n<td align=\"left\"><code>export default<\/code> \u2014 \uc774 \ucef4\ud3ec\ub10c\ud2b8\ub294 \ud30c\uc77c\uc758 \ub300\ud45c \ub0b4\ubcf4\ub0b4\uae30\uc774\ub2e4. <code>App.jsx<\/code>\uc5d0\uc11c <code>import Chatbot<\/code>\uc73c\ub85c \uac00\uc838\uc628\ub2e4.<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">8<\/td>\n<td align=\"left\"><code>open<\/code> \u2014 \ucc44\ud305\ucc3d\uc758 \uc5f4\ub9bc\/\ub2eb\ud798 \uc0c1\ud0dc\uc774\ub2e4.<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">9-11<\/td>\n<td align=\"left\"><code>messages<\/code> \u2014 \ub300\ud654 \ubaa9\ub85d\uc774\ub2e4. \ucd08\uae30\uac12\uc5d0 \ubd07\uc758 \uc778\uc0ac \uba54\uc2dc\uc9c0\ub97c \ub123\uc5b4\ub450\uba74 \ucc44\ud305\ucc3d\uc744 \uc5f4\uc5c8\uc744 \ub54c \ubc14\ub85c \uc548\ub0b4 \uba54\uc2dc\uc9c0\uac00 \ubcf4\uc778\ub2e4.<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">14<\/td>\n<td align=\"left\"><code>bottomRef<\/code> \u2014 \uba54\uc2dc\uc9c0 \ubaa9\ub85d\uc758 \ub9e8 \uc544\ub798 \ube48 <code>div<\/code>\ub97c \uac00\ub9ac\ud0a4\ub294 \ucc38\uc870\uc774\ub2e4. \uc0c8 \uba54\uc2dc\uc9c0\uac00 \ucd94\uac00\ub418\uba74 \uc774 \uc704\uce58\ub85c \uc790\ub3d9 \uc2a4\ud06c\ub864\ud55c\ub2e4.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p><Accordion title=\"useRef\ub780?\"><br \/>\n`useRef`\ub294 \ub80c\ub354\ub9c1\uacfc \ubb34\uad00\ud558\uac8c \uac12\uc744 \uc720\uc9c0\ud558\uac70\ub098, DOM \uc694\uc18c\ub97c \uc9c1\uc811 \ucc38\uc870\ud560 \ub54c \uc0ac\uc6a9\ud558\ub294 \ud6c5\uc774\ub2e4.<\/p>\n<pre><code class=\"language-jsx\">const bottomRef = useRef(null);  \/\/ \ucc38\uc870 \uc0dd\uc131\n\/\/ ...\n&lt;div ref={bottomRef} \/&gt;          \/\/ DOM \uc694\uc18c\uc5d0 \uc5f0\uacb0\n\/\/ ...\nbottomRef.current                \/\/ \uc5f0\uacb0\ub41c \uc2e4\uc81c DOM \uc694\uc18c\n<\/code><\/pre>\n<p><code>useState<\/code>\uc640 \ub2ec\ub9ac \uac12\uc774 \ubc14\ub00c\uc5b4\ub3c4 <strong>\ub9ac\ub80c\ub354\ub9c1\uc774 \ubc1c\uc0dd\ud558\uc9c0 \uc54a\ub294\ub2e4<\/strong>.<br \/>\n<\/Accordion><\/p>\n<hr>\n<h2>3. 2\ub2e8\uacc4 \u2014 \uc790\ub3d9 \uc2a4\ud06c\ub864<\/h2>\n<p>\uac19\uc740 <code>Chatbot.jsx<\/code> \ud30c\uc77c\uc5d0\uc11c, <code>const bottomRef = useRef(null)<\/code> \uc904 \ubc14\ub85c \uc544\ub798\uc5d0 \uc774\uc5b4\uc11c \uc791\uc131\ud55c\ub2e4. \ucc44\ud305 \uc571\uc744 \uc4f8 \ub54c \uc0c8 \uba54\uc2dc\uc9c0\uac00 \uc624\uba74 \uc790\ub3d9\uc73c\ub85c \uc2a4\ud06c\ub864\uc774 \ub0b4\ub824\uac00\ub294 \uac83\uc744 \ubcf8 \uc801\uc774 \uc788\uc744 \uac83\uc774\ub2e4. \uadf8 \uae30\ub2a5\uc744 \ub9cc\ub4dc\ub294 \ucf54\ub4dc\uc774\ub2e4.<\/p>\n<pre><code class=\"language-jsx\">  useEffect(() =&gt; {\n    if (open) bottomRef.current?.scrollIntoView({ behavior: &quot;smooth&quot; });\n  }, [messages, open]);\n<\/code><\/pre>\n<table>\n<thead>\n<tr>\n<th align=\"left\">\uc904<\/th>\n<th align=\"left\">\uc124\uba85<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td align=\"left\">2<\/td>\n<td align=\"left\"><code>scrollIntoView<\/code>(\uc2a4\ud06c\ub864\uc778\ud22c\ubdf0) \u2014 \ud574\ub2f9 DOM \uc694\uc18c\uac00 \ud654\uba74\uc5d0 \ubcf4\uc774\ub3c4\ub85d \uc790\ub3d9 \uc2a4\ud06c\ub864\ud55c\ub2e4. <code>{ behavior: &quot;smooth&quot; }<\/code>\ub294 \ubd80\ub4dc\ub7fd\uac8c \uc2a4\ud06c\ub864\ud558\ub77c\ub294 \uc635\uc158\uc774\ub2e4.<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">2<\/td>\n<td align=\"left\"><code>bottomRef.current?.<\/code> \u2014 \uc635\uc154\ub110 \uccb4\uc774\ub2dd\uc774\ub2e4. <code>bottomRef.current<\/code>\uac00 \uc544\uc9c1 <code>null<\/code>\uc774\uba74 \uc5d0\ub7ec \uc5c6\uc774 \ub118\uc5b4\uac04\ub2e4.<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">3<\/td>\n<td align=\"left\"><code>[messages, open]<\/code> \u2014 \uba54\uc2dc\uc9c0\uac00 \ucd94\uac00\ub418\uac70\ub098 \ucc44\ud305\ucc3d\uc774 \uc5f4\ub9b4 \ub54c\ub9c8\ub2e4 \uc2e4\ud589\ub41c\ub2e4.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>\uc0c8 \uba54\uc2dc\uc9c0\uac00 \ub3c4\ucc29\ud558\uba74 \ucc44\ud305\ucc3d\uc774 \uc790\ub3d9\uc73c\ub85c \ub9e8 \uc544\ub798\ub85c \uc2a4\ud06c\ub864\ub418\ubbc0\ub85c, \uc0ac\uc6a9\uc790\uac00 \uc218\ub3d9\uc73c\ub85c \ub0b4\ub9b4 \ud544\uc694\uac00 \uc5c6\ub2e4.<\/p>\n<hr>\n<h2>4. 3\ub2e8\uacc4 \u2014 \uba54\uc2dc\uc9c0 \uc804\uc1a1 \ud568\uc218<\/h2>\n<p>\uac19\uc740 <code>Chatbot.jsx<\/code> \ud30c\uc77c\uc5d0\uc11c, <code>useEffect<\/code> \ube14\ub85d \ubc14\ub85c \uc544\ub798\uc5d0 \uc774\uc5b4\uc11c \ub450 \uac1c\uc758 \ud568\uc218\ub97c \uc791\uc131\ud55c\ub2e4. <code>sendMessage<\/code>\ub294 \uba54\uc2dc\uc9c0\ub97c \uc11c\ubc84\uc5d0 \ubcf4\ub0b4\uace0 \uc751\ub2f5\uc744 \ubc1b\ub294 \ud568\uc218\uc774\uace0, <code>handleKeyDown<\/code>\uc740 Enter \ud0a4\ub97c \ub204\ub974\uba74 \uc804\uc1a1\ud558\ub294 \ud568\uc218\uc774\ub2e4.<\/p>\n<h4>\u2460 sendMessage<\/h4>\n<pre><code class=\"language-jsx\">  async function sendMessage() {\n    const text = input.trim();\n    if (!text || loading) return;\n\n    setMessages((prev) =&gt; [...prev, { role: &quot;user&quot;, text }]);\n    setInput(&quot;&quot;);\n    setLoading(true);\n\n    try {\n      const res = await fetch(BACKEND, {\n        method: &quot;POST&quot;,\n        headers: { &quot;Content-Type&quot;: &quot;application\/json&quot; },\n        body: JSON.stringify({ text }),\n      });\n      const data = await res.json();\n      setMessages((prev) =&gt; [...prev, { role: &quot;bot&quot;, text: data.reply }]);\n    } catch {\n      setMessages((prev) =&gt; [\n        ...prev,\n        { role: &quot;bot&quot;, text: &quot;\uc11c\ubc84 \uc5f0\uacb0\uc5d0 \uc2e4\ud328\ud588\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.&quot; },\n      ]);\n    } finally {\n      setLoading(false);\n    }\n  }\n<\/code><\/pre>\n<table>\n<thead>\n<tr>\n<th align=\"left\">\uc904<\/th>\n<th align=\"left\">\uc124\uba85<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td align=\"left\">3<\/td>\n<td align=\"left\">`!text<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">5<\/td>\n<td align=\"left\">\uc2a4\ud504\ub808\ub4dc \uc5f0\uc0b0\uc790(<code>...prev<\/code>)\ub85c \uae30\uc874 \uba54\uc2dc\uc9c0\uc5d0 \uc0ac\uc6a9\uc790 \uba54\uc2dc\uc9c0\ub97c \ucd94\uac00\ud55c\ub2e4.<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">9-16<\/td>\n<td align=\"left\"><code>fetch<\/code>(\ud328\uce58)\ub294 \ube0c\ub77c\uc6b0\uc800 \ub0b4\uc7a5 \ud568\uc218\ub85c HTTP \uc694\uccad\uc744 \ubcf4\ub0b8\ub2e4. <code>method: &quot;POST&quot;<\/code>\ub85c \uc11c\ubc84\uc5d0 \ub370\uc774\ud130\ub97c \uc804\uc1a1\ud55c\ub2e4. <code>res.json()<\/code>\uc740 \uc11c\ubc84 \uc751\ub2f5(\ud14d\uc2a4\ud2b8)\uc744 JavaScript \uac1d\uccb4\ub85c \ubcc0\ud658\ud558\ub294 \uba54\uc11c\ub4dc\uc774\ub2e4. \ubcc0\ud658\ub41c \uac1d\uccb4\uc5d0\uc11c <code>data.reply<\/code>\ub85c \ubd07 \ub2f5\ubcc0\uc744 \uaebc\ub0b4 \uba54\uc2dc\uc9c0 \ubaa9\ub85d\uc5d0 \ucd94\uac00\ud55c\ub2e4.<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">17-21<\/td>\n<td align=\"left\"><code>catch<\/code> \u2014 \ub124\ud2b8\uc6cc\ud06c \uc5d0\ub7ec \ub4f1 \uc2e4\ud328 \uc2dc \uc5d0\ub7ec \uba54\uc2dc\uc9c0\ub97c \ubd07 \uc751\ub2f5\uc73c\ub85c \ud45c\uc2dc\ud55c\ub2e4. \uc571\uc774 \uba48\ucd94\uc9c0 \uc54a\ub294\ub2e4.<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">22-24<\/td>\n<td align=\"left\"><code>finally<\/code>(\ud30c\uc774\ub110\ub9ac) \u2014 \uc131\uacf5\uc774\ub4e0 \uc2e4\ud328\ub4e0 <strong>\ubc18\ub4dc\uc2dc<\/strong> \uc2e4\ud589\ub41c\ub2e4. \ub85c\ub529 \uc0c1\ud0dc\ub97c \ub044\ub294 \ub370 \uc0ac\uc6a9\ud55c\ub2e4.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h4>\u2461 handleKeyDown<\/h4>\n<pre><code class=\"language-jsx\">  function handleKeyDown(e) {\n    if (e.key === &quot;Enter&quot; &amp;&amp; !e.shiftKey) {\n      e.preventDefault();\n      sendMessage();\n    }\n  }\n<\/code><\/pre>\n<table>\n<thead>\n<tr>\n<th align=\"left\">\uc904<\/th>\n<th align=\"left\">\uc124\uba85<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td align=\"left\">2<\/td>\n<td align=\"left\"><code>===<\/code>(\uc5c4\uaca9\ud55c \ub3d9\ub4f1 \uc5f0\uc0b0\uc790)\ub294 \uac12\uacfc \ud0c0\uc785\uc774 \ubaa8\ub450 \uac19\uc744 \ub54c\ub9cc <code>true<\/code>\ub97c \ubc18\ud658\ud55c\ub2e4. <code>e.key === &quot;Enter&quot;<\/code>\ub294 \ub20c\ub9b0 \ud0a4\uac00 \uc815\ud655\ud788 \ubb38\uc790\uc5f4 <code>&quot;Enter&quot;<\/code>\uc778\uc9c0 \ud655\uc778\ud55c\ub2e4. Shift+Enter\ub294 \uc904\ubc14\uafc8\uc73c\ub85c \ud5c8\uc6a9\ud558\uae30 \uc704\ud574 <code>!e.shiftKey<\/code>\ub85c Shift \ud0a4\uac00 \ub20c\ub9ac\uc9c0 \uc54a\uc558\ub294\uc9c0\ub3c4 \ud568\uaed8 \ud655\uc778\ud55c\ub2e4.<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">3<\/td>\n<td align=\"left\"><code>e.preventDefault()<\/code>(\ud504\ub9ac\ubca4\ud2b8\ub514\ud3f4\ud2b8) \u2014 Enter \ud0a4\uc758 \uae30\ubcf8 \ub3d9\uc791(\ud3fc \uc81c\ucd9c \ub4f1)\uc744 \ub9c9\ub294\ub2e4.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p><Accordion title=\"try \/ catch \/ finally \ud328\ud134\"><br \/>\n&#8220;`jsx<br \/>\ntry {<br \/>\n  \/\/ \uc131\uacf5\ud560 \uc218\ub3c4 \uc788\ub294 \ucf54\ub4dc<br \/>\n} catch {<br \/>\n  \/\/ \uc2e4\ud328\ud588\uc744 \ub54c \uc2e4\ud589\ub418\ub294 \ucf54\ub4dc<br \/>\n} finally {<br \/>\n  \/\/ \uc131\uacf5\uc774\ub4e0 \uc2e4\ud328\ub4e0 \ubc18\ub4dc\uc2dc \uc2e4\ud589\ub418\ub294 \ucf54\ub4dc<br \/>\n}<br \/>\n&#8220;`<\/p>\n<table>\n<thead>\n<tr>\n<th align=\"left\">\ube14\ub85d<\/th>\n<th align=\"left\">\uc2e4\ud589 \uc870\uac74<\/th>\n<th align=\"left\">\uc6a9\ub3c4<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td align=\"left\"><code>try<\/code><\/td>\n<td align=\"left\">\ud56d\uc0c1 \uc2e4\ud589 \uc2dc\ub3c4<\/td>\n<td align=\"left\">API \ud638\ucd9c, \ub370\uc774\ud130 \ucc98\ub9ac<\/td>\n<\/tr>\n<tr>\n<td align=\"left\"><code>catch<\/code><\/td>\n<td align=\"left\"><code>try<\/code>\uc5d0\uc11c \uc5d0\ub7ec \ubc1c\uc0dd \uc2dc<\/td>\n<td align=\"left\">\uc5d0\ub7ec \uba54\uc2dc\uc9c0 \ud45c\uc2dc, \ub300\uccb4 \ub3d9\uc791<\/td>\n<\/tr>\n<tr>\n<td align=\"left\"><code>finally<\/code><\/td>\n<td align=\"left\">\ud56d\uc0c1 (\uc131\uacf5\/\uc2e4\ud328 \ubb34\uad00)<\/td>\n<td align=\"left\">\ub85c\ub529 \uc0c1\ud0dc \ucd08\uae30\ud654, \uc815\ub9ac \uc791\uc5c5<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>\uc774\uc804 \uad50\uc548(5\ud3b8)\uc5d0\uc11c\ub294 <code>try\/catch<\/code>\ub9cc \uc0ac\uc6a9\ud588\ub2e4. <code>finally<\/code>\ub97c \ucd94\uac00\ud558\uba74 <code>setLoading(false)<\/code>\ub97c <code>try<\/code>\uc640 <code>catch<\/code> \uc591\ucabd\uc5d0 \uc911\ubcf5 \uc791\uc131\ud560 \ud544\uc694\uac00 \uc5c6\uc5b4\uc9c4\ub2e4.<br \/>\n<\/Accordion><\/p>\n<hr>\n<h2>5. 4\ub2e8\uacc4 \u2014 JSX \ub80c\ub354\ub9c1<\/h2>\n<p>\uac19\uc740 <code>Chatbot.jsx<\/code> \ud30c\uc77c\uc5d0\uc11c, <code>handleKeyDown<\/code> \ud568\uc218 \ubc14\ub85c \uc544\ub798\uc5d0 <code>return<\/code>\ubb38\uc744 \uc791\uc131\ud55c\ub2e4. \ucf54\ub4dc\uac00 \uae38\uae30 \ub54c\ubb38\uc5d0 \uc138 \ubd80\ubd84\uc73c\ub85c \ub098\ub204\uc5b4 \uc124\uba85\ud55c\ub2e4. \ud0ed\uc744 \ub20c\ub7ec \uac01 \ubd80\ubd84\uc744 \ud655\uc778\ud558\uba74\uc11c <strong>\uc21c\uc11c\ub300\ub85c \uc774\uc5b4 \ubd99\uc778\ub2e4<\/strong>.<\/p>\n<h4>\u2460 \ud50c\ub85c\ud305 \ubc84\ud2bc<\/h4>\n<pre><code class=\"language-jsx\">  return (\n    &lt;&gt;\n      &lt;Button\n        variant=&quot;primary&quot;\n        onClick={() =&gt; setOpen((v) =&gt; !v)}\n        className=&quot;fixed bottom-8 right-8 w-14 h-14 rounded-full text-2xl shadow-lg z-50 flex items-center justify-center&quot;\n        title=&quot;AI \ucc57\ubd07&quot;\n      &gt;\n        {open ? &lt;FontAwesomeIcon icon={faXmark} \/&gt; : &lt;FontAwesomeIcon icon={faComment} \/&gt;}\n      &lt;\/Button&gt;\n<\/code><\/pre>\n<table>\n<thead>\n<tr>\n<th align=\"left\">\uc904<\/th>\n<th align=\"left\">\uc124\uba85<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td align=\"left\">1<\/td>\n<td align=\"left\">4\ud3b8\uc5d0\uc11c \ub9cc\ub4e0 <code>Button<\/code> \ucef4\ud3ec\ub10c\ud2b8\uc774\ub2e4. <code>variant=&quot;primary&quot;<\/code>\ub85c \ub178\ub780\uc0c9 \uc2a4\ud0c0\uc77c\uc744 \uc801\uc6a9\ud55c\ub2e4.<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">3<\/td>\n<td align=\"left\"><code>setOpen((v) =&gt; !v)<\/code> \u2014 \ud568\uc218\ud615 \uc5c5\ub370\uc774\ud2b8\uc774\ub2e4. \uc774\uc804 \uac12 <code>v<\/code>\ub97c \ubc1b\uc544\uc11c \ubc18\uc804\uc2dc\ud0a8\ub2e4. <code>setOpen(!open)<\/code>\uacfc \uac19\uc740 \uacb0\uacfc\uc774\uc9c0\ub9cc \ub354 \uc548\uc804\ud558\ub2e4.<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">4<\/td>\n<td align=\"left\"><code>fixed bottom-8 right-8<\/code> \u2014 \ud654\uba74 \uc6b0\ud558\ub2e8\uc5d0 \uace0\uc815 \ubc30\uce58\ud55c\ub2e4. <code>rounded-full<\/code> \u2014 \uc644\uc804\ud55c \uc6d0\ud615\uc774\ub2e4. <code>shadow-lg<\/code> \u2014 \ud070 \uadf8\ub9bc\uc790 \ud6a8\uacfc\uc774\ub2e4.<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">5<\/td>\n<td align=\"left\"><code>title<\/code> \uc18d\uc131 \u2014 \ubc84\ud2bc\uc5d0 \ub9c8\uc6b0\uc2a4\ub97c \uc62c\ub9ac\uba74 &quot;AI \ucc57\ubd07&quot; \ud234\ud301\uc774 \ud45c\uc2dc\ub41c\ub2e4.<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">7<\/td>\n<td align=\"left\">\uc0bc\ud56d \uc5f0\uc0b0\uc790\ub85c \uc5f4\ub9bc \uc2dc X \uc544\uc774\ucf58, \ub2eb\ud798 \uc2dc \ub9d0\ud48d\uc120 \uc544\uc774\ucf58\uc774\ub2e4.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h4>\u2461 \ucc44\ud305\ucc3d \ud5e4\ub354 + \uba54\uc2dc\uc9c0 \ubaa9\ub85d<\/h4>\n<pre><code class=\"language-jsx\">      {open &amp;&amp; (\n        &lt;div className=&quot;fixed bottom-24 right-8 w-85 max-h-1\/2 bg-gray-950 border border-gray-600 rounded-xl flex flex-col shadow-2xl z-40&quot;&gt;\n          &lt;div className=&quot;py-3 px-4 bg-yellow-500 rounded-t-xl font-bold text-sm text-gray-700&quot;&gt;Goflix AI \ucc57\ubd07&lt;\/div&gt;\n\n          &lt;div className=&quot;flex-1 overflow-y-auto p-3 flex flex-col gap-2 min-h-50 max-h-85&quot;&gt;\n            {messages.map((m, i) =&gt; (\n              &lt;div\n                key={i}\n                className={`py-2 px-3 max-w-4\/5 text-sm leading-snug whitespace-pre-wrap break-words text-gray-700\n                  ${m.role === &quot;user&quot; ? &quot;self-end bg-yellow-500 rounded-tl-xl rounded-tr-xl rounded-bl-xl&quot; : &quot;self-start bg-gray-100 rounded-tl-xl rounded-tr-xl rounded-br-xl&quot;}`}\n              &gt;\n                {m.text}\n              &lt;\/div&gt;\n            ))}\n            {loading &amp;&amp; &lt;div className=&quot;self-start bg-gray-100 text-gray-400 rounded-tl-xl rounded-tr-xl rounded-br-xl py-2 px-3 text-sm&quot;&gt;...&lt;\/div&gt;}\n            &lt;div ref={bottomRef} \/&gt;\n          &lt;\/div&gt;\n<\/code><\/pre>\n<table>\n<thead>\n<tr>\n<th align=\"left\">\uc904<\/th>\n<th align=\"left\">\uc124\uba85<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td align=\"left\">2<\/td>\n<td align=\"left\"><code>fixed bottom-24 right-8<\/code> \u2014 \ud50c\ub85c\ud305 \ubc84\ud2bc \ubc14\ub85c \uc704\uc5d0 \ucc44\ud305\ucc3d\uc744 \ubc30\uce58\ud55c\ub2e4. <code>w-85<\/code>\ub294 \ub108\ube44 \uc57d 340px\uc774\ub2e4. <code>max-h-1\/2<\/code>\ub294 \ud654\uba74 \ub192\uc774\uc758 \uc808\ubc18\uae4c\uc9c0\ub9cc \ucc28\uc9c0\ud55c\ub2e4.<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">2<\/td>\n<td align=\"left\"><code>bg-gray-950<\/code> \u2014 Tailwind\uc758 \uac00\uc7a5 \uc5b4\ub450\uc6b4 \ud68c\uc0c9\uc774\ub2e4. <code>rounded-xl<\/code> \u2014 \ubaa8\uc11c\ub9ac\ub97c \ub465\uae00\uac8c 12px \ucc98\ub9ac\ud55c\ub2e4.<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">5<\/td>\n<td align=\"left\"><code>overflow-y-auto<\/code> \u2014 \ub0b4\uc6a9\uc774 \ub118\uce58\uba74 \uc138\ub85c \uc2a4\ud06c\ub864\uc774 \uc0dd\uae34\ub2e4. <code>min-h-50<\/code>\uc740 \ucd5c\uc18c \ub192\uc774, <code>max-h-85<\/code>\ub294 \ucd5c\ub300 \ub192\uc774\uc774\ub2e4.<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">9<\/td>\n<td align=\"left\"><code>whitespace-pre-wrap<\/code> \u2014 \uc904\ubc14\uafc8(<code>n<\/code>)\uc744 \uc720\uc9c0\ud558\uba74\uc11c \uae34 \ud14d\uc2a4\ud2b8\ub294 \uc790\ub3d9 \uc904\ubc14\uafc8\ud55c\ub2e4. <code>break-words<\/code> \u2014 \uae34 \ub2e8\uc5b4\uac00 \ub118\uce58\uba74 \uac15\uc81c\ub85c \uc904\ubc14\uafc8\ud55c\ub2e4.<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">10<\/td>\n<td align=\"left\"><code>self-end<\/code>(\uc0ac\uc6a9\uc790 \uba54\uc2dc\uc9c0, \uc624\ub978\ucabd) \/ <code>self-start<\/code>(\ubd07 \uba54\uc2dc\uc9c0, \uc67c\ucabd)\ub85c \uc815\ub82c\ud55c\ub2e4. \uac01\uac01 \ub2e4\ub978 \ubc29\ud5a5\uc758 \ubaa8\uc11c\ub9ac\ub97c \ub465\uae00\uac8c \ucc98\ub9ac\ud558\uc5ec \ub9d0\ud48d\uc120 \ub290\ub08c\uc744 \ub9cc\ub4e0\ub2e4.<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">16<\/td>\n<td align=\"left\"><code>ref={bottomRef}<\/code> \u2014 \uc774 \ube48 <code>div<\/code>\uac00 \uba54\uc2dc\uc9c0 \ubaa9\ub85d\uc758 \ub9e8 \uc544\ub798\uc774\ub2e4. 2\ub2e8\uacc4\uc758 <code>scrollIntoView<\/code>\uac00 \uc774 \uc704\uce58\ub85c \uc2a4\ud06c\ub864\ud55c\ub2e4.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h4>\u2462 \uc785\ub825\ucc3d<\/h4>\n<pre><code class=\"language-jsx\">          &lt;div className=&quot;flex border-t border-[#333] p-2 gap-2&quot;&gt;\n            &lt;input\n              value={input}\n              onChange={(e) =&gt; setInput(e.target.value)}\n              onKeyDown={handleKeyDown}\n              placeholder=&quot;\uba54\uc2dc\uc9c0 \uc785\ub825...&quot;\n              disabled={loading}\n              className=&quot;flex-1 bg-[#1e1e1e] border border-[#444] rounded-lg py-2 px-3 text-white caret-white text-sm outline-none placeholder:text-zinc-300&quot;\n            \/&gt;\n            &lt;Button\n              variant=&quot;primary&quot;\n              onClick={sendMessage}\n              disabled={loading || !input.trim()}\n              className=&quot;rounded-lg py-2 px-3 text-sm disabled:opacity-50 disabled:cursor-not-allowed&quot;\n            &gt;\n              \uc804\uc1a1\n            &lt;\/Button&gt;\n          &lt;\/div&gt;\n        &lt;\/div&gt;\n      )}\n    &lt;\/&gt;\n  );\n}\n<\/code><\/pre>\n<table>\n<thead>\n<tr>\n<th align=\"left\">\uc904<\/th>\n<th align=\"left\">\uc124\uba85<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td align=\"left\">1<\/td>\n<td align=\"left\"><code>border-[#333]<\/code> \u2014 Tailwind\uc758 <strong>\uc784\uc758\uac12<\/strong>(Arbitrary Value) \ubb38\ubc95\uc774\ub2e4. \ubbf8\ub9ac \uc815\uc758\ub418\uc9c0 \uc54a\uc740 \uc0c9\uc0c1\uc744 \ub300\uad04\ud638\ub85c \uc9c1\uc811 \uc9c0\uc815\ud55c\ub2e4.<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">7<\/td>\n<td align=\"left\"><code>disabled={loading}<\/code> \u2014 \ub85c\ub529 \uc911\uc774\uba74 \uc785\ub825\ucc3d\uc744 \ube44\ud65c\uc131\ud654\ud55c\ub2e4.<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">8<\/td>\n<td align=\"left\"><code>outline-none<\/code> \u2014 \ud3ec\ucee4\uc2a4 \uc2dc \uae30\ubcf8 \ud30c\ub780\uc0c9 \uc678\uacfd\uc120\uc744 \uc81c\uac70\ud55c\ub2e4. <code>placeholder:text-zinc-300<\/code> \u2014 \ud50c\ub808\uc774\uc2a4\ud640\ub354 \ud14d\uc2a4\ud2b8 \uc0c9\uc0c1\uc744 \uc9c0\uc815\ud558\ub294 Tailwind \uc218\uc2dd\uc5b4\uc774\ub2e4.<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">11<\/td>\n<td align=\"left\">4\ud3b8\uc5d0\uc11c \ub9cc\ub4e0 <code>Button<\/code> \ucef4\ud3ec\ub10c\ud2b8\ub97c \uc0ac\uc6a9\ud55c\ub2e4. <code>variant=&quot;primary&quot;<\/code>\ub85c \ub178\ub780\uc0c9 \uc2a4\ud0c0\uc77c\uc744 \uc801\uc6a9\ud55c\ub2e4.<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">13<\/td>\n<td align=\"left\"><code>disabled={loading &amp;#124;&amp;#124; !input.trim()}<\/code> \u2014 \ub85c\ub529 \uc911\uc774\uac70\ub098 \ube48 \uc785\ub825\uc774\uba74 \uc804\uc1a1 \ubc84\ud2bc\uc744 \ube44\ud65c\uc131\ud654\ud55c\ub2e4. <code>disabled:opacity-50 disabled:cursor-not-allowed<\/code> \u2014 \ube44\ud65c\uc131\ud654 \uc2dc \ud22c\uba85\ub3c4\uc640 \ucee4\uc11c\ub97c \ubcc0\uacbd\ud55c\ub2e4.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<hr>\n<h2>6. 5\ub2e8\uacc4 \u2014 App.jsx\uc5d0 \uc5f0\uacb0<\/h2>\n<p><code>src\/App.jsx<\/code>\ub97c \uc218\uc815\ud55c\ub2e4. \ub450 \uacf3\uc744 \ubcc0\uacbd\ud55c\ub2e4.<\/p>\n<h3>6.1. import \ucd94\uac00<\/h3>\n<p>\uae30\uc874 import \uc601\uc5ed\uc5d0 \ud55c \uc904\uc744 \ucd94\uac00\ud55c\ub2e4.<\/p>\n<pre><code class=\"language-jsx\">import Chatbot from &quot;.\/components\/Chatbot.jsx&quot;;\n<\/code><\/pre>\n<blockquote>\n<p><strong>Tip<\/strong>: <code>Chatbot<\/code>\uc740 <code>export default<\/code>\ub85c \ub0b4\ubcf4\ub0c8\uc73c\ubbc0\ub85c \uc911\uad04\ud638 <code>{ }<\/code> \uc5c6\uc774 \uac00\uc838\uc628\ub2e4. \ub2e4\ub978 \ucef4\ud3ec\ub10c\ud2b8(<code>Header<\/code>, <code>Footer<\/code> \ub4f1)\ub294 <code>export function<\/code>(named export)\uc774\ub77c\uc11c \uc911\uad04\ud638\uac00 \ud544\uc694\ud558\ub2e4.<\/p>\n<\/blockquote>\n<h3>6.2. return\uc5d0 Chatbot \ucd94\uac00<\/h3>\n<p><code>&lt;Footer \/&gt;<\/code> \uc544\ub798\uc5d0 \ud55c \uc904\uc744 \ucd94\uac00\ud55c\ub2e4.<\/p>\n<pre><code class=\"language-jsx\">  return (\n    &lt;&gt;\n      &lt;Header \/&gt;\n      &lt;Outlet context={ctx} \/&gt;\n      &lt;Footer \/&gt;\n      &lt;Chatbot \/&gt;\n    &lt;\/&gt;\n  );\n<\/code><\/pre>\n<table>\n<thead>\n<tr>\n<th align=\"left\">\uc904<\/th>\n<th align=\"left\">\uc124\uba85<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td align=\"left\">6<\/td>\n<td align=\"left\"><code>&lt;Chatbot \/&gt;<\/code> \u2014 \ud50c\ub85c\ud305 \ubc84\ud2bc\uacfc \ucc44\ud305\ucc3d\uc774 \ubaa8\ub450 \uc774 \ucef4\ud3ec\ub10c\ud2b8 \uc548\uc5d0 \uc788\uc73c\ubbc0\ub85c, \ud55c \uc904\ub9cc \ucd94\uac00\ud558\uba74 \ub41c\ub2e4.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<hr>\n<h2>7. \ub3d9\uc791 \ud655\uc778<\/h2>\n<table>\n<thead>\n<tr>\n<th align=\"left\">\ud655\uc778 \ud56d\ubaa9<\/th>\n<th align=\"left\">\uae30\ub300 \uacb0\uacfc<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td align=\"left\">\ud654\uba74 \uc6b0\ud558\ub2e8<\/td>\n<td align=\"left\">\ub178\ub780\uc0c9 \ub465\uadfc \ubc84\ud2bc\uc5d0 \ub9d0\ud48d\uc120 \uc544\uc774\ucf58 \ud45c\uc2dc<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">\ubc84\ud2bc \ud074\ub9ad<\/td>\n<td align=\"left\">\ubc84\ud2bc \uc704\uc5d0 \ucc44\ud305\ucc3d \ud31d\uc5c5. \uc544\uc774\ucf58\uc774 X\ub85c \uc804\ud658<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">\ucc44\ud305\ucc3d \uc5f4\ub9bc<\/td>\n<td align=\"left\">&quot;\uc548\ub155\ud558\uc138\uc694! \uc601\ud654\uc5d0 \ub300\ud574 \ubb34\uc5c7\uc774\ub4e0 \ubb3c\uc5b4\ubcf4\uc138\uc694&quot; \uba54\uc2dc\uc9c0 \ud45c\uc2dc<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">\uba54\uc2dc\uc9c0 \uc804\uc1a1<\/td>\n<td align=\"left\">\uc624\ub978\ucabd \ub178\ub780 \ub9d0\ud48d\uc120 \u2192 &quot;&#8230;&quot; \ub85c\ub529 \u2192 \uc67c\ucabd \ud770 \ub9d0\ud48d\uc120<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">\uc11c\ubc84 \uc5d0\ub7ec \uc2dc<\/td>\n<td align=\"left\">&quot;\uc11c\ubc84 \uc5f0\uacb0\uc5d0 \uc2e4\ud328\ud588\uc2b5\ub2c8\ub2e4&quot; \uc5d0\ub7ec \uba54\uc2dc\uc9c0 \ud45c\uc2dc<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">\uba54\uc2dc\uc9c0 \ucd94\uac00<\/td>\n<td align=\"left\">\uc790\ub3d9\uc73c\ub85c \ub9e8 \uc544\ub798\ub85c \uc2a4\ud06c\ub864<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">X \ubc84\ud2bc \ud074\ub9ad<\/td>\n<td align=\"left\">\ucc44\ud305\ucc3d \ub2eb\ud798<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<blockquote>\n<p><strong>Info<\/strong>: \ubb34\ub8cc \ud638\uc2a4\ud305(Render)\uc740 \uccab \uc751\ub2f5\uc774 30\ucd08~1\ubd84 \uac78\ub9b4 \uc218 \uc788\ub2e4. \uc11c\ubc84\uac00 \uc2ac\ub9bd \uc0c1\ud0dc\uc5d0\uc11c \uae68\uc5b4\ub098\ub294 \uc2dc\uac04\uc774\ub2e4.<\/p>\n<\/blockquote>\n<hr>\n<h2>8. \ubaa8\ub178\ub808\ud3ec \uad6c\uc870\uc640 GitHub \uc62c\ub9ac\uae30<\/h2>\n<h3>8.1. \ubaa8\ub178\ub808\ud3ec\ub780?<\/h3>\n<p>\uc774 \ud504\ub85c\uc81d\ud2b8\ub294 <strong>\ud558\ub098\uc758 GitHub \uc800\uc7a5\uc18c<\/strong> \uc548\uc5d0 \ud504\ub860\ud2b8\uc5d4\ub4dc(<code>frontend\/<\/code>)\uc640 \ubc31\uc5d4\ub4dc(<code>backend\/<\/code>)\uac00 \ud568\uaed8 \ub4e4\uc5b4 \uc788\ub2e4. \uc774\ub7f0 \uad6c\uc870\ub97c <strong>\ubaa8\ub178\ub808\ud3ec<\/strong>(Monorepo, \ubaa8\ub178\ub9ac\ud3ec)\ub77c\uace0 \ud55c\ub2e4. \ucc45 \ud55c \uad8c\uc5d0 \uc18c\uc124\uacfc \uc0bd\ud654\uac00 \ud568\uaed8 \ub4e4\uc5b4 \uc788\ub294 \uac83\ucc98\ub7fc, \ud558\ub098\uc758 \uc800\uc7a5\uc18c\uc5d0\uc11c \ub450 \ud504\ub85c\uc81d\ud2b8\ub97c \uad00\ub9ac\ud55c\ub2e4.<\/p>\n<pre><code>movie-2026\/\n\u251c\u2500\u2500 frontend\/          \u2190 React \ud504\ub860\ud2b8\uc5d4\ub4dc\n\u2502   \u251c\u2500\u2500 src\/\n\u2502   \u251c\u2500\u2500 public\/\n\u2502   \u251c\u2500\u2500 vite.config.js\n\u2502   \u251c\u2500\u2500 package.json\n\u2502   \u2514\u2500\u2500 .env           \u2190 TMDB API \ud0a4 (GitHub\uc5d0 \uc62c\ub9ac\uba74 \uc548 \ub428)\n\u2514\u2500\u2500 backend\/           \u2190 Python \ucc57\ubd07 \uc11c\ubc84\n    \u251c\u2500\u2500 main.py\n    \u251c\u2500\u2500 requirements.txt\n    \u2514\u2500\u2500 .env           \u2190 HuggingFace API \ud0a4 (GitHub\uc5d0 \uc62c\ub9ac\uba74 \uc548 \ub428)\n<\/code><\/pre>\n<h3>8.2. .gitignore \uc124\uc815<\/h3>\n<p>\ud504\ub85c\uc81d\ud2b8 <strong>\ucd5c\uc0c1\uc704 \ud3f4\ub354<\/strong>(movie-2026\/)\uc5d0 <code>.gitignore<\/code> \ud30c\uc77c\uc744 \ub9cc\ub4e4\uace0 \uc544\ub798 \ub0b4\uc6a9\uc744 \uc785\ub825\ud55c\ub2e4. \ube44\ubc00 \uc815\ubcf4\uc640 \ubd88\ud544\uc694\ud55c \ud30c\uc77c\uc774 GitHub\uc5d0 \uc62c\ub77c\uac00\uc9c0 \uc54a\ub3c4\ub85d \ub9c9\ub294 \uc5ed\ud560\uc774\ub2e4.<\/p>\n<pre><code class=\"language-text\">frontend\/.env\nfrontend\/node_modules\/\nfrontend\/dist\/\nbackend\/.env\nbackend\/.venv\/\nbackend\/__pycache__\/\n<\/code><\/pre>\n<table>\n<thead>\n<tr>\n<th align=\"left\">\uc904<\/th>\n<th align=\"left\">\uc124\uba85<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td align=\"left\">1<\/td>\n<td align=\"left\">\ud504\ub860\ud2b8\uc5d4\ub4dc\uc758 TMDB API \ud0a4 \ud30c\uc77c\uc744 \uc81c\uc678\ud55c\ub2e4.<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">2<\/td>\n<td align=\"left\">\uc124\uce58\ub41c \ud328\ud0a4\uc9c0 \ud3f4\ub354\ub97c \uc81c\uc678\ud55c\ub2e4. \uc6a9\ub7c9\uc774 \ub9e4\uc6b0 \ud06c\uace0, <code>npm install<\/code>\ub85c \ub2e4\uc2dc \uc124\uce58\ud560 \uc218 \uc788\ub2e4.<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">3<\/td>\n<td align=\"left\">\ube4c\ub4dc \uacb0\uacfc\ubb3c\uc744 \uc81c\uc678\ud55c\ub2e4. Render\uac00 \ubc30\ud3ec \uc2dc \uc790\ub3d9\uc73c\ub85c \ube4c\ub4dc\ud55c\ub2e4.<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">4-6<\/td>\n<td align=\"left\">\ubc31\uc5d4\ub4dc\uc758 \ube44\ubc00 \ud0a4, \uac00\uc0c1\ud658\uacbd, \uce90\uc2dc\ub97c \uc81c\uc678\ud55c\ub2e4.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h3>8.3. GitHub\uc5d0 \ucf54\ub4dc \uc62c\ub9ac\uae30<\/h3>\n<ol>\n<li><a href=\"https:\/\/github.com\">https:\/\/github.com<\/a> \uc5d0\uc11c <strong>New repository(\ub274 \ub9ac\ud3ec\uc9c0\ud1a0\ub9ac)<\/strong>\ub97c \ud074\ub9ad\ud558\uc5ec \uc0c8 \uc800\uc7a5\uc18c\ub97c \ub9cc\ub4e0\ub2e4.<\/li>\n<li>\ud504\ub85c\uc81d\ud2b8 \ucd5c\uc0c1\uc704 \ud3f4\ub354(movie-2026\/)\uc5d0\uc11c \ud130\ubbf8\ub110\uc744 \uc5f4\uace0, \uc544\ub798 \uba85\ub839\uc5b4\ub97c <strong>\ud55c \uc904\uc529<\/strong> \uc2e4\ud589\ud55c\ub2e4.<\/li>\n<\/ol>\n<pre><code class=\"language-bash\">git init\ngit add .\ngit commit -m &quot;first commit&quot;\ngit remote add origin https:\/\/github.com\/\ub0b4\uc544\uc774\ub514\/movie-2026.git\ngit push -u origin main\n<\/code><\/pre>\n<table>\n<thead>\n<tr>\n<th align=\"left\">\uc904<\/th>\n<th align=\"left\">\uc124\uba85<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td align=\"left\">1<\/td>\n<td align=\"left\">\uc774 \ud3f4\ub354\ub97c Git \uc800\uc7a5\uc18c\ub85c \ucd08\uae30\ud654\ud55c\ub2e4.<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">2<\/td>\n<td align=\"left\">\ubaa8\ub4e0 \ud30c\uc77c\uc744 \uc2a4\ud14c\uc774\uc9c0(stage, \uc62c\ub9b4 \uc900\ube44)\uc5d0 \uc62c\ub9b0\ub2e4.<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">3<\/td>\n<td align=\"left\">&quot;first commit&quot;\uc774\ub77c\ub294 \uba54\uc2dc\uc9c0\uc640 \ud568\uaed8 \uae30\ub85d\uc744 \ub0a8\uae34\ub2e4.<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">4<\/td>\n<td align=\"left\">GitHub \uc6d0\uaca9 \uc800\uc7a5\uc18c\uc640 \uc5f0\uacb0\ud55c\ub2e4. <code>\ub0b4\uc544\uc774\ub514<\/code>\ub97c \ubcf8\uc778\uc758 GitHub \uc544\uc774\ub514\ub85c \ubc14\uafbc\ub2e4.<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">5<\/td>\n<td align=\"left\">\ucf54\ub4dc\ub97c GitHub\uc5d0 \uc62c\ub9b0\ub2e4.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<hr>\n<h2>9. Render\uc5d0 \ubc30\ud3ec\ud558\uae30<\/h2>\n<p>Render(\ub80c\ub354)\ub294 GitHub \uc800\uc7a5\uc18c\ub97c \uc5f0\uacb0\ud558\uba74 \ucf54\ub4dc\ub97c \uc790\ub3d9\uc73c\ub85c \ubc30\ud3ec\ud574 \uc8fc\ub294 \ubb34\ub8cc \ud638\uc2a4\ud305 \uc11c\ube44\uc2a4\uc774\ub2e4. \ubaa8\ub178\ub808\ud3ec\uc5d0\uc11c\ub294 <strong>\uac19\uc740 \uc800\uc7a5\uc18c<\/strong>\ub97c \ub450 \ubc88 \uc5f0\uacb0\ud558\ub418, \uac01\uac01 \ub2e4\ub978 <strong>Root Directory<\/strong>(\ub8e8\ud2b8 \ub514\ub809\ud1a0\ub9ac, \uc2dc\uc791 \ud3f4\ub354)\ub97c \uc9c0\uc815\ud55c\ub2e4.<\/p>\n<h3>9.1. \ubc31\uc5d4\ub4dc \ubc30\ud3ec (Python \uc11c\ubc84)<\/h3>\n<ol>\n<li><a href=\"https:\/\/render.com\">https:\/\/render.com<\/a> \uc5d0 \uc811\uc18d\ud558\uc5ec GitHub \uacc4\uc815\uc73c\ub85c \ub85c\uadf8\uc778\ud55c\ub2e4.<\/li>\n<li><strong>New<\/strong> \u2192 <strong>Web Service<\/strong>\ub97c \ud074\ub9ad\ud55c\ub2e4.<\/li>\n<li>GitHub \uc800\uc7a5\uc18c(<code>movie-2026<\/code>)\ub97c \uc5f0\uacb0\ud558\uace0 \uc544\ub798\uc640 \uac19\uc774 \uc124\uc815\ud55c\ub2e4.<\/li>\n<\/ol>\n<table>\n<thead>\n<tr>\n<th align=\"left\">\ud56d\ubaa9<\/th>\n<th align=\"left\">\uc785\ub825\uac12<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td align=\"left\"><strong>Name<\/strong><\/td>\n<td align=\"left\"><code>goflex-backend<\/code> (\uc6d0\ud558\ub294 \uc774\ub984)<\/td>\n<\/tr>\n<tr>\n<td align=\"left\"><strong>Root Directory<\/strong><\/td>\n<td align=\"left\"><code>backend<\/code><\/td>\n<\/tr>\n<tr>\n<td align=\"left\"><strong>Runtime<\/strong><\/td>\n<td align=\"left\"><code>Python 3<\/code><\/td>\n<\/tr>\n<tr>\n<td align=\"left\"><strong>Build Command<\/strong><\/td>\n<td align=\"left\"><code>pip install -r requirements.txt<\/code><\/td>\n<\/tr>\n<tr>\n<td align=\"left\"><strong>Start Command<\/strong><\/td>\n<td align=\"left\"><code>uvicorn main:app --host 0.0.0.0 --port 10000<\/code><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<ol start=\"4\">\n<li><strong>Environment<\/strong>(\ud658\uacbd\ubcc0\uc218) \ud0ed\uc5d0\uc11c \uc544\ub798 \uac12\uc744 \ucd94\uac00\ud55c\ub2e4.<\/li>\n<\/ol>\n<table>\n<thead>\n<tr>\n<th align=\"left\">Key<\/th>\n<th align=\"left\">Value<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td align=\"left\"><code>HF_TOKEN<\/code><\/td>\n<td align=\"left\">HuggingFace\uc5d0\uc11c \ubc1c\uae09\ubc1b\uc740 API \ud0a4<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<ol start=\"5\">\n<li><strong>Create Web Service<\/strong>\ub97c \ud074\ub9ad\ud558\uba74 \ubc30\ud3ec\uac00 \uc2dc\uc791\ub41c\ub2e4.<\/li>\n<li>\ubc30\ud3ec \uc644\ub8cc \ud6c4 <code>https:\/\/goflex-backend-xxxx.onrender.com<\/code> \ud615\ud0dc\uc758 \uc8fc\uc18c\uac00 \uc0dd\uc131\ub41c\ub2e4. \uc774 \uc8fc\uc18c\ub97c \uba54\ubaa8\ud574 \ub454\ub2e4.<\/li>\n<\/ol>\n<blockquote>\n<p><strong>Warning<\/strong>: Start Command\ub97c \ubc18\ub4dc\uc2dc <code>uvicorn main:app --host 0.0.0.0 --port 10000<\/code>\uc73c\ub85c \uc9c1\uc811 \uc785\ub825\ud574\uc57c \ud55c\ub2e4. Render\ub294 Python \ud504\ub85c\uc81d\ud2b8\ub97c \uac10\uc9c0\ud558\uba74 \uc790\ub3d9\uc73c\ub85c <code>gunicorn<\/code>\uc744 \uc0ac\uc6a9\ud558\ub824\uace0 \ud558\ub294\ub370, FastAPI\ub294 <code>uvicorn<\/code>\uc744 \uc0ac\uc6a9\ud558\ubbc0\ub85c \uc5d0\ub7ec\uac00 \ubc1c\uc0dd\ud55c\ub2e4.<\/p>\n<\/blockquote>\n<h3>9.2. Chatbot.jsx\uc758 BACKEND \uc8fc\uc18c \ubcc0\uacbd<\/h3>\n<p>\ubc30\ud3ec\ub41c \ubc31\uc5d4\ub4dc \uc8fc\uc18c\ub97c <code>Chatbot.jsx<\/code>\uc5d0 \ubc18\uc601\ud55c\ub2e4. <code>src\/components\/Chatbot.jsx<\/code> \ud30c\uc77c\uc744 \uc5f4\uace0 5\ubc88 \uc904\uc744 \uc218\uc815\ud55c\ub2e4.<\/p>\n<pre><code class=\"language-jsx\">const BACKEND = &quot;https:\/\/goflex-backend-xxxx.onrender.com\/chat&quot;;\n<\/code><\/pre>\n<p><code>xxxx<\/code> \ubd80\ubd84\uc744 9.1\uc5d0\uc11c \uc0dd\uc131\ub41c \uc2e4\uc81c \uc8fc\uc18c\ub85c \ubc14\uafbc\ub2e4. \uc218\uc815 \ud6c4 GitHub\uc5d0 \ub2e4\uc2dc push \ud55c\ub2e4.<\/p>\n<pre><code class=\"language-bash\">git add .\ngit commit -m &quot;update backend url&quot;\ngit push\n<\/code><\/pre>\n<h3>9.3. \ud504\ub860\ud2b8\uc5d4\ub4dc \ubc30\ud3ec (React)<\/h3>\n<ol>\n<li>Render\uc5d0\uc11c <strong>New<\/strong> \u2192 <strong>Static Site<\/strong>(\uc2a4\ud0dc\ud2f1 \uc0ac\uc774\ud2b8)\ub97c \ud074\ub9ad\ud55c\ub2e4.<\/li>\n<li>\uac19\uc740 GitHub \uc800\uc7a5\uc18c(<code>movie-2026<\/code>)\ub97c \ub2e4\uc2dc \uc5f0\uacb0\ud558\uace0 \uc544\ub798\uc640 \uac19\uc774 \uc124\uc815\ud55c\ub2e4.<\/li>\n<\/ol>\n<table>\n<thead>\n<tr>\n<th align=\"left\">\ud56d\ubaa9<\/th>\n<th align=\"left\">\uc785\ub825\uac12<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td align=\"left\"><strong>Name<\/strong><\/td>\n<td align=\"left\"><code>goflex-frontend<\/code> (\uc6d0\ud558\ub294 \uc774\ub984)<\/td>\n<\/tr>\n<tr>\n<td align=\"left\"><strong>Root Directory<\/strong><\/td>\n<td align=\"left\"><code>frontend<\/code><\/td>\n<\/tr>\n<tr>\n<td align=\"left\"><strong>Build Command<\/strong><\/td>\n<td align=\"left\"><code>npm install &amp;&amp; npm run build<\/code><\/td>\n<\/tr>\n<tr>\n<td align=\"left\"><strong>Publish Directory<\/strong><\/td>\n<td align=\"left\"><code>dist<\/code><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<ol start=\"3\">\n<li><strong>Environment<\/strong>(\ud658\uacbd\ubcc0\uc218) \ud0ed\uc5d0\uc11c \uc544\ub798 \uac12\uc744 \ucd94\uac00\ud55c\ub2e4.<\/li>\n<\/ol>\n<table>\n<thead>\n<tr>\n<th align=\"left\">Key<\/th>\n<th align=\"left\">Value<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td align=\"left\"><code>VITE_TMDB_API_KEY<\/code><\/td>\n<td align=\"left\">TMDB\uc5d0\uc11c \ubc1c\uae09\ubc1b\uc740 API \ud0a4<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<ol start=\"4\">\n<li><strong>Create Static Site<\/strong>\ub97c \ud074\ub9ad\ud55c\ub2e4.<\/li>\n<li>\ubc30\ud3ec \uc644\ub8cc \ud6c4 \uc0dd\uc131\ub41c \uc8fc\uc18c(\uc608: <code>https:\/\/goflex-frontend-xxxx.onrender.com<\/code>)\ub85c \uc811\uc18d\ud558\uba74 \uc644\uc131\ub41c GOFLEX\ub97c \ubcfc \uc218 \uc788\ub2e4.<\/li>\n<\/ol>\n<p><Accordion title=\"\ubaa8\ub178\ub808\ud3ec\uc5d0\uc11c Root Directory\uac00 \ud575\uc2ec\uc778 \uc774\uc720\"><br \/>\nRender\ub294 \uae30\ubcf8\uc801\uc73c\ub85c \uc800\uc7a5\uc18c\uc758 \ucd5c\uc0c1\uc704 \ud3f4\ub354\ub97c \ube4c\ub4dc\ud55c\ub2e4. \ubaa8\ub178\ub808\ud3ec\uc5d0\uc11c\ub294 `frontend\/`\uc640 `backend\/`\uac00 \ubd84\ub9ac\ub418\uc5b4 \uc788\uc73c\ubbc0\ub85c, <strong>Root Directory<\/strong>\uc5d0 \uc5b4\ub5a4 \ud3f4\ub354\ub97c \uc9c0\uc815\ud558\ub290\ub0d0\uc5d0 \ub530\ub77c \uc804\ud600 \ub2e4\ub978 \ud504\ub85c\uc81d\ud2b8\uac00 \ubc30\ud3ec\ub41c\ub2e4.<\/p>\n<table>\n<thead>\n<tr>\n<th align=\"left\">\uc11c\ube44\uc2a4<\/th>\n<th align=\"left\">Root Directory<\/th>\n<th align=\"left\">\ube4c\ub4dc \ub300\uc0c1<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td align=\"left\">\ubc31\uc5d4\ub4dc (Web Service)<\/td>\n<td align=\"left\"><code>backend<\/code><\/td>\n<td align=\"left\">Python \uc11c\ubc84<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">\ud504\ub860\ud2b8\uc5d4\ub4dc (Static Site)<\/td>\n<td align=\"left\"><code>frontend<\/code><\/td>\n<td align=\"left\">React \uc571<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>\uac19\uc740 \uc800\uc7a5\uc18c, \uac19\uc740 \ucee4\ubc0b\uc774\uc9c0\ub9cc \uc2dc\uc791 \ud3f4\ub354\ub9cc \ub2e4\ub974\uac8c \uc124\uc815\ud558\uba74 \uac01\uac01 \uc62c\ubc14\ub978 \ud504\ub85c\uc81d\ud2b8\uac00 \ubc30\ud3ec\ub41c\ub2e4.<br \/>\n<\/Accordion><\/p>\n<h3>9.4. SPA \ub9ac\ub2e4\uc774\ub809\ud2b8 \uc124\uc815<\/h3>\n<p>React Router\ub97c \uc0ac\uc6a9\ud558\ub294 SPA(\uc2f1\uae00 \ud398\uc774\uc9c0 \uc560\ud50c\ub9ac\ucf00\uc774\uc158)\ub294 \ud55c \uac00\uc9c0 \ucd94\uac00 \uc124\uc815\uc774 \ud544\uc694\ud558\ub2e4. <code>frontend\/public\/<\/code> \ud3f4\ub354\uc5d0 <code>_redirects<\/code> \ud30c\uc77c\uc744 \ub9cc\ub4e4\uace0 \uc544\ub798 \ud55c \uc904\uc744 \uc785\ub825\ud55c\ub2e4.<\/p>\n<pre><code class=\"language-text\">\/*    \/index.html   200\n<\/code><\/pre>\n<table>\n<thead>\n<tr>\n<th align=\"left\">\uc124\uba85<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td align=\"left\">\ubaa8\ub4e0 URL \uc694\uccad(<code>\/*<\/code>)\uc744 <code>index.html<\/code>\ub85c \ubcf4\ub0b4\uace0 200 \uc0c1\ud0dc\ucf54\ub4dc\ub97c \ubc18\ud658\ud55c\ub2e4. \uc774 \uc124\uc815\uc774 \uc5c6\uc73c\uba74 <code>\/movie\/550<\/code> \uac19\uc740 URL\uc5d0 \uc9c1\uc811 \uc811\uc18d\ud558\uac70\ub098 \uc0c8\ub85c\uace0\uce68\ud560 \ub54c 404 \uc5d0\ub7ec\uac00 \ubc1c\uc0dd\ud55c\ub2e4. React Router\uac00 URL\uc744 \ucc98\ub9ac\ud558\ub824\uba74 \ud56d\uc0c1 <code>index.html<\/code>\uc774 \uba3c\uc800 \ub85c\ub4dc\ub418\uc5b4\uc57c \ud558\uae30 \ub54c\ubb38\uc774\ub2e4.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<hr>\n<h2>10. \uc790\uc8fc \ubc1c\uc0dd\ud558\ub294 \uc5d0\ub7ec<\/h2>\n<p><Accordion title=\"\uc5d0\ub7ec: TMDB API\uc5d0\uc11c 401 \uc5d0\ub7ec\"><br \/>\n`.env` \ud30c\uc77c\uc758 API \ud0a4\ub97c \ud655\uc778\ud55c\ub2e4. \ub85c\uceec \uac1c\ubc1c \uc2dc\uc5d0\ub294 `Ctrl+C` \ud6c4 `npm run dev`\ub85c \uc7ac\uc2dc\uc791\ud574\uc57c \ubc18\uc601\ub41c\ub2e4. Render \ubc30\ud3ec \uc2dc\uc5d0\ub294 Environment \ud0ed\uc5d0\uc11c `VITE_TMDB_API_KEY`\ub97c \uc815\ud655\ud788 \uc785\ub825\ud588\ub294\uc9c0 \ud655\uc778\ud55c\ub2e4.<br \/>\n<\/Accordion><Accordion title=\"\uc5d0\ub7ec: gunicorn command not found\"><br \/>\nRender\uac00 Python \ud504\ub85c\uc81d\ud2b8\ub97c Django\ub85c \uc778\uc2dd\ud558\uc5ec `gunicorn`\uc744 \uc2e4\ud589\ud558\ub824 \ud560 \ub54c \ubc1c\uc0dd\ud55c\ub2e4. Settings \u2192 Start Command\ub97c `uvicorn main:app &#8211;host 0.0.0.0 &#8211;port 10000`\uc73c\ub85c <strong>\uc9c1\uc811 \uc785\ub825<\/strong>\ud558\uace0 \uc800\uc7a5\ud55c \ub4a4 Manual Deploy\ub97c \ud074\ub9ad\ud55c\ub2e4.<br \/>\n<\/Accordion><Accordion title=\"\uc5d0\ub7ec: \uc608\uace0\ud3b8\uc774 \ud45c\uc2dc\ub418\uc9c0 \uc54a\ub294\ub2e4\"><br \/>\n\ubaa8\ub4e0 \uc601\ud654\uc5d0 \uc608\uace0\ud3b8\uc774 \uc788\ub294 \uac83\uc740 \uc544\ub2c8\ub2e4. `trailer`\uac00 `undefined`\uc774\uba74 &#8220;\uc608\uace0\ud3b8 \ubcf4\uae30&#8221; \ubc84\ud2bc \uc790\uccb4\uac00 \ud45c\uc2dc\ub418\uc9c0 \uc54a\ub294\ub2e4. \uc778\uae30 \uc601\ud654 \ub300\ubd80\ubd84\uc740 \uc608\uace0\ud3b8\uc774 \uc788\uc73c\ubbc0\ub85c \uc0c1\uc704 \ub7ad\ud0b9 \uc601\ud654\ub85c \ud14c\uc2a4\ud2b8\ud55c\ub2e4.<br \/>\n<\/Accordion><Accordion title=\"\uc5d0\ub7ec: \uc0c8\ub85c\uace0\uce68\ud558\uba74 404 \ud398\uc774\uc9c0\uac00 \ub098\uc628\ub2e4\"><br \/>\n`frontend\/public\/_redirects` \ud30c\uc77c\uc774 \uc5c6\uac70\ub098 \ub0b4\uc6a9\uc774 \uc798\ubabb\ub41c \uacbd\uc6b0\uc774\ub2e4. 9.4 \ud56d\ubaa9\uc758 SPA \ub9ac\ub2e4\uc774\ub809\ud2b8 \uc124\uc815\uc744 \ud655\uc778\ud55c\ub2e4.<br \/>\n<\/Accordion><\/p>\n<hr>\n<h2>11. \uc804\uccb4 \ucf54\ub4dc<\/h2>\n<h4>Chatbot.jsx<\/h4>\n<pre><code class=\"language-jsx\">import { useState, useRef, useEffect } from &quot;react&quot;;\nimport { FontAwesomeIcon } from &quot;@fortawesome\/react-fontawesome&quot;;\nimport { faComment, faXmark } from &quot;@fortawesome\/free-solid-svg-icons&quot;;\nimport { Button } from &quot;.\/UI.jsx&quot;;\n\nconst BACKEND = &quot;https:\/\/cbot-rfcl.onrender.com\/chat&quot;;\n\nexport default function Chatbot() {\n  const [open, setOpen] = useState(false);\n  const [messages, setMessages] = useState([\n    { role: &quot;bot&quot;, text: &quot;\uc548\ub155\ud558\uc138\uc694! \uc601\ud654\uc5d0 \ub300\ud574 \ubb34\uc5c7\uc774\ub4e0 \ubb3c\uc5b4\ubcf4\uc138\uc694&quot; },\n  ]);\n  const [input, setInput] = useState(&quot;&quot;);\n  const [loading, setLoading] = useState(false);\n  const bottomRef = useRef(null);\n\n  useEffect(() =&gt; {\n    if (open) bottomRef.current?.scrollIntoView({ behavior: &quot;smooth&quot; });\n  }, [messages, open]);\n\n  async function sendMessage() {\n    const text = input.trim();\n    if (!text || loading) return;\n\n    setMessages((prev) =&gt; [...prev, { role: &quot;user&quot;, text }]);\n    setInput(&quot;&quot;);\n    setLoading(true);\n\n    try {\n      const res = await fetch(BACKEND, {\n        method: &quot;POST&quot;,\n        headers: { &quot;Content-Type&quot;: &quot;application\/json&quot; },\n        body: JSON.stringify({ text }),\n      });\n      const data = await res.json();\n      setMessages((prev) =&gt; [...prev, { role: &quot;bot&quot;, text: data.reply }]);\n    } catch {\n      setMessages((prev) =&gt; [\n        ...prev,\n        { role: &quot;bot&quot;, text: &quot;\uc11c\ubc84 \uc5f0\uacb0\uc5d0 \uc2e4\ud328\ud588\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.&quot; },\n      ]);\n    } finally {\n      setLoading(false);\n    }\n  }\n\n  function handleKeyDown(e) {\n    if (e.key === &quot;Enter&quot; &amp;&amp; !e.shiftKey) {\n      e.preventDefault();\n      sendMessage();\n    }\n  }\n\n  return (\n    &lt;&gt;\n      &lt;Button\n        variant=&quot;primary&quot;\n        onClick={() =&gt; setOpen((v) =&gt; !v)}\n        className=&quot;fixed bottom-8 right-8 w-14 h-14 rounded-full text-2xl shadow-lg z-50 flex items-center justify-center&quot;\n        title=&quot;AI \ucc57\ubd07&quot;\n      &gt;\n        {open ? &lt;FontAwesomeIcon icon={faXmark} \/&gt; : &lt;FontAwesomeIcon icon={faComment} \/&gt;}\n      &lt;\/Button&gt;\n\n      {open &amp;&amp; (\n        &lt;div className=&quot;fixed bottom-24 right-8 w-85 max-h-1\/2 bg-gray-950 border border-gray-600 rounded-xl flex flex-col shadow-2xl z-40&quot;&gt;\n          &lt;div className=&quot;py-3 px-4 bg-yellow-500 rounded-t-xl font-bold text-sm text-gray-700&quot;&gt;Goflix AI \ucc57\ubd07&lt;\/div&gt;\n\n          &lt;div className=&quot;flex-1 overflow-y-auto p-3 flex flex-col gap-2 min-h-50 max-h-85&quot;&gt;\n            {messages.map((m, i) =&gt; (\n              &lt;div\n                key={i}\n                className={`py-2 px-3 max-w-4\/5 text-sm leading-snug whitespace-pre-wrap break-words text-gray-700\n                  ${m.role === &quot;user&quot; ? &quot;self-end bg-yellow-500 rounded-tl-xl rounded-tr-xl rounded-bl-xl&quot; : &quot;self-start bg-gray-100 rounded-tl-xl rounded-tr-xl rounded-br-xl&quot;}`}\n              &gt;\n                {m.text}\n              &lt;\/div&gt;\n            ))}\n            {loading &amp;&amp; &lt;div className=&quot;self-start bg-gray-100 text-gray-400 rounded-tl-xl rounded-tr-xl rounded-br-xl py-2 px-3 text-sm&quot;&gt;...&lt;\/div&gt;}\n            &lt;div ref={bottomRef} \/&gt;\n          &lt;\/div&gt;\n\n          &lt;div className=&quot;flex border-t border-[#333] p-2 gap-2&quot;&gt;\n            &lt;input\n              value={input}\n              onChange={(e) =&gt; setInput(e.target.value)}\n              onKeyDown={handleKeyDown}\n              placeholder=&quot;\uba54\uc2dc\uc9c0 \uc785\ub825...&quot;\n              disabled={loading}\n              className=&quot;flex-1 bg-[#1e1e1e] border border-[#444] rounded-lg py-2 px-3 text-white caret-white text-sm outline-none placeholder:text-zinc-300&quot;\n            \/&gt;\n            &lt;Button\n              variant=&quot;primary&quot;\n              onClick={sendMessage}\n              disabled={loading || !input.trim()}\n              className=&quot;rounded-lg py-2 px-3 text-sm disabled:opacity-50 disabled:cursor-not-allowed&quot;\n            &gt;\n              \uc804\uc1a1\n            &lt;\/Button&gt;\n          &lt;\/div&gt;\n        &lt;\/div&gt;\n      )}\n    &lt;\/&gt;\n  );\n}\n<\/code><\/pre>\n<h4>App.jsx (\ucc57\ubd07 \ucd94\uac00)<\/h4>\n<pre><code class=\"language-jsx\">import { useState, useEffect } from &quot;react&quot;;\nimport { Outlet } from &quot;react-router&quot;;\nimport { Header } from &quot;.\/components\/Header.jsx&quot;;\nimport { Footer } from &quot;.\/components\/Footer.jsx&quot;;\nimport api from &quot;.\/api\/axios&quot;;\nimport Chatbot from &quot;.\/components\/Chatbot.jsx&quot;;\n\nexport default function App() {\n  const [now, setNow] = useState(null);\n  const [popular, setPopular] = useState(null);\n  const [topRated, setTopRated] = useState(null);\n\n  async function loadMovie() {\n    const [res1, res2, res3] = await Promise.all([\n      api.get(&quot;movie\/now_playing&quot;),\n      api.get(&quot;movie\/popular&quot;),\n      api.get(&quot;movie\/top_rated&quot;),\n    ]);\n    setNow(res1.data.results.filter((m) =&gt; m.poster_path));\n    setPopular(res2.data.results.filter((m) =&gt; m.poster_path));\n    setTopRated(res3.data.results.filter((m) =&gt; m.poster_path));\n  }\n\n  useEffect(() =&gt; {\n    loadMovie();\n  }, []);\n\n  const loading = now === null || popular === null || topRated === null;\n  const ctx = {\n    now: now || [],\n    popular: popular || [],\n    topRated: topRated || [],\n    loading,\n  };\n\n  return (\n    &lt;&gt;\n      &lt;Header \/&gt;\n      &lt;Outlet context={ctx} \/&gt;\n      &lt;Footer \/&gt;\n      &lt;Chatbot \/&gt;\n    &lt;\/&gt;\n  );\n}\n<\/code><\/pre>\n","protected":false},"excerpt":{"rendered":"<p>Tip: Gemini CLI\ub85c \uad6c\ud604\ud558\uae30 \u2014 AI \ucc57\ubd07 \ucef4\ud3ec\ub10c\ud2b8 \ud504\ub86c\ud504\ud2b8: gemini &quot;src\/components\/Chatbot.jsx\ub97c \uc791\uc131\ud574\uc918. \ud654\uba74 \uc6b0\ud558\ub2e8\uc5d0 \uace0\uc815\ub41c \ud50c\ub85c\ud305 \ubc84\ud2bc\uc744 \ud074\ub9ad\ud558\uba74 \ucc44\ud305\ucc3d\uc774 \uc5f4\ub9ac\ub294 \ucef4\ud3ec\ub10c\ud2b8\uc57c. useState\ub85c \uc5f4\ub9bc\/\ub2eb\ud798, \uba54\uc2dc\uc9c0 \ubaa9\ub85d, \uc785\ub825\uac12, \ub85c\ub529 \uc0c1\ud0dc\ub97c \uad00\ub9ac\ud558\uace0, useRef\ub85c \uc0c8 \uba54\uc2dc\uc9c0\uac00 \uc624\uba74 \uc790\ub3d9 \uc2a4\ud06c\ub864\ud574\uc918. \uba54\uc2dc\uc9c0 \uc804\uc1a1 \uc2dc [\ubc31\uc5d4\ub4dc URL]\/chat \uc5d4\ub4dc\ud3ec\uc778\ud2b8\uc5d0 fetch\ub85c POST \uc694\uccad\uc744 \ubcf4\ub0b4\uc918. Tailwind CSS\ub97c \uc0ac\uc6a9\ud574\uc918.&quot; \uc0ac\uc6a9 \uac00\uc774\ub4dc: [\ubc31\uc5d4\ub4dc URL]\uc744 \ubc30\ud3ec\ub41c \uc11c\ubc84 \uc8fc\uc18c\ub85c &#8230; <a title=\"AI \ucc57\ubd07 \uc5f0\ub3d9\ud558\uae30\" class=\"read-more\" href=\"https:\/\/coalacoding.com\/chatbot\/\" aria-label=\"AI \ucc57\ubd07 \uc5f0\ub3d9\ud558\uae30\uc5d0 \ub300\ud574 \ub354 \uc790\uc138\ud788 \uc54c\uc544\ubcf4\uc138\uc694\">\ub354 \uc77d\uae30<\/a><\/p>\n","protected":false},"author":0,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[10],"tags":[],"class_list":["post-84","post","type-post","status-publish","format-standard","hentry","category-react-movie-app"],"_links":{"self":[{"href":"https:\/\/coalacoding.com\/wp-json\/wp\/v2\/posts\/84","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/coalacoding.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/coalacoding.com\/wp-json\/wp\/v2\/types\/post"}],"replies":[{"embeddable":true,"href":"https:\/\/coalacoding.com\/wp-json\/wp\/v2\/comments?post=84"}],"version-history":[{"count":1,"href":"https:\/\/coalacoding.com\/wp-json\/wp\/v2\/posts\/84\/revisions"}],"predecessor-version":[{"id":2253,"href":"https:\/\/coalacoding.com\/wp-json\/wp\/v2\/posts\/84\/revisions\/2253"}],"wp:attachment":[{"href":"https:\/\/coalacoding.com\/wp-json\/wp\/v2\/media?parent=84"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/coalacoding.com\/wp-json\/wp\/v2\/categories?post=84"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/coalacoding.com\/wp-json\/wp\/v2\/tags?post=84"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}