nodebookls/templates/index.html
2025-10-29 13:56:24 +08:00

1004 lines
38 KiB
HTML
Raw Permalink 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>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>