hgzero/develop/dev/dev-ai-sample-data-guide.md
Minseo-Jo 14d03dcacf STT-AI 통합 작업 진행 중 변경사항 커밋
- AI 서비스 CORS 설정 업데이트
- 회의 진행 프로토타입 수정
- 빌드 리포트 및 로그 파일 업데이트

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-27 13:17:47 +09:00

401 lines
10 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 서비스 개발이 완료되지 않은 상황에서 프론트엔드 개발을 병행하기 위해 **샘플 데이터 자동 발행 기능**을 구현했습니다.
### 목적
- 프론트엔드 개발자가 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 = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#039;'
};
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) | 초안 작성 |