리액트 종합 프로젝트 회고 (UNOA) (1등!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!)

심영민·2025년 7월 7일
2

유레카

목록 보기
33/33
post-thumbnail

우선 상장 올려두고 써보겠습니다..
우리 유노아팀원들 모두에게 감사한마음입니다 ㅠㅠ..

요번 프로젝트는 주제도 통신사 요금제 관련 챗봇으로 정해졌고 기간도 미니프로젝트에 비해 2배인만큼 규모가 조금은 있던 프로젝트였다.

근데 AI를 사용할 줄만알지(ㅋㅋ) 직접 API를 가져와 만들라니... 처음으로 꿈에서도 코딩하고 모든팀이 다 완료했는데 우리팀만 프로젝트를 완료못하는거 아닌가도 싶을정도로 걱정이 태산이었던 기억이난다.

하지만 맨땅에 헤딩이 성공적이었다. 우리 프로젝트가 나름 성공적으로 마무리된 이유에 대해서 생각나는건 딱 3가지있다.

1. 팀워크가 좋았다

사실 모든 협업이 그렇듯 포지션이 겹치면 결과는 산으로 가기 마련이다.
우리팀은 적절하게 적극적으로 의견을 제안하시는분들과, 적극적으로 해당의견을 수용하고 반영하시는 분들이 잘 섞인 것 같았다.

또한 자신이 맡은 파트를 모두 다 책임감있고 완벽하게 구현해주셨다.🙇‍♀️🙏

2. 기획이 꼼꼼했다

초반엔 내가 했던 프로젝트중 노션 및 피그마 등에 생각보다 많은 시간을 투자하여 걱정했었다. 개발을 빨리 들어가야할 것만 같았다. 하지만 팀장님과 주경님의 적극적인 추진으로 꼼꼼한게 구조와 디자인을 짠 결과 개발할 때 버벅임이 없었다.

실제로 위와같이 시안을 구체화하니 개발에서는 기능적 어려움만 있지 UI적 고민은 없었다.

3. 배웠다

"배웠다" 라는 생각은 나의 생각이지만 아마 다른 팀원분들도 느끼시지 않을까싶다.

나는 특히나 요번 프로젝트를 하면서 기획의 중요성, 책임감, 협업 능력, 개발지식 등 많이 배우면서 팀원들과 같이 성장했다.

특히 이전에는 API 호출방식, 리덕스 설정 등 기술적인 부분만 신경썼고 디자인은 별로 신경을 안썼었다.

근데 요번 프로젝트에서는 반응형으로 구현하면서 테일윈드를 사용했고, 진짜 1PX 단위로 신경썼던 것 같다 ㅋㅋㅋ 팀원들 덕이 크다. 1PX단위의 꼼꼼함이 좋은 성적을 거둔 것 같다. 아마 앞으로도 이러한 꼼꼼한 개발 태도가 고수될 것 같다는 확신이 들었다. 정말 많이 배웠다.

나는 그럼 기술적으로 뭘 배웠을까

협업에 대해서는 위에서 한참 말했으니 이제 기술적으로 뭘 배웠는지 써보자면 우선 나는 챗봇페이지를 담당했다.

참 초반에 뭘 믿고 챗봇을 내가 하겠다고 한지는 모르겠지만 결론은 성공적이었으니 ^^..

챗봇자체가 처음이었는데 특히 백엔드를 모르는 내가 챗봇의 모든 MERN 스택구조를 내가해야해서 막막했다. 하지만 프론트를 구축하고 SOCKET.IO로 실시간 스트리밍을 구현도 됐고 OPENAI API를 가져와 연동도 성공적이었다. 의외로 OPENAI를 가져오는건 어렵지 않았다. 실시간 스트리밍이 어려웠었음.

챗봇 백엔드

프론트엔드 개발자를 목표로 하지만 종합프로젝트인만큼 백엔드 개발팀원이 없었고, 챗봇이라서 백엔드부분이 꽤 중요했다.

내가 구현하면서 배웠거나 중요한 개념들을 정리해보겠다.

  • Mongoose 스키마 설계: 단순히 대화만 저장하는 것을 넘어, 추천 카드 데이터(recommendedPlans)나 대화 요약본(summary) 같은 확장성 있는 필드를 messageSchema와 conversationSchema에 직접 설계하며, 유연한 데이터 구조의 중요성을 배움.

  • 동적 프롬프트 엔지니어링: AI의 지시서(프롬프트)를 고정된 텍스트가 아닌, generateSystemPrompt 함수를 통해 DB에서 최신 요금제 정보를 실시간으로 가져와 동적으로 생성함. 이를 통해 AI가 항상 최신 정보를 기반으로 답변하도록 구현.

  • 토큰 최적화 기법: AI에게 DB의 모든 정보를 보내는 대신, summarizePlans 함수로 핵심 정보만 요약하여 전달함으로써, API 비용과 응답 속도를 획기적으로 개선하는 최적화 기법을 직접 적용함.

실제로 토큰 관련 라이브러리로 확인해보니 토큰이 최적화되었었고, 강사님과 심사위원님도 빠르다고 했었음 :)

  • Socket.IO 이벤트 핸들링: setupSocketConnection과 handleUserMessage 함수를 통해, 사용자의 접속, 메시지 전송, 연결 종료 등 실시간 이벤트를 안정적으로 처리하고, IP 기반으로 사용자를 식별하는 로직을 구현함.

  • AI 응답 분석 및 데이터 재조회: AI의 자연어 답변에서 includes()나 정규식을 사용해 '요금제 이름'이라는 핵심 의도를 추출하고, 이 이름으로 다시 DB를 조회하여 가장 정확하고 신뢰성 있는 데이터를 프론트엔드에 전달하는, 안정적인 데이터 처리 파이프라인을 구축함.

  • IP 기반 세션 관리: 사용자의 IP와 브라우저 정보를 crypto 모듈로 해싱하여, 로그인 없이도 사용자를 식별할 수 있는 고유하고 안전한 세션 ID를 생성하고, 이를 통해 대화 기록을 관리하는 방식을 배움.

