Files
news-tracker/templates/sources.html
T

157 lines
7.3 KiB
HTML
Raw Normal View History

{% extends "base.html" %}
{% set active = 'sources' %}
{% block title %}数据源 - 新闻智能跟踪{% endblock %}
{% block content %}
<div class="page-head">
<h1>📡 数据源管理</h1>
<button class="btn accent" onclick="openModal()"> 新增数据源</button>
</div>
<div class="card">
<table>
<thead><tr><th>ID</th><th>名称</th><th>监控方式</th><th>类型</th><th>权重</th><th>状态</th><th>最近采集</th><th>新增条数</th><th>操作</th></tr></thead>
<tbody>
{% for s in sources %}
<tr data-sid="{{ s.id }}" data-name="{{ s.name }}" data-type="{{ s.type }}" data-url="{{ s.url }}" data-desc="{{ s.description }}" data-weight="{{ s.weight }}" data-kind="{{ s.kind }}" data-standard="{{ s.monitor_standard or '' }}">
<td>{{ s.id }}</td>
<td><b>{{ s.name }}</b><br><span class="muted small">{{ s.description }}</span><br><span class="muted small" style="word-break:break-all;">{{ s.url }}</span></td>
<td>{% if s.kind == 'custom' %}<span class="tag hot">🎯 定制监控</span>{% else %}<span class="tag ok">普通</span>{% endif %}</td>
<td>{{ s.type }}</td>
<td>{% if s.kind == 'custom' %}<span class="muted small"></span>{% else %}{{ s.weight }}{% endif %}</td>
<td>{% if s.enabled %}<span class="tag ok">启用</span>{% else %}<span class="tag">停用</span>{% endif %}</td>
<td>{{ s.last_fetch or '—' }}</td>
<td>{{ s.last_count }}</td>
<td>
<button class="btn mini" onclick="openModal({{ s.id }})">✏️ 编辑</button>
<button class="btn mini" onclick="toggleSource({{ s.id }})">{{ '停用' if s.enabled else '启用' }}</button>
<button class="btn mini danger" onclick="delSource({{ s.id }})">删除</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="muted small" style="margin-top:8px;">
💡 真实 URL 源自动抓取页面并清洗可读正文入库;example.com 等占位地址走模拟数据。<br>
🎯 <b>定制监控</b>:不设权重,填写「推送标准」后由大模型逐条判断是否达到标准,达到即实时邮件推送。
</div>
</div>
<!-- 新增 / 编辑 数据源 模态框 -->
<div class="modal-mask" id="sourceModal" style="display:none;">
<div class="modal">
<div class="modal-head">
<h3 id="modalTitle">新增数据源</h3>
<span class="modal-x" onclick="closeModal()"></span>
</div>
<div class="modal-body">
<label>名称 *</label>
<input id="m_name" placeholder="如:36氪 AI频道">
<label>监控方式</label>
<select id="m_kind" onchange="onKindChange()">
<option value="normal">普通数据源(按权重打分推送)</option>
<option value="custom">定制监控(无权重,按推送标准由大模型判断推送)</option>
</select>
<div id="m_weight_row">
<label>权重(综合评分中该源占比,0.1~2.0)</label>
<input id="m_weight" type="number" step="0.1" min="0.1" max="2" value="0.8">
</div>
<div id="m_standard_row" style="display:none;">
<label>推送标准 *(大模型据此判断每条资讯是否达到推送条件)</label>
<textarea id="m_standard" rows="3" placeholder="例如:仅推送英伟达重大事件——股价单日涨跌超10%,或重大产品发布/回购/分拆/收购等实质性公告,或分析师大幅上调目标价;普通行情波动与日常新闻一律不推送。"></textarea>
<div class="form-row" style="justify-content:flex-start;">
<button class="btn mini" type="button" onclick="testStandard()">🧪 测试推送标准</button>
<span id="m_test_result" class="muted small"></span>
</div>
</div>
<label>类型(分类标签)</label>
<input id="m_type" placeholder="科技媒体/公司动态/学术/政策...">
<label>URL / RSS 地址</label>
<input id="m_url" placeholder="example.com 开头走模拟数据">
<label>描述</label>
<textarea id="m_desc" rows="2" placeholder="该源提供哪些内容"></textarea>
</div>
<div class="modal-foot">
<button class="btn" onclick="closeModal()">取消</button>
<button class="btn accent" onclick="saveSource()">保存</button>
</div>
</div>
</div>
{% endblock %}
{% block script %}
<script>
let editId = null;
function openModal(id){
editId = id || null;
document.getElementById('modalTitle').textContent = editId ? '编辑数据源' : '新增数据源';
document.getElementById('m_test_result').textContent = '';
if (editId){
const row = document.querySelector(`tr[data-sid="${editId}"]`);
if (!row) return;
setV('m_name', row.dataset.name);
setV('m_type', row.dataset.type);
setV('m_url', row.dataset.url);
setV('m_desc', row.dataset.desc);
setV('m_weight', row.dataset.weight || '0.8');
setV('m_kind', row.dataset.kind || 'normal');
setV('m_standard', row.dataset.standard || '');
} else {
['m_name','m_type','m_url','m_desc','m_standard'].forEach(k=>setV(k,''));
setV('m_weight', '0.8');
setV('m_kind', 'normal');
}
onKindChange();
document.getElementById('sourceModal').style.display = 'flex';
}
function closeModal(){ document.getElementById('sourceModal').style.display = 'none'; editId = null; }
function onKindChange(){
const custom = getV('m_kind') === 'custom';
document.getElementById('m_weight_row').style.display = custom ? 'none' : 'block';
document.getElementById('m_standard_row').style.display = custom ? 'block' : 'none';
}
async function saveSource(){
const kind = getV('m_kind');
const body = {
name: getV('m_name').trim(), type: getV('m_type').trim(), url: getV('m_url').trim(),
description: getV('m_desc').trim(), kind,
monitor_standard: kind === 'custom' ? getV('m_standard').trim() : '',
weight: parseFloat(getV('m_weight') || '1') || 1
};
if (!body.name){ toast('请填写名称', false); return; }
if (kind === 'custom' && !body.monitor_standard){ toast('请填写推送标准', false); return; }
const r = await API.json('/api/sources',
editId ? {...body, action:'update', id: editId} : {...body, action:'add'});
toast(r.ok ? '✅ 已保存' : '❌ 保存失败', r.ok);
if (r.ok){ closeModal(); setTimeout(()=>location.reload(), 400); }
}
async function testStandard(){
const standard = getV('m_standard').trim();
if (!standard){ toast('请先填写推送标准', false); return; }
const el = document.getElementById('m_test_result');
el.textContent = '⏳ 大模型分析中...';
const r = await API.json('/api/sources', {action:'test_standard', monitor_standard: standard});
if (r.ok){
el.textContent = r.meets ? '✅ 命中标准,会推送 · ' + (r.reason||'') : '❌ 未达标准,不推送 · ' + (r.reason||'');
} else {
el.textContent = '❌ ' + (r.error || '测试失败');
}
}
async function toggleSource(id){ await API.json('/api/sources', {action:'toggle', id}); location.reload(); }
async function delSource(id){
if (!confirm('确认删除该数据源?')) return;
await API.json('/api/sources', {action:'delete', id}); toast('✅ 已删除'); setTimeout(()=>location.reload(), 400);
}
function getV(id){ return document.getElementById(id).value; }
function setV(id, v){ document.getElementById(id).value = v; }
</script>
{% endblock %}