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
+95 -5
View File
@@ -83,6 +83,17 @@ CREATE TABLE IF NOT EXISTS settings (
value TEXT DEFAULT ''
);
CREATE TABLE IF NOT EXISTS llm_providers (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
base_url TEXT DEFAULT '',
api_key TEXT DEFAULT '',
model TEXT DEFAULT '',
active INTEGER DEFAULT 0, -- 1=当前激活(网页一键切换)
enabled INTEGER DEFAULT 1,
created_at TEXT DEFAULT ''
);
CREATE INDEX IF NOT EXISTS idx_articles_collected ON articles(collected_at);
CREATE INDEX IF NOT EXISTS idx_articles_score ON articles(total_score);
CREATE INDEX IF NOT EXISTS idx_articles_status ON articles(llm_status);
@@ -100,7 +111,11 @@ def init_db():
os.makedirs(config.DATA_DIR, exist_ok=True)
conn = get_conn()
conn.executescript(_SCHEMA)
conn.commit()
# 迁移:旧库补充 full_text 列(存页面可读正文)
cols = [r["name"] for r in conn.execute("PRAGMA table_info(articles)").fetchall()]
if "full_text" not in cols:
conn.execute("ALTER TABLE articles ADD COLUMN full_text TEXT DEFAULT ''")
conn.commit()
conn.close()
@@ -221,14 +236,15 @@ def add_article(a):
cur = conn.execute(
"""INSERT INTO articles(source_id,title,url,author,content,summary,domain,entities,
importance,relevance,total_score,is_important,analysis,llm_status,status,
published_at,collected_at)
VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)""",
published_at,collected_at,full_text)
VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) """,
(a.get("source_id", 0), a["title"], a.get("url", ""), a.get("author", ""),
a.get("content", ""), a.get("summary", ""), a.get("domain", ""),
json.dumps(a.get("entities", []), ensure_ascii=False),
a.get("importance", 0), a.get("relevance", 0), a.get("total_score", 0),
a.get("is_important", 0), a.get("analysis", ""), a.get("llm_status", "pending"),
a.get("status", "new"), a.get("published_at", ""), now_str()),
a.get("status", "new"), a.get("published_at", ""), now_str(),
a.get("full_text", "")),
)
conn.commit()
conn.close()
@@ -237,7 +253,8 @@ def add_article(a):
def update_article(aid, **fields):
allowed = {"summary", "domain", "entities", "importance", "relevance",
"total_score", "is_important", "analysis", "llm_status", "notified", "status"}
"total_score", "is_important", "analysis", "llm_status", "notified", "status",
"full_text", "content", "title", "url"}
sets, vals = [], []
for k, v in fields.items():
if k in allowed:
@@ -447,6 +464,79 @@ def delete_company(cid):
conn.close()
# ---------------- 大模型接口(llm_providers ----------------
def list_providers():
conn = get_conn()
rows = conn.execute("SELECT * FROM llm_providers ORDER BY active DESC, id").fetchall()
conn.close()
return [dict(r) for r in rows]
def get_provider(pid):
conn = get_conn()
row = conn.execute("SELECT * FROM llm_providers WHERE id=?", (pid,)).fetchone()
conn.close()
return dict(row) if row else None
def add_provider(name, base_url, api_key, model, active=0, enabled=1):
conn = get_conn()
cur = conn.execute(
"INSERT INTO llm_providers(name,base_url,api_key,model,active,enabled,created_at) "
"VALUES(?,?,?,?,?,?,?)",
(name, base_url, api_key, model, active, enabled, now_str()),
)
conn.commit()
conn.close()
return cur.lastrowid
def update_provider(pid, **fields):
allowed = {"name", "base_url", "api_key", "model", "active", "enabled"}
sets, vals = [], []
for k, v in fields.items():
if k in allowed:
sets.append(f"{k}=?")
vals.append(v)
if not sets:
return
vals.append(pid)
conn = get_conn()
conn.execute(f"UPDATE llm_providers SET {','.join(sets)} WHERE id=?", vals)
conn.commit()
conn.close()
def delete_provider(pid):
conn = get_conn()
conn.execute("DELETE FROM llm_providers WHERE id=?", (pid,))
conn.commit()
conn.close()
def get_active_provider():
conn = get_conn()
row = conn.execute("SELECT * FROM llm_providers WHERE active=1 AND enabled=1 LIMIT 1").fetchone()
conn.close()
return dict(row) if row else None
def set_active_provider(pid):
conn = get_conn()
conn.execute("UPDATE llm_providers SET active=0")
conn.execute("UPDATE llm_providers SET active=1, enabled=1 WHERE id=?", (pid,))
conn.commit()
conn.close()
def enabled_providers_except(pid):
conn = get_conn()
rows = conn.execute(
"SELECT * FROM llm_providers WHERE enabled=1 AND id!=? ORDER BY id", (pid,)).fetchall()
conn.close()
return [dict(r) for r in rows]
# ---------------- 通知日志 ----------------
def add_log(type_, subject, count, article_ids, status="ok", detail=""):
conn = get_conn()