mirror of
https://github.com/hwanny1128/HGZero.git
synced 2026-06-12 22:59:10 +00:00
Merge feature/stt-ai into main
주요 변경사항: - EventHub 공유 액세스 정책 재설정 (send-policy, listen-policy) - Redis DB 2번 읽기 전용 문제 해결 - AI-Python 서비스 추가 (FastAPI 기반) - STT WebSocket 실시간 스트리밍 구현 - AI 제안사항 실시간 추출 기능 구현 - 테스트 페이지 추가 (stt-test-wav.html) - 개발 가이드 문서 추가 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -857,6 +857,10 @@ components:
|
||||
type: string
|
||||
description: 공통 키워드
|
||||
example: ["MSA", "API Gateway", "Spring Boot"]
|
||||
summary:
|
||||
type: string
|
||||
description: 회의록 핵심 내용 요약 (1-2문장)
|
||||
example: "MSA 아키텍처 설계 및 API Gateway 도입을 결정. 서비스별 독립 배포 전략 수립."
|
||||
link:
|
||||
type: string
|
||||
description: 회의록 링크
|
||||
@@ -880,9 +884,22 @@ components:
|
||||
example: 0.92
|
||||
category:
|
||||
type: string
|
||||
enum: [기술, 업무, 도메인]
|
||||
enum: [기술, 업무, 도메인, 기획, 비즈니스, 전략, 마케팅]
|
||||
description: 용어 카테고리
|
||||
example: "기술"
|
||||
definition:
|
||||
type: string
|
||||
description: 용어 정의 (간단한 설명)
|
||||
example: "Microservices Architecture의 약자. 애플리케이션을 작은 독립적인 서비스로 나누는 아키텍처 패턴"
|
||||
context:
|
||||
type: string
|
||||
description: 용어가 사용된 맥락 (과거 회의록 참조)
|
||||
example: "신제품 기획 회의(2024-09-15)에서 언급"
|
||||
relatedMeetingId:
|
||||
type: string
|
||||
format: uuid
|
||||
description: 관련 회의 ID (용어가 논의된 과거 회의)
|
||||
example: "bb0e8400-e29b-41d4-a716-446655440006"
|
||||
highlight:
|
||||
type: boolean
|
||||
description: 하이라이트 여부
|
||||
|
||||
+188
@@ -0,0 +1,188 @@
|
||||
/**
|
||||
* AI 제안사항 SSE 연동 예시
|
||||
* 05-회의진행.html에 추가할 JavaScript 코드
|
||||
*/
|
||||
|
||||
// ============================================
|
||||
// 1. 전역 변수 선언
|
||||
// ============================================
|
||||
let eventSource = null;
|
||||
const meetingId = "test-meeting-001"; // 실제로는 URL 파라미터에서 가져옴
|
||||
|
||||
// ============================================
|
||||
// 2. SSE 연결 초기화
|
||||
// ============================================
|
||||
function initializeAiSuggestions() {
|
||||
console.log('AI 제안사항 SSE 연결 시작 - meetingId:', meetingId);
|
||||
|
||||
// SSE 연결
|
||||
eventSource = new EventSource(
|
||||
`http://localhost:8083/api/suggestions/meetings/${meetingId}/stream`
|
||||
);
|
||||
|
||||
// 연결 성공
|
||||
eventSource.onopen = function() {
|
||||
console.log('SSE 연결 성공');
|
||||
};
|
||||
|
||||
// AI 제안사항 수신
|
||||
eventSource.addEventListener('ai-suggestion', function(event) {
|
||||
console.log('AI 제안사항 수신:', event.data);
|
||||
|
||||
try {
|
||||
const data = JSON.parse(event.data);
|
||||
handleAiSuggestions(data);
|
||||
} catch (error) {
|
||||
console.error('JSON 파싱 오류:', error);
|
||||
}
|
||||
});
|
||||
|
||||
// 연결 오류
|
||||
eventSource.onerror = function(error) {
|
||||
console.error('SSE 연결 오류:', error);
|
||||
|
||||
// 자동 재연결은 브라우저가 처리
|
||||
// 필요시 수동 재연결 로직 추가 가능
|
||||
};
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 3. AI 제안사항 처리
|
||||
// ============================================
|
||||
function handleAiSuggestions(data) {
|
||||
console.log('AI 제안사항 처리:', data);
|
||||
|
||||
// data 형식:
|
||||
// {
|
||||
// "suggestions": [
|
||||
// {
|
||||
// "id": "sugg-001",
|
||||
// "content": "신제품의 타겟 고객층을...",
|
||||
// "timestamp": "00:05:23",
|
||||
// "confidence": 0.92
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
|
||||
if (data.suggestions && data.suggestions.length > 0) {
|
||||
data.suggestions.forEach(suggestion => {
|
||||
addSuggestionCard(suggestion);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 4. 제안사항 카드 추가
|
||||
// ============================================
|
||||
function addSuggestionCard(suggestion) {
|
||||
const card = document.createElement('div');
|
||||
card.className = 'ai-suggestion-card';
|
||||
card.id = 'suggestion-' + suggestion.id;
|
||||
|
||||
// 타임스탬프 (있으면 사용, 없으면 현재 시간)
|
||||
const timestamp = suggestion.timestamp || getCurrentRecordingTime();
|
||||
|
||||
card.innerHTML = `
|
||||
<div class="ai-suggestion-header">
|
||||
<span class="ai-suggestion-time">${timestamp}</span>
|
||||
<button class="ai-suggestion-add-btn"
|
||||
onclick="addToMemo('${escapeHtml(suggestion.content)}', document.getElementById('suggestion-${suggestion.id}'))"
|
||||
title="메모에 추가">
|
||||
➕
|
||||
</button>
|
||||
</div>
|
||||
<div class="ai-suggestion-text">
|
||||
${escapeHtml(suggestion.content)}
|
||||
</div>
|
||||
${suggestion.confidence ? `
|
||||
<div class="ai-suggestion-confidence">
|
||||
<span style="font-size: 11px; color: var(--gray-500);">
|
||||
신뢰도: ${Math.round(suggestion.confidence * 100)}%
|
||||
</span>
|
||||
</div>
|
||||
` : ''}
|
||||
`;
|
||||
|
||||
// aiSuggestionList의 맨 위에 추가 (최신 항목이 위로)
|
||||
const listElement = document.getElementById('aiSuggestionList');
|
||||
if (listElement) {
|
||||
listElement.insertBefore(card, listElement.firstChild);
|
||||
|
||||
// 부드러운 등장 애니메이션
|
||||
setTimeout(() => {
|
||||
card.style.opacity = '0';
|
||||
card.style.transform = 'translateY(-10px)';
|
||||
card.style.transition = 'all 0.3s ease';
|
||||
|
||||
setTimeout(() => {
|
||||
card.style.opacity = '1';
|
||||
card.style.transform = 'translateY(0)';
|
||||
}, 10);
|
||||
}, 0);
|
||||
} else {
|
||||
console.error('aiSuggestionList 엘리먼트를 찾을 수 없습니다.');
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 5. 유틸리티 함수
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* 현재 녹음 시간 가져오기 (HH:MM 형식)
|
||||
*/
|
||||
function getCurrentRecordingTime() {
|
||||
const timerElement = document.getElementById('recordingTime');
|
||||
if (timerElement) {
|
||||
const time = timerElement.textContent;
|
||||
return time.substring(0, 5); // "00:05:23" -> "00:05"
|
||||
}
|
||||
return "00:00";
|
||||
}
|
||||
|
||||
/**
|
||||
* HTML 이스케이프 (XSS 방지)
|
||||
*/
|
||||
function escapeHtml(text) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
/**
|
||||
* SSE 연결 종료
|
||||
*/
|
||||
function closeAiSuggestions() {
|
||||
if (eventSource) {
|
||||
console.log('SSE 연결 종료');
|
||||
eventSource.close();
|
||||
eventSource = null;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 6. 페이지 로드 시 자동 시작
|
||||
// ============================================
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
console.log('페이지 로드 완료 - AI 제안사항 초기화');
|
||||
|
||||
// SSE 연결 시작
|
||||
initializeAiSuggestions();
|
||||
|
||||
// 페이지 닫을 때 SSE 연결 종료
|
||||
window.addEventListener('beforeunload', function() {
|
||||
closeAiSuggestions();
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================
|
||||
// 7. 회의 종료 시 SSE 연결 종료
|
||||
// ============================================
|
||||
// 기존 endMeeting 함수 수정
|
||||
const originalEndMeeting = window.endMeeting;
|
||||
window.endMeeting = function() {
|
||||
closeAiSuggestions(); // SSE 연결 종료
|
||||
if (originalEndMeeting) {
|
||||
originalEndMeeting(); // 기존 로직 실행
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user