|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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'); |
|
|
|
|
|
|
|
|
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; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function loadLLMs() { |
|
|
try { |
|
|
|
|
|
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}๊ฐ ๋ชจ๋ธ`); |
|
|
|
|
|
|
|
|
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); |
|
|
}); |
|
|
|
|
|
|
|
|
updateCurrentLLMInfo(data.current_llm); |
|
|
} catch (error) { |
|
|
console.error('LLM ๋ชฉ๋ก ๋ก๋ ์คํจ:', error); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function changeLLM(llmId) { |
|
|
try { |
|
|
|
|
|
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 ๋ณ๊ฒฝ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค.'); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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('๋ฉ์์ง ์ ์ก ์์'); |
|
|
|
|
|
|
|
|
addMessage(message, 'user'); |
|
|
userInput.value = ''; |
|
|
adjustTextareaHeight(); |
|
|
|
|
|
|
|
|
const loadingMessageId = addLoadingMessage(); |
|
|
|
|
|
try { |
|
|
|
|
|
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 |
|
|
}) |
|
|
}); |
|
|
|
|
|
|
|
|
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 { |
|
|
|
|
|
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('๋
น์ ์์๋จ'); |
|
|
|
|
|
|
|
|
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; |
|
|
|
|
|
|
|
|
micButton.style.display = 'flex'; |
|
|
recordingStatus.classList.add('hidden'); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function sendAudioMessage() { |
|
|
if (audioChunks.length === 0) { |
|
|
console.log('์ค๋์ค ์ฒญํฌ๊ฐ ์์'); |
|
|
return; |
|
|
} |
|
|
|
|
|
console.log(`์ค๋์ค ๋ฉ์์ง ์ ์ก ์์: ${audioChunks.length}๊ฐ ์ฒญํฌ`); |
|
|
|
|
|
|
|
|
const audioBlob = new Blob(audioChunks, { type: 'audio/wav' }); |
|
|
|
|
|
|
|
|
const loadingMessageId = addLoadingMessage(); |
|
|
|
|
|
try { |
|
|
|
|
|
const formData = new FormData(); |
|
|
formData.append('audio', audioBlob, 'recording.wav'); |
|
|
|
|
|
formData.append('llm_id', currentLLM); |
|
|
|
|
|
console.log('/api/voice 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 { |
|
|
|
|
|
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}`); |
|
|
|
|
|
|
|
|
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]); |
|
|
|
|
|
|
|
|
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('๋ฌธ์ ๋ชฉ๋ก ๋ก๋ ์์'); |
|
|
|
|
|
|
|
|
docsList.querySelector('tbody').innerHTML = ''; |
|
|
docsLoading.classList.remove('hidden'); |
|
|
noDocsMessage.classList.add('hidden'); |
|
|
|
|
|
try { |
|
|
|
|
|
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 = '๋ฌธ์ ๋ชฉ๋ก์ ๋ถ๋ฌ์ค๋ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค.'; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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) { |
|
|
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; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function removeLoadingMessage(id) { |
|
|
console.log(`๋ก๋ฉ ๋ฉ์์ง ์ ๊ฑฐ: ${id}`); |
|
|
const loadingMessage = document.getElementById(id); |
|
|
if (loadingMessage) { |
|
|
loadingMessage.remove(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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('์ฑ์ด ์ค๋น๋์์ต๋๋ค.'); |
|
|
|
|
|
|
|
|
loadLLMs(); |
|
|
} |
|
|
}, 5000); |
|
|
} |
|
|
|
|
|
|
|
|
chatTab.addEventListener('click', () => { |
|
|
console.log('์ฑํ
ํญ ํด๋ฆญ'); |
|
|
switchTab('chat'); |
|
|
}); |
|
|
|
|
|
docsTab.addEventListener('click', () => { |
|
|
console.log('๋ฌธ์ ๊ด๋ฆฌ ํญ ํด๋ฆญ'); |
|
|
switchTab('docs'); |
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 ์ด๊ธฐํ ์๋ฃ'); |
|
|
}); |