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

10 KiB
Raw Permalink Blame History

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 데이터 자동 발행 메서드

/**
 * 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 스트리밍 메서드 수정

@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 샘플 데이터 구조

{
  "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 연결 함수

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 추가 함수

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

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

3. 테스트 방법

3.1 서비스 실행

1단계: AI 서비스 실행

# IntelliJ 실행 프로파일 사용
python3 tools/run-intellij-service-profile.py ai

# 또는 직접 실행
cd ai
./gradlew bootRun

실행 확인:

  • 포트: 8082
  • 로그 확인: ai/logs/ai-service.log

2단계: 프론트엔드 HTML 열기

# 브라우저에서 직접 열기
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초 대기: 세 번째 제안사항 표시

백엔드 로그 확인

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)

# 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 확인

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

// TODO: AI 개발 완료 후 제거 - 이 줄 삭제
startMockDataEmission(meetingId, sink);

// TODO: AI 개발 완료 후 제거 - 이 메서드 전체 삭제
private void startMockDataEmission(...) { ... }

프론트엔드 (회의진행.html)

  • SSE 연결 코드는 그대로 유지
  • API URL만 실제 환경에 맞게 수정:
// 개발 환경
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 서비스가 실행 중인지 확인
    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. 관련 문서


문서 이력

버전 작성일 작성자 변경 내용
1.0 2025-10-27 준호 (Backend Developer) 초안 작성