챗봇 프론트엔드 

실시간 스트리밍 기능의 챗봇이고 자연어 처리를 하는만큼 프론트에서 보여지는 부분도 꽤 중요했었다. 특히 반응형하면서 깨지는 부분이 있었는데 최종적으로 해결됐다.

아래도 역시 마찬가지로 내가 구현하면서 배웠거나 중요한 개념들을 정리해보겠다.

  • React 커스텀 훅 설계: 소켓 연결(useSocket), 대화 로직(useChat), UI 제어(useUI) 등 각 기능의 관심사를 명확하게 분리하여, 재사용 가능하고 유지보수가 용이한 훅(Hook) 기반 아키텍처를 직접 설계함.

  • 실시간 스트리밍 상태 관리: AI가 생성하는 텍스트 조각(chunk)을 handleStreamChunk 함수로 실시간으로 받아 messages 상태에 누적시키면서, 사용자에게는 AI가 타이핑하는 것처럼 보이는 자연스러운 UI/UX를 구현함.

  • 조건부 렌더링 및 컴포넌트 분기: message.role 값('user', 'assistant', 'card')에 따라 MessageItem이 일반 말풍선이나 PlanCardGroup 컴포넌트를 선택적으로 렌더링하도록 하여, 하나의 컴포넌트가 다양한 형태의 메시지를 유연하게 처리하도록 함.

  • 마크다운 렌더링 및 자동 교정: react-markdown 라이브러리를 사용해 AI가 보낸 마크다운 텍스트를 풍부한 HTML로 변환함. 더 나아가, AI가 생성하는 미묘한 마크다운 오류(* *텍스트** 등)를 preprocessMessage라는 전처리기 함수를 통해 실시간으로 자동 교정하여, 렌더링 안정성을 크게 높임.

  • 이벤트 기반 통신: socket.emit으로 서버에 요청을 보내고, socket.on으로 서버가 보내는 다양한 이벤트(stream-end, conversation-history 등)를 수신하여 비동기 데이터를 처리하는 실시간 웹 애플리케이션의 핵심 통신 방식을 익힘. 이를통해 최종적으로는 메시지 카드가 보이는 것까지 구현을 했음.

특히나 어려웠던 점은..

1. 예측 불가능한 AI의 응답 제어

가장 큰 난관은 AI의 비일관성과 예측 불가능성을 제어하는 것이었다. 프롬프트에 "요금제 이름은 **로 감싸달라"고 명확히 지시했음에도, AI는 종종 **텍스트**나처럼 미세하게 다른 형식으로 응답을 보내 마크다운 렌더링이 깨지는 현상이 반복됐다. (진짜 후..)

처음에는 프롬프트 규칙을 더 강화하는 방식으로 해결하려 했지만, AI의 모든 변칙적인 실수를 예측하고 막는 것은 불가능에 가까웠다. 그래서 'AI의 실수를 막는 것'에서 'AI의 실수를 받아주고 고쳐주는 것'으로 관점을 전환했다.

프론트엔드 useChat.js 훅에 preprocessMessage라는 '마크다운 자동 교정기' 함수를 도입, AI가 어떤 형태로 응답하더라도 사용자에게는 항상 완벽한 결과물을 보여주는 '방어적 시스템'을 구축했다. 이 경험을 통해 예측 불가능한 외부 API와 상호작용할 때는, 엄격한 규칙보다 유연하게 오류를 처리하는 시스템이 훨씬 더 견고한 해결책이라는 것을 깨달을 수 있었다.

2. 모드 전환 시 발생하는 상태 충돌 문제

'간단 모드'는 정적인 테스트를 통해 최종 결과를 DB에 가져와 추천해주는 로직인데, '간단 모드'의 추천 결과가 다시 '채팅 모드'로 돌아왔을 때 대화창에 그대로 남아있는 버그가 있었다.

이는 두 모드가 하나의 messages 상태를 공유하면서, 상태가 제대로 초기화되지 않아 발생한 문제였다.

이 문제를 해결하기 위해, 처음에는 모드 변경 시 대화 기록 전체를 초기화하는 방식을 생각했다. 하지만 이는 '채팅 모드'의 기록까지 날리는 단점이 있었다.

최종적으로는 ChatbotPage.jsx에서 useEffect로 모드 변경을 감지하고, '채팅 모드'로 돌아올 때만 서버에 대화 기록을 다시 요청하는 loadHistory 함수를 호출하는 방식으로 해결했다. 이를 통해 각 모드의 상태를 분리하면서도 채팅 기록은 안전하게 보존하는 더 나은 사용자 경험을 구현할 수 있었다.

앞으로는..

사실 지금 이제 최종융합프로젝트 진행중인데 이전보다 더 성장한 프로젝트 결과물이 나오면 좋겠다..ㅎㅎ

백엔드팀원분들도 있는 프로젝트니 프론트 단에서 최고의 결과물을 내보겠다!!!!!!

profile
코딩너무어려운대 어떡할과 재학중

0개의 댓글