jeongsoo's picture
merge1
bf43f8e
/**
* 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 ์ดˆ๊ธฐํ™” ์™„๋ฃŒ');
});