Skip to content

[WIP] Convert Gemini chatbot to Vercel serverless function structure#2

Merged
redsunjin merged 1 commit intomainfrom
copilot/convert-gemini-chat-to-serverless
Mar 6, 2026
Merged

[WIP] Convert Gemini chatbot to Vercel serverless function structure#2
redsunjin merged 1 commit intomainfrom
copilot/convert-gemini-chat-to-serverless

Conversation

Copy link
Contributor

Copilot AI commented Mar 6, 2026

  • Explore existing repository structure and code
  • Create api/chat.js - Gemini API serverless function with retry logic, CORS, system instruction
  • Create api/send-email.js - EmailJS serverless function with honeypot validation, CORS
  • Modify index.html - Remove all API keys, implement 4-step state machine (inquiry → privacy → contact → send), add rate limiting, honeypot, XSS protection
  • Run code review and security scan
Original prompt

목표

기존 index.html의 Gemini 챗봇을 Vercel 서버리스 함수 구조로 전환하여 API 키를 브라우저에 노출하지 않고 환경변수로 안전하게 관리한다.

배포 도메인: https://flashgta-github-io.vercel.app


파일 구조

flashgta.github.io/
├── index.html          ← 기존 파일 수정 (키값 완전 제거, /api/ 호출로 변경)
├── style.css           ← 기존 파일 유지 (수정 없음)
└── api/
    ├── chat.js         ← Gemini API 호출 서버리스 함수
    └── send-email.js   ← EmailJS 이메일 발송 서버리스 함수

1. api/chat.js — Gemini API 서버리스 함수

Vercel Serverless Function으로 작성한다.

기능

  • POST /api/chat 요청을 받아 Gemini API를 호출하고 응답 반환
  • 환경변수 GEMINI_API_KEY 사용 (process.env.GEMINI_API_KEY)
  • 멀티턴 대화를 위해 contents 배열(대화 히스토리) 전체를 request body로 받아 Gemini에 전달
  • 재시도 로직: 최대 3회, 지수 백오프 (1초→2초→4초), 429/503 에러 대상
  • CORS 헤더 설정: Access-Control-Allow-Origin: https://flashgta-github-io.vercel.app
  • OPTIONS preflight 처리

systemInstruction (기존 유지 + [INQUIRY_COMPLETE] 태그 추가)

당신은 Gov-Tech AI 에이전시 '라이트닝(Lightning)'의 전문 AI 컨설턴트입니다. 사용자(공공기관/대기업 담당자)가 프로젝트 아이디어나 요구사항을 말하면, 이를 '라이트닝'의 핵심 역량(전자정부프레임워크 준수, 72시간 내 프로토타이핑, 보안 규정 준수, 2주 내 런칭)을 바탕으로 분석하여 답변하세요. 답변은 전문적이고 신뢰감 있게 하되, 핵심 기술(AI, 모바일, 웹 익스텐션)을 어떻게 활용할지 구체적으로 제안하세요. 답변은 한국어로 작성하며, 중요한 키워드는 **굵게** 표시하세요. 너무 길지 않게 핵심 위주로 답변하세요. 사용자의 요구사항이 충분히 파악되었다고 판단되면(또는 3~4번의 대화 이후), 반드시 응답 마지막에 정확히 "[INQUIRY_COMPLETE]" 라는 태그를 포함하세요.

Request Body

{
  "contents": [
    {"role": "user", "parts": [{"text": "..."}]},
    {"role": "model", "parts": [{"text": "..."}]}
  ]
}

Response

{
  "text": "AI 응답 텍스트"
}

Gemini 모델

gemini-2.0-flash 사용


2. api/send-email.js — 이메일 발송 서버리스 함수

기능

  • POST /api/send-email 요청을 받아 EmailJS REST API를 호출하여 이메일 발송
  • 환경변수 사용:
    • EMAILJS_PUBLIC_KEY
    • EMAILJS_PRIVATE_KEY (EmailJS REST API 호출 시 필요)
    • EMAILJS_SERVICE_ID
    • EMAILJS_TEMPLATE_ID
  • CORS 헤더 설정: Access-Control-Allow-Origin: https://flashgta-github-io.vercel.app
  • OPTIONS preflight 처리
  • Rate Limit 검증: 요청 헤더의 IP 기반 간단 검증 (선택)
  • Honeypot 검증: body에 honeypot 필드가 있으면 400 반환

EmailJS REST API 호출

POST https://api.emailjs.com/api/v1.0/email/send
Content-Type: application/json

{
  "service_id": process.env.EMAILJS_SERVICE_ID,
  "template_id": process.env.EMAILJS_TEMPLATE_ID,
  "user_id": process.env.EMAILJS_PUBLIC_KEY,
  "accessToken": process.env.EMAILJS_PRIVATE_KEY,
  "template_params": {
    "visitor_name": "...",
    "contact": "...",
    "inquiry_summary": "...",
    "full_conversation": "...",
    "date": "..."
  }
}

Request Body

{
  "visitor_name": "홍길동",
  "contact": "hong@example.com",
  "inquiry_summary": "재난지원금 신청 앱 개발 문의",
  "full_conversation": "전체 대화 텍스트",
  "date": "2026-03-06 15:30",
  "honeypot": ""
}

