Skip to content

챗봇 POC

  1. 챗봇 백앤드와 프런트앤드를 구현

  1. express 프로젝트 생성
    Terminal window
    mkdir chatbot-server
    cd chatbot-server
  2. 라이브러리 설치
    1. express
      Terminal window
      pnpm add express cors body-parser
    2. openai
      Terminal window
      pnpm add openai
  3. package.json 파일 수정
    package.json
    {
    "type": "module",
    "dependencies": {
    "body-parser": "^2.2.0",
    "cors": "^2.8.5",
    "express": "^5.1.0",
    "openai": "^6.6.0"
    }
    }
  4. app.js 파일 생성
    Terminal window
    touch app.js
    app.js
    import express from "express";
    import bodyParser from "body-parser";
    import cors from "cors";
    const app = express();
    const port = 2000;
    app.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}`);
    });
  5. 모듈 작성
    1. 디렉토리 생성
      Terminal window
      mkdir -p src/modules/openai
      cd src/modules/openai
    2. 모듈 작성
      Terminal window
      touch index.js
      src/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,
      });
      };
  6. 실행
    Terminal window
    node app.js
  1. 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, done
    dependencies:
    + 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 p
    erformance as no swc plugins are used. More information at https://vite.dev/roll
    down
    ROLLDOWN-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.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;
}
}
App.tsx
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;
index.css
html,
body,
#root {
height: 100%;
font-size: 14px;
font-family: arial, sans-serif;
margin: 0;
background-color: #242424;
}

image image