mirror of
https://github.com/hwanny1128/HGZero.git
synced 2025-12-06 13:46:24 +00:00
- AI 서비스 CORS 설정 업데이트 - 회의 진행 프로토타입 수정 - 빌드 리포트 및 로그 파일 업데이트 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
401 lines
10 KiB
Markdown
401 lines
10 KiB
Markdown
# AI 샘플 데이터 통합 가이드
|
||
|
||
## 개요
|
||
|
||
AI 서비스 개발이 완료되지 않은 상황에서 프론트엔드 개발을 병행하기 위해 **샘플 데이터 자동 발행 기능**을 구현했습니다.
|
||
|
||
### 목적
|
||
- 프론트엔드 개발자가 AI 기능 완성을 기다리지 않고 화면 개발 가능
|
||
- 실시간 SSE(Server-Sent Events) 스트리밍 동작 테스트
|
||
- 회의 진행 중 AI 제안사항 표시 기능 검증
|
||
|
||
### 주요 기능
|
||
- **백엔드**: AI Service에서 5초마다 샘플 제안사항 3개 자동 발행
|
||
- **프론트엔드**: EventSource API를 통한 실시간 데이터 수신 및 화면 표시
|
||
|
||
---
|
||
|
||
## 1. 백엔드 구현
|
||
|
||
### 1.1 수정 파일
|
||
- **파일**: `ai/src/main/java/com/unicorn/hgzero/ai/biz/service/SuggestionService.java`
|
||
- **수정 내용**: `startMockDataEmission()` 메서드 추가
|
||
|
||
### 1.2 구현 내용
|
||
|
||
#### Mock 데이터 자동 발행 메서드
|
||
```java
|
||
/**
|
||
* TODO: AI 개발 완료 후 제거
|
||
* Mock 데이터 자동 발행 (프론트엔드 개발용)
|
||
* 5초마다 샘플 제안사항을 발행합니다.
|
||
*/
|
||
private void startMockDataEmission(String meetingId, Sinks.Many<RealtimeSuggestionsDto> sink) {
|
||
// 프론트엔드 HTML에 맞춘 샘플 데이터 (3개)
|
||
List<SimpleSuggestionDto> mockSuggestions = List.of(
|
||
SimpleSuggestionDto.builder()
|
||
.id("suggestion-1")
|
||
.content("신제품의 타겟 고객층을 20-30대로 설정하고, 모바일 우선 전략을 취하기로 논의 중입니다.")
|
||
.timestamp("00:05:23")
|
||
.confidence(0.92)
|
||
.build(),
|
||
// ... 3개의 샘플 데이터
|
||
);
|
||
|
||
// 5초마다 하나씩 발행 (총 3개)
|
||
Flux.interval(Duration.ofSeconds(5))
|
||
.take(3)
|
||
.map(index -> {
|
||
SimpleSuggestionDto suggestion = mockSuggestions.get(index.intValue());
|
||
return RealtimeSuggestionsDto.builder()
|
||
.suggestions(List.of(suggestion))
|
||
.build();
|
||
})
|
||
.subscribe(
|
||
suggestions -> {
|
||
sink.tryEmitNext(suggestions);
|
||
log.info("Mock 제안사항 발행 완료");
|
||
}
|
||
);
|
||
}
|
||
```
|
||
|
||
#### SSE 스트리밍 메서드 수정
|
||
```java
|
||
@Override
|
||
public Flux<RealtimeSuggestionsDto> streamRealtimeSuggestions(String meetingId) {
|
||
// Sink 생성
|
||
Sinks.Many<RealtimeSuggestionsDto> sink = Sinks.many()
|
||
.multicast()
|
||
.onBackpressureBuffer();
|
||
|
||
meetingSinks.put(meetingId, sink);
|
||
|
||
// TODO: AI 개발 완료 후 제거 - Mock 데이터 자동 발행
|
||
startMockDataEmission(meetingId, sink);
|
||
|
||
return sink.asFlux()
|
||
.doOnCancel(() -> {
|
||
meetingSinks.remove(meetingId);
|
||
cleanupMeetingData(meetingId);
|
||
});
|
||
}
|
||
```
|
||
|
||
### 1.3 샘플 데이터 구조
|
||
|
||
```json
|
||
{
|
||
"suggestions": [
|
||
{
|
||
"id": "suggestion-1",
|
||
"content": "신제품의 타겟 고객층을 20-30대로 설정하고, 모바일 우선 전략을 취하기로 논의 중입니다.",
|
||
"timestamp": "00:05:23",
|
||
"confidence": 0.92
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 2. 프론트엔드 구현
|
||
|
||
### 2.1 수정 파일
|
||
- **파일**: `design/uiux/prototype/05-회의진행.html`
|
||
- **수정 내용**: SSE 연결 및 실시간 데이터 수신 코드 추가
|
||
|
||
### 2.2 구현 내용
|
||
|
||
#### SSE 연결 함수
|
||
```javascript
|
||
function connectAiSuggestionStream() {
|
||
const apiUrl = `http://localhost:8082/api/suggestions/meetings/${meetingId}/stream`;
|
||
|
||
eventSource = new EventSource(apiUrl);
|
||
|
||
eventSource.addEventListener('ai-suggestion', function(event) {
|
||
const data = JSON.parse(event.data);
|
||
const suggestions = data.suggestions;
|
||
|
||
if (suggestions && suggestions.length > 0) {
|
||
suggestions.forEach(suggestion => {
|
||
addAiSuggestionToUI(suggestion);
|
||
});
|
||
}
|
||
});
|
||
|
||
eventSource.onerror = function(error) {
|
||
console.error('SSE 연결 오류:', error);
|
||
eventSource.close();
|
||
};
|
||
}
|
||
```
|
||
|
||
#### UI 추가 함수
|
||
```javascript
|
||
function addAiSuggestionToUI(suggestion) {
|
||
const listContainer = document.getElementById('aiSuggestionList');
|
||
const cardId = `suggestion-${suggestion.id}`;
|
||
|
||
// 중복 방지
|
||
if (document.getElementById(cardId)) {
|
||
return;
|
||
}
|
||
|
||
// AI 제안 카드 HTML 생성
|
||
const cardHtml = `
|
||
<div class="ai-suggestion-card" id="${cardId}">
|
||
<div class="ai-suggestion-header">
|
||
<span class="ai-suggestion-time">${suggestion.timestamp}</span>
|
||
<button class="ai-suggestion-add-btn"
|
||
onclick="addToMemo('${escapeHtml(suggestion.content)}', document.getElementById('${cardId}'))"
|
||
title="메모에 추가">
|
||
➕
|
||
</button>
|
||
</div>
|
||
<div class="ai-suggestion-text">
|
||
${escapeHtml(suggestion.content)}
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
listContainer.insertAdjacentHTML('beforeend', cardHtml);
|
||
}
|
||
```
|
||
|
||
#### XSS 방지
|
||
```javascript
|
||
function escapeHtml(text) {
|
||
const map = {
|
||
'&': '&',
|
||
'<': '<',
|
||
'>': '>',
|
||
'"': '"',
|
||
"'": '''
|
||
};
|
||
return text.replace(/[&<>"']/g, m => map[m]);
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 3. 테스트 방법
|
||
|
||
### 3.1 서비스 실행
|
||
|
||
#### 1단계: AI 서비스 실행
|
||
```bash
|
||
# IntelliJ 실행 프로파일 사용
|
||
python3 tools/run-intellij-service-profile.py ai
|
||
|
||
# 또는 직접 실행
|
||
cd ai
|
||
./gradlew bootRun
|
||
```
|
||
|
||
**실행 확인**:
|
||
- 포트: `8082`
|
||
- 로그 확인: `ai/logs/ai-service.log`
|
||
|
||
#### 2단계: 프론트엔드 HTML 열기
|
||
```bash
|
||
# 브라우저에서 직접 열기
|
||
open design/uiux/prototype/05-회의진행.html
|
||
|
||
# 또는 HTTP 서버 실행
|
||
cd design/uiux/prototype
|
||
python3 -m http.server 8000
|
||
# 브라우저: http://localhost:8000/05-회의진행.html
|
||
```
|
||
|
||
### 3.2 동작 확인
|
||
|
||
#### 브라우저 콘솔 확인
|
||
1. 개발자 도구 열기 (F12)
|
||
2. Console 탭 확인
|
||
|
||
**예상 로그**:
|
||
```
|
||
AI 제안사항 SSE 스트림 연결됨: http://localhost:8082/api/suggestions/meetings/550e8400-e29b-41d4-a716-446655440000/stream
|
||
AI 제안사항 수신: {"suggestions":[{"id":"suggestion-1", ...}]}
|
||
AI 제안사항 추가됨: 신제품의 타겟 고객층을 20-30대로 설정하고...
|
||
```
|
||
|
||
#### 화면 동작 확인
|
||
1. **페이지 로드**: 회의진행.html 열기
|
||
2. **AI 제안 탭 클릭**: "AI 제안" 탭으로 이동
|
||
3. **5초 대기**: 첫 번째 제안사항 표시
|
||
4. **10초 대기**: 두 번째 제안사항 표시
|
||
5. **15초 대기**: 세 번째 제안사항 표시
|
||
|
||
#### 백엔드 로그 확인
|
||
```bash
|
||
tail -f ai/logs/ai-service.log
|
||
```
|
||
|
||
**예상 로그**:
|
||
```
|
||
실시간 AI 제안사항 스트리밍 시작 - meetingId: 550e8400-e29b-41d4-a716-446655440000
|
||
Mock 데이터 자동 발행 시작 - meetingId: 550e8400-e29b-41d4-a716-446655440000
|
||
Mock 제안사항 발행 - meetingId: 550e8400-e29b-41d4-a716-446655440000, 제안: 신제품의 타겟 고객층...
|
||
```
|
||
|
||
### 3.3 API 직접 테스트 (curl)
|
||
|
||
```bash
|
||
# SSE 스트림 연결
|
||
curl -N http://localhost:8082/api/suggestions/meetings/550e8400-e29b-41d4-a716-446655440000/stream
|
||
```
|
||
|
||
**예상 응답**:
|
||
```
|
||
event: ai-suggestion
|
||
id: 123456789
|
||
data: {"suggestions":[{"id":"suggestion-1","content":"신제품의 타겟 고객층...","timestamp":"00:05:23","confidence":0.92}]}
|
||
|
||
event: ai-suggestion
|
||
id: 987654321
|
||
data: {"suggestions":[{"id":"suggestion-2","content":"개발 일정...","timestamp":"00:08:45","confidence":0.88}]}
|
||
```
|
||
|
||
---
|
||
|
||
## 4. CORS 설정 (필요 시)
|
||
|
||
프론트엔드를 다른 포트에서 실행할 경우 CORS 설정이 필요합니다.
|
||
|
||
### 4.1 application.yml 확인
|
||
```yaml
|
||
# ai/src/main/resources/application.yml
|
||
spring:
|
||
web:
|
||
cors:
|
||
allowed-origins:
|
||
- http://localhost:8000
|
||
- http://localhost:3000
|
||
allowed-methods:
|
||
- GET
|
||
- POST
|
||
- PUT
|
||
- DELETE
|
||
allowed-headers:
|
||
- "*"
|
||
allow-credentials: true
|
||
```
|
||
|
||
---
|
||
|
||
## 5. 주의사항
|
||
|
||
### 5.1 Mock 데이터 제거 시점
|
||
⚠️ **AI 개발 완료 후 반드시 제거해야 할 코드**:
|
||
|
||
#### 백엔드 (SuggestionService.java)
|
||
```java
|
||
// TODO: AI 개발 완료 후 제거 - 이 줄 삭제
|
||
startMockDataEmission(meetingId, sink);
|
||
|
||
// TODO: AI 개발 완료 후 제거 - 이 메서드 전체 삭제
|
||
private void startMockDataEmission(...) { ... }
|
||
```
|
||
|
||
#### 프론트엔드 (회의진행.html)
|
||
- SSE 연결 코드는 **그대로 유지**
|
||
- API URL만 실제 환경에 맞게 수정:
|
||
```javascript
|
||
// 개발 환경
|
||
const apiUrl = `http://localhost:8082/api/suggestions/meetings/${meetingId}/stream`;
|
||
|
||
// 운영 환경 (예시)
|
||
const apiUrl = `/api/suggestions/meetings/${meetingId}/stream`;
|
||
```
|
||
|
||
### 5.2 제한사항
|
||
|
||
1. **회의 ID 고정**
|
||
- 현재 테스트용 회의 ID가 하드코딩됨
|
||
- 실제 환경에서는 회의 생성 API 응답에서 받아야 함
|
||
|
||
2. **샘플 데이터 개수**
|
||
- 현재 3개로 제한
|
||
- 실제 AI는 회의 진행에 따라 동적으로 생성
|
||
|
||
3. **재연결 처리 없음**
|
||
- SSE 연결이 끊어지면 재연결하지 않음
|
||
- 실제 환경에서는 재연결 로직 필요
|
||
|
||
4. **인증/인가 없음**
|
||
- 현재 JWT 토큰 검증 없이 테스트
|
||
- 실제 환경에서는 인증 헤더 추가 필요
|
||
|
||
---
|
||
|
||
## 6. 트러블슈팅
|
||
|
||
### 문제 1: SSE 연결 안 됨
|
||
**증상**: 브라우저 콘솔에 "SSE 연결 오류" 표시
|
||
|
||
**해결 방법**:
|
||
1. AI 서비스가 실행 중인지 확인
|
||
```bash
|
||
curl http://localhost:8082/actuator/health
|
||
```
|
||
2. CORS 설정 확인
|
||
3. 방화벽/포트 확인
|
||
|
||
### 문제 2: 제안사항이 표시되지 않음
|
||
**증상**: SSE는 연결되지만 화면에 아무것도 표시되지 않음
|
||
|
||
**해결 방법**:
|
||
1. 브라우저 콘솔에서 에러 확인
|
||
2. Network 탭에서 SSE 이벤트 확인
|
||
3. 백엔드 로그 확인
|
||
|
||
### 문제 3: 중복 제안사항 표시
|
||
**증상**: 같은 제안이 여러 번 표시됨
|
||
|
||
**해결 방법**:
|
||
- 페이지 새로고침 (SSE 연결 재시작)
|
||
- 브라우저 캐시 삭제
|
||
|
||
---
|
||
|
||
## 7. 다음 단계
|
||
|
||
### AI 개발 완료 후 작업
|
||
1. **Mock 코드 제거**
|
||
- `startMockDataEmission()` 메서드 삭제
|
||
- 관련 TODO 주석 제거
|
||
|
||
2. **실제 AI 로직 연결**
|
||
- Claude API 연동
|
||
- Event Hub 메시지 수신
|
||
- Redis 텍스트 축적 및 분석
|
||
|
||
3. **프론트엔드 개선**
|
||
- 재연결 로직 추가
|
||
- 에러 핸들링 강화
|
||
- 로딩 상태 표시
|
||
|
||
4. **성능 최적화**
|
||
- SSE 연결 풀 관리
|
||
- 메모리 누수 방지
|
||
- 네트워크 재시도 전략
|
||
|
||
---
|
||
|
||
## 8. 관련 문서
|
||
|
||
- [AI Service API 설계서](../../design/backend/api/spec/ai-service-api-spec.md)
|
||
- [SSE 스트리밍 가이드](dev-ai-realtime-streaming.md)
|
||
- [백엔드 개발 가이드](dev-backend.md)
|
||
|
||
---
|
||
|
||
## 문서 이력
|
||
|
||
| 버전 | 작성일 | 작성자 | 변경 내용 |
|
||
|------|--------|--------|----------|
|
||
| 1.0 | 2025-10-27 | 준호 (Backend Developer) | 초안 작성 |
|