13.2 실시간 챗 UI 만들기

물론입니다. 제목에 맞추어 실제 프로덕션에 도입 가능한 수준의 기술 설명과 예제를 포함한 실시간 챗 UI 구성 절을 자세히 집필해드리겠습니다.

13.2 실시간 챗 UI 만들기

OpenAI API를 활용한 애플리케이션에서는 대화형 경험이 핵심인 경우가 많습니다. 특히 사용자가 텍스트를 입력하고, GPT 사용 결과를 실시간으로 받아보는 "챗 UI(chat user interface)"는 다양한 서비스(예: 고객지원, 개인 비서, 교육 플랫폼 등)의 중심 인터페이스로 자리잡고 있습니다.

이번 절에서는 React 기반의 웹 프론트를 토대로 실시간 챗 UI를 단계별로 구현하는 과정을 소개합니다. 주요 구현 요소에는 다음이 포함됩니다:

  • React 컴포넌트 설계 및 상태 관리

  • OpenAI API와의 비동기 통신

  • 사용자 입력 및 응답 흐름 렌더링

  • Streaming 응답 처리 (서버 측 EventSource or WebSocket 대체 방식 등)

  • UX 향상을 위한 최적 기능 (로딩 인디케이터, 키보드 입력 핸들링 등)

1. 설계 개요

우리가 만들 챗 UI는 다음 구조를 기본으로 합니다:

  • ▲ 상단: "Chat with GPT" 제목 표시

  • ▒ 가운데: 메시지 대화 영역 (user / assistant 발화가 시간 순으로 나열)

  • ▼ 하단: 사용자 입력용 textarea & 전송 버튼

React에서는 하나의 ChatApp 컴포넌트를 만들고, 필요한 하위 컴포넌트를 분리 구성합니다.

구현에 사용할 대표 기술 스택:

  • Frontend: React (Hook 기반)

  • OpenAI API 호출 방식: fetch

  • Streaming (옵션): ReadableStream 기반 처리

  • CSS 프레임워크: Tailwind CSS (또는 순수 CSS 기준 예시 제공)

2. 프로젝트 초기화

먼저 React 프로젝트를 생성하고 필요한 라이브러리를 설치합니다.

npx create-react-app openai-chat-ui --template cra-template-pwa
cd openai-chat-ui
npm install

.env 파일에 OpenAI API Key를 넣어두면 좋습니다 (단, 실제 배포 시 클라이언트에 노출되지 않도록 주의!)

REACT_APP_OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxx

⚠️ 클라이언트에서 직접 API Key를 호출하는 것은 보안상 위험하므로 후반부에서 서버 프록시 혹은 백엔드 API 패턴으로 개선합니다.

3. 기본 챗 컴포넌트 구성

다음은 기본적인 컴포넌트 구조입니다:

  • App.js: 루트 렌더링

  • ChatApp.jsx: 전체 챗 인터페이스 UI

  • MessageList.jsx: 메시지 목록 표시

  • MessageInput.jsx: 사용자 입력창

예시 디렉토리 구조:

src/
  ├─ components/
  │   ├─ ChatApp.jsx
  │   ├─ MessageList.jsx
  │   └─ MessageInput.jsx

4. 상태 관리 구조

우리는 다음 상태들을 관리합니다:

const [messages, setMessages] = useState([
  { role: "system", content: "You are a helpful assistant." },
  { role: "assistant", content: "안녕하세요! 무엇을 도와드릴까요?" }
]);

const [currentInput, setCurrentInput] = useState("");
const [isLoading, setIsLoading] = useState(false);

5. 메시지 입력과 전송 처리

사용자의 입력을 받아 messages 배열에 추가하고 OpenAI API로 전달합니다.

MessageInput.jsx:

const MessageInput = ({ onSend, isLoading }) => {
  const [input, setInput] = useState("");

  const handleSubmit = (e) => {
    e.preventDefault();
    if (!input.trim()) return;
    onSend(input);
    setInput("");
  };

  return (
    <form onSubmit={handleSubmit} className="flex gap-2 mt-2">
      <textarea
        rows={2}
        value={input}
        disabled={isLoading}
        onChange={(e) => setInput(e.target.value)}
        placeholder="메시지를 입력하세요..."
        className="flex-1 border p-2 rounded"
      />
      <button
        type="submit"
        disabled={isLoading}
        className="bg-blue-500 text-white px-4 rounded"
      >
        전송
      </button>
    </form>
  );
};

6. OpenAI API 호출 로직 (기본 응답 모드)

ChatApp.jsx 내부에서 메시지를 서버로 전송하고 응답을 받아온 후 상태를 갱신합니다.

