# 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) | 초안 작성 |
