feat(compliance): content moderation on model input and output — local lexicon, Aliyun adapter, free completion, admin log

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N4f2nya58RoRu4yEmJgRGE
This commit is contained in:
Jesse_Chen
2026-09-30 09:42:11 +08:00
co-authored by Claude Opus 5.5
parent e742da268d
commit 4f3d8d7c62
28 changed files with 1143 additions and 6 deletions
+6
View File
@@ -574,6 +574,12 @@ The chart page, the ephemeris and the report list wait with one motion: the home
- **/terms and /privacy:** public, static, one 720px column on `--color-canvas-soft`; a top row with 「返回 Jyotisha」 and a link to the other document; while the text is a draft, a warning-tinted note 「草稿 · 版本 …,待律师审定」 above the title; h1 in the display face, h2 per section, bullet lists for enumerations.
- **Re-consent:** when a final version replaces the one a signed-in user accepted, one modal 「协议已更新」 with the same checkbox and 「同意并继续」; it cannot be dismissed without agreeing. Off while the documents are a draft.
### Content moderation (2026-09-30, compliance round)
- **Blocked input** comes back before the turn starts, as the same error notice a rejected send already uses (HTTP 400, `code: "content_blocked"`); nothing is added to the conversation and nothing is charged.
- **Blocked output** in 普通咨询 arrives as one `answer.delta` with `replace: true` at the end of the stream: the streamed answer is swapped for the one-line notice in place (no second bubble, no error styling), and the turn completes free through `complete_consultation_moderated`. 生时校正 uses its existing replace path and releases the turn's charge. The legacy text/plain runtime cannot replace in place; its stored reply is the notice.
- **Admin:** 「内容审核」 is a read-only list (time, side, verdict tag, route, categories, rule ids, provider, user id, request id) with a 14-day per-category count strip; it never shows user or model text.
### Personal report centre
- **Structure:** inside the app shell, not a page of its own. The name 「我的报告」 sits alone in the 46px header. The body opens with the **generate card** (`.report-center-create`, 2026-09-29, product sketch): a centred `--color-canvas` sheet, `--space-6` below the header (BUG-1103: it used to sit flush under it), with a file icon, the title 「完整本命报告」 (what you get), one line 「星盘、力量、大运、年运、瑜伽共 6 章,中英两版;生成后可离开,完成时下方自动出现。」 and the filled 「生成报告」 button (what it does) — title and button no longer say the same thing — the page's only generate entry; the header button was deleted rather than kept as a second one. Below it, 「过往的报告」 heads the **row list** of reports; with none yet, a single quiet line 「还没有个人报告。」 replaces the old second big empty card. The supporting paragraph (「有填报到分钟的出生时间即可生成……」) and the 「共 N 份 · 已完成 N 份」 overview line above it were removed on 2026-09-28 (TASK-self-edit-avatar-menu-20260928 S3, product: 「这里的提示去掉」); the minute requirement still lives in the 生成 button's hover title.