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
+74 -1
View File
@@ -8,6 +8,8 @@ from datetime import datetime, timedelta
from flask import Flask, render_template, request, jsonify, redirect, url_for
import requests
import config
import db
import simulate
@@ -87,7 +89,8 @@ def logs():
@app.route("/settings")
def settings_page():
return render_template("settings.html", auto=db.get_all_settings())
return render_template("settings.html", auto=db.get_all_settings(),
providers=db.list_providers())
# ---------------- API ----------------
@@ -170,6 +173,69 @@ def api_settings():
return jsonify({"ok": True})
@app.route("/api/llm", methods=["POST"])
def api_llm():
"""大模型接口管理:增删改 / 一键切换 / 测试"""
data = request.get_json(force=True) or {}
action = data.get("action")
if action == "add":
pid = db.add_provider(
data.get("name", ""), data.get("base_url", ""), data.get("api_key", ""),
data.get("model", ""),
active=1 if data.get("active") else 0,
enabled=1 if data.get("enabled", 1) else 0,
)
if data.get("active"):
db.set_active_provider(pid)
return jsonify({"ok": True, "id": pid})
if action == "update":
db.update_provider(data["id"], name=data.get("name"), base_url=data.get("base_url"),
api_key=data.get("api_key"), model=data.get("model"),
enabled=1 if data.get("enabled", 1) else 0)
return jsonify({"ok": True})
if action == "delete":
db.delete_provider(data["id"])
return jsonify({"ok": True})
if action == "switch":
db.set_active_provider(data["id"])
p = db.get_provider(data["id"])
return jsonify({"ok": True, "name": p["name"] if p else ""})
if action == "toggle":
p = db.get_provider(data["id"])
if not p:
return jsonify({"ok": False, "error": "not found"})
db.update_provider(data["id"], enabled=0 if p["enabled"] else 1)
return jsonify({"ok": True})
if action == "test":
# 用指定接口(或当前激活接口)发一条测试消息
cfg = None
if data.get("id"):
p = db.get_provider(data["id"])
if p and p.get("base_url"):
cfg = {"name": p["name"], "base_url": p["base_url"].rstrip("/"),
"api_key": p.get("api_key", ""), "model": p.get("model", "")}
try:
if cfg is None:
import analysis
cfg = analysis.get_llm_cfg()
r = requests.post(
f"{cfg['base_url']}/chat/completions",
headers={"Authorization": f"Bearer {cfg['api_key']}",
"Content-Type": "application/json"},
json={"model": cfg["model"],
"messages": [{"role": "user", "content": "请回复:连接正常"}],
"max_tokens": 60, "temperature": 0.3},
timeout=60,
)
r.raise_for_status()
content = r.json()["choices"][0]["message"]["content"]
return jsonify({"ok": True, "name": cfg["name"], "model": cfg["model"],
"reply": content})
except Exception as e:
return jsonify({"ok": False, "error": str(e)})
return jsonify({"ok": False, "error": "unknown action"})
@app.route("/api/actions", methods=["POST"])
def api_actions():
data = request.get_json(force=True) or {}
@@ -223,6 +289,13 @@ def main():
db.add_domain(d, w)
for c in config.DEFAULT_COMPANIES:
db.add_company(c)
# 预置大模型接口(可一键切换)
if not db.list_providers():
for p in config.LLM_PROVIDERS_DEFAULT:
pid = db.add_provider(p["name"], p["base_url"], p["api_key"], p["model"],
active=1 if p.get("active") else 0)
if p.get("active"):
db.set_active_provider(pid)
db.set_setting("initialized", 1)
db.set_setting("auto", dict(config.AUTO_DEFAULTS))
db.set_setting("mail", dict(config.MAIL_DEFAULTS))