운세 봐주는 챗봇 서비스 만들기 제 1 장

소개

시도하고자 했던 것과 그 이유를 알려주세요.

  1. GPT API를 활용하여 HTML 웹 서비스를 구현하는 것을 시도하였습니다.

  1. 최근 회사 업무 스트레스가 심해지면서 스트레스 해소를 위해 재미있는 프로젝트를 진행 해보고자 생각이 들던 차에 어린 시절 연말 연초에는 보험사 사이트에서 운세를 보던 것이 생각 나서 AI를 활용한 무료 운세 서비스를 만들어 보길 희망하였습니다.

  2. React 를 활용할 생각도 했었지만, 코딩은 거의 초보 수준이라 AI에게 모든 코드를 맡겨보기로 생각하고 단순하게 HTML로 만들기로 결정하였습니다.

진행 방법

어떤 도구를 사용했고, 어떻게 활용하셨나요?

Tip: 사용한 프롬프트 전문을 꼭 포함하고, 내용을 짧게 소개해 주세요.

Tip: 활용 이미지나 캡처 화면을 꼭 남겨주세요.

Tip: 코드 전문은 코드블록에 감싸서 작성해주세요. ( / 을 눌러 '코드 블록'을 선택)

  1. 프로젝트 아이디어 회의

1.1. 회의 참석자 : 나, GPT 음성 모드

1.2. 회의 시간 : 퇴근시간 차량 이동 시간을 활용 - 약 30분 소요

1.2.1. 회의 내용 및 프롬프트 요약

- 프로젝트 : 힐링 운세 웹앱 서비스

- 컨설팅 요청을 통해 아이디어 생성 및 확장, 시장조사 후 요약본 보고서 생성

1.3. 프롬프트(대화 구조)

1.3.1. 비즈니스 아이디어 컨설팅 요청

1.3.2. 아이디어 공유 및 확장 요청

한국어로 된 문자 메시지의 스크린샷

1.3.3. 시장조사 - 사실 Perplexity 를 활용하는 것이 더 정확합니다.

