group-wbl/templates/index.html

502 lines
16 KiB
HTML
Raw Permalink Normal View History

2026-01-08 14:11:42 +08:00
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>智能知识库问答系统</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
background: #f5f5f7;
min-height: 100vh;
padding: 20px;
color: #1d1d1f;
}
.container {
max-width: 800px;
margin: 40px auto;
background: white;
border-radius: 18px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
padding: 40px;
}
h1 {
text-align: center;
color: #1d1d1f;
margin-bottom: 10px;
font-size: 2.2em;
font-weight: 700;
}
h3 {
color: #1d1d1f;
margin-bottom: 24px;
font-size: 1.3em;
font-weight: 600;
}
.section {
margin-bottom: 40px;
padding: 24px;
background: #ffffff;
border-radius: 12px;
border: 1px solid #e6e6e6;
}
.form-group {
margin-bottom: 24px;
}
label {
display: block;
font-size: 1em;
margin-bottom: 8px;
color: #86868b;
font-weight: 500;
}
input[type="text"], input[type="file"] {
width: 100%;
padding: 12px 16px;
font-size: 1.05em;
border: 1px solid #d2d2d7;
border-radius: 8px;
transition: border-color 0.2s, background-color 0.2s;
background: #ffffff;
}
input[type="text"]:focus, input[type="file"]:focus {
outline: none;
border-color: #0071e3;
background: #ffffff;
}
button {
display: inline-block;
padding: 12px 24px;
font-size: 1em;
background: #0071e3;
color: white;
border: none;
border-radius: 980px;
cursor: pointer;
transition: background-color 0.2s;
font-weight: 600;
margin-right: 10px;
}
button:hover {
background: #0077ed;
}
button:disabled {
background: #d1d1d6;
color: #86868b;
cursor: not-allowed;
}
.response-box {
margin-top: 16px;
padding: 20px;
background: white;
border-radius: 12px;
border: 1px solid #e6e6e6;
min-height: 120px;
font-size: 1em;
line-height: 1.7;
color: #1d1d1f;
white-space: pre-wrap;
}
.loading {
display: inline-block;
width: 16px;
height: 16px;
border: 2px solid rgba(255,255,255,.3);
border-radius: 50%;
border-top-color: #fff;
animation: spin 1s linear infinite;
margin-right: 8px;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
#document-list {
margin-top: 16px;
}
.document-item {
padding: 16px;
background: white;
border-radius: 12px;
margin-bottom: 8px;
border: 1px solid #e6e6e6;
display: flex;
justify-content: space-between;
align-items: center;
}
.document-info {
flex: 1;
}
.document-title {
font-weight: 600;
color: #1d1d1f;
margin-bottom: 4px;
}
.document-meta {
font-size: 0.9em;
color: #86868b;
}
.delete-btn {
background: #ff453a;
padding: 8px 16px;
font-size: 0.9em;
}
.delete-btn:hover {
background: #ff3b30;
}
.success-message, .error-message {
padding: 12px 16px;
border-radius: 12px;
margin-bottom: 16px;
font-weight: 600;
}
.success-message {
background: #32d74b;
color: white;
}
.error-message {
background: #ff453a;
color: white;
}
@media (max-width: 768px) {
body {
padding: 16px;
}
.container {
padding: 24px;
margin: 16px auto;
}
h1 {
font-size: 1.8em;
}
.section {
padding: 16px;
}
button {
width: 100%;
margin-right: 0;
margin-bottom: 12px;
}
.document-item {
flex-direction: column;
align-items: flex-start;
}
.delete-btn {
width: auto;
margin-top: 12px;
}
}
</style>
</head>
<body>
<div class="container">
<h1>智能知识库问答系统</h1>
<!-- 文档上传区 -->
<div class="section">
<h3>文档上传</h3>
<div id="upload-message"></div>
<div class="form-group">
<label for="file-upload">选择文档支持PDF、Word、TXT</label>
<input type="file" id="file-upload" accept=".pdf,.doc,.docx,.txt">
</div>
<div class="form-group">
<label for="document-title">文档标题(可选):</label>
<input type="text" id="document-title" placeholder="为文档添加标题...">
</div>
<button id="upload-btn" onclick="uploadDocument()">上传文档</button>
</div>
<!-- 知识库管理区 -->
<div class="section">
<h3>知识库管理</h3>
<button id="refresh-btn" onclick="loadDocuments()">刷新文档列表</button>
<div id="document-list">
<p>点击刷新按钮查看已上传的文档...</p>
</div>
</div>
<!-- 问答区 -->
<div class="section">
<h3>知识库问答</h3>
<div class="form-group">
<label for="question">请输入您的问题:</label>
<input type="text" id="question" placeholder="例如什么是Python">
</div>
<button id="ask-btn" onclick="askQuestion()">提问</button>
<div class="response-box" id="answer">
输入问题并点击提问按钮获取答案...
</div>
</div>
</div>
<script>
// 页面加载时加载文档列表
document.addEventListener('DOMContentLoaded', function() {
loadDocuments();
});
// 上传文档
function uploadDocument() {
const fileInput = document.getElementById('file-upload');
const titleInput = document.getElementById('document-title');
const uploadBtn = document.getElementById('upload-btn');
const uploadMessage = document.getElementById('upload-message');
// 检查是否选择了文件
if (!fileInput.files || fileInput.files.length === 0) {
showMessage(uploadMessage, '请选择一个文件', 'error');
return;
}
const file = fileInput.files[0];
const title = titleInput.value;
// 重置消息
uploadMessage.innerHTML = '';
// 禁用按钮并显示加载状态
uploadBtn.disabled = true;
uploadBtn.innerHTML = '<span class="loading"></span>正在上传...';
// 创建表单数据
const formData = new FormData();
formData.append('file', file);
if (title) {
formData.append('title', title);
}
// 发送请求
fetch('/upload', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
showMessage(uploadMessage, `文档上传成功!已处理 ${data.count} 个文档块`, 'success');
// 重置表单
fileInput.value = '';
titleInput.value = '';
// 更新文档列表
loadDocuments();
} else {
showMessage(uploadMessage, `上传失败:${data.error}`, 'error');
}
})
.catch(error => {
showMessage(uploadMessage, `上传失败:${error.message}`, 'error');
})
.finally(() => {
// 恢复按钮状态
uploadBtn.disabled = false;
uploadBtn.innerHTML = '上传文档';
});
}
// 加载文档列表
function loadDocuments() {
const documentList = document.getElementById('document-list');
const refreshBtn = document.getElementById('refresh-btn');
// 禁用按钮并显示加载状态
refreshBtn.disabled = true;
refreshBtn.innerHTML = '<span class="loading"></span>正在加载...';
// 发送请求
fetch('/documents')
.then(response => response.json())
.then(data => {
if (data.success) {
if (data.documents.length === 0) {
documentList.innerHTML = '<p>知识库中暂无文档,请先上传文档...</p>';
} else {
// 按文档分组显示
const documentsByFile = {};
data.documents.forEach(doc => {
const fileName = doc.parent_file || '未知文件';
if (!documentsByFile[fileName]) {
documentsByFile[fileName] = [];
}
documentsByFile[fileName].push(doc);
});
let html = '';
for (const fileName in documentsByFile) {
const docs = documentsByFile[fileName];
const firstDoc = docs[0];
html += `
<div class="document-item">
<div class="document-info">
<div class="document-title">${firstDoc.metadata?.title || fileName}</div>
<div class="document-meta">
文档块数量: ${docs.length} | 上传时间: ${new Date(firstDoc.timestamp).toLocaleString()}
</div>
</div>
<button class="delete-btn" onclick="deleteDocument('${firstDoc.id}')">删除</button>
</div>
`;
}
documentList.innerHTML = html;
}
} else {
documentList.innerHTML = `<p class="error-message">加载失败:${data.error}</p>`;
}
})
.catch(error => {
documentList.innerHTML = `<p class="error-message">加载失败:${error.message}</p>`;
})
.finally(() => {
// 恢复按钮状态
refreshBtn.disabled = false;
refreshBtn.innerHTML = '刷新文档列表';
});
}
// 删除文档
function deleteDocument(documentId) {
if (!confirm('确定要删除这个文档吗?')) {
return;
}
// 发送请求
fetch(`/documents/${documentId}`, {
method: 'DELETE'
})
.then(response => response.json())
.then(data => {
if (data.success) {
// 更新文档列表
loadDocuments();
} else {
alert(`删除失败:${data.error}`);
}
})
.catch(error => {
alert(`删除失败:${error.message}`);
});
}
// 提问
function askQuestion() {
const questionInput = document.getElementById('question');
const askBtn = document.getElementById('ask-btn');
const answerBox = document.getElementById('answer');
const question = questionInput.value.trim();
if (!question) {
alert('请输入问题');
return;
}
// 重置内容
answerBox.textContent = '';
// 禁用按钮并显示加载状态
askBtn.disabled = true;
askBtn.innerHTML = '<span class="loading"></span>正在思考...';
// 发送请求
fetch('/ask', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ query: question })
})
.then(response => {
if (!response.ok) {
throw new Error('问答失败');
}
// 处理流式响应
const reader = response.body.getReader();
const decoder = new TextDecoder('utf-8');
function read() {
return reader.read().then(({ done, value }) => {
if (done) {
return;
}
// 解码并显示内容
const chunk = decoder.decode(value, { stream: true });
answerBox.textContent += chunk;
// 继续读取
return read();
});
}
return read();
})
.catch(error => {
answerBox.textContent = `问答失败:${error.message}`;
})
.finally(() => {
// 恢复按钮状态
askBtn.disabled = false;
askBtn.innerHTML = '提问';
});
}
// 显示消息
function showMessage(element, message, type) {
const messageElement = document.createElement('div');
messageElement.className = type === 'success' ? 'success-message' : 'error-message';
messageElement.textContent = message;
element.innerHTML = '';
element.appendChild(messageElement);
// 3秒后自动隐藏
setTimeout(() => {
messageElement.remove();
}, 3000);
}
// 按下Enter键也可以提问
document.getElementById('question').addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
askQuestion();
}
});
</script>
</body>
</html>