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

12 KiB
Raw Blame History

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)

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)
}

샘플 응답

{
  "suggestions": [
    {
      "id": "suggestion-1",
      "content": "신제품의 타겟 고객층을 20-30대로 설정하고, 모바일 우선 전략을 취하기로 논의 중입니다.",
      "timestamp": "00:05:23",
      "confidence": 0.92
    }
  ]
}

3. 프론트엔드 구현 방법

3.1 EventSource로 연결

// 회의 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에 제안사항 추가

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 방지

function escapeHtml(text) {
  const map = {
    '&': '&amp;',
    '<': '&lt;',
    '>': '&gt;',
    '"': '&quot;',
    "'": '&#039;'
  };
  return text.replace(/[&<>"']/g, m => map[m]);
}

3.4 연결 종료

// 페이지 종료 시 또는 회의 종료 시
window.addEventListener('beforeunload', function() {
  if (eventSource) {
    eventSource.close();
  }
});

4. React 통합 예시

4.1 Custom Hook

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 사용

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 개발 환경

// Python 버전 (권장)
const API_BASE_URL = 'http://localhost:8087';

// Java 버전 (구버전 - 사용 중단 예정)
// const API_BASE_URL = 'http://localhost:8083';

5.2 테스트 환경

const API_BASE_URL = 'https://test-api.hgzero.com';

5.3 운영 환경

// 같은 도메인에서 실행될 경우
const API_BASE_URL = '';

// 또는 환경변수 사용
const API_BASE_URL = process.env.REACT_APP_AI_API_URL;

6. 에러 처리

6.1 연결 실패

eventSource.onerror = function(error) {
  console.error('SSE 연결 실패:', error);

  // 사용자에게 알림
  showErrorNotification('AI 제안사항을 받을 수 없습니다. 다시 시도해주세요.');

  // 재연결 시도 (옵션)
  setTimeout(() => {
    reconnect();
  }, 5000);
};

6.2 파싱 오류

try {
  const data = JSON.parse(event.data);
} catch (error) {
  console.error('데이터 파싱 오류:', error);
  console.error('원본 데이터:', event.data);

  // Sentry 등 에러 모니터링 서비스에 전송
  reportError(error, { eventData: event.data });
}

6.3 네트워크 오류

// 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 토큰이 필요합니다.

// 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 설정에 포함되어 있는지 확인:

# application.yml
cors:
  allowed-origins: https://your-production-domain.com

8. AI 개발 완료 후 변경 사항

8.1 제거할 백엔드 코드

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 로컬 테스트

# 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 디버깅

// 브라우저 개발자 도구 Console 탭에서 확인
// [DEBUG] 로그로 상세 정보 출력
// [ERROR] 로그로 에러 추적

10.3 curl 테스트

# 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. 참고 문서


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