hgzero/develop/dev/dev-ai-frontend-integration.md
Minseo-Jo ab39e8d4ea ai-python 포트 8087 완전 통일 및 meeting-ai 테스트 완료
[포트 통일]
- ai-python 서비스 포트를 8087로 완전 통일
- 모든 문서에서 8086 참조 제거
- README.md, 개발 가이드 문서 전부 8087로 업데이트

변경 파일:
- ai-python/README.md
- develop/dev/ai-frontend-integration-guide.md
- develop/dev/dev-*.md (5개 파일)

[meeting-ai 테스트]
테스트 완료 항목:
✓ 회의록 통합 및 취합
✓ AI 한줄 요약/상세 요약 생성
✓ 회의 전체 결정사항 추출
✓ TODO 자동 추출 (9개)
✓ 통계 정보 생성
✓ 주요 키워드 추출 (10개)

테스트 파일:
- develop/test/meeting-ai-test-data.json (테스트 데이터)
- develop/test/consolidate-response.json (API 응답)
- develop/test/meeting-ai-test-result.md (상세 결과서)
2025-10-29 17:53:16 +09:00

483 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 = `
<div class="ai-suggestion-card" id="suggestion-${suggestion.id}">
<div class="ai-suggestion-header">
<span class="ai-suggestion-time">${escapeHtml(suggestion.timestamp)}</span>
<button onclick="handleAddToMemo('${escapeHtml(suggestion.content)}')">
</button>
</div>
<div class="ai-suggestion-text">
${escapeHtml(suggestion.content)}
</div>
</div>
`;
container.insertAdjacentHTML('beforeend', html);
}
```
### 3.3 XSS 방지
```javascript
function escapeHtml(text) {
const map = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#039;'
};
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<Suggestion[]>([]);
const [isConnected, setIsConnected] = useState(false);
const [error, setError] = useState<Error | null>(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 <div>Error: {error.message}</div>;
}
return (
<div>
<div>연결 상태: {isConnected ? '연결됨' : '연결 안 됨'}</div>
<div className="suggestions-list">
{suggestions.map(suggestion => (
<div key={suggestion.id} className="suggestion-card">
<span className="timestamp">{suggestion.timestamp}</span>
<p>{suggestion.content}</p>
<button onClick={() => addToMemo(suggestion.content)}>
메모에 추가
</button>
</div>
))}
</div>
</div>
);
}
```
---
## 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) | 초안 작성 |