# 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:8086/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:8086/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:8086/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:8086';

// 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:8086/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) | 초안 작성 |
