Files
news-tracker/templates/settings.html
T

184 lines
8.4 KiB
HTML
Raw Normal View History

{% extends "base.html" %}
{% set active = 'settings' %}
{% block title %}设置 - 新闻智能跟踪{% endblock %}
{% block content %}
<div class="page-head">
<h1>⚙️ 设置</h1>
<button class="btn accent" onclick="saveAll()">💾 保存设置</button>
</div>
<div class="two-col">
<section class="card">
<h3>🕐 自动化</h3>
<div class="setting-row">
<label>自动定时采集</label>
<input type="checkbox" id="s_auto_collect" {{ 'checked' if auto.auto.auto_collect }}>
</div>
<div class="setting-row">
<label>采集间隔(分钟)</label>
<input type="number" id="s_scan_interval_min" value="{{ auto.auto.scan_interval_min }}">
</div>
<div class="setting-row">
<label>实时重要资讯邮件</label>
<input type="checkbox" id="s_realtime_enabled" {{ 'checked' if auto.auto.realtime_enabled }}>
</div>
<div class="setting-row">
<label>实时通知阈值(综合分)</label>
<input type="number" id="s_realtime_threshold" value="{{ auto.auto.realtime_threshold }}">
</div>
<div class="setting-row">
<label>LLM 深度分析阈值(规则分)</label>
<input type="number" id="s_llm_threshold" value="{{ auto.auto.llm_threshold }}">
</div>
<div class="setting-row">
<label>每日汇总邮件</label>
<input type="checkbox" id="s_summary_enabled" {{ 'checked' if auto.auto.summary_enabled }}>
</div>
<div class="setting-row">
<label>汇总时间</label>
<input type="time" id="s_summary_time" value="{{ auto.auto.summary_time }}">
</div>
<div class="setting-row">
<label>汇总窗口(小时)</label>
<input type="number" id="s_summary_window_hours" value="{{ auto.auto.summary_window_hours }}">
</div>
<div class="setting-row">
<label>汇总邮件最多条目</label>
<input type="number" id="s_max_summary_items" value="{{ auto.auto.max_summary_items }}">
</div>
</section>
<section class="card">
<h3>📮 邮件配置</h3>
<div class="setting-row"><label>SMTP 服务器</label><input id="m_host" value="{{ auto.mail.smtp_host }}"></div>
<div class="setting-row"><label>端口</label><input id="m_port" value="{{ auto.mail.smtp_port }}"></div>
<div class="setting-row"><label>账号</label><input id="m_user" value="{{ auto.mail.smtp_user }}"></div>
<div class="setting-row"><label>口令</label><input id="m_pass" type="password" value="{{ auto.mail.smtp_pass }}"></div>
<div class="setting-row">
<label>加密方式</label>
<select id="m_mode">
<option value="plain" {{ 'selected' if auto.mail.smtp_mode=='plain' }}>无加密</option>
<option value="starttls" {{ 'selected' if auto.mail.smtp_mode=='starttls' }}>STARTTLS</option>
<option value="ssl" {{ 'selected' if auto.mail.smtp_mode=='ssl' }}>SSL</option>
</select>
</div>
<div class="setting-row"><label>收件人</label><input id="m_to" value="{{ auto.mail.email_to }}"></div>
<div class="setting-row"><label>发件人名称</label><input id="m_name" value="{{ auto.mail.sender_name }}"></div>
</section>
</div>
<section class="card">
<h3>🤖 大模型接口
<span class="muted small">(切换后即时生效,智能分析将使用当前接口;失败自动切换下一个可用接口)</span>
</h3>
<div class="form-row" style="margin-bottom:12px;">
<input id="lp_name" placeholder="名称(如:SiliconFlow / DeepSeek 官方)">
<input id="lp_url" placeholder="Base URL(如 https://api.siliconflow.cn/v1" style="min-width:280px;">
<input id="lp_key" type="password" placeholder="API Key">
<input id="lp_model" placeholder="模型名(如 deepseek-ai/DeepSeek-V4-Flash">
<label style="display:flex;align-items:center;gap:4px;white-space:nowrap;">
<input type="checkbox" id="lp_active" checked> 设为当前
</label>
<button class="btn" onclick="addProvider()"> 添加接口</button>
</div>
<table>
<thead><tr><th>名称</th><th>Base URL</th><th>模型</th><th>状态</th><th>操作</th></tr></thead>
<tbody>
{% for p in providers %}
<tr data-pid="{{ p.id }}" data-name="{{ p.name }}" data-url="{{ p.base_url }}" data-model="{{ p.model }}" data-key="{{ p.api_key }}">
<td>
<b>{{ p.name }}</b>
{% if p.active %}<span class="tag ok">当前</span>{% endif %}
<div class="muted small" style="word-break:break-all;">{{ p.api_key[:6] }}…{{ p.api_key[-4:] if p.api_key|length>10 }}</div>
</td>
<td class="small" style="word-break:break-all;">{{ p.base_url }}</td>
<td class="small">{{ p.model }}</td>
<td>{% if p.enabled %}<span class="tag ok">启用</span>{% else %}<span class="tag">停用</span>{% endif %}</td>
<td>
{% if not p.active %}
<button class="btn mini" onclick="switchProvider({{ p.id }})">⚡ 切换为当前</button>
{% endif %}
<button class="btn mini" onclick="editProvider({{ p.id }})">编辑</button>
<button class="btn mini" onclick="testProvider({{ p.id }})">测试</button>
<button class="btn mini" onclick="toggleProvider({{ p.id }})">{{ '停用' if p.enabled else '启用' }}</button>
<button class="btn mini danger" onclick="delProvider({{ p.id }})">删除</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<div id="lp_test_result" class="muted small" style="margin-top:10px;"></div>
</section>
{% endblock %}
{% block script %}
<script>
const G = id => document.getElementById(id).value;
async function saveAll(){
const auto = {
auto_collect: document.getElementById('s_auto_collect').checked ? 1 : 0,
realtime_enabled: document.getElementById('s_realtime_enabled').checked ? 1 : 0,
summary_enabled: document.getElementById('s_summary_enabled').checked ? 1 : 0,
scan_interval_min: parseInt(G('s_scan_interval_min'))||30,
realtime_threshold: parseInt(G('s_realtime_threshold'))||80,
llm_threshold: parseInt(G('s_llm_threshold'))||60,
summary_time: G('s_summary_time')||'10:00',
summary_window_hours: parseInt(G('s_summary_window_hours'))||24,
max_summary_items: parseInt(G('s_max_summary_items'))||15,
};
const mail = {
smtp_host: G('m_host'), smtp_port: parseInt(G('m_port'))||587,
smtp_user: G('m_user'), smtp_pass: G('m_pass'), smtp_mode: G('m_mode'),
email_to: G('m_to'), sender_name: G('m_name'),
};
const r = await API.json('/api/settings', { ...auto, mail });
toast(r.ok ? '✅ 设置已保存' : '❌ 保存失败', r.ok);
}
async function addProvider(){
const r = await API.json('/api/llm', {
action:'add',
name: G('lp_name')||'未命名接口',
base_url: G('lp_url'), api_key: G('lp_key'), model: G('lp_model'),
active: document.getElementById('lp_active').checked ? 1 : 0,
});
if (r.ok) { toast('✅ 接口已添加'); setTimeout(()=>location.reload(), 500); }
else toast('❌ 添加失败', false);
}
async function switchProvider(id){
const r = await API.json('/api/llm', {action:'switch', id});
toast(r.ok ? `⚡ 已切换:${r.name}` : '❌ 切换失败', r.ok);
if (r.ok) setTimeout(()=>location.reload(), 400);
}
async function editProvider(id){
const row = document.querySelector(`tr[data-pid="${id}"]`);
if (!row) return;
const name = prompt('接口名称', row.dataset.name);
if (name === null) return;
const url = prompt('Base URL', row.dataset.url);
if (url === null) return;
const model = prompt('模型名', row.dataset.model);
if (model === null) return;
const key = prompt('API Key(留空则不变)', '') || row.dataset.key;
const r = await API.json('/api/llm', {action:'update', id, name, base_url:url, api_key:key, model});
toast(r.ok ? '✅ 已更新' : '❌ 更新失败', r.ok);
if (r.ok) setTimeout(()=>location.reload(), 400);
}
async function testProvider(id){
const el = document.getElementById('lp_test_result');
el.textContent = '⏳ 测试中…';
const r = await API.json('/api/llm', {action:'test', id});
if (r.ok) el.textContent = `✅ ${r.name}${r.model})连接正常,回复:${r.reply}`;
else el.textContent = `❌ 测试失败:${r.error}`;
}
async function toggleProvider(id){
await API.json('/api/llm', {action:'toggle', id}); location.reload();
}
async function delProvider(id){
if (!confirm('确认删除该大模型接口?')) return;
await API.json('/api/llm', {action:'delete', id});
toast('✅ 已删除'); setTimeout(()=>location.reload(), 400);
}
</script>
{% endblock %}