Files
news-tracker/templates/sources.html
T
hz4th_coder e4efd24e1c v1.1.0: 真实网页采集(可读正文全文入库) + 大模型接口多预置一键切换(SiliconFlow默认) + 数据源可编辑
- 新增 crawler.py: requests+bs4 readability风格清洗, 提取候选链接按文章相似度排序, 前5条抓全文存 articles.full_text, 详情页展示; example.com占位源走模拟, 真实源失败标记error不造假
- 大模型: 新增 llm_providers 表(预置 SiliconFlow/DeepSeek官方/Autodl/Local Qwen), 设置页增删改/测试/一键切换, 激活接口失败自动切换备用
- 数据源: 前端补编辑按钮+弹窗(后端 update 已支持), 列表显示URL与采集状态
- 数据库迁移: articles.full_text 列 + llm_providers 表
2026-08-28 16:06:07 +08:00

81 lines
3.9 KiB
HTML
Raw 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="toggleAdd()"> 新增数据源</button>
</div>
<div class="card" id="add-box" style="display:none;">
<h3>新增数据源</h3>
<div class="form-row">
<input id="f_name" placeholder="名称(如:36氪 AI频道)">
<input id="f_type" placeholder="类型(科技媒体/公司动态/学术/政策...)">
<input id="f_url" placeholder="URL / RSS 地址(example.com 开头走模拟数据)">
<input id="f_weight" type="number" step="0.1" value="0.8" placeholder="权重">
</div>
<textarea id="f_desc" placeholder="描述(该源提供哪些内容)" rows="2"></textarea>
<div class="form-row"><button class="btn" onclick="saveSource()">保存</button></div>
</div>
<div class="card">
<table>
<thead><tr><th>ID</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 }}">
<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>{{ s.type }}</td>
<td>{{ s.weight }}</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="editSource({{ 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 等占位地址走模拟数据。</div>
</div>
{% endblock %}
{% block script %}
<script>
function toggleAdd(){ document.getElementById('add-box').style.display = document.getElementById('add-box').style.display==='none' ? 'block' : 'none'; }
async function saveSource(){
const r = await API.json('/api/sources', {
action:'add', name:val('f_name'), type:val('f_type'), url:val('f_url'),
weight:parseFloat(val('f_weight')||'1'), description:val('f_desc')
});
if (r.ok) { toast('✅ 已添加'); setTimeout(()=>location.reload(), 600); } else toast('❌ 添加失败', false);
}
async function editSource(id){
const row = document.querySelector(`tr[data-sid="${id}"]`);
if (!row) return;
const name = prompt('名称', row.dataset.name);
if (name === null) return;
const type = prompt('类型', row.dataset.type);
if (type === null) return;
const url = prompt('URL / RSS 地址', row.dataset.url);
if (url === null) return;
const weight = parseFloat(prompt('权重', row.dataset.weight) || '1') || 1;
const desc = prompt('描述', row.dataset.desc);
if (desc === null) return;
const r = await API.json('/api/sources', {action:'update', id, name, type, url, weight, description:desc});
toast(r.ok ? '✅ 已更新' : '❌ 更新失败', r.ok);
if (r.ok) setTimeout(()=>location.reload(), 400);
}
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 val(id){ return document.getElementById(id).value; }
</script>
{% endblock %}