jeongsoo's picture
init
d93e680
raw
history blame
10.5 kB
/**
* RAG 검색 챗봇 UI μ½”μ–΄ JavaScript
*/
// μ „μ—­ λ³€μˆ˜
let currentLLM = 'openai';
let supportedLLMs = [];
// DOM λ³€μˆ˜ 미리 μ„ μ–Έ
let chatTab, docsTab, deviceTab, chatSection, docsSection, deviceSection;
let chatMessages, userInput, sendButton;
let micButton, stopRecordingButton, recordingStatus;
let llmSelect, currentLLMInfo;
// λ…ΉμŒ κ΄€λ ¨ λ³€μˆ˜
let mediaRecorder = null;
let audioChunks = [];
let isRecording = false;
/**
* μ•± μ΄ˆκΈ°ν™” μƒνƒœ 확인 ν•¨μˆ˜
*/
async function checkAppStatus() {
try {
const response = await fetch('/api/status');
if (!response.ok) {
return false;
}
const data = await response.json();
return data.ready;
} catch (error) {
console.error('μƒνƒœ 확인 μ‹€νŒ¨:', error);
return false;
}
}
/**
* DOM μš”μ†Œ μ΄ˆκΈ°ν™” ν•¨μˆ˜
*/
function initDomElements() {
console.log('DOM μš”μ†Œ μ΄ˆκΈ°ν™” 쀑...');
// νƒ­ κ΄€λ ¨ μš”μ†Œ
chatTab = document.getElementById('chatTab');
docsTab = document.getElementById('docsTab');
deviceTab = document.getElementById('deviceTab');
chatSection = document.getElementById('chatSection');
docsSection = document.getElementById('docsSection');
deviceSection = document.getElementById('deviceSection');
// μ±„νŒ… κ΄€λ ¨ μš”μ†Œ
chatMessages = document.getElementById('chatMessages');
userInput = document.getElementById('userInput');
sendButton = document.getElementById('sendButton');
// μŒμ„± λ…ΉμŒ κ΄€λ ¨ μš”μ†Œ
micButton = document.getElementById('micButton');
stopRecordingButton = document.getElementById('stopRecordingButton');
recordingStatus = document.getElementById('recordingStatus');
// LLM κ΄€λ ¨ μš”μ†Œ
llmSelect = document.getElementById('llmSelect');
currentLLMInfo = document.getElementById('currentLLMInfo');
console.log('DOM μš”μ†Œ μ΄ˆκΈ°ν™” μ™„λ£Œ');
}
/**
* 이벀트 λ¦¬μŠ€λ„ˆ μ΄ˆκΈ°ν™” ν•¨μˆ˜
*/
function initEventListeners() {
console.log('이벀트 λ¦¬μŠ€λ„ˆ μ΄ˆκΈ°ν™” 쀑...');
// νƒ­ μ „ν™˜ 이벀트 λ¦¬μŠ€λ„ˆ
chatTab.addEventListener('click', () => {
switchTab('chat');
});
docsTab.addEventListener('click', () => {
switchTab('docs');
loadDocuments();
});
deviceTab.addEventListener('click', () => {
switchTab('device');
loadDeviceStatus();
});
// LLM 선택 이벀트 λ¦¬μŠ€λ„ˆ
llmSelect.addEventListener('change', (event) => {
changeLLM(event.target.value);
});
// λ©”μ‹œμ§€ 전솑 이벀트 λ¦¬μŠ€λ„ˆ
sendButton.addEventListener('click', sendMessage);
userInput.addEventListener('keydown', (event) => {
if (event.key === 'Enter' && !event.shiftKey) {
event.preventDefault();
sendMessage();
}
});
// μŒμ„± 인식 이벀트 λ¦¬μŠ€λ„ˆ
micButton.addEventListener('click', startRecording);
stopRecordingButton.addEventListener('click', stopRecording);
// μžλ™ μž…λ ₯ ν•„λ“œ 크기 μ‘°μ •
userInput.addEventListener('input', adjustTextareaHeight);
console.log('이벀트 λ¦¬μŠ€λ„ˆ μ΄ˆκΈ°ν™” μ™„λ£Œ');
}
/**
* νƒ­ μ „ν™˜ ν•¨μˆ˜
* @param {string} tabName - ν™œμ„±ν™”ν•  νƒ­ 이름 ('chat', 'docs', λ˜λŠ” 'device')
*/
function switchTab(tabName) {
console.log(`νƒ­ μ „ν™˜: ${tabName}`);
// λͺ¨λ“  탭을 λΉ„ν™œμ„±ν™”
[chatTab, docsTab, deviceTab].forEach(tab => tab.classList.remove('active'));
[chatSection, docsSection, deviceSection].forEach(section => section.classList.remove('active'));
// μ„ νƒν•œ νƒ­ ν™œμ„±ν™”
if (tabName === 'chat') {
chatTab.classList.add('active');
chatSection.classList.add('active');
} else if (tabName === 'docs') {
docsTab.classList.add('active');
docsSection.classList.add('active');
} else if (tabName === 'device') {
deviceTab.classList.add('active');
deviceSection.classList.add('active');
}
}
/**
* μ‹œμŠ€ν…œ μ•Œλ¦Ό λ©”μ‹œμ§€ μΆ”κ°€
* @param {string} message - μ•Œλ¦Ό λ©”μ‹œμ§€
*/
function addSystemNotification(message) {
console.log(`μ‹œμŠ€ν…œ μ•Œλ¦Ό μΆ”κ°€: ${message}`);
const messageDiv = document.createElement('div');
messageDiv.classList.add('message', 'system');
const contentDiv = document.createElement('div');
contentDiv.classList.add('message-content');
const messageP = document.createElement('p');
messageP.innerHTML = `<i class="fas fa-info-circle"></i> ${message}`;
contentDiv.appendChild(messageP);
messageDiv.appendChild(contentDiv);
chatMessages.appendChild(messageDiv);
// μŠ€ν¬λ‘€μ„ κ°€μž₯ μ•„λž˜λ‘œ 이동
chatMessages.scrollTop = chatMessages.scrollHeight;
}
/**
* λ©”μ‹œμ§€ μΆ”κ°€ ν•¨μˆ˜
* @param {string} text - λ©”μ‹œμ§€ λ‚΄μš©
* @param {string} sender - λ©”μ‹œμ§€ λ°œμ‹ μž ('user' λ˜λŠ” 'bot' λ˜λŠ” 'system')
* @param {string|null} transcription - μŒμ„± 인식 ν…μŠ€νŠΈ (선택 사항)
* @param {Array|null} sources - μ†ŒμŠ€ 정보 λ°°μ—΄ (선택 사항)
*/
function addMessage(text, sender, transcription = null, sources = null) {
console.log(`λ©”μ‹œμ§€ μΆ”κ°€: ${sender}`);
const messageDiv = document.createElement('div');
messageDiv.classList.add('message', sender);
const contentDiv = document.createElement('div');
contentDiv.classList.add('message-content');
// μŒμ„± 인식 ν…μŠ€νŠΈ μΆ”κ°€ (μžˆλŠ” 경우)
if (transcription && sender === 'bot') {
const transcriptionP = document.createElement('p');
transcriptionP.classList.add('transcription');
transcriptionP.textContent = `"${transcription}"`;
contentDiv.appendChild(transcriptionP);
}
// λ©”μ‹œμ§€ ν…μŠ€νŠΈ μΆ”κ°€
const textP = document.createElement('p');
textP.textContent = text;
contentDiv.appendChild(textP);
// μ†ŒμŠ€ 정보 μΆ”κ°€ (μžˆλŠ” 경우)
if (sources && sources.length > 0 && sender === 'bot') {
const sourcesDiv = document.createElement('div');
sourcesDiv.classList.add('sources');
const sourcesTitle = document.createElement('strong');
sourcesTitle.textContent = '좜처: ';
sourcesDiv.appendChild(sourcesTitle);
sources.forEach((source, index) => {
if (index < 3) { // μ΅œλŒ€ 3κ°œκΉŒμ§€λ§Œ ν‘œμ‹œ
const sourceSpan = document.createElement('span');
sourceSpan.classList.add('source-item');
sourceSpan.textContent = source.source;
sourcesDiv.appendChild(sourceSpan);
}
});
contentDiv.appendChild(sourcesDiv);
}
messageDiv.appendChild(contentDiv);
chatMessages.appendChild(messageDiv);
// μŠ€ν¬λ‘€μ„ κ°€μž₯ μ•„λž˜λ‘œ 이동
chatMessages.scrollTop = chatMessages.scrollHeight;
}
/**
* λ‘œλ”© λ©”μ‹œμ§€ μΆ”κ°€ ν•¨μˆ˜
* @returns {string} λ‘œλ”© λ©”μ‹œμ§€ ID
*/
function addLoadingMessage() {
console.log('λ‘œλ”© λ©”μ‹œμ§€ μΆ”κ°€');
const id = 'loading-' + Date.now();
const messageDiv = document.createElement('div');
messageDiv.classList.add('message', 'bot');
messageDiv.id = id;
const contentDiv = document.createElement('div');
contentDiv.classList.add('message-content');
const loadingP = document.createElement('p');
loadingP.innerHTML = '<div class="spinner" style="width: 20px; height: 20px; display: inline-block; margin-right: 10px;"></div> 생각 쀑...';
contentDiv.appendChild(loadingP);
messageDiv.appendChild(contentDiv);
chatMessages.appendChild(messageDiv);
// μŠ€ν¬λ‘€μ„ κ°€μž₯ μ•„λž˜λ‘œ 이동
chatMessages.scrollTop = chatMessages.scrollHeight;
return id;
}
/**
* λ‘œλ”© λ©”μ‹œμ§€ 제거 ν•¨μˆ˜
* @param {string} id - λ‘œλ”© λ©”μ‹œμ§€ ID
*/
function removeLoadingMessage(id) {
console.log(`λ‘œλ”© λ©”μ‹œμ§€ 제거: ${id}`);
const loadingMessage = document.getElementById(id);
if (loadingMessage) {
loadingMessage.remove();
}
}
/**
* 였λ₯˜ λ©”μ‹œμ§€ μΆ”κ°€ ν•¨μˆ˜
* @param {string} errorText - 였λ₯˜ λ©”μ‹œμ§€ λ‚΄μš©
*/
function addErrorMessage(errorText) {
console.log(`였λ₯˜ λ©”μ‹œμ§€ μΆ”κ°€: ${errorText}`);
const messageDiv = document.createElement('div');
messageDiv.classList.add('message', 'system');
const contentDiv = document.createElement('div');
contentDiv.classList.add('message-content');
contentDiv.style.backgroundColor = 'rgba(239, 68, 68, 0.1)';
contentDiv.style.color = 'var(--error-color)';
const errorP = document.createElement('p');
errorP.innerHTML = `<i class="fas fa-exclamation-circle"></i> ${errorText}`;
contentDiv.appendChild(errorP);
messageDiv.appendChild(contentDiv);
chatMessages.appendChild(messageDiv);
// μŠ€ν¬λ‘€μ„ κ°€μž₯ μ•„λž˜λ‘œ 이동
chatMessages.scrollTop = chatMessages.scrollHeight;
}
/**
* textarea 높이 μžλ™ μ‘°μ • ν•¨μˆ˜
*/
function adjustTextareaHeight() {
userInput.style.height = 'auto';
userInput.style.height = Math.min(userInput.scrollHeight, 100) + 'px';
}
// μ•± μ΄ˆκΈ°ν™” (νŽ˜μ΄μ§€ λ‘œλ“œ μ‹œ)
document.addEventListener('DOMContentLoaded', function() {
console.log('νŽ˜μ΄μ§€ λ‘œλ“œ μ™„λ£Œ, μ•± μ΄ˆκΈ°ν™” μ‹œμž‘');
// DOM μš”μ†Œ μ΄ˆκΈ°ν™”
initDomElements();
// 이벀트 λ¦¬μŠ€λ„ˆ μ΄ˆκΈ°ν™”
initEventListeners();
// μ•± μƒνƒœ 확인 (λ‘œλ”© νŽ˜μ΄μ§€κ°€ μ•„λ‹Œ κ²½μš°μ—λ§Œ)
if (window.location.pathname === '/' && !document.getElementById('app-loading-indicator')) {
// μ•± μƒνƒœ 주기적으둜 확인
const statusInterval = setInterval(async () => {
const isReady = await checkAppStatus();
if (isReady) {
clearInterval(statusInterval);
console.log('앱이 μ€€λΉ„λ˜μ—ˆμŠ΅λ‹ˆλ‹€.');
// 앱이 μ€€λΉ„λ˜λ©΄ LLM λͺ©λ‘ λ‘œλ“œ
loadLLMs();
// ν™œμ„± νƒ­ 확인 및 데이터 λ‘œλ“œ
if (docsSection.classList.contains('active')) {
loadDocuments();
} else if (deviceSection.classList.contains('active')) {
loadDeviceStatus();
}
}
}, 5000);
}
console.log('μ•± μ΄ˆκΈ°ν™” μ™„λ£Œ');
});