Spaces:
No application file
No application file
/** | |
* 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('μ± μ΄κΈ°ν μλ£'); | |
}); | |