Spaces:
Sleeping
Sleeping
| /** | |
| * 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 ์ด๊ธฐํ ์๋ฃ'); | |
| }); |