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 表
This commit is contained in:
2026-08-28 16:06:07 +08:00
parent 2b8e78a4cc
commit e4efd24e1c
13 changed files with 757 additions and 97 deletions
+21 -3
View File
@@ -12,7 +12,7 @@
<div class="form-row">
<input id="f_name" placeholder="名称(如:36氪 AI频道)">
<input id="f_type" placeholder="类型(科技媒体/公司动态/学术/政策...)">
<input id="f_url" placeholder="URL / RSS 地址">
<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>
@@ -24,15 +24,16 @@
<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>
<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></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>
@@ -40,6 +41,7 @@
{% endfor %}
</tbody>
</table>
<div class="muted small" style="margin-top:8px;">💡 真实 URL 源会自动抓取页面并清洗可读正文入库;example.com 等占位地址走模拟数据。</div>
</div>
{% endblock %}
{% block script %}
@@ -52,6 +54,22 @@ async function saveSource(){
});
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;