273 lines
8.0 KiB
JavaScript
273 lines
8.0 KiB
JavaScript
|
|
const uploadArea = document.getElementById('upload-area');
|
|||
|
|
const fileInput = document.getElementById('file-input');
|
|||
|
|
const documentList = document.getElementById('document-list');
|
|||
|
|
const chatMessages = document.getElementById('chat-messages');
|
|||
|
|
const questionInput = document.getElementById('question-input');
|
|||
|
|
const docCount = document.getElementById('doc-count');
|
|||
|
|
const charCount = document.getElementById('char-count');
|
|||
|
|
const toast = document.getElementById('toast');
|
|||
|
|
|
|||
|
|
function showToast(message, duration = 3000) {
|
|||
|
|
toast.textContent = message;
|
|||
|
|
toast.classList.add('show');
|
|||
|
|
|
|||
|
|
setTimeout(() => {
|
|||
|
|
toast.classList.remove('show');
|
|||
|
|
}, duration);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
uploadArea.addEventListener('click', () => fileInput.click());
|
|||
|
|
|
|||
|
|
uploadArea.addEventListener('dragover', (e) => {
|
|||
|
|
e.preventDefault();
|
|||
|
|
uploadArea.style.borderColor = '#1e3c72';
|
|||
|
|
uploadArea.style.background = '#e8f0fe';
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
uploadArea.addEventListener('dragleave', () => {
|
|||
|
|
uploadArea.style.borderColor = '#cbd5e0';
|
|||
|
|
uploadArea.style.background = 'transparent';
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
uploadArea.addEventListener('drop', (e) => {
|
|||
|
|
e.preventDefault();
|
|||
|
|
uploadArea.style.borderColor = '#cbd5e0';
|
|||
|
|
uploadArea.style.background = 'transparent';
|
|||
|
|
|
|||
|
|
const files = e.dataTransfer.files;
|
|||
|
|
if (files.length > 0) {
|
|||
|
|
uploadFile(files[0]);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
fileInput.addEventListener('change', (e) => {
|
|||
|
|
if (e.target.files.length > 0) {
|
|||
|
|
uploadFile(e.target.files[0]);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
async function uploadFile(file) {
|
|||
|
|
const formData = new FormData();
|
|||
|
|
formData.append('file', file);
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
showToast('⏳ 正在上传文档...');
|
|||
|
|
|
|||
|
|
const response = await fetch('/api/upload', {
|
|||
|
|
method: 'POST',
|
|||
|
|
body: formData
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
const data = await response.json();
|
|||
|
|
|
|||
|
|
if (data.error) {
|
|||
|
|
showToast(`❌ ${data.error}`);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
loadDocuments();
|
|||
|
|
showToast(`✅ 文档 "${data.name}" 上传成功,正在处理中...`);
|
|||
|
|
addMessage('bot', `📄 文档 "${data.name}" 已上传,系统正在智能解析文档内容...`);
|
|||
|
|
|
|||
|
|
setTimeout(() => {
|
|||
|
|
loadDocuments();
|
|||
|
|
}, 2000);
|
|||
|
|
|
|||
|
|
} catch (error) {
|
|||
|
|
showToast('❌ 上传失败,请重试');
|
|||
|
|
console.error(error);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async function loadDocuments() {
|
|||
|
|
try {
|
|||
|
|
const response = await fetch('/api/documents');
|
|||
|
|
const documents = await response.json();
|
|||
|
|
|
|||
|
|
docCount.textContent = `${documents.length} 个文档`;
|
|||
|
|
|
|||
|
|
if (documents.length === 0) {
|
|||
|
|
documentList.innerHTML = `
|
|||
|
|
<div class="empty-state">
|
|||
|
|
<div class="empty-icon">📭</div>
|
|||
|
|
<p>暂无文档</p>
|
|||
|
|
<p class="empty-hint">上传文档后即可开始问答</p>
|
|||
|
|
</div>
|
|||
|
|
`;
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
documentList.innerHTML = documents.map(doc => `
|
|||
|
|
<div class="document-item">
|
|||
|
|
<div class="document-info">
|
|||
|
|
<div class="document-name">📄 ${doc.name}</div>
|
|||
|
|
<div class="document-status ${doc.status}">
|
|||
|
|
${doc.status === 'processing' ? '⏳ 处理中...' : '✅ 已完成'}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<button class="delete-btn" onclick="deleteDocument('${doc.id}')">🗑️ 删除</button>
|
|||
|
|
</div>
|
|||
|
|
`).join('');
|
|||
|
|
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error(error);
|
|||
|
|
showToast('❌ 加载文档列表失败');
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async function deleteDocument(docId) {
|
|||
|
|
if (!confirm('确定要删除这个文档吗?')) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
showToast('⏳ 正在删除文档...');
|
|||
|
|
|
|||
|
|
await fetch(`/api/documents/${docId}`, {
|
|||
|
|
method: 'DELETE'
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
loadDocuments();
|
|||
|
|
showToast('✅ 文档已删除');
|
|||
|
|
addMessage('bot', '🗑️ 文档已从知识库中删除');
|
|||
|
|
|
|||
|
|
} catch (error) {
|
|||
|
|
showToast('❌ 删除失败,请重试');
|
|||
|
|
console.error(error);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async function askQuestion() {
|
|||
|
|
const question = questionInput.value.trim();
|
|||
|
|
|
|||
|
|
if (!question) {
|
|||
|
|
showToast('⚠️ 请输入问题');
|
|||
|
|
questionInput.focus();
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (question.length < 3) {
|
|||
|
|
showToast('⚠️ 问题太短,请输入至少3个字符');
|
|||
|
|
questionInput.focus();
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
addMessage('user', question);
|
|||
|
|
questionInput.value = '';
|
|||
|
|
updateCharCount();
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
showToast('🤖 正在思考中...');
|
|||
|
|
|
|||
|
|
const response = await fetch('/api/ask', {
|
|||
|
|
method: 'POST',
|
|||
|
|
headers: {
|
|||
|
|
'Content-Type': 'application/json'
|
|||
|
|
},
|
|||
|
|
body: JSON.stringify({ question })
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
const data = await response.json();
|
|||
|
|
|
|||
|
|
if (data.error) {
|
|||
|
|
showToast(`❌ ${data.error}`);
|
|||
|
|
addMessage('bot', `❌ 抱歉,${data.error}`);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
let answerHtml = `<p>${data.answer}</p>`;
|
|||
|
|
|
|||
|
|
if (data.sources && data.sources.length > 0) {
|
|||
|
|
answerHtml += `
|
|||
|
|
<div class="sources">
|
|||
|
|
<div class="sources-title">📚 参考来源:</div>
|
|||
|
|
${data.sources.map(source => `
|
|||
|
|
<div class="source-item">📄 ${source.name} (第${source.page}页)</div>
|
|||
|
|
`).join('')}
|
|||
|
|
</div>
|
|||
|
|
`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
showToast('✅ 回答完成');
|
|||
|
|
addMessage('bot', answerHtml);
|
|||
|
|
|
|||
|
|
} catch (error) {
|
|||
|
|
showToast('❌ 回答问题时出错了,请重试');
|
|||
|
|
addMessage('bot', '❌ 抱歉,回答问题时出错了,请重试');
|
|||
|
|
console.error(error);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function addMessage(type, content) {
|
|||
|
|
const messageDiv = document.createElement('div');
|
|||
|
|
messageDiv.className = `message ${type}`;
|
|||
|
|
messageDiv.innerHTML = `
|
|||
|
|
<div class="message-content">
|
|||
|
|
${content}
|
|||
|
|
</div>
|
|||
|
|
`;
|
|||
|
|
|
|||
|
|
chatMessages.appendChild(messageDiv);
|
|||
|
|
chatMessages.scrollTop = chatMessages.scrollHeight;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function updateCharCount() {
|
|||
|
|
const length = questionInput.value.length;
|
|||
|
|
charCount.textContent = `${length} 字`;
|
|||
|
|
|
|||
|
|
if (length > 500) {
|
|||
|
|
charCount.style.color = '#e53e3e';
|
|||
|
|
} else if (length > 300) {
|
|||
|
|
charCount.style.color = '#d69e2e';
|
|||
|
|
} else {
|
|||
|
|
charCount.style.color = '#718096';
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async function loadConversationHistory() {
|
|||
|
|
try {
|
|||
|
|
const response = await fetch('/api/conversations');
|
|||
|
|
const conversations = await response.json();
|
|||
|
|
|
|||
|
|
if (conversations.length === 0) {
|
|||
|
|
addMessage('bot', '👋 欢迎使用智能知识库问答系统!上传文档后,您可以向我提问任何与文档相关的问题。');
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
conversations.forEach(conv => {
|
|||
|
|
addMessage('user', conv.question);
|
|||
|
|
|
|||
|
|
let answerHtml = `<p>${conv.answer}</p>`;
|
|||
|
|
|
|||
|
|
if (conv.sources && conv.sources.length > 0) {
|
|||
|
|
answerHtml += `
|
|||
|
|
<div class="sources">
|
|||
|
|
<div class="sources-title">📚 参考来源:</div>
|
|||
|
|
${conv.sources.map(source => `
|
|||
|
|
<div class="source-item">📄 ${source.name} (第${source.page}页)</div>
|
|||
|
|
`).join('')}
|
|||
|
|
</div>
|
|||
|
|
`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
addMessage('bot', answerHtml);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error(error);
|
|||
|
|
addMessage('bot', '👋 欢迎使用智能知识库问答系统!上传文档后,您可以向我提问任何与文档相关的问题。');
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
questionInput.addEventListener('input', updateCharCount);
|
|||
|
|
|
|||
|
|
questionInput.addEventListener('keydown', (e) => {
|
|||
|
|
if (e.key === 'Enter' && !e.shiftKey) {
|
|||
|
|
e.preventDefault();
|
|||
|
|
askQuestion();
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
loadDocuments();
|
|||
|
|
loadConversationHistory();
|