1.3.4. 대화내용 요약 및 정리

  1. 프로젝트 개요 및 계획 수립

    2.1. 사용모델 : GPT-o1

    2.2. 프로젝트 개요 및 보고서 생성: 개요를 바탕으로 프로젝트 구체화 및 보고서 작성

    2.3. 프로젝트 보고서

    [프로젝트 준비 보고서]
    
    프로젝트 개요 (Overview)
    
    프로젝트 명: “힐링 운세 웹앱”
    목표 및 배경: 20대 타겟 사용자에게 일상 속 힐링과 즐거움을 제공하는 맞춤형 운세 서비스를 구축하고자 함. GPT API를 통해 자연스러운 운세 메시지를 제공하고, 쿠팡파트너스 연계를 통해 부가 수익 창출을 목표로 한다. 초기에는 간결한 MVP를 출시하여 사용자 반응을 검증하고, 피드백을 반영해 점진적으로 기능을 확대할 예정.
    주요 기능 및 서비스 컨셉:
    사용자 입력(생년월일, 관심사, 감정 상태 등)을 반영한 맞춤형 운세 제공
    매일 업데이트되는 힐링 컨텐츠(명상 오디오, 감성 글귀, 홈카페 소품, 피트니스 기구 등) 추천
    쿠팡파트너스 상품 연계를 통한 관련 상품 제안 및 수익화
    대상 사용자 및 시장 타겟층: 20대 초중반 MZ세대. 스마트폰 기반 콘텐츠 소비에 익숙하며, SNS나 숏폼 콘텐츠에 민감하게 반응하는 소비자층
    진행 현황 (Progress Status)
    
    현재까지 완료된 업무(마일스톤, 주요 Deliverables):
    
    현재까지 실제 개발 착수나 디자인 구현 등 물리적 산출물 없음.
    내부 아이디어 브레인스토밍 및 시장 조사 결과를 바탕으로 서비스 컨셉 및 스펙 정의 완료.
    계획 중인 작업 (담당자: 본인(프로젝트 매니저 겸 개발자)):
    
    작업 항목	예정 완료일	비고
    프론트엔드 기본 구조 설계	D+7	React 기반 화면 골격 구현
    백엔드 API 및 DB 설계	D+14	Express, PostgreSQL 스키마 정의
    GPT API 연동 및 프롬프트 설계	D+21	사용자 정보 기반 프롬프트 작성 및 테스트
    UI/UX 프로토타입 구축	D+28	피그마 기반 핵심 화면 흐름 설계
    단위/기본 E2E 테스트	D+35	Jest, Cypress 활용
    MVP 초기 버전 런칭	D+40	소규모 베타테스트 진행
    식별된 리스크 및 이슈(해결 계획 포함):
    
    GPT 응답 품질 변동성 → 프롬프트 튜닝, 응답 캐싱 전략
    쿠팡파트너스 상품 정보 변동 → 정기 크론잡으로 상품 정보 업데이트
    1인 프로젝트 일정 지연 가능성 → 핵심 기능 우선 구현, MVP 범위 최소화로 일정 관리
    기술 구현 현황 (Technical Status)
    
    핵심 기술 스택:
    프론트엔드: React, TailwindCSS, Vite
    백엔드: Node.js(Express), PostgreSQL
    AI: OpenAI GPT API 연동
    CI/CD: GitHub Actions 기반 자동화 빌드 및 테스트 파이프라인 계획
    아키텍처 개요:
    [React 클라이언트] → [Express API] → [PostgreSQL DB]
    [GPT Integration Module] / [쿠팡파트너스 상품관리 모듈]
    품질 관리 방안:
    JMeter로 API 성능 측정
    Jest, Cypress로 테스트 자동화
    MVP 이후 성능 및 품질 지표 개선 예정
    디자인/UX 현황 (Design & UX)
    
    와이어프레임/프로토타입: D+14 이후 피그마로 기본 레이아웃
    사용자 피드백 반영: MVP 출시 후 설문, 인터뷰 결과 반영
    반응형 디자인: 모바일 우선 접근, 반응형 CSS 적용 계획
    비즈니스 전략 및 KPI 추적 (Business & KPI Tracking)
    
    구독 모델: MVP 무료 → 추후 프리미엄 기능(테마 운세, 고급 힐링 컨텐츠) 유료 전환
    쿠팡파트너스: MVP 단계에서 기본 상품 추천 후 클릭률/구매 전환율 추적
    KPI 목표:
    MVP 1개월 내 MAU 1,000명
    상품 클릭 전환율 5%
    프리미엄 전환율 초기 2%
    차기 일정 계획 (Next Steps & Timeline)
    
    마일스톤:
    D+7: 프론트엔드 기본 화면 구현
    D+14: 백엔드/API/DB 설계 완료
    D+21: GPT 연동 프로토타입 완성
    D+35: UI/UX 개선, MVP 베타 테스트
    D+40: MVP 공개
    협업 요청/의사결정:
    베타테스터 확보(지인/SNS)
    쿠팡파트너스 상품 추천 로직 개선 시점 결정
    

  1. 아키텍쳐

    사실 아키텍쳐라고 부를 것도 못되는게, 간단한 웹 서비스 개발이기 때문에 백엔드용 index.js 하나와 프론트엔드용 index.html 하나면 충분 합니다.

    이것 저것 수정하며 진행 되었고, 현재 베타 서비스 오픈을 하였기 때문에 코드 전문을 올리지 못하고 베이스 코드를 작성하던 프롬프트와 결과물을 공유하는 점 양해 부탁드립니다.

    코딩을 전혀 배우지 못한 비전공자라 어설프지만 1인 프로젝트를 진행한 과정을 봐주신다 생각해 주시길.. 바랍니다 😃

    3.1. 프로젝트 개요 제공 및 아키텍처 개요 작성

3.2. index.js 코드 작성

// 파일명: server.js
// 실행 전 사전준비: npm install express axios pg dotenv
// 실행: node server.js

require('dotenv').config();
const express = require('express');
const { Pool } = require('pg');
const axios = require('axios');

const app = express();
app.use(express.json());

// PostgreSQL 연결 풀
const pool = new Pool({
  connectionString: process.env.DATABASE_URL,
});