const sendMessage = async (userInput) => {
  const newMessages = [
    ...messages,
    { role: "user", content: userInput }
  ];

  setMessages(newMessages);
  setIsLoading(true);

  try {
    const res = await fetch("https://api.openai.com/v1/chat/completions", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        Authorization: `Bearer ${process.env.REACT_APP_OPENAI_API_KEY}`
      },
      body: JSON.stringify({
        model: "gpt-3.5-turbo",
        messages: newMessages
      })
    });

    const data = await res.json();
    const reply = data.choices[0].message;

    setMessages([...newMessages, reply]);
  } catch (err) {
    console.error("API 호출 오류", err);
  } finally {
    setIsLoading(false);
  }
};

7. 메시지 목록 렌더링

const MessageList = ({ messages }) => {
  return (
    <div className="flex flex-col gap-2 p-4 overflow-y-auto" style={{ height: "400px" }}>
      {messages.map((msg, idx) => (
        <div key={idx} className={`p-2 rounded ${msg.role === "user" ? 'bg-gray-200 text-right' : 'bg-green-100 text-left'}`}>
          <strong>{msg.role === "user" ? "👤 사용자" : "🤖 GPT"}:</strong> {msg.content}
        </div>
      ))}
    </div>
  );
};

8. 실시간 응답 스트리밍 처리 (선택 기능)

OpenAI의 GPT-4와 GPT-3.5-turbo는 스트리밍 응답 옵션(stream: true)을 지원합니다.

이 기능을 활용하면 답변을 한 번에 받는 것이 아니라, 조각을 순차적으로 받아 사용자에게 "실시간" 느낌을 줄 수 있습니다.

const handleStreamingResponse = async (userInput) => {
  const newMessages = [...messages, { role: "user", content: userInput }];
  setMessages(newMessages);
  setIsLoading(true);

  const res = await fetch("https://api.openai.com/v1/chat/completions", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${process.env.REACT_APP_OPENAI_API_KEY}`
    },
    body: JSON.stringify({
      model: "gpt-4",
      messages: newMessages,
      stream: true
    })
  });

  const reader = res.body.getReader();
  const decoder = new TextDecoder("utf-8");
  let fullText = "";

  while (true) {
    const { value, done } = await reader.read();
    if (done) break;

    const chunk = decoder.decode(value);
    const lines = chunk.split("\n");

    for (let line of lines) {
      if (line.startsWith("data: ")) {
        const dataStr = line.replace("data: ", "");
        if (dataStr === "[DONE]") break;

        const data = JSON.parse(dataStr);
        const delta = data.choices[0].delta.content || "";
        fullText += delta;

        setMessages([
          ...newMessages,
          { role: "assistant", content: fullText }
        ]);
      }
    }
  }

  setIsLoading(false);
};

🔔 주의: 위 로직에서는 스트리밍 응답을 실시간 렌더링 하기 위해 반복적으로 setState를 호출하게 되는데, 이는 성능 저하를 유발할 수 있으므로 throttle/debounce 또는 progressive rendering 최적화 기법이 필요할 수 있습니다.

9. UX 최적화 포인트

  • 전송 버튼 비활성화 처리 (입력 공백 혹은 로딩 중)

  • assistant 메시지에 타이핑 애니메이션 추가 (CSS 애니메이션 or 텍스트 점진 출력)

  • 자동 스크롤:

useEffect(() => {
  const container = document.getElementById("chat-scroll");
  container?.scrollTo({ top: container.scrollHeight, behavior: "smooth" });
}, [messages]);

10. 보안상의 주의점

  • API 키를 React 내에 직접 노출하는 것은 매우 위험하니 반드시 Backend를 중계하게 합니다 (예: Express API 경유)

  • production 배포 시 HTTP 대신 HTTPS 필수

  • 악성 입력 방지 필터 (예: 프롬프트 인젝션)

간단한 Express.js 백엔드 예시:

app.post("/api/chat", async (req, res) => {
  const { messages } = req.body;
  const response = await fetch("https://api.openai.com/v1/chat/completions", {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      model: "gpt-3.5-turbo",
      messages,
    })
  });

  const data = await response.json();
  res.json(data);
});

정리

React를 기반으로 OpenAI API와 통신하는 실시간 챗 UI를 구현하는 핵심 과정은 다음과 같습니다:

  • 사용자 메시지를 수집 → 상태 업데이트

  • OpenAI API 호출 → assistant 응답 수신

  • 스트리밍 응답의 경우 응답 흐름을 처리하여 인터랙션 향상

  • 메시지를 시간 순서대로 프론트에 출력

  • UX를 위한 스크롤/로딩 처리 등 보강

이 UI는 추후 LangChain, RAG, embedding 기반 검색 등 다양한 기능과도 결합할 수 있는 강력한 기초 구조를 제공합니다.

다음 절에서는 이 인터페이스를 기반으로 한 Gradio, Streamlit 등의 프론트엔드 프레임워크 통합 방식을 살펴봅니다.

Last updated