Mavis
|
764de4e85c
|
fix(worker): enrichment_loop 改并发 + 加大 batch
之前每轮只跑 3 篇串行,587 篇待 enrich 队列要 4.7h 才清完。
改动:
- ENRICHMENT_BATCH_SIZE: 3 -> 8
- ENRICHMENT_INTERVAL_SEC: 5 -> 2
- 处理 todo_ids 改 asyncio.gather 并发 3 篇
- LlmClient 内部 interval_sec 限速不变,这里只加并发上限
效果:每分钟 ~7 篇 -> 587 篇约 84 分钟清完。
排查过程中还发现根因: llm_settings 表空行导致 enrichment 静默跳过,
已手动 INSERT 默认 LlmSetting(id=1, enabled=true) 触发循环。
|
2026-06-10 17:20:53 +08:00 |
|
Mavis
|
81c83ced8d
|
feat(feed): 列表展示翻译正文摘要 + 页码分页
首页 Feed.vue 改造:
- 卡片在中文标题下直接展示 body_zh_text(前 220 字)
用户不进详情就能看到译文正文,提升阅读效率
- 配图(image_ai_url 或 image_url)也直接显示在卡片中
- 把原标题作为副标题(灰色,辅助参考)
分页从 cursor 无限滚动换成 page + page_size:
- 后端 /articles 加 page/page_size 参数,返回 total/total_pages
- 干掉 _encode_cursor/_decode_cursor
- 前端用 n-pagination,显示 1,2,3,4,5 + 快速跳转
- 筛选/搜索变化自动回到第 1 页
- 切页自动滚到顶部
|
2026-06-10 12:07:04 +08:00 |
|
Mavis
|
3e56fed541
|
feat(translate): 接入腾讯 MaaS u2 作为 TMT 备用翻译通道
新通道:腾讯 MaaS u2 模型(云知声),OpenAI 兼容协议
- 端点:https://maas-api.hivoice.cn/v1
- 模型:u2(翻译专用,实测 + 锁定 prompt 后译文质量稳定)
- 备用链路:TMT 配额耗尽 / TMT 失败时自动降级到 MaaS
关键 prompt 工程(锁定):
- 必须用 user 提供的固定中文 prompt,否则 u2 会把译文放进 reasoning_content 而 content 返乱码
- 限定只接 EN/JA → ZH
- 中文输入固定返回拒绝文案
新增/改动:
- backend/app/services/translation/tencent_maas.py: 新建
- backend/app/services/translation/service.py: 备用链 maas → local,初始化失败友好降级
- backend/app/config.py: 加 tencent_maas_* 4 个配置
- .env.example: 文档化
|
2026-06-09 17:33:45 +08:00 |
|
Mavis
|
a5bfb7d49a
|
fix(worker): enrichment_loop 永远只扫老文章(已 enrich),新文章被排到最后
bug 复现:
- order_by translated_at asc nullslast
- 老文章已 enrich,translated_at 有值,排前
- 新文章 translated_at=NULL,nullslast 排最后,limit5 永远拿不到
修复:
- order_by 改为 Article.id.asc()(新文章 id 大)
- ENRICHMENT_BATCH_SIZE 1→3(并发候选)
- 文章间 sleep 0.5→0.2s
效果:enrichment_loop 现在会持续 enrich 新进文章,首页列表会逐步有分类/评论
|
2026-06-09 17:07:07 +08:00 |
|
Mavis
|
474299baf9
|
feat(feed): 首页列表展示分类标签 + LLM 评论预览
- 后端 ArticleListItem schema 加 commentary / commentary_status / image_ai_url
- 后端 articles.list 接口把以上字段写入响应
- 前端 API 类型同步
- 前端 Feed.vue 卡片:
* 分类 tag(逗号分隔,多 tag)
* 评论预览(蓝色引线块,140 字截断,带状态点)
* 用户点进详情页前就能看到 LLM 点评钩子
|
2026-06-09 15:59:48 +08:00 |
|
Mavis
|
76e95908e8
|
fix(llm): _safe_format 防 ValueError,模板里示例 JSON 也能正常 format
bug: classify_prompt 默认值里含示例 JSON {\\"categories\\": [...]},str.format
看到花括号就试图解析为 placeholder/format spec,遇到 \\" 时抛:
ValueError: Invalid format specifier ' [\\"时政\\"]' for object of type 'str'
修复:
- 引入 placeholder_re 提取所有合法 {varname} 占位符,stash 成 sentinel
- 剩余的 { / } 一律 escape 成 {{ / }},str.format 自然还原
- 用户显式写的 {{ / }}(标准转义)单独 stash,不被重复 escape
- 极端情况(KeyError/IndexError/ValueError)兜底:按原文返回,记录 warn
8 个本地单测全过(含示例 JSON 模板 / 老 prompt 缺变量 / 用户显式 {{ 场景)
|
2026-06-09 15:14:53 +08:00 |
|
Mavis
|
728e8c9be3
|
feat(api): LlmSetting/Source 暴露 blocklist_tags,admin 编辑入口就绪
- schemas/source.SourceOut/In/Update 加 blocklist_tags
- admin.create_source 透传 blocklist_tags
- admin.update_source 走 setattr 通用,自动支持新字段
- admin_llm.get_settings 在 row=None 分支返回默认值时补 blocklist_tags=[]
- update_settings 走 setattr 通用,自动支持新字段
|
2026-06-09 14:35:54 +08:00 |
|
Mavis
|
da895c2c5f
|
feat(llm): classify 前置 + 黑名单 drop 删文章 + 排版用 .diary-para
- enrichment._enrich_classify 前置,返回 (drop, categories)
- 注入 {blocklist} 占位符到 prompt(全局 + per-source 合并)
- drop=True → 整篇 DELETE,后 3 步直接 skip
- 兜底:即使 LLM 没正确返回 drop 字段,本地也匹配一次
- enrichment._enrich_format 排版段落 class 名固定为 diary-para
- CSS 仍内联到 style,前端 .diary-para 兜底
- enrichment._merge_blocklist: 全局 + per-source 合并去重保序
- schemas/llm.LlmSettingOut/Update 暴露 blocklist_tags
- DEFAULT_PROMPTS.classify_prompt 加 {blocklist} + drop 字段说明
|
2026-06-09 14:34:18 +08:00 |
|
Mavis
|
d0d1014505
|
feat(db): 0003 migration + LlmSetting/Source 模型加 blocklist_tags
- 新增 alembic 0003: sources.blocklist_tags + llm_settings.blocklist_tags(JSONB)
- 两层配置:全局(llm_settings) + per-source(sources),合并去重后注入 classify prompt
- 默认空数组,不影响存量数据;admin API 在下个 commit 暴露编辑入口
|
2026-06-09 14:30:38 +08:00 |
|
Mavis
|
8d73f4fb28
|
fix(llm+worker+deploy): 兼容老 prompt 模板 + 消除 startup_run 日志噪音
- enrichment: 新增 _safe_format (基于 _SafeDict),缺失占位符保留原样不抛 KeyError。
_enrich_format / _enrich_classify / _enrich_image / _enrich_commentary
全部走 _safe_format,数据库里老 prompt(不支持 {body})不再让整条 article 卡住。
复现: 388183 classify 一直 KeyError,enrichment_loop 反复重试它,316 篇全卡在 n/a。
- workers.__main__: startup_run 从 IntervalTrigger(minutes=0) 改成 DateTrigger
(只跑一次),消除 'maximum number of running instances reached' 刷屏 WARNING。
- deploy_pull: 改 _connect 自动识别 RSA / Ed25519 / ECDSA key(原硬编码 Ed25519Key)
|
2026-06-08 21:20:43 +08:00 |
|
Mavis
|
380e8b124e
|
feat(llm): 排版容器固定CSS + 插图用正文第一段 + 适中尺寸
- enrichment._enrich_format:把排版好的段落包到带固定 CSS 的 <div class=article-body> 里
(font: system-ui / 17px / line-height 1.7 / color #3e3e3e / p margin-bottom 1.5em)
CSS 同时内联到 style 属性,前端 .article-body 全局类做兑底
- enrichment._enrich_image:prompt 改用 body_zh_text 的第一段(原为 title);
新增 {body} 占位符,image_prompt_template 默认模板同步改写
- 插图尺寸写死为 768x512(适中);image_size 字段保留供用户手改但默认行为不依赖它
- 分类明确多标签(2-5 个),提示词加 {body} 变量,容错读 categories/tags 两种 key
- AdminLlmSettings.vue:placeholder / 变量说明同步更新
|
2026-06-08 20:53:21 +08:00 |
|
Mavis
|
4cc5d41e39
|
fix(orm): Article 模型加 body_zh_formatted/image_ai_url/4 个 *_status 字段(同步 0002 migration)
|
2026-06-08 16:08:39 +08:00 |
|
Mavis
|
a5548d6e64
|
fix(fetcher): fulltext 抓取用真实浏览器 UA,绕过 NHK 等 403
|
2026-06-08 15:55:30 +08:00 |
|
Mavis
|
6b5828c1c0
|
fix(translation): 规范化 BCP-47 lang_src(避免 en-gb/zh-cn 等被 TMT 拒)
|
2026-06-08 15:49:03 +08:00 |
|
Mavis
|
ba2298da0a
|
chore: 集成 LLM 增强 — config/main/articles schema/workers + .env.example 加 Agnes 配置
|
2026-06-08 14:24:23 +08:00 |
|
Mavis
|
ffd667f0dc
|
feat(llm): 新增 LLM 智能增强服务(Agnes client + 4 项 enrichment 任务 + admin API + migration)
|
2026-06-08 14:24:00 +08:00 |
|
Mavis
|
523c82f7a5
|
fix: NHK 源配置改 ja(seed 写错了); translate_article 加强 lang_src 兜底
|
2026-06-08 00:54:02 +08:00 |
|
Mavis
|
639562593e
|
fix: 翻译失败/降级文本不再写 cache(避免 30 天污染)
之前 service.translate 写 cache 无条件,导致:
- 第一次翻译失败时,'[翻译失败: ...]' 占位符被写进 cache
- 30 天内相同文本的请求(新文章 title 与老文章 title 相同时)全部返回占位符
- 触发 200+ 文章 title_zh 字段被永久污染
修法:仅在 engine ∈ {tencent, nllb, cache} 且文本不含错误标记时,才写 cache。
|
2026-06-08 00:48:36 +08:00 |
|
Mavis
|
9862a92423
|
perf: 翻译独立后台循环(1 篇/秒)+ Semaphore 1
之前 fetch_one_source 入库后立即调翻译(可能并发触发腾讯 TMT 限速)
改为独立 translation_loop 后台循环:
- 完全不和 RSS 抓取并行
- 1 篇/秒节拍(Semaphore 1 + sleep 1.0)
- 没活时空闲 5 秒再轮询
- pending/failed 都重试
|
2026-06-08 00:27:09 +08:00 |
|
Mavis
|
e79cfaa5f7
|
fix: articles.py get_article 链式 await coroutine 报错(.first())
|
2026-06-08 00:19:03 +08:00 |
|
Mavis
|
cc02d39d29
|
fix: 翻译主流程失败时 raise(不再返回占位符); add_usage TTL 用 replace(day=1) 防 0 TTL
|
2026-06-07 23:58:13 +08:00 |
|
Mavis
|
501713a3e8
|
fix: deps.py 修 await chain (3 处 .scalars())
|
2026-06-07 23:38:04 +08:00 |
|
Mavis
|
3ebf280278
|
fix: pipeline INSERT 去掉不存在的 translate_to 字段
|
2026-06-07 23:32:13 +08:00 |
|
Mavis
|
30acd6af54
|
fix: create_user 默认 role=owner(而非 member)
|
2026-06-07 23:27:52 +08:00 |
|
Mavis
|
ce903ac58e
|
fix: 修剩余的 (await ...)).scalar_one_or_none() 链式 + bookmark
|
2026-06-07 23:25:53 +08:00 |
|
Mavis
|
5109d6f824
|
fix: API 全部改用显式两步走 await session.execute + result.scalars()
之前 (await ...).scalars() 链式在 SQLAlchemy 2.0 async 下报
'coroutine' has no attribute 'scalars' 错误。改为先 await 拿 result
再 .scalars(),这是 SQLAlchemy 2.0 推荐的 async 写法。
|
2026-06-07 23:22:56 +08:00 |
|
Mavis
|
2e75985a3c
|
fix: healthz 路径改成 /api/v1/healthz(归到 API 前缀下)
|
2026-06-07 23:15:33 +08:00 |
|
Mavis
|
6635b8fea8
|
fix: enum 写入 PG 用 value 而非 name
Source/SourceKind、UserRole、SubscriptionMatch 三个 enum 改用 values_callable
保证 PG 看到 'rss' / 'owner' / 'any' 等小写 value,而非 'RSS' 大写 name。
seed_sources 同步改为显式字符串。
|
2026-06-07 23:11:32 +08:00 |
|
Mavis
|
60b062daf2
|
feat: initial MVP - FastAPI backend + Vue3 frontend + docker-compose
- backend: FastAPI + SQLAlchemy 2.0(async) + asyncpg + Alembic
- 7 API routes: auth/me/articles/sources/bookmarks/subscriptions/admin
- models: User/Source/Article/Bookmark/Subscription/ApiToken
- services: RSS fetcher (feedparser) + Tencent TMT translator with quota + cache + local NLLB fallback
- workers: APScheduler + asyncio pipeline (fetch -> dedupe -> insert -> translate)
- seed scripts: create_user, seed_sources (5 RSS: Reuters/BBC/Al Jazeera/NHK/DW)
- frontend: Vue 3 + Vite + Naive UI + Pinia + vue-router
- pages: Login, Feed (24h), ArticleDetail, Sources, Bookmarks, AdminSources
- deploy: docker-compose (postgres/redis/api/worker/frontend/caddy)
- docs: README, DEPLOY, architecture, acceptance
|
2026-06-07 21:51:01 +08:00 |
|