// 사용자별로 대화 상태를 관리하기 위한 임시 메모리 스토어 (세션처리 가정)
// 실제 서비스에서는 session id를 활용하거나 DB/Redis 등을 통한 상태 관리 필요
const userConversations = {};

// GPT Chat Completion 호출 함수 예시
async function getChatResponse(userId, userMessage) {
  // 대화 내역 가져오기(없으면 초기화)
  if (!userConversations[userId]) {
    // 초기 시스템 메시지를 통해 모델에게 운세와 힐링 관련 답변 맥락을 설정
    userConversations[userId] = [
      { role: "system", content: "너는 사용자에게 매일 운세를 부드럽게 안내하고 힐링 관련 조언을 제공하는 조력자다. 사용자가 제공한 정보(생년월일, 관심사, 기분)에 따라 맞춤형 운세를 만들어주고, 필요하다면 부드럽게 추가 조언을 한다." }
    ];
  }

  // 사용자 메시지 추가
  userConversations[userId].push({ role: "user", content: userMessage });

  try {
    const response = await axios.post('https://api.openai.com/v1/chat/completions',
      {
        model: "gpt-3.5-turbo",
        messages: userConversations[userId],
        temperature: 0.7
      },
      {
        headers: {
          'Authorization': `Bearer ${process.env.OPENAI_API_KEY}`,
          'Content-Type': 'application/json'
        }
      }
    );

    const assistantMessage = response.data.choices[0].message.content.trim();
    // GPT 응답 추가
    userConversations[userId].push({ role: "assistant", content: assistantMessage });

    return assistantMessage;
  } catch (error) {
    console.error("GPT API 호출 오류:", error.message);
    return "운세를 불러오는 중에 문제가 발생했습니다. 잠시 후 다시 시도해주세요.";
  }
}

// 운세 대화 API 예제
// 요청 바디: { userId: "임의의 사용자 식별자", message: "사용자 메시지" }
app.post('/api/fortune/chat', async (req, res) => {
  const { userId, message } = req.body;
  if (!userId || !message) {
    return res.status(400).json({ error: "userId와 message 필드가 필요합니다." });
  }

  const reply = await getChatResponse(userId, message);

  // (선택) 대화 내용 DB 저장 등 추가 가능
  // await pool.query('INSERT INTO log_fortune (user_id, message, reply) VALUES ($1, $2, $3)', [userId, message, reply]);

  res.json({ reply });
});

// 테스트용 간단한 상태 조회
app.get('/api/fortune/conversation', (req, res) => {
  const { userId } = req.query;
  if (!userId || !userConversations[userId]) {
    return res.status(404).json({ error: "해당 사용자의 대화 내용이 없습니다." });
  }
  res.json({ conversation: userConversations[userId] });
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`서버가 포트 ${PORT}에서 동작 중입니다.`);
});