3. index.html 수정

핵심 변경사항

  • API 키 완전 제거 (Gemini API Key, EmailJS 키 모두 제거)
  • Gemini API 직접 호출 → /api/chat 호출로 변경
  • EmailJS SDK 제거 → /api/send-email 호출로 변경

단계별 상담 흐름 (State Machine)

챗봇이 다음 4단계를 순서대로 진행:

STEP 1 inquiry: /api/chat 호출로 Gemini와 멀티턴 대화

  • Gemini 응답에 [INQUIRY_COMPLETE] 태그가 포함되면 자동으로 STEP 2로 전환
  • 대화 히스토리를 배열로 누적하여 매 요청마다 전체 전송

STEP 2 privacy: 개인정보 수집 동의 안내 (Gemini API 호출 없음, 챗봇 자체 로직)

  • 안내 메시지 자동 출력:
    상담 내용을 담당자에게 전달하기 위해 연락처를 수집합니다.
    수집된 정보는 상담 목적으로만 사용되며, 응대 완료 후 즉시 파기됩니다.
    개인정보 수집에 동의하시겠습니까? (예 / 아니오)
    
  • "예" 또는 "네" 입력 시 STEP 3으로 전환
  • "아니오" 입력 시: "소중한 시간 내주셔서 감사합니다. 언제든지 다시 방문해 주세요. 😊" 출력 후 입력창 비활성화

STEP 3 contact: 이름과 연락처(이메일 또는 전화번호) 수집 (Gemini API 호출 없음)

  • 이름 먼저 요청: "성함을 알려주세요."
  • 이름 입력 후 연락처 요청: "연락 가능한 이메일 또는 전화번호를 알려주세요."
  • 이메일 형식이면 정규식 검증, 숫자만이면 전화번호로 처리
  • 이름과 연락처 모두 수집되면 자동으로 STEP 4로 전환

STEP 4 send: /api/send-email 호출 (Gemini API 호출 없음)

  • 대화 내용 요약 생성 (첫 번째 사용자 메시지를 inquiry_summary로 사용)
  • 전체 대화 내용을 텍스트로 변환하여 full_conversation에 포함
  • 발송 성공 시: "감사합니다! 담당자가 검토 후 연락드리겠습니다. ⚡" 출력
  • 발송 실패 시: "발송 중 오류가 발생했습니다. redsunjin@gmail.com으로 직접 문의해 주세요." 출력

봇 방지 (프론트엔드)

  • Rate Limiting: sessionStorage 기반, 10분 내 메시지 20회 초과 시 입력 차단
  • 중복 발송 차단: sessionStorage 기준 이메일 1회 발송 후 재발송 차단
  • Honeypot 필드: display:none 숨겨진 input 추가, 값이 있으면 전송 차단
  • 입력값 검증: XSS 방지 (사용자 입력을 innerHTML에 직접 삽입 금지, textContent 사용)

초기 메시지

안녕하세요! ⚡
라이트닝 AI 컨설턴트입니다.
구축하고 싶은 공공 서비스나 프로젝트 아이디어를 말씀해주시면, 2주 완성 플랜을 제안해 드립니다.
궁금한 점을 자유롭게 말씀해주세요. 충분한 내용이 파악되면 담당자 연결을 도와드립니다. 😊

Gemini 재시도 로직 (프론트엔드 /api/chat 호출 시)

  • 최대 3회 재시도
  • 지수 백오프: 1초 → 2초 → 4초
  • 재시도 중 타이핑 인디케이터 유지
  • 재시도 중 메시지 표시: "잠시 후 재시도 중... (N/3)"
  • 3회 모두 실패 시: "현재 AI 응답이 지연되고 있습니다. 잠시 후 다시 시도해주세요." 출력

4. 환경변수 설정 안내 (README 또는 주석)

Vercel 대시보드에서 아래 환경변수를 설정해야 한다:

GEMINI_API_KEY          = (Google AI Studio에서 발급)
EMAILJS_PUBLIC_KEY      = (EmailJS 대시보드 > Account > Public Key)
EMAILJS_PRIVATE_KEY     = (EmailJS 대시보드 > Account > Private Key)
EMAILJS_SERVICE_ID      = (EmailJS 대시보드 > Email Services)
EMAILJS_TEMPLATE_ID     = (EmailJS 대시보드 > Email Templates)

api/chat.jsapi/send-email.js 상단 주석에 위 안내를 포함한다.


주의사항

  • index.html에서 API 키 관련 코드 완전히 제거
  • 기존 UI 스타일(.ai-fab, .ai-chat-window, 애니메이션 등) 완전 유지
  • style.css는 수...

This pull request was created from Copilot chat.


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

@vercel
Copy link

vercel bot commented Mar 6, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
flashgta-github-io Ready Ready Preview, Comment Mar 6, 2026 3:01pm

@redsunjin redsunjin marked this pull request as ready for review March 6, 2026 15:03
@redsunjin redsunjin merged commit 9be8e8c into main Mar 6, 2026
2 of 3 checks passed
Copilot stopped work on behalf of redsunjin due to an error March 6, 2026 15:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants