feat: AI 구매 패턴 분석 기능 추가 (OpenAI GPT 통합)
- 사용자 구매 이력 AI 분석 및 마케팅 전략 제안
- 업셀링을 위한 추천 제품 기능 추가
주요 변경사항:
1. 백엔드 API (app.py)
- OpenAI API 통합 (GPT-4o-mini 사용)
- 환경 변수 로드 (.env 파일)
- AI 분석 엔드포인트: POST /admin/ai-analyze-user/<user_id>
- 헬퍼 함수 추가:
* prepare_analysis_prompt(): 프롬프트 생성
* parse_openai_response(): JSON 응답 파싱
* call_openai_with_retry(): 재시도 로직
* categorize_product(): 제품 카테고리 추정
- 에러 처리 및 fallback 로직
2. 프론트엔드 UI (admin.html)
- AI 분석 버튼 추가 (사용자 상세 모달)
- AI 분석 모달 추가 (결과 표시)
- Lottie 로딩 애니메이션 통합 (무료 라이선스)
- JavaScript 함수:
* showAIAnalysisModal(): 모달 열기 및 API 호출
* renderAIAnalysis(): 분석 결과 렌더링
* showAIAnalysisError(): 에러 표시
* 5분 캐싱 기능
- 섹션별 시각화:
* 구매 패턴 분석 (📊)
* 주요 구매 품목 (💊)
* 추천 제품 (✨)
* 마케팅 전략 (🎯)
3. 환경 설정
- requirements.txt: openai, python-dotenv 추가
- .env: OpenAI API 키 및 설정 저장
- Lottie CDN 통합 (버전 5.12.2)
기술 스택:
- OpenAI GPT-4o-mini (비용 효율적)
- Lottie 애니메이션 (로딩 UX 개선)
- 재시도 로직 (지수 백오프)
- 응답 캐싱 (5분)
보안:
- API 키 환경 변수 관리
- .env 파일 .gitignore 처리
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -601,6 +601,24 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- AI 분석 모달 -->
|
||||
<div id="aiAnalysisModal" style="display: none; position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); z-index: 10001; padding: 20px; overflow-y: auto;">
|
||||
<div style="max-width: 800px; margin: 40px auto; background: #fff; border-radius: 20px; padding: 32px; position: relative;">
|
||||
<button onclick="closeAIAnalysisModal()" style="position: absolute; top: 20px; right: 20px; background: #f1f3f5; border: none; width: 36px; height: 36px; border-radius: 50%; cursor: pointer; font-size: 20px; color: #495057;">×</button>
|
||||
|
||||
<h2 style="font-size: 24px; font-weight: 700; color: #212529; margin-bottom: 24px; letter-spacing: -0.5px;">
|
||||
🤖 AI 구매 패턴 분석
|
||||
</h2>
|
||||
|
||||
<div id="aiAnalysisContent" style="min-height: 200px;">
|
||||
<div style="text-align: center; padding: 60px; color: #868e96;">
|
||||
<div style="font-size: 14px;">AI 분석을 시작하려면 버튼을 클릭하세요.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/lottie-web/5.12.2/lottie.min.js"></script>
|
||||
<script>
|
||||
function showTransactionDetail(transactionId) {
|
||||
document.getElementById('transactionModal').style.display = 'block';
|
||||
@@ -799,7 +817,10 @@
|
||||
<div style="color: #212529; font-size: 16px; font-weight: 600;">${user.created_at}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="text-align: right;">
|
||||
<div style="text-align: right; display: flex; gap: 8px; justify-content: flex-end;">
|
||||
<button onclick="showAIAnalysisModal(${user.id})" style="padding: 10px 24px; background: linear-gradient(135deg, #8b5cf6 0%, #6366f1 100%); color: white; border: none; border-radius: 10px; font-size: 14px; font-weight: 600; cursor: pointer; transition: all 0.2s;">
|
||||
🤖 AI 분석
|
||||
</button>
|
||||
<button onclick="showUsePointsModal(${user.id}, ${user.balance})" style="padding: 10px 24px; background: #f03e3e; color: white; border: none; border-radius: 10px; font-size: 14px; font-weight: 600; cursor: pointer; transition: all 0.2s;">
|
||||
💳 포인트 사용
|
||||
</button>
|
||||
@@ -1322,6 +1343,202 @@
|
||||
closeSearchResults();
|
||||
}
|
||||
});
|
||||
|
||||
// ===== AI 분석 기능 =====
|
||||
|
||||
let aiAnalysisCache = {}; // 캐싱용
|
||||
let lottieAnimation = null; // Lottie 애니메이션 인스턴스
|
||||
|
||||
function showAIAnalysisModal(userId) {
|
||||
// 모달 열기
|
||||
document.getElementById('aiAnalysisModal').style.display = 'block';
|
||||
|
||||
// 캐시 확인 (5분 이내)
|
||||
const cacheKey = `ai_analysis_${userId}`;
|
||||
const cached = aiAnalysisCache[cacheKey];
|
||||
const now = Date.now();
|
||||
|
||||
if (cached && (now - cached.timestamp) < 300000) {
|
||||
renderAIAnalysis(cached.data);
|
||||
return;
|
||||
}
|
||||
|
||||
// Lottie 로딩 애니메이션 표시
|
||||
document.getElementById('aiAnalysisContent').innerHTML = `
|
||||
<div id="lottie-container" style="text-align: center; padding: 40px;">
|
||||
<div id="lottie-animation" style="width: 200px; height: 200px; margin: 0 auto;"></div>
|
||||
<div style="font-size: 16px; color: #495057; font-weight: 600; margin-top: 16px;">
|
||||
AI가 구매 패턴을 분석하고 있습니다...
|
||||
</div>
|
||||
<div style="font-size: 14px; color: #868e96; margin-top: 8px;">
|
||||
최대 10-15초 소요될 수 있습니다
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Lottie 애니메이션 초기화 (무료 AI/로봇 애니메이션)
|
||||
if (lottieAnimation) {
|
||||
lottieAnimation.destroy();
|
||||
}
|
||||
|
||||
lottieAnimation = lottie.loadAnimation({
|
||||
container: document.getElementById('lottie-animation'),
|
||||
renderer: 'svg',
|
||||
loop: true,
|
||||
autoplay: true,
|
||||
path: 'https://lottie.host/d5cb5c0e-1b0f-4f0a-8e5f-9c3e9d6e5a3a/3R3xKR0P0r.json' // AI 로봇 애니메이션
|
||||
});
|
||||
|
||||
// API 호출
|
||||
fetch(`/admin/ai-analyze-user/${userId}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (lottieAnimation) {
|
||||
lottieAnimation.destroy();
|
||||
lottieAnimation = null;
|
||||
}
|
||||
|
||||
if (data.success) {
|
||||
// 캐시 저장
|
||||
aiAnalysisCache[cacheKey] = {
|
||||
data: data,
|
||||
timestamp: Date.now()
|
||||
};
|
||||
renderAIAnalysis(data);
|
||||
} else {
|
||||
showAIAnalysisError(data.message);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
if (lottieAnimation) {
|
||||
lottieAnimation.destroy();
|
||||
lottieAnimation = null;
|
||||
}
|
||||
showAIAnalysisError('네트워크 오류가 발생했습니다. 다시 시도해주세요.');
|
||||
console.error('AI Analysis Error:', error);
|
||||
});
|
||||
}
|
||||
|
||||
function renderAIAnalysis(data) {
|
||||
const user = data.user;
|
||||
const analysis = data.analysis;
|
||||
|
||||
let html = `
|
||||
<!-- 사용자 정보 헤더 -->
|
||||
<div style="background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); border-radius: 12px; padding: 16px; margin-bottom: 24px;">
|
||||
<div style="font-size: 14px; color: #495057; margin-bottom: 4px;">분석 대상</div>
|
||||
<div style="font-size: 18px; font-weight: 700; color: #212529;">
|
||||
${user.name} (${user.phone})
|
||||
</div>
|
||||
<div style="font-size: 13px; color: #868e96; margin-top: 4px;">
|
||||
${user.balance.toLocaleString()}P 보유
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 구매 패턴 분석 -->
|
||||
<div style="margin-bottom: 24px;">
|
||||
<h3 style="font-size: 16px; font-weight: 700; color: #495057; margin-bottom: 12px; display: flex; align-items: center;">
|
||||
<span style="margin-right: 8px;">📊</span> 구매 패턴 분석
|
||||
</h3>
|
||||
<div style="background: #f8f9fa; border-radius: 8px; padding: 16px; font-size: 14px; line-height: 1.8; color: #212529; white-space: pre-line;">
|
||||
${analysis.pattern}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 주요 구매 품목 -->
|
||||
<div style="margin-bottom: 24px;">
|
||||
<h3 style="font-size: 16px; font-weight: 700; color: #495057; margin-bottom: 12px; display: flex; align-items: center;">
|
||||
<span style="margin-right: 8px;">💊</span> 주요 구매 품목
|
||||
</h3>
|
||||
<ul style="list-style: none; padding: 0; margin: 0;">
|
||||
`;
|
||||
|
||||
analysis.main_products.forEach(product => {
|
||||
html += `
|
||||
<li style="background: #fff; border: 1px solid #e9ecef; border-radius: 8px; padding: 12px; margin-bottom: 8px; font-size: 14px; color: #212529;">
|
||||
• ${product}
|
||||
</li>
|
||||
`;
|
||||
});
|
||||
|
||||
html += `
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- 추천 제품 -->
|
||||
<div style="margin-bottom: 24px;">
|
||||
<h3 style="font-size: 16px; font-weight: 700; color: #495057; margin-bottom: 12px; display: flex; align-items: center;">
|
||||
<span style="margin-right: 8px;">✨</span> 추천 제품 (업셀링)
|
||||
</h3>
|
||||
<div style="background: linear-gradient(135deg, #e0f2fe 0%, #ddd6fe 100%); border-radius: 8px; padding: 16px;">
|
||||
<ul style="margin: 0; padding-left: 20px; font-size: 14px; line-height: 1.8; color: #212529;">
|
||||
`;
|
||||
|
||||
analysis.recommendations.forEach(rec => {
|
||||
html += `<li>${rec}</li>`;
|
||||
});
|
||||
|
||||
html += `
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 마케팅 전략 -->
|
||||
<div>
|
||||
<h3 style="font-size: 16px; font-weight: 700; color: #495057; margin-bottom: 12px; display: flex; align-items: center;">
|
||||
<span style="margin-right: 8px;">🎯</span> 마케팅 전략 제안
|
||||
</h3>
|
||||
<div style="background: #fff3cd; border-left: 4px solid #ffc107; padding: 16px; border-radius: 4px; font-size: 14px; line-height: 1.8; color: #856404;">
|
||||
${analysis.marketing_strategy}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
${data.metadata ? `
|
||||
<div style="margin-top: 24px; padding-top: 16px; border-top: 1px solid #e9ecef; font-size: 12px; color: #868e96; text-align: right;">
|
||||
분석 모델: ${data.metadata.model_used} | 분석 시간: ${data.metadata.analysis_time}
|
||||
</div>
|
||||
` : ''}
|
||||
`;
|
||||
|
||||
document.getElementById('aiAnalysisContent').innerHTML = html;
|
||||
}
|
||||
|
||||
function showAIAnalysisError(message) {
|
||||
document.getElementById('aiAnalysisContent').innerHTML = `
|
||||
<div style="text-align: center; padding: 60px;">
|
||||
<div style="font-size: 48px; margin-bottom: 16px;">⚠️</div>
|
||||
<div style="font-size: 16px; color: #f03e3e; font-weight: 600; margin-bottom: 8px;">
|
||||
AI 분석 실패
|
||||
</div>
|
||||
<div style="font-size: 14px; color: #868e96; margin-bottom: 20px;">
|
||||
${message}
|
||||
</div>
|
||||
<button onclick="closeAIAnalysisModal()" style="padding: 10px 24px; background: #f1f3f5; border: none; border-radius: 10px; cursor: pointer; font-size: 14px; font-weight: 600;">
|
||||
닫기
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function closeAIAnalysisModal() {
|
||||
document.getElementById('aiAnalysisModal').style.display = 'none';
|
||||
if (lottieAnimation) {
|
||||
lottieAnimation.destroy();
|
||||
lottieAnimation = null;
|
||||
}
|
||||
}
|
||||
|
||||
// AI 분석 모달 배경 클릭 시 닫기
|
||||
document.getElementById('aiAnalysisModal').addEventListener('click', function(e) {
|
||||
if (e.target === this) {
|
||||
closeAIAnalysisModal();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user