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:
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user