
AI Talk / 챗봇 서비스
운영 중2026.08 ~ 2026.09 · 개인 프로젝트
사용자와 1:1로 대화하며 AI 답변이 실시간으로 스트리밍 출력되는 챗봇 웹앱
왜 만들었나
AI를 많이 써보다 보니 AI특유의 '환각'과 오류 정보를 어떻게 하면 없애는 걸까 호기심에 토이프로젝트로 API를 연동해 간단한 1:1 대화, 나만의 일기를 만들어 봤습니다.
주요 기능
- 채팅: 메시지를 보내면 답변이 실시간으로 조금씩 채워짐
- 이전 대화 기억: 앞에서 나눈 내용을 이어서 대화
- 답변 멈추기: 답변이 나오는 중에 전송 버튼이 중지 버튼으로 전환
- 새 대화: 헤더 버튼으로 대화를 처음부터 다시 시작
- 추천 질문: 대화 시작 전 칩을 누르면 바로 질문 전송
- 답변 서식: 굵은 글씨, 목록, 코드블록, 표 등 마크다운 렌더링
- 오늘의 일기 모드: 일기를 쓰면 공감과 위로, 짧은 조언을 답으로 반환
- 일기 저장: 일기와 답변을 날짜별로 저장, '지난 일기' 목록에서 다시 조회
- 대화 유지: 새로고침해도 채팅 내역 유지
- 오류 재시도: 답변 실패 시 같은 질문 재전송 버튼 제공
- 로딩 문구: 답변이 길어지면 짧은 개발자 개그 문구 노출
- 미니게임: 헤더 버튼으로 지렁이 게임 실행 (화면 크기에 따라 옆 패널/전체 화면)
- 반응형 레이아웃: 화면 크기에 맞춰 UI 배치 변경
트러블 슈팅
구버전 Gemini 모델 사용 불가
문제:gemini-2.5-flash 모델로 요청하면 신규 사용자에게 404 에러가 발생하던 문제.
해결 결과:gemini-3.6-flash로 모델명을 교체해 해결.
배운 점:빠르게 바뀌는 AI API는 모델 버전을 하드코딩하기보다, 배포 전에 최신 지원 모델인지 항상 확인해야 한다는 걸 배움.
API 키 포맷 변경으로 HTTP 리퍼러 제한 불가
문제:새로 발급받은 API 키가 신규 포맷(AQ...)이라 기존처럼 HTTP 리퍼러 기반으로 키 사용처를 제한할 수 없었음.
해결 결과:결제수단을 연결하지 않은 무료 등급으로 유지하고 키는 환경변수로만 관리해 노출 리스크를 낮춤.
배운 점:리퍼러 제한이 막히면 요금 상한 자체를 낮게 유지하는 것도 실질적인 방어책이 될 수 있다는 걸 배움.
한글 입력 시 메시지 이중 전송
문제:한글 입력기(IME) 조합 확정용 Enter가 전송까지 발생시키던 문제.
해결 결과:isComposing 체크를 추가해 막음.
배운 점:한글 입력기(IME) 조합 중 이벤트 처리는 영어 입력과 다르게 별도로 신경 써야 한다는 걸 배움.
스트리밍 중단 시 크래시
문제:답변 생성 중 '중지'나 '새 대화'를 누르면 빈 배열에 접근해 에러가 나던 문제.
해결 결과:루프 중단 플래그와 배열 길이 가드를 추가해 해결.
배운 점:비동기 스트리밍 중간에 사용자가 상태를 바꿀 수 있는 액션(중지, 새 대화)이 있다면, 그 타이밍에 대한 가드를 항상 고려해야 한다는 걸 배움.
무료 API 등급 요청 횟수 제한(429)
문제:테스트 중 하루 요청 횟수 제한에 걸려 429 에러가 발생.
해결 결과:429/401/404 등 상태 코드별로 사용자에게 다른 안내 메시지를 보여주도록 에러 처리를 분기함.
배운 점:무료 등급 API는 요청 제한에 걸리는 상황을 예외가 아니라 정상적으로 벌어지는 케이스로 놓고 UX를 설계해야 한다는 걸 배움.
배포 후 첫 응답 지연 체감
문제:배포 환경에서 모델의 내부 thinking 단계 때문에 첫 응답이 오래 걸리는 것처럼 느껴짐.
해결 결과:thinkingBudget을 0으로 설정해 불필요한 thinking 단계를 줄이고 첫 응답 속도를 개선.
배운 점:체감 성능 이슈가 항상 네트워크 문제는 아니고, 모델 자체의 응답 생성 방식일 수도 있다는 걸 배움.
로딩 문구 로테이션 함수의 TypeScript 타입 에러
문제:`let next = prev`(`string | null`)로 시작해 while문 안에서 `string`으로 재할당해도, TypeScript가 루프 이후에도 `string | null`로 좁혀주지 않아 TS2322 에러가 발생.
해결 결과:`next`를 처음부터 항상 string인 값으로 초기화하도록 수정해 해결.
배운 점:타입 좁히기는 조건/루프 흐름을 완벽히 추적하지 못할 때가 있어서, 초기값 자체를 보장된 타입으로 주는 게 더 안전하다는 걸 배움.
React Compiler ESLint 규칙(set-state-in-effect) 위반
문제:로딩 문구 컴포넌트의 useEffect에서 조건이 false일 때 setState를 effect 본문 최상단에서 동기적으로 호출해 react-hooks/set-state-in-effect 에러가 발생 (에디터에는 타입 에러처럼 표시됨).
해결 결과:effect 안에서 상태를 리셋하는 대신, 렌더링 시점에 조건에 따라 아무것도 표시하지 않도록 로직을 바꿔 동기 setState 호출 자체를 없앰.
배운 점:React Compiler 환경에서는 effect 안 동기 setState를 렌더링 로직으로 옮겨 해결하는 패턴이 반복적으로 유효하다는 걸 배움.
느낀 점
짧은 기간 안에 실제 서비스처럼 동작하는 챗봇을 만들어보면서, 스트리밍 응답이나 한글 입력 같은 디테일이 생각보다 까다롭다는 걸 알게 됐습니다. AI API를 붙이는 것 자체보다, 사용자가 답변 중간에 상태를 바꾸는 예외 상황들을 다 챙기는 게 진짜 어려운 부분이라는 걸 배운 프로젝트였습니다.