챗봇 POC
-
챗봇 백앤드와 프런트앤드를 구현
-
express 프로젝트 생성
Terminal window mkdir chatbot-servercd chatbot-server -
라이브러리 설치
- express
Terminal window pnpm add express cors body-parser - openai
Terminal window pnpm add openai
- express
-
package.json 파일 수정
package.json {"type": "module","dependencies": {"body-parser": "^2.2.0","cors": "^2.8.5","express": "^5.1.0","openai": "^6.6.0"}} -
app.js 파일 생성
Terminal window touch app.jsapp.js import express from 'express'import bodyParser from 'body-parser'import cors from 'cors'const app = express()const port = 2000app.use(bodyParser.json())app.use(bodyParser.urlencoded({extended: false}))app.use(cors())app.get('/', (req, res) => {return res.json({data: 'success'})})app.listen(port, () => {console.log(`Example app listening on port ${port}`)}) -
모듈 작성
-
디렉토리 생성
Terminal window mkdir -p src/modules/openaicd src/modules/openai -
모듈 작성
Terminal window touch index.jssrc/modules/openai/index.js import OpenAI from 'openai'const client = new OpenAI({organization: '<>',apiKey: '<>',})const systemMessage = {role: 'system',content:'You are a Askbot. You are supposed to answer the questions asked by the users. Validate the prompts to be a question and it should not in approprite. Give funky responses',}export const getStreamingCompletion = async ({userPrompt}) => {return client.chat.completions.create({model: 'gpt-3.5-turbo',messages: [systemMessage, {role: 'user', content: userPrompt}],stream: true,})}
-
-
실행
Terminal window node app.js
-
vite 프로젝트 생성
Terminal window pnpm create vite$ pnpm create vite|o Project name:| chatbot-client|o Select a framework:| React|o Select a variant:| TypeScript + SWC|o Use rolldown-vite (Experimental)?:| Yes|o Install with pnpm and start now?| Yes|o Scaffolding project in D:\study\chatbot-client...|o Installing dependencies with pnpm...Packages: +153++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++Progress: resolved 192, reused 159, downloaded 0, added 153, donedependencies:+ react 19.2.0+ react-dom 19.2.0+ vite <- rolldown-vite 7.1.14 (7.1.16 is available)devDependencies:+ @eslint/js 9.38.0+ @types/node 24.9.1+ @types/react 19.2.2+ @types/react-dom 19.2.2+ @vitejs/plugin-react-swc 4.1.0+ eslint 9.38.0+ eslint-plugin-react-hooks 5.2.0 (7.0.0 is available)+ eslint-plugin-react-refresh 0.4.24+ globals 16.4.0+ typescript 5.9.3+ typescript-eslint 8.46.2╭ Warning ─────────────────────────────────────────────────────────────────────╮│ ││ Ignored build scripts: @swc/core. ││ Run "pnpm approve-builds" to pick which dependencies should be allowed ││ to run scripts. ││ │╰──────────────────────────────────────────────────────────────────────────────╯Done in 3.4s using pnpm v10.17.1|o Starting dev server...> chatbot-client@0.0.0 dev D:\study\chatbot-client> vite[vite:react-swc] We recommend switching to `@vitejs/plugin-react` for improved performance as no swc plugins are used. More information at https://vite.dev/rolldownROLLDOWN-VITE v7.1.14 ready in 211 ms➜ Local: http://localhost:5173/➜ Network: use --host to expose➜ press h + enter to show help
프런트앤드에서 변경/추가된 파일 목록
Section titled “프런트앤드에서 변경/추가된 파일 목록”Directorysrc
Directoryassets
- lens.png
- loading.gif
- App.css
- App.tsx
- index.css
.app { height: 100%; display: flex; flex-direction: column; align-items: center; justify-content: center; background-color: rgba(0, 0, 0, 0.1);}
.app-container {width: 1000px;max-width: 100%;padding: 0 20px;text-align: center;}
.spotlight\_\_wrapper {border-radius: 12px;border: 1px solid #dfe1e5;margin: auto;max-width: 600px;background-color: #fff;}
.spotlight**wrapper:hover,.spotlight**wrapper:focus {background-color: #fff;box-shadow: 0 1px 6px rgb(32 33 36 / 28%);border-color: rgba(223, 225, 229, 0);}
.spotlight\_\_input {display: block;height: 56px;width: 80%;border: 0;border-radius: 12px;outline: none;font-size: 1.2rem;color: #000;background-position: left 17px center;background-repeat: no-repeat;background-color: #fff;background-size: 3.5%;padding-left: 60px;}
.spotlight\_\_input::placeholder {line-height: 1.5em;}
.spotlight\_\_answer {min-height: 115px;line-height: 1.5em;letter-spacing: 0.1px;padding: 10px 30px;display: flex;align-items: center;justify-content: center;}
.spotlight\_\_answer p::after {content: "";width: 2px;height: 14px;position: relative;top: 2px;left: 2px;background: black;display: inline-block;animation: cursor-blink 1s steps(2) infinite;}
@keyframes cursor-blink {0% {opacity: 0;}}import React, { useState, useEffect } from "react";import "./App.css";import lens from "./assets/lens.png";import loadingGif from "./assets/loading.gif";
function App() { const [prompt, updatePrompt] = useState<string>(); const [loading, setLoading] = useState<boolean>(false); const [answer, setAnswer] = useState<string>("");
useEffect(() => { if (prompt != null && prompt.trim() === "") { setAnswer(""); } }, [prompt]);
const sendPrompt = async (event: React.KeyboardEvent) => { if (event.key !== "Enter") { return; }
try { setLoading(true); setAnswer(""); const response = await fetch("http://localhost:2000/aiCompletion", { method: "post", headers: { Accept: "application/json, text/plain, */*", // indicates which files we are able to understand "Content-Type": "application/json", // indicates what the server actually sent }, body: JSON.stringify({ userPrompt: prompt }), // server is expecting JSON }); if (!response.ok || !response.body) { throw response.statusText; } const reader = response.body.getReader(); const decoder = new TextDecoder(); const loopRunner = true;
while (loopRunner) { const { value, done } = await reader.read(); if (done) { break; } const decodedChunk = decoder.decode(value, { stream: true }); setAnswer((answer) => answer + decodedChunk); } } catch (err) { console.error(err, "err"); } finally { setLoading(false); } };
return ( <div className="app"> <div className="app-container"> <div className="spotlight__wrapper"> <input type="text" className="spotlight__input" placeholder="Ask me anything..." disabled={loading} style={{ backgroundImage: loading ? `url(${loadingGif})` : `url(${lens})`, }} onChange={(e) => updatePrompt(e.target.value)} onKeyDown={(e) => sendPrompt(e)} /> <div className="spotlight__answer">{answer && <p>{answer}</p>}</div> </div> </div> </div> );}
export default App;html,body,#root { height: 100%; font-size: 14px; font-family: arial, sans-serif; margin: 0; background-color: #242424;}
