jeongsoo's picture
Add application file
babf3f3
/**
* RAG ์ฑ—๋ด‡ ํด๋ผ์ด์–ธํŠธ - ์ง€์‹๋ฒ ์ด์Šค ๊ด€๋ฆฌ ๊ธฐ๋Šฅ
* ๋ฌธ์„œ ์—…๋กœ๋“œ ๋ฐ ์ง€์‹๋ฒ ์ด์Šค ์ƒํƒœ ๊ด€๋ฆฌ
*/
document.addEventListener('DOMContentLoaded', function() {
// DOM ์š”์†Œ
const uploadButton = document.getElementById('uploadButton');
const uploadSuccess = document.getElementById('uploadSuccess');
const uploadSuccessMessage = document.getElementById('uploadSuccessMessage');
const uploadError = document.getElementById('uploadError');
const uploadErrorMessage = document.getElementById('uploadErrorMessage');
const refreshStatus = document.getElementById('refreshStatus');
const databaseStats = document.getElementById('databaseStats');
const documentsContainer = document.getElementById('documentsContainer');
// Dropzone ์„ค์ •
Dropzone.autoDiscover = false;
const documentUploadDropzone = new Dropzone("#documentUploadForm", {
url: "/api/upload",
maxFilesize: 10, // MB
acceptedFiles: ".txt,.md,.pdf,.docx,.csv",
addRemoveLinks: true,
dictDefaultMessage: "ํŒŒ์ผ์„ ๋Œ์–ด๋‹ค ๋†“๊ฑฐ๋‚˜ ํด๋ฆญํ•˜์—ฌ ์„ ํƒํ•˜์„ธ์š”",
dictRemoveFile: "์ œ๊ฑฐ",
dictCancelUpload: "์—…๋กœ๋“œ ์ทจ์†Œ",
dictUploadCanceled: "์—…๋กœ๋“œ ์ทจ์†Œ๋จ",
dictFileTooBig: "ํŒŒ์ผ์ด ๋„ˆ๋ฌด ํฝ๋‹ˆ๋‹ค ({{filesize}}MB). ์ตœ๋Œ€ ํŒŒ์ผ ํฌ๊ธฐ: {{maxFilesize}}MB.",
dictInvalidFileType: "์ด ํ˜•์‹์˜ ํŒŒ์ผ์€ ์—…๋กœ๋“œํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.",
autoProcessQueue: false,
maxFiles: 1
});
// ํŒŒ์ผ์ด ์ถ”๊ฐ€๋˜๋ฉด ์—…๋กœ๋“œ ๋ฒ„ํŠผ ํ™œ์„ฑํ™”
documentUploadDropzone.on("addedfile", function(file) {
uploadButton.disabled = false;
hideAlerts();
});
// ํŒŒ์ผ์ด ์ œ๊ฑฐ๋˜๋ฉด ์—…๋กœ๋“œ ๋ฒ„ํŠผ ๋น„ํ™œ์„ฑํ™”
documentUploadDropzone.on("removedfile", function(file) {
if (documentUploadDropzone.files.length === 0) {
uploadButton.disabled = true;
}
});
// ์—…๋กœ๋“œ ๋ฒ„ํŠผ ํด๋ฆญ ์ด๋ฒคํŠธ
uploadButton.addEventListener('click', function() {
if (documentUploadDropzone.files.length > 0) {
uploadButton.disabled = true;
uploadButton.innerHTML = '<span class="spinner-border spinner-border-sm me-2" role="status"></span>์—…๋กœ๋“œ ์ค‘...';
documentUploadDropzone.processQueue();
}
});
// ์—…๋กœ๋“œ ์„ฑ๊ณต ์ด๋ฒคํŠธ
documentUploadDropzone.on("success", function(file, response) {
uploadButton.innerHTML = '<i class="fas fa-upload me-2"></i>์—…๋กœ๋“œ';
uploadButton.disabled = true;
// ์„ฑ๊ณต ๋ฉ”์‹œ์ง€ ํ‘œ์‹œ
uploadSuccess.classList.remove('d-none');
uploadSuccessMessage.textContent = response.message || "๋ฌธ์„œ๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ์—…๋กœ๋“œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.";
// ํŒŒ์ผ ์ œ๊ฑฐ
documentUploadDropzone.removeFile(file);
// ์ง€์‹๋ฒ ์ด์Šค ์ƒํƒœ ์ƒˆ๋กœ๊ณ ์นจ
setTimeout(fetchDatabaseStats, 1000);
setTimeout(fetchDocuments, 1000);
});
// ์—…๋กœ๋“œ ์˜ค๋ฅ˜ ์ด๋ฒคํŠธ
documentUploadDropzone.on("error", function(file, errorMessage, xhr) {
uploadButton.innerHTML = '<i class="fas fa-upload me-2"></i>์—…๋กœ๋“œ';
uploadButton.disabled = false;
// ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€ ํ‘œ์‹œ
uploadError.classList.remove('d-none');
if (typeof errorMessage === 'object' && errorMessage.error) {
uploadErrorMessage.textContent = errorMessage.error;
} else if (typeof errorMessage === 'string') {
uploadErrorMessage.textContent = errorMessage;
} else {
uploadErrorMessage.textContent = "์—…๋กœ๋“œ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.";
}
});
// ์ƒˆ๋กœ๊ณ ์นจ ๋ฒ„ํŠผ ํด๋ฆญ ์ด๋ฒคํŠธ
refreshStatus.addEventListener('click', function() {
fetchDatabaseStats();
fetchDocuments();
});
/**
* ์•Œ๋ฆผ์ฐฝ ์ˆจ๊ธฐ๊ธฐ
*/
function hideAlerts() {
uploadSuccess.classList.add('d-none');
uploadError.classList.add('d-none');
}
/**
* ์ง€์‹๋ฒ ์ด์Šค ์ƒํƒœ ๊ฐ€์ ธ์˜ค๊ธฐ
*/
async function fetchDatabaseStats() {
try {
databaseStats.innerHTML = `
<div class="d-flex justify-content-center align-items-center" style="height: 100px;">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Loading...</span>
</div>
</div>
`;
const response = await fetch('/api/documents');
if (!response.ok) {
throw new Error('API ์š”์ฒญ ์‹คํŒจ');
}
const data = await response.json();
if (data.enabled === false) {
databaseStats.innerHTML = `
<div class="alert alert-warning mb-0">
<i class="fas fa-exclamation-triangle me-2"></i>์บ์‹œ๊ฐ€ ํ™œ์„ฑํ™”๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.
</div>
`;
return;
}
const stats = data.stats || {};
databaseStats.innerHTML = `
<div class="row text-center">
<div class="col-6 border-end">
<h3 class="mb-0">${stats.size || 0}</h3>
<p class="text-muted mb-0">์บ์‹œ ํ•ญ๋ชฉ</p>
</div>
<div class="col-6">
<h3 class="mb-0">${stats.max_size || 0}</h3>
<p class="text-muted mb-0">์ตœ๋Œ€ ํ•ญ๋ชฉ ์ˆ˜</p>
</div>
</div>
<hr>
<div class="text-center">
<p class="mb-0">์บ์‹œ TTL: ${stats.ttl || 0}์ดˆ</p>
<p class="mb-2">์ตœ๋Œ€ ํ•ญ๋ชฉ ๋‚˜์ด: ${(stats.oldest_item_age || 0).toFixed(1)}์ดˆ</p>
</div>
<div class="d-grid">
<button class="btn btn-sm btn-outline-primary" id="clearCache">์บ์‹œ ๋น„์šฐ๊ธฐ</button>
</div>
`;
// ์บ์‹œ ๋น„์šฐ๊ธฐ ๋ฒ„ํŠผ ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ
document.getElementById('clearCache').addEventListener('click', clearCache);
} catch (err) {
console.error('์ง€์‹๋ฒ ์ด์Šค ์ƒํƒœ ๊ฐ€์ ธ์˜ค๊ธฐ ์‹คํŒจ:', err);
databaseStats.innerHTML = `
<div class="alert alert-danger mb-0">
<i class="fas fa-exclamation-circle me-2"></i>์ง€์‹๋ฒ ์ด์Šค ์ƒํƒœ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.
</div>
`;
}
}
/**
* ๋ฌธ์„œ ๋ชฉ๋ก ๊ฐ€์ ธ์˜ค๊ธฐ
*/
async function fetchDocuments() {
try {
documentsContainer.innerHTML = `
<div class="text-center p-4">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Loading...</span>
</div>
<p class="mt-2">๋ฌธ์„œ ๋ชฉ๋ก์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘...</p>
</div>
`;
const response = await fetch('/api/documents');
if (!response.ok) {
throw new Error('API ์š”์ฒญ ์‹คํŒจ');
}
const data = await response.json();
const documents = data.documents || [];
if (documents.length === 0) {
documentsContainer.innerHTML = `
<div class="text-center p-4">
<i class="fas fa-file-alt fa-3x mb-3 text-muted"></i>
<p>์ง€์‹๋ฒ ์ด์Šค์— ๋“ฑ๋ก๋œ ๋ฌธ์„œ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.</p>
<p class="text-muted small">์™ผ์ชฝ ํŒจ๋„์—์„œ ๋ฌธ์„œ๋ฅผ ์—…๋กœ๋“œํ•˜์„ธ์š”.</p>
</div>
`;
return;
}
// ๋ฌธ์„œ ๋ชฉ๋ก ์ƒ์„ฑ
const documentsList = document.createElement('div');
documentsList.className = 'list-group list-group-flush';
documents.forEach(doc => {
const docItem = createDocumentItem(doc);
documentsList.appendChild(docItem);
});
documentsContainer.innerHTML = '';
documentsContainer.appendChild(documentsList);
} catch (err) {
console.error('๋ฌธ์„œ ๋ชฉ๋ก ๊ฐ€์ ธ์˜ค๊ธฐ ์‹คํŒจ:', err);
documentsContainer.innerHTML = `
<div class="alert alert-danger m-3">
<i class="fas fa-exclamation-circle me-2"></i>๋ฌธ์„œ ๋ชฉ๋ก์„ ๊ฐ€์ ธ์˜ค๋Š” ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.
</div>
`;
}
}
/**
* ๋ฌธ์„œ ํ•ญ๋ชฉ ์ƒ์„ฑ
* @param {Object} doc - ๋ฌธ์„œ ์ •๋ณด
* @returns {HTMLElement} - ๋ฌธ์„œ ํ•ญ๋ชฉ ์š”์†Œ
*/
function createDocumentItem(doc) {
const template = document.getElementById('documentItemTemplate');
const docNode = template.content.cloneNode(true);
docNode.querySelector('.document-name').textContent = doc.filename || doc.source || 'Unnamed Document';
docNode.querySelector('.document-chunks').textContent = doc.chunks || 0;
docNode.querySelector('.document-type').textContent = doc.filetype || 'UNKNOWN';
return docNode.firstElementChild;
}
/**
* ์บ์‹œ ๋น„์šฐ๊ธฐ
*/
async function clearCache() {
try {
if (!confirm('์ •๋ง ์บ์‹œ๋ฅผ ๋น„์šฐ์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?')) {
return;
}
const response = await fetch('/api/cache/clear', {
method: 'POST'
});
if (!response.ok) {
throw new Error('API ์š”์ฒญ ์‹คํŒจ');
}
alert('์บ์‹œ๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ๋น„์›Œ์กŒ์Šต๋‹ˆ๋‹ค.');
fetchDatabaseStats();
} catch (err) {
console.error('์บ์‹œ ๋น„์šฐ๊ธฐ ์‹คํŒจ:', err);
alert('์บ์‹œ ๋น„์šฐ๊ธฐ ์‹คํŒจ: ' + err.message);
}
}
// ์ดˆ๊ธฐ ๋ฐ์ดํ„ฐ ๋กœ๋“œ
fetchDatabaseStats();
fetchDocuments();
});