# AI 서비스 프론트엔드 통합 가이드 ## 개요 AI 서비스의 실시간 제안사항 API를 프론트엔드에서 사용하기 위한 통합 가이드입니다. **⚠️ 중요**: AI 서비스가 **Python (FastAPI)**로 마이그레이션 되었습니다. - **기존 포트**: 8083 (Java Spring Boot) → **새 포트**: 8086 (Python FastAPI) - **엔드포인트 경로**: `/api/suggestions/...` → `/api/v1/ai/suggestions/...` --- ## 1. API 정보 ### 엔드포인트 ``` GET /api/v1/ai/suggestions/meetings/{meetingId}/stream ``` **변경 사항**: - ✅ **새 경로** (Python): `/api/v1/ai/suggestions/meetings/{meetingId}/stream` - ❌ **구 경로** (Java): `/api/suggestions/meetings/{meetingId}/stream` ### 메서드 - **HTTP Method**: GET - **Content-Type**: text/event-stream (SSE) - **인증**: 개발 환경에서는 불필요 (운영 환경에서는 JWT 필요) ### 파라미터 | 파라미터 | 타입 | 필수 | 설명 | |---------|------|------|------| | meetingId | string (UUID) | 필수 | 회의 고유 ID | ### 예시 ``` # Python (새 버전) http://localhost:8087/api/v1/ai/suggestions/meetings/550e8400-e29b-41d4-a716-446655440000/stream # Java (구 버전 - 사용 중단 예정) http://localhost:8083/api/suggestions/meetings/550e8400-e29b-41d4-a716-446655440000/stream ``` --- ## 2. 응답 데이터 구조 ### SSE 이벤트 형식 ``` event: ai-suggestion id: 123456789 data: {"suggestions":[...]} ``` ### 데이터 스키마 (JSON) ```typescript interface RealtimeSuggestionsDto { suggestions: SimpleSuggestionDto[]; } interface SimpleSuggestionDto { id: string; // 제안 고유 ID (예: "suggestion-1") content: string; // 제안 내용 (예: "신제품의 타겟 고객층...") timestamp: string; // 시간 정보 (HH:MM:SS 형식, 예: "00:05:23") confidence: number; // 신뢰도 점수 (0.0 ~ 1.0) } ``` ### 샘플 응답 ```json { "suggestions": [ { "id": "suggestion-1", "content": "신제품의 타겟 고객층을 20-30대로 설정하고, 모바일 우선 전략을 취하기로 논의 중입니다.", "timestamp": "00:05:23", "confidence": 0.92 } ] } ``` --- ## 3. 프론트엔드 구현 방법 ### 3.1 EventSource로 연결 ```javascript // 회의 ID (실제로는 회의 생성 API에서 받아야 함) const meetingId = '550e8400-e29b-41d4-a716-446655440000'; // SSE 연결 (Python 버전) const apiUrl = `http://localhost:8087/api/v1/ai/suggestions/meetings/${meetingId}/stream`; const eventSource = new EventSource(apiUrl); // 연결 성공 eventSource.onopen = function(event) { console.log('SSE 연결 성공'); }; // ai-suggestion 이벤트 수신 eventSource.addEventListener('ai-suggestion', function(event) { const data = JSON.parse(event.data); const suggestions = data.suggestions; suggestions.forEach(suggestion => { console.log('제안:', suggestion.content); addSuggestionToUI(suggestion); }); }); // 에러 처리 eventSource.onerror = function(error) { console.error('SSE 연결 오류:', error); eventSource.close(); }; ``` ### 3.2 UI에 제안사항 추가 ```javascript function addSuggestionToUI(suggestion) { const container = document.getElementById('aiSuggestionList'); // 중복 방지 if (document.getElementById(`suggestion-${suggestion.id}`)) { return; } // HTML 생성 const html = `
${escapeHtml(suggestion.timestamp)}
${escapeHtml(suggestion.content)}
`; container.insertAdjacentHTML('beforeend', html); } ``` ### 3.3 XSS 방지 ```javascript function escapeHtml(text) { const map = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }; return text.replace(/[&<>"']/g, m => map[m]); } ``` ### 3.4 연결 종료 ```javascript // 페이지 종료 시 또는 회의 종료 시 window.addEventListener('beforeunload', function() { if (eventSource) { eventSource.close(); } }); ``` --- ## 4. React 통합 예시 ### 4.1 Custom Hook ```typescript import { useEffect, useState } from 'react'; interface Suggestion { id: string; content: string; timestamp: string; confidence: number; } export function useAiSuggestions(meetingId: string) { const [suggestions, setSuggestions] = useState([]); const [isConnected, setIsConnected] = useState(false); const [error, setError] = useState(null); useEffect(() => { const apiUrl = `http://localhost:8087/api/v1/ai/suggestions/meetings/${meetingId}/stream`; const eventSource = new EventSource(apiUrl); eventSource.onopen = () => { setIsConnected(true); setError(null); }; eventSource.addEventListener('ai-suggestion', (event) => { try { const data = JSON.parse(event.data); setSuggestions(prev => [...prev, ...data.suggestions]); } catch (err) { setError(err as Error); } }); eventSource.onerror = (err) => { setError(new Error('SSE connection failed')); setIsConnected(false); eventSource.close(); }; return () => { eventSource.close(); setIsConnected(false); }; }, [meetingId]); return { suggestions, isConnected, error }; } ``` ### 4.2 Component 사용 ```typescript function MeetingPage({ meetingId }: { meetingId: string }) { const { suggestions, isConnected, error } = useAiSuggestions(meetingId); if (error) { return
Error: {error.message}
; } return (
연결 상태: {isConnected ? '연결됨' : '연결 안 됨'}
{suggestions.map(suggestion => (
{suggestion.timestamp}

{suggestion.content}

))}
); } ``` --- ## 5. 환경별 설정 ### 5.1 개발 환경 ```javascript // Python 버전 (권장) const API_BASE_URL = 'http://localhost:8087'; // Java 버전 (구버전 - 사용 중단 예정) // const API_BASE_URL = 'http://localhost:8083'; ``` ### 5.2 테스트 환경 ```javascript const API_BASE_URL = 'https://test-api.hgzero.com'; ``` ### 5.3 운영 환경 ```javascript // 같은 도메인에서 실행될 경우 const API_BASE_URL = ''; // 또는 환경변수 사용 const API_BASE_URL = process.env.REACT_APP_AI_API_URL; ``` --- ## 6. 에러 처리 ### 6.1 연결 실패 ```javascript eventSource.onerror = function(error) { console.error('SSE 연결 실패:', error); // 사용자에게 알림 showErrorNotification('AI 제안사항을 받을 수 없습니다. 다시 시도해주세요.'); // 재연결 시도 (옵션) setTimeout(() => { reconnect(); }, 5000); }; ``` ### 6.2 파싱 오류 ```javascript try { const data = JSON.parse(event.data); } catch (error) { console.error('데이터 파싱 오류:', error); console.error('원본 데이터:', event.data); // Sentry 등 에러 모니터링 서비스에 전송 reportError(error, { eventData: event.data }); } ``` ### 6.3 네트워크 오류 ```javascript // Timeout 설정 (EventSource는 기본 타임아웃 없음) const connectionTimeout = setTimeout(() => { if (!isConnected) { console.error('연결 타임아웃'); eventSource.close(); handleConnectionTimeout(); } }, 10000); // 10초 eventSource.onopen = function() { clearTimeout(connectionTimeout); setIsConnected(true); }; ``` --- ## 7. 운영 환경 배포 시 변경 사항 ### 7.1 인증 헤더 추가 (운영 환경) ⚠️ **중요**: 개발 환경에서는 인증이 해제되어 있지만, **운영 환경에서는 JWT 토큰이 필요**합니다. ```javascript // EventSource는 헤더를 직접 설정할 수 없으므로 URL에 토큰 포함 const token = getAccessToken(); const apiUrl = `${API_BASE_URL}/api/suggestions/meetings/${meetingId}/stream?token=${token}`; // 또는 fetch API + ReadableStream 사용 (권장) const response = await fetch(apiUrl, { headers: { 'Authorization': `Bearer ${token}` } }); const reader = response.body.getReader(); // SSE 파싱 로직 구현 ``` ### 7.2 CORS 설정 확인 운영 환경 도메인이 백엔드 CORS 설정에 포함되어 있는지 확인: ```yaml # application.yml cors: allowed-origins: https://your-production-domain.com ``` --- ## 8. AI 개발 완료 후 변경 사항 ### 8.1 제거할 백엔드 코드 - [SuggestionService.java:102](ai/src/main/java/com/unicorn/hgzero/ai/biz/service/SuggestionService.java:102) - Mock 데이터 발행 호출 - [SuggestionService.java:192-236](ai/src/main/java/com/unicorn/hgzero/ai/biz/service/SuggestionService.java:192-236) - Mock 메서드 전체 - [SecurityConfig.java:49](ai/src/main/java/com/unicorn/hgzero/ai/infra/config/SecurityConfig.java:49) - 인증 해제 설정 ### 8.2 프론트엔드는 변경 불필요 - SSE 연결 코드는 그대로 유지 - API URL만 운영 환경에 맞게 수정 - JWT 토큰 추가 (위 7.1 참고) ### 8.3 실제 AI 동작 방식 (예상) ``` STT 텍스트 생성 → Event Hub 전송 → AI 서비스 수신 → 텍스트 축적 (Redis) → 임계값 도달 → Claude API 분석 → SSE로 제안사항 발행 → 프론트엔드 수신 ``` 현재 Mock은 **5초, 10초, 15초**에 발행하지만, 실제 AI는 **회의 진행 상황에 따라 동적으로** 발행됩니다. --- ## 9. 알려진 제한사항 ### 9.1 브라우저 호환성 - **EventSource는 IE 미지원** (Edge, Chrome, Firefox, Safari는 지원) - 필요 시 Polyfill 사용: `event-source-polyfill` ### 9.2 연결 제한 - 동일 도메인에 대한 SSE 연결은 브라우저당 **6개로 제한** - 여러 탭에서 동시 접속 시 주의 ### 9.3 재연결 - EventSource는 자동 재연결을 시도하지만, 서버에서 연결을 끊으면 재연결 안 됨 - 수동 재연결 로직 구현 권장 ### 9.4 Mock 데이터 특성 - **개발 환경 전용**: 3개 제안 후 자동 종료 - **실제 AI**: 회의 진행 중 계속 발행, 회의 종료 시까지 연결 유지 --- ## 10. 테스트 방법 ### 10.1 로컬 테스트 ```bash # 1. AI 서비스 실행 python3 tools/run-intellij-service-profile.py ai # 2. HTTP 서버 실행 (file:// 프로토콜은 CORS 제한) cd design/uiux/prototype python3 -m http.server 8000 # 3. 브라우저에서 접속 open http://localhost:8000/05-회의진행.html ``` ### 10.2 디버깅 ```javascript // 브라우저 개발자 도구 Console 탭에서 확인 // [DEBUG] 로그로 상세 정보 출력 // [ERROR] 로그로 에러 추적 ``` ### 10.3 curl 테스트 ```bash # Python 버전 (새 포트) curl -N http://localhost:8087/api/v1/ai/suggestions/meetings/test-meeting/stream # Java 버전 (구 포트 - 사용 중단 예정) # curl -N http://localhost:8083/api/suggestions/meetings/550e8400-e29b-41d4-a716-446655440000/stream ``` --- ## 11. 참고 문서 - [AI Service API 설계서](../../design/backend/api/spec/ai-service-api-spec.md) - [AI 샘플 데이터 통합 가이드](dev-ai-sample-data-guide.md) - [SSE 스트리밍 가이드](dev-ai-realtime-streaming.md) --- ## 12. FAQ ### Q1. 왜 EventSource를 사용하나요? **A**: WebSocket보다 단방향 통신에 적합하고, 자동 재연결 기능이 있으며, 구현이 간단합니다. ### Q2. 제안사항이 중복으로 표시되는 경우? **A**: `addSuggestionToUI` 함수에 중복 체크 로직이 있는지 확인하세요. ### Q3. 연결은 되는데 데이터가 안 오는 경우? **A**: 1. 백엔드 로그 확인 (`ai/logs/ai-service.log`) 2. Network 탭에서 `stream` 요청 확인 3. `ai-suggestion` 이벤트 리스너가 등록되었는지 확인 ### Q4. 운영 환경에서 401 Unauthorized 에러? **A**: JWT 토큰이 필요합니다. 7.1절 "인증 헤더 추가" 참고. --- ## 문서 이력 | 버전 | 작성일 | 작성자 | 변경 내용 | |------|--------|--------|----------| | 1.0 | 2025-10-27 | 준호 (Backend), 유진 (Frontend) | 초안 작성 |