feat: 主题核心思路功能 - 每个主题可设定核心思路,AI分析和生成文章时自动引入
This commit is contained in:
@@ -73,6 +73,9 @@
|
||||
<span class="config-indicator text-muted" id="configIndicator">
|
||||
<i class="bi bi-circle text-warning"></i> 未配置
|
||||
</span>
|
||||
<button class="btn btn-outline-accent btn-sm" onclick="showPromptTemplateModal()">
|
||||
<i class="bi bi-chat-square-text me-1"></i>Prompt模板
|
||||
</button>
|
||||
<button class="btn btn-outline-accent btn-sm" onclick="showConfigModal()">
|
||||
<i class="bi bi-gear me-1"></i>模型配置
|
||||
</button>
|
||||
@@ -193,6 +196,11 @@
|
||||
<label class="form-label">主题说明</label>
|
||||
<textarea class="form-control" id="topicDesc" rows="2" placeholder="简要描述这个主题的方向"></textarea>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">核心思路</label>
|
||||
<textarea class="form-control" id="topicCoreIdea" rows="3" placeholder="概括整体思路、文章风格、创作方向等,将指导AI分析和创作"></textarea>
|
||||
<div class="form-text">简洁概括核心思路与风格要求,AI分析和生成文章时会参考此内容</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">分类(可多选)</label>
|
||||
<div class="select-pills" id="categoryPills">
|
||||
@@ -214,7 +222,14 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">默认Prompt</label>
|
||||
<label class="form-label">生成Prompt</label>
|
||||
<div class="mb-2">
|
||||
<select class="form-select form-select-sm" id="topicPromptTemplate" onchange="applyPromptTemplate(this)">
|
||||
<option value="">-- 选择Prompt模板 --</option>
|
||||
<option value="__custom__">自定义输入</option>
|
||||
</select>
|
||||
<div class="form-text">选择模板自动填充,也可手动编辑</div>
|
||||
</div>
|
||||
<textarea class="form-control" id="topicPrompt" rows="4" placeholder="请根据以下素材撰写一篇结构清晰、内容丰富的文章:"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
@@ -310,12 +325,44 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Prompt模板管理模态框 -->
|
||||
<div class="modal fade" id="promptTemplateModal" tabindex="-1">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title"><i class="bi bi-chat-square-text me-2"></i>Prompt模板管理</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="card-dark p-3 mb-3" style="border:1px solid var(--border);">
|
||||
<div class="mb-2">
|
||||
<label class="form-label small">模板名称</label>
|
||||
<input type="text" class="form-control form-control-sm" id="newTplName" placeholder="如:通用写作">
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label class="form-label small">Prompt内容</label>
|
||||
<textarea class="form-control form-control-sm" id="newTplPrompt" rows="4" placeholder="请根据以下素材撰写一篇..."></textarea>
|
||||
</div>
|
||||
<button class="btn btn-sm btn-accent" onclick="createPromptTemplate()"><i class="bi bi-plus-lg me-1"></i>添加模板</button>
|
||||
</div>
|
||||
<div id="promptTemplateList">
|
||||
<!-- 动态填充 -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script>
|
||||
const createModalInstance = new bootstrap.Modal(document.getElementById('createModal'));
|
||||
const configModalInstance = new bootstrap.Modal(document.getElementById('configModal'));
|
||||
const categoryModalInstance = new bootstrap.Modal(document.getElementById('categoryModal'));
|
||||
const tagModalInstance = new bootstrap.Modal(document.getElementById('tagModal'));
|
||||
const promptTemplateModalInstance = new bootstrap.Modal(document.getElementById('promptTemplateModal'));
|
||||
|
||||
// Prompt模板数据缓存
|
||||
let promptTemplatesCache = [];
|
||||
|
||||
// 筛选状态
|
||||
let activeCategoryId = '';
|
||||
@@ -387,11 +434,14 @@
|
||||
newTagNames = [];
|
||||
document.getElementById('topicName').value = '';
|
||||
document.getElementById('topicDesc').value = '';
|
||||
document.getElementById('topicCoreIdea').value = '';
|
||||
document.getElementById('topicPrompt').value = '';
|
||||
document.getElementById('topicPromptTemplate').value = '';
|
||||
document.getElementById('newTagInput').value = '';
|
||||
document.getElementById('newTagPills').innerHTML = '';
|
||||
loadCategoryPills();
|
||||
loadTagPills();
|
||||
loadPromptTemplates();
|
||||
createModalInstance.show();
|
||||
}
|
||||
|
||||
@@ -473,6 +523,7 @@
|
||||
body: JSON.stringify({
|
||||
name,
|
||||
description: document.getElementById('topicDesc').value,
|
||||
core_idea: document.getElementById('topicCoreIdea').value,
|
||||
prompt: document.getElementById('topicPrompt').value,
|
||||
category_ids: selectedCategoryIds,
|
||||
tag_ids: selectedTagIds,
|
||||
@@ -687,6 +738,138 @@
|
||||
}
|
||||
}
|
||||
|
||||
// ===== Prompt模板管理 =====
|
||||
async function loadPromptTemplates() {
|
||||
try {
|
||||
const res = await fetch('/api/prompt-templates');
|
||||
promptTemplatesCache = await res.json();
|
||||
// 填充新建主题的模板下拉
|
||||
const sel = document.getElementById('topicPromptTemplate');
|
||||
const currentVal = sel.value;
|
||||
sel.innerHTML = '<option value="">-- 选择Prompt模板 --</option><option value="__custom__">自定义输入</option>';
|
||||
promptTemplatesCache.forEach(t => {
|
||||
sel.innerHTML += `<option value="${t.id}">${t.name}</option>`;
|
||||
});
|
||||
sel.value = currentVal;
|
||||
} catch(e) {}
|
||||
}
|
||||
|
||||
function applyPromptTemplate(sel) {
|
||||
const val = sel.value;
|
||||
if (!val || val === '__custom__') return;
|
||||
const tpl = promptTemplatesCache.find(t => t.id === val);
|
||||
if (tpl) {
|
||||
document.getElementById('topicPrompt').value = tpl.prompt;
|
||||
}
|
||||
}
|
||||
|
||||
async function showPromptTemplateModal() {
|
||||
promptTemplateModalInstance.show();
|
||||
await loadPromptTemplateList();
|
||||
}
|
||||
|
||||
async function loadPromptTemplateList() {
|
||||
try {
|
||||
const res = await fetch('/api/prompt-templates');
|
||||
const templates = await res.json();
|
||||
promptTemplatesCache = templates;
|
||||
const container = document.getElementById('promptTemplateList');
|
||||
if (templates.length === 0) {
|
||||
container.innerHTML = '<div class="text-muted small text-center py-2">暂无模板</div>';
|
||||
return;
|
||||
}
|
||||
container.innerHTML = templates.map(t => `
|
||||
<div class="d-flex align-items-start p-2 mb-2" style="background:var(--bg-dark);border-radius:8px;border:1px solid var(--border);" id="tpl-row-${t.id}">
|
||||
<div class="flex-grow-1 me-2">
|
||||
<div id="tpl-display-${t.id}">
|
||||
<div class="d-flex align-items-center gap-2 mb-1">
|
||||
<strong class="small">${escapeHtml(t.name)}</strong>
|
||||
${t.is_default ? '<span class="badge bg-secondary" style="font-size:0.65rem;">内置</span>' : ''}
|
||||
</div>
|
||||
<div class="small text-muted" style="white-space:pre-wrap;max-height:60px;overflow:hidden;">${escapeHtml(t.prompt)}</div>
|
||||
</div>
|
||||
<div id="tpl-edit-${t.id}" style="display:none;">
|
||||
<input type="text" class="form-control form-control-sm mb-1" id="tpl-input-name-${t.id}" value="${escapeHtml(t.name)}">
|
||||
<textarea class="form-control form-control-sm" id="tpl-input-prompt-${t.id}" rows="3">${escapeHtml(t.prompt)}</textarea>
|
||||
<div class="mt-1 d-flex gap-1">
|
||||
<button class="btn btn-sm btn-accent py-0 px-2" onclick="savePromptTemplate('${t.id}')"><i class="bi bi-check-lg"></i></button>
|
||||
<button class="btn btn-sm btn-secondary py-0 px-2" onclick="cancelTplEdit('${t.id}')"><i class="bi bi-x-lg"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex gap-1 flex-shrink-0" id="tpl-actions-${t.id}">
|
||||
<button class="btn btn-sm btn-link text-accent p-0" onclick="editPromptTemplate('${t.id}')" title="编辑"><i class="bi bi-pencil"></i></button>
|
||||
<button class="btn btn-sm btn-link text-danger p-0" onclick="deletePromptTemplate('${t.id}','${escapeHtml(t.name)}')" title="删除"><i class="bi bi-trash3"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
} catch(e) {}
|
||||
}
|
||||
|
||||
function escapeHtml(text) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
function editPromptTemplate(id) {
|
||||
document.getElementById('tpl-display-' + id).style.display = 'none';
|
||||
document.getElementById('tpl-edit-' + id).style.display = '';
|
||||
document.getElementById('tpl-actions-' + id).style.display = 'none';
|
||||
document.getElementById('tpl-input-name-' + id).focus();
|
||||
}
|
||||
|
||||
function cancelTplEdit(id) {
|
||||
document.getElementById('tpl-display-' + id).style.display = '';
|
||||
document.getElementById('tpl-edit-' + id).style.display = 'none';
|
||||
document.getElementById('tpl-actions-' + id).style.display = 'flex';
|
||||
}
|
||||
|
||||
async function savePromptTemplate(id) {
|
||||
const name = document.getElementById('tpl-input-name-' + id).value.trim();
|
||||
const prompt = document.getElementById('tpl-input-prompt-' + id).value.trim();
|
||||
if (!name) return alert('模板名称不能为空');
|
||||
if (!prompt) return alert('模板内容不能为空');
|
||||
const res = await fetch(`/api/prompt-templates/${id}`, {
|
||||
method: 'PUT',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({name, prompt})
|
||||
});
|
||||
if (res.ok) {
|
||||
await loadPromptTemplateList();
|
||||
await loadPromptTemplates();
|
||||
} else {
|
||||
const d = await res.json(); alert(d.error || '保存失败');
|
||||
}
|
||||
}
|
||||
|
||||
async function createPromptTemplate() {
|
||||
const name = document.getElementById('newTplName').value.trim();
|
||||
const prompt = document.getElementById('newTplPrompt').value.trim();
|
||||
if (!name) return alert('请输入模板名称');
|
||||
if (!prompt) return alert('请输入Prompt内容');
|
||||
const res = await fetch('/api/prompt-templates', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({name, prompt})
|
||||
});
|
||||
if (res.ok) {
|
||||
document.getElementById('newTplName').value = '';
|
||||
document.getElementById('newTplPrompt').value = '';
|
||||
await loadPromptTemplateList();
|
||||
await loadPromptTemplates();
|
||||
} else {
|
||||
const d = await res.json(); alert(d.error || '创建失败');
|
||||
}
|
||||
}
|
||||
|
||||
async function deletePromptTemplate(id, name) {
|
||||
if (!confirm(`确定删除Prompt模板「${name}」?`)) return;
|
||||
await fetch(`/api/prompt-templates/${id}`, {method: 'DELETE'});
|
||||
await loadPromptTemplateList();
|
||||
await loadPromptTemplates();
|
||||
}
|
||||
|
||||
async function saveConfig() {
|
||||
const data = {
|
||||
url: document.getElementById('cfgUrl').value.trim(),
|
||||
|
||||
Reference in New Issue
Block a user