이후 수정은 클로드를 통해 진행.(프로젝트 보고서 설명 후 백엔드 코드 베이스로 던져주고 수정 요청 하는 방식)

  1. 프론트엔드 구축(GPT-o1 활용)

    4.1. 채팅형 운세 서비스 웹앱 디자인을 위한 가이드를 작성.

    아래와 같이 프롬프트를 작성하면서 이미지를 생성.(하나의 프롬프트로 여러장의 이미지를 찍어내고 싶다면, 그마저도 GPT에게 이미지생성 프롬프트를 던져주고 "한번에 각 이미지를 생성하는 프롬프트를 알려달라"고 질문해서 받아내면 된다.

화면에 한국어로 된 메시지가 뜹니다

4.2. html로 서비스를 만들기 위해 필요한 이미지를 생성하기 위한 프롬프트 생성

4.3. 프론트엔드 코드 생성

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>    
    <title>힐링 운세 웹앱</title>
    <style>
        body {
            margin: 0;
            padding: 0;
            font-family: 'Noto Sans KR', sans-serif;
            background: linear-gradient(160deg, #e6f2e6 0%, #f6f2fc 100%);
            color: #333;
            display: flex;
            flex-direction: column;
            min-height: 100vh;
        }

        .header {
            display: flex;
            align-items: center;
            padding: 10px 20px;
            background: transparent;
        }
        .logo-area .logo {
            height: 50px;
        }

        .hero-section {
            text-align: center;
            padding: 40px 20px;
        }
        .hero-img {
            max-width: 300px;
            width: 100%;
            border-radius: 20px;
            margin-bottom: 20px;
        }
        .hero-title {
            font-size: 1.8rem;
            margin: 10px 0;
            color: #444;
        }
        .hero-subtitle {
            font-size: 1rem;
            color: #666;
            margin-bottom: 20px;
        }
        .start-btn {
            background: #c7e8d1;
            border: none;
            padding: 10px 20px;
            border-radius: 20px;
            font-size: 1rem;
            cursor: pointer;
            transition: background 0.3s;
        }
        .start-btn:hover {
            background: #b6dec5;
        }

        .chat-section {
            flex: 1;
            display: flex;
            justify-content: center;
            align-items: flex-start;
            padding: 20px;
        }
        .chat-container {
            width: 100%;
            max-width: 400px;
            background: #ffffffdd;
            backdrop-filter: blur(8px);
            border-radius: 20px;
            box-shadow: 0 0 10px rgba(0,0,0,0.1);
            overflow: hidden;
            display: flex;
            flex-direction: column;
        }

        .chat-messages {
            flex: 1;
            padding: 20px;
            overflow-y: auto;
        }

        .message {
            margin-bottom: 15px;
            display: flex;
            align-items: flex-start;
        }

        .message-bot, .message-user {
            max-width: 80%;
            border-radius: 10px;
            padding: 10px 15px;
            font-size: 0.9rem;
            line-height: 1.4;
        }

        .message-bot {
            background: #e7f5e7;
            color: #333;
            border-top-left-radius: 0;
            position: relative;
        }
        .message-bot::before {
            content: "";
            position: absolute;
            left: -10px;
            top: 10px;
            border: 6px solid transparent;
            border-right: 10px solid #e7f5e7;
        }

        .message-user {
            background: #f0f0f0;
            color: #333;
            margin-left: auto;
            border-top-right-radius: 0;
            position: relative;
        }
        .message-user::before {
            content: "";
            position: absolute;
            right: -10px;
            top: 10px;
            border: 6px solid transparent;
            border-left: 10px solid #f0f0f0;
        }

        .chat-input-area {
            display: flex;
            border-top: 1px solid #ddd;
            padding: 10px;
            background: #fff;
        }

        .chat-input-area input {
            flex: 1;
            padding: 10px;
            border: none;
            border-radius: 20px;
            background: #f5f5f5;
            font-size: 1rem;
            outline: none;
            margin-right: 10px;
        }

        .send-btn {
            background: #c7e8d1;
            border: none;
            padding: 10px 20px;
            border-radius: 20px;
            cursor: pointer;
            transition: background 0.3s;
        }
        .send-btn:hover {
            background: #b6dec5;
        }

        ::-webkit-scrollbar {
            width: 6px;
        }
        ::-webkit-scrollbar-thumb {
            background: #ccc;
            border-radius: 3px;
        }
    </style>
</head>
<body>
    <header class="header">
        <div class="logo-area">
            <img src="logo.png" alt="힐링 운세 로고" class="logo"/>
        </div>
    </header>

    <main>
        <section class="hero-section">
            <img src="hero.png" alt="힐링 분위기의 히어로 일러스트" class="hero-img"/>
            <h1 class="hero-title">오늘의 힐링 운세</h1>
            <p class="hero-subtitle">2025년 청록빛 뱀의 기운과 함께하는 편안한 하루</p>
            <button class="start-btn" onclick="startChat()">채팅 시작하기</button>
        </section>

        <section class="chat-section" id="chatSection" style="display:none;">
            <div class="chat-container">
                <div class="chat-messages" id="chatMessages">
                    <!-- 대화 메시지들이 JavaScript로 추가될 예정 -->
                </div>
                <div class="chat-input-area">
                    <input type="text" id="userInput" placeholder="메시지를 입력하세요..." />
                    <button class="send-btn" onclick="sendMessage()">전송</button>
                </div>
            </div>
        </section>
    </main>

    <script>
        function startChat() {
            document.getElementById('chatSection').style.display = 'block';
            scrollToChatBottom();
        }

        function sendMessage() {
            const input = document.getElementById('userInput');
            const text = input.value.trim();
            if(text !== "") {
                addMessage(text, 'user');
                input.value = "";
                botReply();
            }
        }

        function addMessage(message, type) {
            const chatMessages = document.getElementById('chatMessages');
            const msgDiv = document.createElement('div');
            msgDiv.classList.add('message');

            const msgContent = document.createElement('div');
            msgContent.classList.add(type === 'bot' ? 'message-bot' : 'message-user');
            msgContent.textContent = message;

            msgDiv.appendChild(msgContent);
            chatMessages.appendChild(msgDiv);
            scrollToChatBottom();
        }

        function botReply() {
            setTimeout(() => {
                addMessage("오늘은 마음의 여유를 찾기 좋은 날이에요. 짧은 명상이나 산책을 해보는 건 어떨까요?", 'bot');
            }, 500);
        }

        function scrollToChatBottom() {
            const chatMessages = document.getElementById('chatMessages');
            chatMessages.scrollTop = chatMessages.scrollHeight;
        }
    </script>
</body>
</html>

결과와 배운 점

배운 점과 나만의 꿀팁을 알려주세요.

과정 중에 어떤 시행착오를 겪었나요?

도움이 필요한 부분이 있나요?

앞으로의 계획이 있다면 들려주세요.

  1. GPT는 아이디어 싱크탱크 같은 느낌이라면 클로드는 저의 손 발을 움직이게 하는 느낌이었어요. 코드를 생성하는 과정에서 콘솔에러가 종종 발생하였지만 에러 내용을 공유해서 거의 즉각 처리가 가능했습니다.

  2. 중간에 생략된 부분이 있는데 CSS 코드는 웹에서 개발자모드(F12)로 접속해서 일부 수정을 했지만, JavaScript 코드를 베이스 코드에 추가할 때 버벅 거리는 일이 많았는데 이럴때 마다 역으로 클로드와 GPT 에게 전체 코드를 알려주고 어느 부분에 들어가야하는지 확인 하는 과정을 거쳤기 때문에 수월하게 진행 되었다는 생각이 듭니다.

  3. 이미지 생성을 미드저니 또는 레오나르도AI를 활용할 수도 있었지만, 우선 빠른 시간 내에 베타 서비스를 오픈하겠다는 목표가 있었기 때문에 GPT-4o 모델을 활용하여 DALLE-3로 이미지를 생성 했어요. 이 이미지만 해도 충분히 사용 가능한 퀄리티가 나왔기 때문에 우선 만족하고 추후에 다른 이미지 생성 AI로도 캐릭터를 뽑아 보려 합니다.

  4. GPT-4o를 활용한 이미지 생성 프롬프트는 이미지 게시판에 따로 공유하도록 하겠습니다.

  5. 혹시 궁금하실 분들이 있을까봐 첨언하자면 프론트엔드는 Cloudflare, 백엔드는 AWS를 이용 하였습니다. 결과물은 이미지와 함께 제일 하단에 링크를 남겨두겠습니다.

  6. 앞으로는 운세 서비스를 좀 더 구체화 하고 결과를 공유할 수 있는 게시판, 그리고 각 운세에 대한 여러가지 콘텐츠 페이지를 삽입할 예정입니다. 회사를 다니고 있어서(개발과 무관한 총무 직무) 곧 사옥이전 등 큰 프로젝트가 진행 되기 전에 이번 힐링 운세 프로젝트를 마무리하는것이 목표라 가능한 12월 말, 늦어도 1월 중순에는 서비스 기반을 마련하는 것이 목표입니다. 첫 서비스이기도 하고 사이드 프로젝트로 하는 것들이 재미가 있어서 더 애정이 가네요 ^^

결과물

결과물 보러가기 - 데이브리즈 || 채팅형 AI 운세 서비스

2

👉 이 게시글도 읽어보세요