nodebookls/templates/index.html

1004 lines
38 KiB
HTML
Raw Normal View History

2025-10-29 13:56:24 +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>TXT版轻量NotebookLM</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 1200px;
margin: 0 auto;
padding: 20px;
background-color: #f5f5f5;
}
.container {
background-color: white;
padding: 30px;
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
h1 {
color: #333;
text-align: center;
}
.nav {
text-align: center;
margin-bottom: 30px;
}
.nav a {
display: inline-block;
padding: 10px 20px;
margin: 0 10px;
background-color: #4CAF50;
color: white;
text-decoration: none;
border-radius: 5px;
}
.nav a:hover {
background-color: #45a049;
}
.nav a.settings {
background-color: #2196F3;
}
.nav a.settings:hover {
background-color: #0b7dda;
}
.nav a.kb {
background-color: #9C27B0;
}
.nav a.kb:hover {
background-color: #7B1FA2;
}
.section {
margin-bottom: 30px;
padding: 20px;
border: 1px solid #ddd;
border-radius: 5px;
}
.section h2 {
margin-top: 0;
color: #333;
}
button {
background-color: #4CAF50;
color: white;
padding: 10px 20px;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 16px;
margin: 5px;
}
button:hover {
background-color: #45a049;
}
button:disabled {
background-color: #cccccc;
cursor: not-allowed;
}
.btn-secondary {
background-color: #2196F3;
}
.btn-secondary:hover {
background-color: #0b7dda;
}
.btn-warning {
background-color: #FF9800;
}
.btn-warning:hover {
background-color: #e68a00;
}
.btn-danger {
background-color: #f44336;
}
.btn-danger:hover {
background-color: #d32f2f;
}
.form-group {
margin-bottom: 15px;
}
.form-group label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
textarea, input, select {
width: 100%;
padding: 10px;
margin: 5px 0;
border: 1px solid #ddd;
border-radius: 5px;
box-sizing: border-box;
}
.result {
margin-top: 20px;
padding: 15px;
background-color: #f9f9f9;
border-left: 4px solid #4CAF50;
border-radius: 5px;
}
.alert {
padding: 15px;
margin: 15px 0;
border-radius: 5px;
}
.alert.info {
background-color: #e3f2fd;
border-left: 4px solid #2196F3;
color: #0d47a1;
}
.alert.success {
background-color: #e8f5e9;
border-left: 4px solid #4CAF50;
color: #1b5e20;
}
.alert.warning {
background-color: #fff3e0;
border-left: 4px solid #FF9800;
color: #e65100;
}
.alert.error {
background-color: #ffebee;
border-left: 4px solid #f44336;
color: #b71c1c;
}
.hallucination-warning {
background-color: #fff3e0;
border-left: 4px solid #FF9800;
padding: 10px;
margin: 10px 0;
border-radius: 5px;
}
.progress {
height: 20px;
background-color: #f0f0f0;
border-radius: 10px;
margin: 10px 0;
}
.progress-bar {
height: 100%;
background-color: #4CAF50;
border-radius: 10px;
text-align: center;
line-height: 20px;
color: white;
font-size: 12px;
}
.hidden {
display: none !important;
}
.search-results {
margin-top: 20px;
padding: 15px;
background-color: #fff3e0;
border-left: 4px solid #FF9800;
border-radius: 5px;
}
.modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0,0,0,0.5);
z-index: 9999;
display: flex;
justify-content: center;
align-items: center;
}
.modal-content {
background-color: white;
padding: 20px;
border-radius: 10px;
width: 80%;
max-width: 800px;
max-height: 80vh;
overflow-y: auto;
position: relative;
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
padding-bottom: 10px;
border-bottom: 1px solid #eee;
}
.modal-header h3 {
margin: 0;
}
.close {
font-size: 24px;
cursor: pointer;
color: #999;
}
.close:hover {
color: #333;
}
@media (max-width: 768px) {
body {
padding: 10px;
}
.container {
padding: 15px;
}
.nav a {
display: block;
margin: 5px 0;
}
}
</style>
</head>
<body>
<div class="container">
<h1>TXT版轻量NotebookLM</h1>
<div class="nav">
<a href="/" class="active">主页面</a>
<a href="/settings" class="settings">设置</a>
<a href="/knowledge_bases" class="kb">知识库管理</a>
</div>
<div id="globalAlert" class="alert hidden"></div>
<!-- 知识库选择和问题输入 -->
<div class="section">
<h2>基于知识库提问</h2>
<p>选择知识库并向AI提问系统将基于知识库内容为您提供答案</p>
<div class="form-group">
<label for="kbSelect">选择知识库:</label>
<select id="kbSelect">
<option value="">请选择知识库</option>
<!-- 知识库选项将通过JavaScript动态加载 -->
</select>
</div>
<div class="form-group">
<label for="questionInput">请输入您的问题:</label>
<textarea id="questionInput" rows="3" placeholder="例如:根据文档内容,项目的主要功能是什么?"></textarea>
</div>
<div class="form-group">
<label for="styleSelect">回答风格:</label>
<select id="styleSelect">
<option value="通用文案">通用文案</option>
<option value="小红书种草风">小红书种草风</option>
<option value="官方通告">官方通告</option>
<option value="知乎科普">知乎科普</option>
<option value="微博热点">微博热点</option>
</select>
</div>
<div class="form-group">
<label>回答长度:</label>
<input type="number" id="minLength" value="50" min="10" max="500"> ~
<input type="number" id="maxLength" value="500" min="50" max="2000"> 字符
</div>
<button id="askBtn" disabled>获取答案</button>
<button id="refreshKbList">刷新知识库列表</button>
<div id="generateProgress" class="progress hidden">
<div class="progress-bar" id="generateProgressBar" style="width: 0%">0%</div>
</div>
</div>
<!-- AI回答结果 -->
<div class="section">
<h2>AI回答</h2>
<div id="answerResult" class="result hidden">
<div id="answerContent"></div>
<div style="margin-top: 15px;">
<button id="exportBtn" class="btn-secondary hidden">导出为Markdown</button>
<button id="exportDocxBtn" class="btn-secondary hidden">导出为DOCX</button>
<button id="editBtn" class="btn-warning hidden">人工微调</button>
</div>
</div>
</div>
<!-- 相关知识内容 -->
<div class="section">
<h2>支持回答的知识内容</h2>
<div id="searchResults" class="search-results hidden">
<div id="relatedContent"></div>
</div>
</div>
</div>
<!-- 文本编辑器模态框 -->
<div id="editorModal" class="modal hidden">
<div class="modal-content">
<div class="modal-header">
<h3>人工微调</h3>
<span class="close" onclick="cancelEdit()">&times;</span>
</div>
<textarea id="editorTextarea" style="width: 100%; height: 300px;"></textarea>
<div style="margin-top: 10px; text-align: right;">
<button id="saveEditBtn" class="btn-secondary">保存</button>
<button id="cancelEditBtn" class="btn-danger">取消</button>
</div>
</div>
</div>
<script>
// 全局变量
let lastGeneratedResult = null;
let currentEditingText = "";
let selectedKb = "";
// DOM元素
const kbSelect = document.getElementById('kbSelect');
const questionInput = document.getElementById('questionInput');
const askBtn = document.getElementById('askBtn');
const refreshKbListBtn = document.getElementById('refreshKbList');
const generateProgress = document.getElementById('generateProgress');
const generateProgressBar = document.getElementById('generateProgressBar');
const answerResult = document.getElementById('answerResult');
const answerContent = document.getElementById('answerContent');
const exportBtn = document.getElementById('exportBtn');
const exportDocxBtn = document.getElementById('exportDocxBtn');
const editBtn = document.getElementById('editBtn');
const searchResults = document.getElementById('searchResults');
const relatedContent = document.getElementById('relatedContent');
// 编辑器元素
const editorModal = document.getElementById('editorModal');
const editorTextarea = document.getElementById('editorTextarea');
const saveEditBtn = document.getElementById('saveEditBtn');
const cancelEditBtn = document.getElementById('cancelEditBtn');
// 事件监听器
kbSelect.addEventListener('change', handleKbSelect);
questionInput.addEventListener('input', handleQuestionInput);
askBtn.addEventListener('click', askQuestion);
refreshKbListBtn.addEventListener('click', loadKnowledgeBases);
exportBtn.addEventListener('click', exportToMarkdown);
exportDocxBtn.addEventListener('click', exportToDocx);
editBtn.addEventListener('click', openEditor);
// 编辑器事件监听器
saveEditBtn.addEventListener('click', saveEdit);
cancelEditBtn.addEventListener('click', cancelEdit);
// 点击模态框背景关闭编辑器
editorModal.addEventListener('click', (e) => {
if (e.target === editorModal) {
cancelEdit();
}
});
// 页面加载时获取知识库列表
document.addEventListener('DOMContentLoaded', async function() {
await loadKnowledgeBases();
});
// 加载知识库列表
async function loadKnowledgeBases() {
try {
const response = await fetch('/knowledge_bases');
if (response.ok) {
const data = await response.json();
displayKnowledgeBases(data.knowledge_bases);
} else {
throw new Error('获取知识库列表失败');
}
} catch (error) {
console.error('加载知识库列表失败:', error);
showMessage('加载知识库列表失败: ' + error.message, 'error');
}
}
// 显示知识库列表
function displayKnowledgeBases(knowledgeBases) {
kbSelect.innerHTML = '<option value="">请选择知识库</option>';
knowledgeBases.forEach(kb => {
const option = document.createElement('option');
option.value = kb.name;
option.textContent = `${kb.name} (${kb.document_count} 个文档)`;
kbSelect.appendChild(option);
});
// 如果之前选择了知识库,重新选择它
if (selectedKb) {
kbSelect.value = selectedKb;
askBtn.disabled = !questionInput.value.trim();
}
}
// 处理知识库选择
function handleKbSelect() {
selectedKb = kbSelect.value;
askBtn.disabled = !selectedKb || !questionInput.value.trim();
}
// 处理问题输入
function handleQuestionInput() {
askBtn.disabled = !selectedKb || !questionInput.value.trim();
}
// 提问
async function askQuestion() {
const question = questionInput.value.trim();
if (!selectedKb || !question) {
alert('请选择知识库并输入问题');
return;
}
// 显示进度条
generateProgress.classList.remove('hidden');
askBtn.disabled = true;
try {
// 构造表单数据
const formData = new FormData();
formData.append('query', question);
formData.append('style', document.getElementById('styleSelect').value);
formData.append('min_length', document.getElementById('minLength').value);
formData.append('max_length', document.getElementById('maxLength').value);
formData.append('knowledge_base', selectedKb);
// 调用新的问答API
const response = await fetch('/ask', {
method: 'POST',
body: formData
});
const result = await response.json();
if (response.ok) {
// 保存结果用于导出
lastGeneratedResult = {
query: result.query,
generated_text: result.answer,
knowledge_base: selectedKb
};
// 显示AI回答
let answerHTML = `
<h3>问题: ${result.query}</h3>
<p><strong>基于知识库 "${selectedKb}" 的回答:</strong></p>
<p>${result.answer.replace(/\n/g, '<br>')}</p>
`;
// 显示幻觉警告信息
if (result.hallucination_warnings &&
(result.hallucination_warnings.keywords.length > 0 ||
result.hallucination_warnings.entities.length > 0)) {
let warningHTML = '<div class="hallucination-warning">';
warningHTML += '<p class="warning">⚠️ 检测到可能的幻觉内容:</p><ul>';
if (result.hallucination_warnings.keywords.length > 0) {
warningHTML += `<li><strong>幻觉关键词:</strong> ${result.hallucination_warnings.keywords.join(', ')}</li>`;
}
if (result.hallucination_warnings.entities.length > 0) {
warningHTML += `<li><strong>幻觉实体:</strong> ${result.hallucination_warnings.entities.join(', ')}</li>`;
}
warningHTML += '</ul><p class="warning">请核对原文确认表述准确性</p></div>';
answerHTML += warningHTML;
}
answerContent.innerHTML = answerHTML;
answerResult.classList.remove('hidden');
exportBtn.classList.remove('hidden');
editBtn.classList.remove('hidden');
// 显示支持回答的知识内容
if (result.related_content && result.related_content.length > 0) {
displayRelatedContent(result.related_content);
} else {
searchResults.classList.add('hidden');
}
} else {
throw new Error(result.detail || '获取答案失败');
}
} catch (error) {
console.error('提问错误:', error);
answerContent.innerHTML = `<p class="error">获取答案失败: ${error.message}</p>`;
answerResult.classList.remove('hidden');
} finally {
// 隐藏进度条
generateProgress.classList.add('hidden');
askBtn.disabled = false;
}
}
// 显示相关知识内容
function displayRelatedContent(contents) {
let html = '<h3>支持回答的知识内容:</h3>';
contents.forEach((item, index) => {
html += `
<div style="margin: 15px 0; padding: 10px; border: 1px solid #ddd; border-radius: 5px;">
<p><strong>内容片段 ${index + 1} (${item.metadata.segment_id})</strong></p>
<p><strong>相关性得分:</strong> ${(item.score * 100).toFixed(2)}%</p>
<p>${item.content}</p>
</div>
`;
});
relatedContent.innerHTML = html;
searchResults.classList.remove('hidden');
}
// 导出为Markdown
async function exportToMarkdown() {
if (!lastGeneratedResult) {
alert('没有可导出的内容');
return;
}
try {
// 创建FormData
const formData = new FormData();
formData.append('query', lastGeneratedResult.query);
formData.append('generated_text', lastGeneratedResult.generated_text);
formData.append('knowledge_base', lastGeneratedResult.knowledge_base);
// 发送导出请求
const response = await fetch('/export_markdown_qa', {
method: 'POST',
body: formData
});
if (response.ok) {
// 创建下载链接
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = response.headers.get('content-disposition')
.split('filename=')[1]
.replace(/"/g, '');
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
alert('导出成功!');
} else {
throw new Error('导出失败');
}
} catch (error) {
console.error('导出错误:', error);
alert('导出失败: ' + error.message);
}
}
// 导出为DOCX
async function exportToDocx() {
if (!lastGeneratedResult) {
alert('没有可导出的内容');
return;
}
try {
// 创建FormData
const formData = new FormData();
formData.append('query', lastGeneratedResult.query);
formData.append('generated_text', lastGeneratedResult.generated_text);
formData.append('knowledge_base', lastGeneratedResult.knowledge_base);
// 发送导出请求
const response = await fetch('/export_docx_qa', {
method: 'POST',
body: formData
});
if (response.ok) {
// 创建下载链接
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = response.headers.get('content-disposition')
.split('filename=')[1]
.replace(/"/g, '');
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
alert('DOCX导出成功');
} else {
throw new Error('导出失败');
}
} catch (error) {
console.error('导出错误:', error);
alert('导出失败: ' + error.message);
}
}
// 打开编辑器
function openEditor() {
if (!lastGeneratedResult) {
alert('没有可编辑的内容');
return;
}
currentEditingText = lastGeneratedResult.generated_text;
editorTextarea.value = currentEditingText;
editorModal.classList.remove('hidden');
document.body.style.overflow = 'hidden';
}
// 保存编辑
function saveEdit() {
const editedText = editorTextarea.value;
lastGeneratedResult.generated_text = editedText;
// 更新显示内容
const answerParagraphs = answerContent.querySelectorAll('p');
for (let p of answerParagraphs) {
if (p.innerHTML.includes('回答:')) {
const nextP = p.nextElementSibling;
if (nextP && nextP.tagName === 'P' && !nextP.classList.contains('warning')) {
nextP.innerHTML = editedText.replace(/\n/g, '<br>');
break;
}
}
}
// 关闭编辑器
editorModal.classList.add('hidden');
document.body.style.overflow = 'auto';
alert('编辑已保存');
}
// 取消编辑
function cancelEdit() {
editorTextarea.value = currentEditingText;
editorModal.classList.add('hidden');
document.body.style.overflow = 'auto';
}
// 显示消息
function showMessage(message, type) {
const globalAlert = document.getElementById('globalAlert');
globalAlert.textContent = message;
globalAlert.className = 'alert ' + type;
globalAlert.classList.remove('hidden');
// 3秒后自动隐藏消息
setTimeout(() => {
globalAlert.classList.add('hidden');
}, 3000);
}
</script>
</body>
</html>
throw new Error(result.detail || '生成失败');
}
} catch (error) {
console.error('生成错误:', error);
generateResultContent.innerHTML = `<p class="error">生成失败: ${error.message}</p>`;
generateResult.classList.remove('hidden');
// 重新启用按钮
generateBtn.disabled = false;
generateAsyncBtn.disabled = false;
}
}
// 检查生成任务状态
async function checkGenerateTaskStatus() {
if (!generateTaskId) {
alert('没有正在运行的生成任务');
return;
}
try {
const response = await fetch(`/task_status/${generateTaskId}`);
const result = await response.json();
if (result.state === 'PENDING') {
generateTaskStatusSpan.textContent = '任务等待中...';
} else if (result.state === 'PROGRESS') {
generateTaskStatusSpan.textContent = `${result.status} (${result.percent}%)`;
// 更新进度条
generateProgress.classList.remove('hidden');
generateProgressBar.style.width = `${result.percent}%`;
generateProgressBar.textContent = `${result.percent}%`;
} else if (result.state === 'SUCCESS') {
generateTaskStatusSpan.textContent = '任务完成';
// 显示结果
const taskResult = result.result;
// 保存结果用于导出
lastGeneratedResult = {
query: taskResult.query,
generated_text: taskResult.generated_text
};
let resultHTML = `
<h3 class="success">生成结果</h3>
<p><strong>问题:</strong> ${taskResult.query}</p>
<p><strong>生成文案:</strong></p>
<p>${taskResult.generated_text.replace(/\n/g, '<br>')}</p>
`;
// 显示幻觉警告信息
if (taskResult.hallucination_warnings) {
const warnings = taskResult.hallucination_warnings;
let warningHTML = '';
if (warnings.keywords && warnings.keywords.length > 0) {
warningHTML += `<p><strong>幻觉关键词:</strong> ${warnings.keywords.join(', ')}</p>`;
}
if (warnings.entities && warnings.entities.length > 0) {
warningHTML += `<p><strong>幻觉实体:</strong> ${warnings.entities.join(', ')}</p>`;
}
if (warningHTML) {
resultHTML += `
<div class="hallucination-warning">
<p class="warning">⚠️ 检测到可能的幻觉内容:</p>
${warningHTML}
<p class="warning">请核对原文确认表述准确性</p>
</div>
`;
}
}
generateResultContent.innerHTML = resultHTML;
generateResult.classList.remove('hidden');
exportBtn.classList.remove('hidden');
editBtn.classList.remove('hidden');
scoreBtn.classList.remove('hidden');
// 隐藏进度条
generateProgress.classList.add('hidden');
// 标记步骤4为完成
steps[3].classList.add('completed');
} else {
generateTaskStatusSpan.textContent = `任务失败: ${result.error}`;
// 显示错误
generateResultContent.innerHTML = `<p class="error">任务失败: ${result.error}</p>`;
generateResult.classList.remove('hidden');
// 隐藏进度条
generateProgress.classList.add('hidden');
}
} catch (error) {
console.error('检查任务状态错误:', error);
generateTaskStatusSpan.textContent = '检查状态失败';
}
}
// 生成评分
async function scoreGeneration() {
if (!lastGeneratedResult || !lastContext || !lastQuery) {
alert('没有可评分的内容');
return;
}
try {
// 创建FormData
const formData = new FormData();
formData.append('generated_text', lastGeneratedResult.generated_text);
formData.append('context', lastContext);
formData.append('query', lastQuery);
// 发送评分请求
const response = await fetch('/score_generation', {
method: 'POST',
body: formData
});
const scoreResultData = await response.json();
if (response.ok) {
// 显示评分结果
displayScoreResult(scoreResultData);
} else {
throw new Error(scoreResultData.detail || '评分失败');
}
} catch (error) {
console.error('评分错误:', error);
scoreResult.innerHTML = `<p class="error">评分失败: ${error.message}</p>`;
scoreResult.classList.remove('hidden');
}
}
// 显示评分结果
function displayScoreResult(scoreData) {
let scoreHTML = `
<h3 class="success">生成评分结果</h3>
<p><strong>总分:</strong> ${scoreData.total_score || scoreData.score}/100</p>
`;
if (scoreData.dimensions) {
scoreHTML += `
<p><strong>各维度得分:</strong></p>
<ul>
<li>相关性: ${scoreData.dimensions.relevance}/30</li>
<li>准确性: ${scoreData.dimensions.accuracy}/30</li>
<li>完整性: ${scoreData.dimensions.completeness}/20</li>
<li>流畅性: ${scoreData.dimensions.fluency}/20</li>
</ul>
`;
}
if (scoreData.feedback) {
scoreHTML += `<p><strong>反馈意见:</strong> ${scoreData.feedback}</p>`;
}
if (scoreData.suggestions) {
scoreHTML += `<p><strong>改进建议:</strong> ${scoreData.suggestions}</p>`;
}
scoreResult.innerHTML = scoreHTML;
scoreResult.classList.remove('hidden');
}
// 导出为Markdown
async function exportToMarkdown() {
if (!lastGeneratedResult) {
alert('没有可导出的内容');
return;
}
try {
// 创建FormData
const formData = new FormData();
formData.append('query', lastGeneratedResult.query);
formData.append('generated_text', lastGeneratedResult.generated_text);
// 发送导出请求
const response = await fetch('/export_markdown', {
method: 'POST',
body: formData
});
if (response.ok) {
// 创建下载链接
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = response.headers.get('content-disposition')
.split('filename=')[1]
.replace(/"/g, '');
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
alert('导出成功!');
} else {
throw new Error('导出失败');
}
} catch (error) {
console.error('导出错误:', error);
alert('导出失败: ' + error.message);
}
}
// 导出为DOCX
async function exportToDocx() {
if (!lastGeneratedResult) {
alert('没有可导出的内容');
return;
}
try {
// 创建FormData
const formData = new FormData();
formData.append('query', lastGeneratedResult.query);
formData.append('generated_text', lastGeneratedResult.generated_text);
// 发送导出请求
const response = await fetch('/export_docx', {
method: 'POST',
body: formData
});
if (response.ok) {
// 创建下载链接
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = response.headers.get('content-disposition')
.split('filename=')[1]
.replace(/"/g, '');
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
alert('DOCX导出成功');
} else {
throw new Error('导出失败');
}
} catch (error) {
console.error('导出错误:', error);
alert('导出失败: ' + error.message);
}
}
// 打开编辑器
function openEditor() {
if (!lastGeneratedResult) {
alert('没有可编辑的内容');
return;
}
currentEditingText = lastGeneratedResult.generated_text;
editorTextarea.value = currentEditingText;
editorModal.classList.remove('hidden');
editorModal.style.display = 'flex'; // 确保显示
editorModal.style.visibility = 'visible'; // 确保可见
// 防止背景滚动
document.body.style.overflow = 'hidden';
}
// 保存编辑
function saveEdit() {
const editedText = editorTextarea.value;
lastGeneratedResult.generated_text = editedText;
// 更新显示内容
const paragraphs = generateResultContent.querySelectorAll('p');
for (let p of paragraphs) {
if (p.innerHTML.includes('生成文案:')) {
// 找到生成文案的段落并更新内容
const nextP = p.nextElementSibling;
if (nextP && nextP.tagName === 'P') {
nextP.innerHTML = editedText.replace(/\n/g, '<br>');
break;
}
}
}
// 关闭编辑器
editorModal.classList.add('hidden');
editorModal.style.display = 'none'; // 确保隐藏
editorModal.style.visibility = 'hidden'; // 确保隐藏
// 恢复背景滚动
document.body.style.overflow = 'auto';
editorTextarea.blur(); // 清除焦点
alert('编辑已保存');
}
// 取消编辑
function cancelEdit() {
// 恢复原始文本
editorTextarea.value = currentEditingText;
// 关闭编辑器
editorModal.classList.add('hidden');
editorModal.style.display = 'none'; // 确保隐藏
// 恢复背景滚动
document.body.style.overflow = 'auto';
// 清除焦点
editorTextarea.blur();
// 防止事件冒泡
if (event) {
event.stopPropagation();
}
// 确保完全隐藏
setTimeout(() => {
editorModal.style.visibility = 'hidden';
}, 10);
}
// 定期检查任务状态
setInterval(() => {
if (uploadTaskId) {
checkUploadTaskStatus();
}
if (generateTaskId) {
checkGenerateTaskStatus();
}
}, 3000); // 每3秒检查一次
</script>
</body>
</html>