group-wbl/templates/index.html
2026-01-08 14:11:42 +08:00

502 lines
16 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!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>