Spaces:
Running
Running
/** | |
* RAG ๊ฒ์ ์ฑ๋ด UI JavaScript | |
* ๋ฉ์ธ ํ์ผ - ์ฝ์ด ๋ฐ ์ฅ์น ์ ์ด ๋ชจ๋๊ณผ ํตํฉ | |
*/ | |
// DOM ์์ | |
const chatTab = document.getElementById('chatTab'); | |
const docsTab = document.getElementById('docsTab'); | |
const deviceTab = document.getElementById('deviceTab'); // ์ฅ์น ์ ์ด ํญ ์ถ๊ฐ | |
const chatSection = document.getElementById('chatSection'); | |
const docsSection = document.getElementById('docsSection'); | |
const deviceSection = document.getElementById('deviceSection'); // ์ฅ์น ์ ์ด ์น์ ์ถ๊ฐ | |
const chatMessages = document.getElementById('chatMessages'); | |
const userInput = document.getElementById('userInput'); | |
const sendButton = document.getElementById('sendButton'); | |
const micButton = document.getElementById('micButton'); | |
const stopRecordingButton = document.getElementById('stopRecordingButton'); | |
const recordingStatus = document.getElementById('recordingStatus'); | |
const uploadForm = document.getElementById('uploadForm'); | |
const documentFile = document.getElementById('documentFile'); | |
const fileName = document.getElementById('fileName'); | |
const uploadButton = document.getElementById('uploadButton'); | |
const uploadStatus = document.getElementById('uploadStatus'); | |
const refreshDocsButton = document.getElementById('refreshDocsButton'); | |
const docsList = document.getElementById('docsList'); | |
const docsLoading = document.getElementById('docsLoading'); | |
const noDocsMessage = document.getElementById('noDocsMessage'); | |
const llmSelect = document.getElementById('llmSelect'); | |
const currentLLMInfo = document.getElementById('currentLLMInfo'); | |
// LLM ๊ด๋ จ ๋ณ์ | |
let currentLLM = 'openai'; | |
let supportedLLMs = []; | |
// ๋ น์ ๊ด๋ จ ๋ณ์ | |
let mediaRecorder = null; | |
let audioChunks = []; | |
let isRecording = false; | |
// ์ฑ ์ด๊ธฐํ ์ํ ํ์ธ ํจ์ | |
async function checkAppStatus() { | |
try { | |
console.log('์ฑ ์ํ ํ์ธ ์์ฒญ ์ ์ก'); | |
const response = await fetch('/api/status'); | |
if (!response.ok) { | |
console.error(`์ฑ ์ํ ํ์ธ ์คํจ: ${response.status}`); | |
return false; | |
} | |
const data = await response.json(); | |
console.log(`์ฑ ์ํ ํ์ธ ๊ฒฐ๊ณผ: ${data.ready ? '์ค๋น๋จ' : '์ค๋น ์๋จ'}`); | |
return data.ready; | |
} catch (error) { | |
console.error('์ฑ ์ํ ํ์ธ ์ค ์ค๋ฅ ๋ฐ์:', error); | |
return false; | |
} | |
} | |
/** | |
* LLM ๋ชฉ๋ก ๋ก๋ ํจ์ | |
*/ | |
async function loadLLMs() { | |
try { | |
// API ์์ฒญ | |
console.log('LLM ๋ชฉ๋ก ์์ฒญ ์ ์ก'); | |
const response = await AppUtils.fetchWithTimeout('/api/llm', { | |
method: 'GET' | |
}); | |
const data = await response.json(); | |
supportedLLMs = data.supported_llms; | |
currentLLM = data.current_llm.id; | |
console.log(`LLM ๋ชฉ๋ก ๋ก๋ ์ฑ๊ณต: ${supportedLLMs.length}๊ฐ ๋ชจ๋ธ`); | |
// LLM ์ ํ ๋๋กญ๋ค์ด ์ ๋ฐ์ดํธ | |
llmSelect.innerHTML = ''; | |
supportedLLMs.forEach(llm => { | |
const option = document.createElement('option'); | |
option.value = llm.id; | |
option.textContent = llm.name; | |
option.selected = llm.current; | |
llmSelect.appendChild(option); | |
}); | |
// ํ์ฌ LLM ํ์ | |
updateCurrentLLMInfo(data.current_llm); | |
} catch (error) { | |
console.error('LLM ๋ชฉ๋ก ๋ก๋ ์คํจ:', error); | |
} | |
} | |
/** | |
* LLM ๋ณ๊ฒฝ ํจ์ | |
* @param {string} llmId - ๋ณ๊ฒฝํ LLM ID | |
*/ | |
async function changeLLM(llmId) { | |
try { | |
// API ์์ฒญ | |
console.log(`LLM ๋ณ๊ฒฝ ์์ฒญ: ${llmId}`); | |
const response = await AppUtils.fetchWithTimeout('/api/llm', { | |
method: 'POST', | |
headers: { | |
'Content-Type': 'application/json' | |
}, | |
body: JSON.stringify({ llm_id: llmId }) | |
}); | |
const data = await response.json(); | |
if (data.success) { | |
currentLLM = llmId; | |
updateCurrentLLMInfo(data.current_llm); | |
console.log(`LLM ๋ณ๊ฒฝ ์ฑ๊ณต: ${data.current_llm.name}`); | |
// ์์คํ ๋ฉ์์ง ์ถ๊ฐ | |
const systemMessage = `LLM์ด ${data.current_llm.name}(์ผ)๋ก ๋ณ๊ฒฝ๋์์ต๋๋ค. ๋ชจ๋ธ: ${data.current_llm.model}`; | |
AppUtils.addSystemNotification(systemMessage); | |
} else if (data.error) { | |
console.error('LLM ๋ณ๊ฒฝ ์ค๋ฅ:', data.error); | |
alert(`LLM ๋ณ๊ฒฝ ์ค๋ฅ: ${data.error}`); | |
} | |
} catch (error) { | |
console.error('LLM ๋ณ๊ฒฝ ์คํจ:', error); | |
alert('LLM ๋ณ๊ฒฝ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค.'); | |
} | |
} | |
/** | |
* ํ์ฌ LLM ์ ๋ณด ํ์ ์ ๋ฐ์ดํธ | |
* @param {Object} llmInfo - LLM ์ ๋ณด ๊ฐ์ฒด | |
*/ | |
function updateCurrentLLMInfo(llmInfo) { | |
if (currentLLMInfo) { | |
currentLLMInfo.textContent = `${llmInfo.name} (${llmInfo.model})`; | |
} | |
} | |
// ํญ ์ ํ ํจ์ | |
function switchTab(tabName) { | |
console.log(`ํญ ์ ํ: ${tabName}`); | |
// ๋ชจ๋ ํญ๊ณผ ์น์ ๋นํ์ฑํ | |
chatTab.classList.remove('active'); | |
docsTab.classList.remove('active'); | |
deviceTab.classList.remove('active'); | |
chatSection.classList.remove('active'); | |
docsSection.classList.remove('active'); | |
deviceSection.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'); | |
// ๋ฌธ์ ๋ชฉ๋ก ๋ก๋ | |
loadDocuments(); | |
} else if (tabName === 'device') { | |
deviceTab.classList.add('active'); | |
deviceSection.classList.add('active'); | |
} | |
} | |
// ์ฑํ ๋ฉ์์ง ์ ์ก ํจ์ | |
async function sendMessage() { | |
const message = userInput.value.trim(); | |
if (!message) { | |
console.log('๋ฉ์์ง๊ฐ ๋น์ด์์ด ์ ์กํ์ง ์์'); | |
return; | |
} | |
console.log('๋ฉ์์ง ์ ์ก ์์'); | |
// UI ์ ๋ฐ์ดํธ | |
addMessage(message, 'user'); | |
userInput.value = ''; | |
adjustTextareaHeight(); | |
// ๋ก๋ฉ ๋ฉ์์ง ์ถ๊ฐ | |
const loadingMessageId = addLoadingMessage(); | |
try { | |
// API ์์ฒญ | |
console.log(`/api/chat API ํธ์ถ: ${message.substring(0, 30)}${message.length > 30 ? '...' : ''}`); | |
const response = await AppUtils.fetchWithTimeout('/api/chat', { | |
method: 'POST', | |
headers: { | |
'Content-Type': 'application/json' | |
}, | |
body: JSON.stringify({ | |
query: message, | |
llm_id: currentLLM // ํ์ฌ ์ ํ๋ LLM ์ ์ก | |
}) | |
}); | |
// ๋ก๋ฉ ๋ฉ์์ง ์ ๊ฑฐ | |
removeLoadingMessage(loadingMessageId); | |
// ์๋ต ํ์ ํ์ธ | |
let data; | |
try { | |
data = await response.json(); | |
console.log('API ์๋ต ์์ ์๋ฃ'); | |
// ๋๋ฒ๊น : ์๋ต ๊ตฌ์กฐ ๋ฐ ๋ด์ฉ ๋ก๊น | |
console.log('์๋ต ๊ตฌ์กฐ:', Object.keys(data)); | |
if (data.answer) { | |
console.log('์๋ต ๊ธธ์ด:', data.answer.length); | |
console.log('์๋ต ๋ด์ฉ ์ผ๋ถ:', data.answer.substring(0, 50) + '...'); | |
} | |
} catch (jsonError) { | |
console.error('์๋ต JSON ํ์ฑ ์คํจ:', jsonError); | |
AppUtils.addErrorMessage('์๋ฒ ์๋ต์ ์ฒ๋ฆฌํ ์ ์์ต๋๋ค. ๋ค์ ์๋ํด ์ฃผ์ธ์.'); | |
return; | |
} | |
// ์๋ต ํ์ | |
if (data.error) { | |
console.error(`API ์ค๋ฅ ์๋ต: ${data.error}`); | |
AppUtils.addErrorMessage(data.error); | |
} else if (!data.answer || data.answer.trim() === '') { | |
console.error('์๋ต ๋ด์ฉ์ด ๋น์ด์์'); | |
AppUtils.addErrorMessage('์๋ฒ์์ ๋น ์๋ต์ ๋ฐ์์ต๋๋ค. ๋ค์ ์๋ํด ์ฃผ์ธ์.'); | |
} else { | |
// LLM ์ ๋ณด ์ ๋ฐ์ดํธ | |
if (data.llm) { | |
console.log(`LLM ์ ๋ณด ์ ๋ฐ์ดํธ: ${data.llm.name}`); | |
updateCurrentLLMInfo(data.llm); | |
} | |
try { | |
// ๋ฉ์์ง ์ถ๊ฐ | |
addMessage(data.answer, 'bot', null, data.sources); | |
console.log('์ฑ๋ด ์๋ต ํ์ ์๋ฃ'); | |
} catch (displayError) { | |
console.error('์๋ต ํ์ ์ค ์ค๋ฅ:', displayError); | |
AppUtils.addErrorMessage('์๋ต์ ํ์ํ๋ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค. ๋ค์ ์๋ํด ์ฃผ์ธ์.'); | |
} | |
} | |
} catch (error) { | |
console.error('๋ฉ์์ง ์ ์ก ์ค ์ค๋ฅ ๋ฐ์:', error); | |
removeLoadingMessage(loadingMessageId); | |
AppUtils.addErrorMessage('์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค. ๋ค์ ์๋ํด ์ฃผ์ธ์.'); | |
} | |
} | |
/** | |
* ์์ฑ ๋ น์ ์์ ํจ์ | |
*/ | |
async function startRecording() { | |
if (isRecording) { | |
console.log('์ด๋ฏธ ๋ น์ ์ค'); | |
return; | |
} | |
try { | |
console.log('๋ง์ดํฌ ์ ๊ทผ ๊ถํ ์์ฒญ'); | |
const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); | |
isRecording = true; | |
audioChunks = []; | |
mediaRecorder = new MediaRecorder(stream); | |
mediaRecorder.addEventListener('dataavailable', (event) => { | |
if (event.data.size > 0) { | |
console.log('์ค๋์ค ์ฒญํฌ ๋ฐ์ดํฐ ์์ '); | |
audioChunks.push(event.data); | |
} | |
}); | |
mediaRecorder.addEventListener('stop', () => { | |
console.log('๋ น์ ์ค์ง ์ด๋ฒคํธ - ์ค๋์ค ๋ฉ์์ง ์ ์ก'); | |
sendAudioMessage(); | |
}); | |
// ๋ น์ ์์ | |
mediaRecorder.start(); | |
console.log('๋ น์ ์์๋จ'); | |
// UI ์ ๋ฐ์ดํธ | |
micButton.style.display = 'none'; | |
recordingStatus.classList.remove('hidden'); | |
} catch (error) { | |
console.error('์์ฑ ๋ น์ ๊ถํ์ ์ป์ ์ ์์ต๋๋ค:', error); | |
alert('๋ง์ดํฌ ์ ๊ทผ ๊ถํ์ด ํ์ํฉ๋๋ค.'); | |
} | |
} | |
/** | |
* ์์ฑ ๋ น์ ์ค์ง ํจ์ | |
*/ | |
function stopRecording() { | |
if (!isRecording || !mediaRecorder) { | |
console.log('๋ น์ ์ค์ด ์๋'); | |
return; | |
} | |
console.log('๋ น์ ์ค์ง ์์ฒญ'); | |
mediaRecorder.stop(); | |
isRecording = false; | |
// UI ์ ๋ฐ์ดํธ | |
micButton.style.display = 'flex'; | |
recordingStatus.classList.add('hidden'); | |
} | |
/** | |
* ๋ น์๋ ์ค๋์ค ๋ฉ์์ง ์ ์ก ํจ์ | |
*/ | |
async function sendAudioMessage() { | |
if (audioChunks.length === 0) { | |
console.log('์ค๋์ค ์ฒญํฌ๊ฐ ์์'); | |
return; | |
} | |
console.log(`์ค๋์ค ๋ฉ์์ง ์ ์ก ์์: ${audioChunks.length}๊ฐ ์ฒญํฌ`); | |
// ์ค๋์ค Blob ์์ฑ | |
const audioBlob = new Blob(audioChunks, { type: 'audio/wav' }); | |
// ๋ก๋ฉ ๋ฉ์์ง ์ถ๊ฐ | |
const loadingMessageId = addLoadingMessage(); | |
try { | |
// FormData์ ์ค๋์ค ์ถ๊ฐ | |
const formData = new FormData(); | |
formData.append('audio', audioBlob, 'recording.wav'); | |
// ํ์ฌ ์ ํ๋ LLM ์ถ๊ฐ | |
formData.append('llm_id', currentLLM); | |
console.log('/api/voice API ํธ์ถ'); | |
// API ์์ฒญ | |
const response = await AppUtils.fetchWithTimeout('/api/voice', { | |
method: 'POST', | |
body: formData | |
}, 30000); // ์์ฑ ์ฒ๋ฆฌ๋ ๋ ๊ธด ํ์์์ | |
// ๋ก๋ฉ ๋ฉ์์ง ์ ๊ฑฐ | |
removeLoadingMessage(loadingMessageId); | |
// ์๋ต ํ์ ํ์ธ | |
let data; | |
try { | |
data = await response.json(); | |
console.log('์์ฑ API ์๋ต ์์ ์๋ฃ'); | |
// ๋๋ฒ๊น : ์๋ต ๊ตฌ์กฐ ๋ฐ ๋ด์ฉ ๋ก๊น | |
console.log('์์ฑ ์๋ต ๊ตฌ์กฐ:', Object.keys(data)); | |
if (data.answer) { | |
console.log('์์ฑ ์๋ต ๊ธธ์ด:', data.answer.length); | |
console.log('์์ฑ ์๋ต ๋ด์ฉ ์ผ๋ถ:', data.answer.substring(0, 50) + '...'); | |
} | |
if (data.transcription) { | |
console.log('์์ฑ ์ธ์ ๊ธธ์ด:', data.transcription.length); | |
} | |
} catch (jsonError) { | |
console.error('์์ฑ ์๋ต JSON ํ์ฑ ์คํจ:', jsonError); | |
AppUtils.addErrorMessage('์๋ฒ ์๋ต์ ์ฒ๋ฆฌํ ์ ์์ต๋๋ค. ๋ค์ ์๋ํด ์ฃผ์ธ์.'); | |
return; | |
} | |
// ์๋ต ํ์ | |
if (data.error) { | |
console.error(`์์ฑ API ์ค๋ฅ ์๋ต: ${data.error}`); | |
AppUtils.addErrorMessage(data.error); | |
} else if (!data.answer || data.answer.trim() === '') { | |
console.error('์์ฑ ์๋ต ๋ด์ฉ์ด ๋น์ด์์'); | |
AppUtils.addErrorMessage('์๋ฒ์์ ๋น ์๋ต์ ๋ฐ์์ต๋๋ค. ๋ค์ ์๋ํด ์ฃผ์ธ์.'); | |
} else { | |
try { | |
// LLM ์ ๋ณด ์ ๋ฐ์ดํธ | |
if (data.llm) { | |
console.log(`LLM ์ ๋ณด ์ ๋ฐ์ดํธ: ${data.llm.name}`); | |
updateCurrentLLMInfo(data.llm); | |
} | |
// ์ฌ์ฉ์ ๋ฉ์์ง(์์ฑ ํ ์คํธ) ์ถ๊ฐ | |
if (data.transcription) { | |
console.log(`์์ฑ ์ธ์ ๊ฒฐ๊ณผ: ${data.transcription.substring(0, 30)}${data.transcription.length > 30 ? '...' : ''}`); | |
addMessage(data.transcription, 'user'); | |
} | |
// ๋ด ์๋ต ์ถ๊ฐ | |
addMessage(data.answer, 'bot', data.transcription, data.sources); | |
console.log('์์ฑ ์ฑํ ์๋ต ํ์ ์๋ฃ'); | |
} catch (displayError) { | |
console.error('์์ฑ ์๋ต ํ์ ์ค ์ค๋ฅ:', displayError); | |
AppUtils.addErrorMessage('์๋ต์ ํ์ํ๋ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค. ๋ค์ ์๋ํด ์ฃผ์ธ์.'); | |
} | |
} | |
} catch (error) { | |
console.error('์์ฑ ๋ฉ์์ง ์ ์ก ์ค ์ค๋ฅ ๋ฐ์:', error); | |
removeLoadingMessage(loadingMessageId); | |
AppUtils.addErrorMessage('์ค๋์ค ์ฒ๋ฆฌ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค. ๋ค์ ์๋ํด ์ฃผ์ธ์.'); | |
} | |
} | |
/** | |
* ๋ฌธ์ ์ ๋ก๋ ํจ์ | |
*/ | |
async function uploadDocument() { | |
if (documentFile.files.length === 0) { | |
console.log('์ ํ๋ ํ์ผ ์์'); | |
alert('ํ์ผ์ ์ ํํด ์ฃผ์ธ์.'); | |
return; | |
} | |
console.log(`๋ฌธ์ ์ ๋ก๋ ์์: ${documentFile.files[0].name}`); | |
// UI ์ ๋ฐ์ดํธ | |
uploadStatus.classList.remove('hidden'); | |
uploadStatus.className = 'upload-status'; | |
uploadStatus.innerHTML = '<div class="spinner"></div><p>์ ๋ก๋ ์ค...</p>'; | |
uploadButton.disabled = true; | |
try { | |
const formData = new FormData(); | |
formData.append('document', documentFile.files[0]); | |
// API ์์ฒญ | |
console.log('/api/upload API ํธ์ถ'); | |
const response = await AppUtils.fetchWithTimeout('/api/upload', { | |
method: 'POST', | |
body: formData | |
}, 20000); // ์ ๋ก๋๋ ๋ ๊ธด ํ์์์ | |
const data = await response.json(); | |
console.log('์ ๋ก๋ API ์๋ต ์์ ์๋ฃ'); | |
// ์๋ต ์ฒ๋ฆฌ | |
if (data.error) { | |
console.error(`์ ๋ก๋ ์ค๋ฅ: ${data.error}`); | |
uploadStatus.className = 'upload-status error'; | |
uploadStatus.textContent = `์ค๋ฅ: ${data.error}`; | |
} else if (data.warning) { | |
console.warn(`์ ๋ก๋ ๊ฒฝ๊ณ : ${data.message}`); | |
uploadStatus.className = 'upload-status warning'; | |
uploadStatus.textContent = data.message; | |
} else { | |
console.log(`์ ๋ก๋ ์ฑ๊ณต: ${data.message}`); | |
uploadStatus.className = 'upload-status success'; | |
uploadStatus.textContent = data.message; | |
// ๋ฌธ์ ๋ชฉ๋ก ์๋ก๊ณ ์นจ | |
loadDocuments(); | |
// ์ ๋ ฅ ํ๋ ์ด๊ธฐํ | |
documentFile.value = ''; | |
fileName.textContent = '์ ํ๋ ํ์ผ ์์'; | |
} | |
} catch (error) { | |
console.error('๋ฌธ์ ์ ๋ก๋ ์ค ์ค๋ฅ ๋ฐ์:', error); | |
uploadStatus.className = 'upload-status error'; | |
uploadStatus.textContent = '์ ๋ก๋ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค. ๋ค์ ์๋ํด ์ฃผ์ธ์.'; | |
} finally { | |
uploadButton.disabled = false; | |
} | |
} | |
/** | |
* ๋ฌธ์ ๋ชฉ๋ก ๋ก๋ ํจ์ | |
*/ | |
async function loadDocuments() { | |
console.log('๋ฌธ์ ๋ชฉ๋ก ๋ก๋ ์์'); | |
// UI ์ ๋ฐ์ดํธ | |
docsList.querySelector('tbody').innerHTML = ''; | |
docsLoading.classList.remove('hidden'); | |
noDocsMessage.classList.add('hidden'); | |
try { | |
// API ์์ฒญ | |
console.log('/api/documents API ํธ์ถ'); | |
const response = await AppUtils.fetchWithTimeout('/api/documents', { | |
method: 'GET' | |
}); | |
const data = await response.json(); | |
console.log(`๋ฌธ์ ๋ชฉ๋ก ๋ก๋ ์ฑ๊ณต: ${data.documents ? data.documents.length : 0}๊ฐ ๋ฌธ์`); | |
// ์๋ต ์ฒ๋ฆฌ | |
docsLoading.classList.add('hidden'); | |
if (!data.documents || data.documents.length === 0) { | |
console.log('๋ฌธ์ ์์'); | |
noDocsMessage.classList.remove('hidden'); | |
return; | |
} | |
// ๋ฌธ์ ๋ชฉ๋ก ์ ๋ฐ์ดํธ | |
const tbody = docsList.querySelector('tbody'); | |
data.documents.forEach(doc => { | |
const row = document.createElement('tr'); | |
const fileNameCell = document.createElement('td'); | |
fileNameCell.textContent = doc.filename || doc.source; | |
row.appendChild(fileNameCell); | |
const chunksCell = document.createElement('td'); | |
chunksCell.textContent = doc.chunks; | |
row.appendChild(chunksCell); | |
const typeCell = document.createElement('td'); | |
typeCell.textContent = doc.filetype || '-'; | |
row.appendChild(typeCell); | |
tbody.appendChild(row); | |
}); | |
} catch (error) { | |
console.error('๋ฌธ์ ๋ชฉ๋ก ๋ก๋ ์ค ์ค๋ฅ ๋ฐ์:', error); | |
docsLoading.classList.add('hidden'); | |
noDocsMessage.classList.remove('hidden'); | |
noDocsMessage.querySelector('p').textContent = '๋ฌธ์ ๋ชฉ๋ก์ ๋ถ๋ฌ์ค๋ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค.'; | |
} | |
} | |
/** | |
* ๋ฉ์์ง ์ถ๊ฐ ํจ์ | |
* @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=${sender}, length=${text ? text.length : 0}`); | |
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') { | |
console.log(`์์ค ์ ๋ณด ์ถ๊ฐ: ${sources.length}๊ฐ ์์ค`); | |
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(); | |
} | |
} | |
/** | |
* textarea ๋์ด ์๋ ์กฐ์ ํจ์ | |
*/ | |
function adjustTextareaHeight() { | |
userInput.style.height = 'auto'; | |
userInput.style.height = Math.min(userInput.scrollHeight, 100) + 'px'; | |
} | |
// ํ์ด์ง ๋ก๋ ์ ์ด๊ธฐํ | |
document.addEventListener('DOMContentLoaded', () => { | |
console.log('๋ฉ์ธ UI ์ด๊ธฐํ ์ค...'); | |
// ์ฑ ์ํ ํ์ธ (๋ก๋ฉ ํ์ด์ง๊ฐ ์๋ ๊ฒฝ์ฐ์๋ง) | |
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(); | |
} | |
}, 5000); | |
} | |
// ํญ ์ ํ ์ด๋ฒคํธ ๋ฆฌ์ค๋ | |
chatTab.addEventListener('click', () => { | |
console.log('์ฑํ ํญ ํด๋ฆญ'); | |
switchTab('chat'); | |
}); | |
docsTab.addEventListener('click', () => { | |
console.log('๋ฌธ์ ๊ด๋ฆฌ ํญ ํด๋ฆญ'); | |
switchTab('docs'); | |
}); | |
// ์ฅ์น ์ ์ด ํญ์ DeviceControl ๋ชจ๋์์ ์ด๋ฒคํธ ๋ฆฌ์ค๋ ๋ฑ๋กํจ | |
// LLM ์ ํ ์ด๋ฒคํธ ๋ฆฌ์ค๋ | |
llmSelect.addEventListener('change', (event) => { | |
console.log(`LLM ๋ณ๊ฒฝ: ${event.target.value}`); | |
changeLLM(event.target.value); | |
}); | |
// ๋ฉ์์ง ์ ์ก ์ด๋ฒคํธ ๋ฆฌ์ค๋ | |
sendButton.addEventListener('click', () => { | |
console.log('๋ฉ์์ง ์ ์ก ๋ฒํผ ํด๋ฆญ'); | |
sendMessage(); | |
}); | |
userInput.addEventListener('keydown', (event) => { | |
if (event.key === 'Enter' && !event.shiftKey) { | |
console.log('ํ ์คํธ ์ ๋ ฅ์์ ์ํฐ ํค ๊ฐ์ง'); | |
event.preventDefault(); | |
sendMessage(); | |
} | |
}); | |
// ์์ฑ ์ธ์ ์ด๋ฒคํธ ๋ฆฌ์ค๋ | |
micButton.addEventListener('click', () => { | |
console.log('๋ง์ดํฌ ๋ฒํผ ํด๋ฆญ'); | |
startRecording(); | |
}); | |
stopRecordingButton.addEventListener('click', () => { | |
console.log('๋ น์ ์ค์ง ๋ฒํผ ํด๋ฆญ'); | |
stopRecording(); | |
}); | |
// ๋ฌธ์ ์ ๋ก๋ ์ด๋ฒคํธ ๋ฆฌ์ค๋ | |
documentFile.addEventListener('change', (event) => { | |
console.log('ํ์ผ ์ ํ ๋ณ๊ฒฝ'); | |
if (event.target.files.length > 0) { | |
fileName.textContent = event.target.files[0].name; | |
} else { | |
fileName.textContent = '์ ํ๋ ํ์ผ ์์'; | |
} | |
}); | |
uploadForm.addEventListener('submit', (event) => { | |
console.log('๋ฌธ์ ์ ๋ก๋ ํผ ์ ์ถ'); | |
event.preventDefault(); | |
uploadDocument(); | |
}); | |
// ๋ฌธ์ ๋ชฉ๋ก ์๋ก๊ณ ์นจ ์ด๋ฒคํธ ๋ฆฌ์ค๋ | |
refreshDocsButton.addEventListener('click', () => { | |
console.log('๋ฌธ์ ๋ชฉ๋ก ์๋ก๊ณ ์นจ ๋ฒํผ ํด๋ฆญ'); | |
loadDocuments(); | |
}); | |
// ์๋ ์ ๋ ฅ ํ๋ ํฌ๊ธฐ ์กฐ์ | |
userInput.addEventListener('input', adjustTextareaHeight); | |
// ์ด๊ธฐ ๋ฌธ์ ๋ชฉ๋ก ๋ก๋ | |
if (docsSection.classList.contains('active')) { | |
loadDocuments(); | |
} | |
console.log('๋ฉ์ธ UI ์ด๊ธฐํ ์๋ฃ'); | |
}); |