You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

482 lines
17 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>【少年读史记】知识库</title>
<link rel="icon" href="data:,">
<style>
body {
font-family: 'Microsoft YaHei', sans-serif;
margin: 0;
padding: 0;
background-color: #f5f5f5;
}
.container {
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
h1 {
color: #333;
text-align: center;
}
.data-area {
background-color: white;
border-radius: 8px;
padding: 20px;
margin-bottom: 20px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.input-area {
background-color: white;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
textarea {
width: 100%;
height: 40px;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
resize: none;
margin-bottom: 10px;
margin-right: 20px;
}
button {
background-color: #4CAF50;
color: white;
border: none;
padding: 10px 15px;
border-radius: 4px;
cursor: pointer;
margin-right: 10px;
}
button:hover {
background-color: #45a049;
}
#clearBtn {
background-color: #f44336;
}
#clearBtn:hover {
background-color: #d32f2f;
}
#saveWordBtn {
background-color: #2196F3;
}
.icon {
margin-right: 5px;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.doc-links {
display: flex;
gap: 20px;
margin: 20px 0;
align-items: center;
}
.doc-checkboxes {
display: flex;
gap: 20px;
margin-bottom: 10px;
}
.doc-checkboxes label {
display: flex;
align-items: center;
gap: 5px;
cursor: pointer;
}
.examples {
margin: 20px 0;
}
.example-category {
margin-bottom: 15px;
}
.example-category h3 {
margin-bottom: 10px;
color: #333;
}
.example-list {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.example-item {
padding: 8px 12px;
background-color: #e9f5ff;
border-radius: 4px;
cursor: pointer;
transition: all 0.2s;
}
.example-item:hover {
background-color: #d0e3fa;
}
.input-area textarea#questionInput {
margin-right: 10px !important;
width: calc(100% - 10px);
box-sizing: border-box;
}
#answerArea {
min-height: 240px;
height: auto;
overflow-y: auto;
max-height: 500px;
}
.loading-animation {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 200px;
color: #666;
}
.spinner {
width: 40px;
height: 40px;
border: 4px solid #f3f3f3;
border-top: 4px solid #3498db;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: 10px;
}
</style>
</head>
<body>
<div class="container">
<h1>【少年读史记】知识问答</h1>
<a href="./Txt/少年读史记套装全5册.pdf">原文下载</a>
<div class="data-area" id="answerArea">
<div style="color:#666; padding:20px; text-align:center;">
<p>请在下方输入您的问题,答案将在此处显示</p>
<p>您也可以点击"示例问题"快速体验</p>
</div>
</div>
<div class="input-area">
<label for="questionInput"></label><textarea id="questionInput" placeholder="请输入您的问题..."></textarea>
<div class="examples">
<div class="example-category">
<h3>简单问题示例</h3>
<div class="example-list">
<div class="example-item" onclick="fillExample('“先写后编”的历史写作方式是什么意思?')">
“先写后编”的历史写作方式是什么意思?
</div>
<div class="example-item" onclick="fillExample('“皇帝”称号的由来')">
“皇帝”称号的由来
</div>
<div class="example-item" onclick="fillExample('什么是龙相?')">
什么是龙相?
</div>
</div>
</div>
<div class="example-category">
<h3>总结性问题示例</h3>
<div class="example-list">
<div class="example-item" onclick="fillExample('本书主要讲了哪些内容?')">
本书主要讲了哪些内容?
</div>
<div class="example-item" onclick="fillExample('总结一下各年代的名将')">
总结一下各年代的名将
</div>
<div class="example-item" onclick="fillExample('项羽犯了哪些重要错误?')">
项羽犯了哪些重要错误?
</div>
<div class="example-item" onclick="fillExample('刘邦最终成功是因为什么?')">
刘邦最终成功是因为什么?
</div>
</div>
</div>
<div class="example-category">
<h3>生成关系图谱示例【成品演示】</h3>
<div class="example-list">
<a href="./temp/relation_1d104eb9f61a41ebbc1935e4944d9fb5.html">刘帮集团的人物关系图</a>
<a href="./temp/relation_c364556b55a64aa68279eea99db3392f.html">秦汉时期主要的人物关系图</a>
</div>
</div>
<div class="example-category">
<h3>生成关系图谱示例【即时生成】</h3>
<div class="example-list">
<button id="relationBtn1" onclick="generateRelation('生成刘帮集团的人物关系图')"
style="background-color:#9c27b0">
<span class="icon">👥</span>生成刘帮集团的人物关系图
</button>
<button id="relationBtn2" onclick="generateRelation('生成秦汉时期主要的人物关系图')"
style="background-color:#9c27b0">
<span class="icon">👥</span>生成秦汉时期主要的人物关系图
</button>
</div>
</div>
</div>
</div>
<button id="submitBtn" onclick="submitQuestion()"><span class="icon">💡</span>提问</button>
<button id="clearBtn" onclick="clearAll()"><span class="icon">🗑️</span>清空</button>
</div>
<script>
// 在页面加载时显示加载动画
document.addEventListener('DOMContentLoaded', function () {
const answerArea = document.getElementById('answerArea');
answerArea.innerHTML = '<div class="loading-animation"><div class="spinner"></div><div>正在加载资源...</div></div>';
// 禁用所有按钮
const buttons = document.querySelectorAll('button');
buttons.forEach(btn => btn.disabled = true);
});
MathJax = {
loader: {load: ['[tex]/mhchem']},
tex: {
inlineMath: [['$', '$'], ['\\(', '\\)']],
displayMath: [['$$', '$$'], ['\\[', '\\]']],
packages: {'[+]': ['mhchem']}
},
options: {
enableMenu: false,
processHtmlClass: 'tex2jax_process',
ignoreHtmlClass: 'tex2jax_ignore'
},
svg: {
fontCache: 'global'
},
startup: {
pageReady: () => {
return MathJax.startup.defaultPageReady().then(() => {
console.log('MathJax initialized');
// 移除加载动画并启用所有按钮
const answerArea = document.getElementById('answerArea');
answerArea.innerHTML = '<div style="color:#666; padding:20px; text-align:center;"><p>请在下方输入您的问题,答案将在此处显示</p><p>您也可以点击"示例问题"快速体验</p></div>';
const buttons = document.querySelectorAll('button');
buttons.forEach(btn => btn.disabled = false);
MathJax.typesetPromise();
// 自动滚动到底部
answerArea.scrollTop = answerArea.scrollHeight;
});
}
}
};
// marked配置
const markedOptions = {
mangle: false,
headerIds: false
};
function fillExample(question) {
document.getElementById('questionInput').value = question;
}
function submitQuestion() {
const question = document.getElementById('questionInput').value.trim();
const answerArea = document.getElementById('answerArea');
const topic = 'ShiJi';
const mode = 'hybrid';
if (!question) {
alert('请输入问题!');
return;
}
// 添加加载动画
answerArea.innerHTML = '<div class="loading-animation"><div class="spinner"></div><div>思考中...</div></div>';
fetch('/api/rag', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'text/event-stream'
},
body: JSON.stringify({
query: question,
topic: topic,
mode: mode
})
})
.then(response => {
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
let accumulatedContent = '';
function processChunk() {
return reader.read().then(({done, value}) => {
if (done) return;
buffer += decoder.decode(value, {stream: true});
const lines = buffer.split('\n');
buffer = lines.pop();
for (const line of lines) {
if (line.includes('data:')) {
const jsonStr = line.replace(/^data:\s*/, '').replace(/^data:\s*/, '').trim();
if (jsonStr) {
try {
const data = JSON.parse(jsonStr);
if (data.reply) {
accumulatedContent += data.reply;
answerArea.innerHTML = marked.parse(accumulatedContent, markedOptions);
MathJax.typesetPromise();
// 自动滚动到底部
answerArea.scrollTop = answerArea.scrollHeight;
}
} catch (e) {
console.log('忽略解析错误:', e);
}
}
}
}
return processChunk().then(() => {
// 在流处理完成后保存完整的markdown内容
localStorage.setItem('lastMarkdownContent', accumulatedContent);
});
});
}
return processChunk();
})
.catch(error => {
// 移除加载动画
answerArea.innerHTML = '';
console.error('Error:', error);
// 出错时也移除加载动画
answerArea.innerHTML = '<div style="color:red">请求出错,请重试</div>';
});
}
function clearAll() {
document.getElementById('questionInput').value = '';
document.getElementById('answerArea').innerHTML = '';
}
// 在script部分添加新函数
function generateRelation(query) {
const answerArea = document.getElementById('answerArea');
answerArea.innerHTML = '<div class="loading-animation"><div class="spinner"></div><div>正在生成人物关系图...</div></div>';
fetch('/api/rag', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'text/event-stream'
},
body: JSON.stringify({
query: query,
topic: "ShiJi",
mode: "hybrid",
output_model: "html"
})
})
.then(response => {
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
let accumulatedContent = '';
function processChunk() {
return reader.read().then(({done, value}) => {
if (done) return;
buffer += decoder.decode(value, {stream: true});
const lines = buffer.split('\n');
buffer = lines.pop();
for (const line of lines) {
if (line.includes('[DONE]')) {
// 在这里处理流结束的逻辑
console.log('SSE流处理完成');
return fetch('/api/render_html', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
html_content: accumulatedContent
})
}).then(response => response.json())
.then(data => {
if (data.url) {
window.location = data.url;
}
});
}
if (line.includes('data:')) {
const jsonStr = line.replace(/^data:\s*/, '').replace(/^data:\s*/, '').trim();
if (jsonStr) {
try {
const data = JSON.parse(jsonStr);
if (data.reply) {
accumulatedContent += data.reply;
answerArea.innerHTML = marked.parse(accumulatedContent, markedOptions);
MathJax.typesetPromise();
// 自动滚动到底部
answerArea.scrollTop = answerArea.scrollHeight;
}
} catch (e) {
console.log('忽略解析错误:', e);
}
}
}
}
return processChunk().then(() => {
// 在流处理完成后保存完整的markdown内容
//localStorage.setItem('lastMarkdownContent', accumulatedContent);
});
});
}
return processChunk();
})
.catch(error => {
// 移除加载动画
answerArea.innerHTML = '';
console.error('Error:', error);
// 出错时也移除加载动画
answerArea.innerHTML = '<div style="color:red">请求出错,请重试</div>';
});
}
</script>
<script src="/static/mathjax/es5/tex-mml-chtml.js" id="MathJax-script" async></script>
<script src="/static/js/marked.min.js"></script>
</body>
</html>