Files
news-tracker/templates/sources.html
T

158 lines
7.3 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.
{% 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 %}