Skip to content

feat(content): every field the creator must fill now starts from AI candidates - #133

Merged
wu908 merged 1 commit into
mainfrom
feat/ai-field-suggestions
Sep 18, 2026
Merged

wu908 merged 1 commit into
mainfrom
feat/ai-field-suggestions

Conversation

@wu908

@wu908 wu908 commented Sep 18, 2026

Copy link
Copy Markdown
Owner

用户要求:「所有需要填的内容都应该先由 AI 给出候选选项,让 AI 进行推荐,然后如果 AI 推荐的不对也需要支持自定义」。创作流程里有六个必填/必答的地方,之前每一个都是从空输入框开始——空框是最难的一步。

候选不是"选项墙"

不是 select、不是选择器:每条候选是一句可以点进去的话,点一下填进输入框,输入框仍然是普通可编辑文本。目的是把"面对空框"变成"改一句话"。

  • POST /projects/{id}/field-suggestions:入参是字段名、正在回答的问题、以及用户已经写下的内容(所以再取一次是改进他的草稿,而不是另起一句),返回最多 5 条候选,每条带一句推荐理由。
  • 提示词的约束与产品其他部分一致:只能用本项目里已确认的素材、不得编造经历;没有素材时明确要求"不要编造"(提示词里有这句话,也有测试锁住)。
  • AI 关掉/失败时依然返回候选——rubric 的通用方向或可照填的骨架(「(什么时间/什么场景),我(具体做了什么)…」),并在 limitations 里说明,前端显示为「先给你几个方向」+说明文字。不留空白。

覆盖字段:关键问题的回答、希望读者发生的变化、读者遇到的问题、你准备给出的答案、视角锚点、持续承诺。

前端

一个 FieldSuggestions 组件:进入面板自动取一次(不该为了看到候选先点按钮),显示三条候选与理由,提供「换一批」,点击填入。异步守卫用 ref 里的递增令牌:一次性布尔锁会被 StrictMode 的 cleanup 永久锁死(R7 踩过),而放进 state 的守卫会让第二次挂载再发一次真实请求——这次是测试测出来的(实测 2 次)。

验证

后端 6 个契约测试(长度裁剪、空候选丢弃、提示词含"不要编造"、失败降级带原因、无 AI 时的骨架形状、别人项目可读拒绝、未知字段 422);前端 6 个组件测试(StrictMode 下只自动取一次、点击填入且仍可编辑、换一批、降级来源说明、失败重试)+ 1 个页面级测试(候选落进回答框)。后端 519 passed、前端 44 文件 286 passed、E2E 6/6,ruff/mypy/tsc/eslint 全干净。部署后会在线上真打一次,确认候选来自模型(source: ai)而不是降级。

…andidates

「所有需要填的内容都应该先由 AI 给出候选选项,让 AI 进行推荐,然后如果 AI 推荐的
不对也需要支持自定义。」 Six fields make up the creation flow and every one of them
started as an empty box: the answer to the key question, the reader change, the reader's
problem, the reader promise, the viewpoint anchor, the continuation promise. An empty box
is the hardest part of writing.

**Candidates are not an options wall.** They are not a select, not a picker: each one is
a sentence you can click to drop into the field, and the field stays an ordinary editable
text box. The point is to turn "face an empty box" into "edit one sentence".

- `POST /projects/{id}/field-suggestions` takes the field, the question being answered,
  and whatever the creator has already written (so a second pass improves their draft
  instead of replacing it), and returns up to five candidates with a one-line reason each.
- The prompt is constrained the way the rest of the product is: only material already
  confirmed in this project, never invent an experience, and say when there is no material
  at all ("do not fabricate" is in the prompt and asserted in a test).
- With AI off or failing, the endpoint still returns candidates — the rubric's generic
  direction or a fill-in scaffold like 「(什么时间/什么场景),我(具体做了什么)…」 — and
  says so in `limitations`, which the panel renders as 「先给你几个方向」 plus the note.
  Nothing is silently empty.

Frontend: one component (`FieldSuggestions`) loads once when the panel opens (nobody should
have to click a button to see candidates), shows three chips with their reasons, offers
「换一批」, and fills the field on click. Wired into the answer panel, the confirm panel's
reader change, and all five publish-judgement fields. The async guard is an incrementing
token in a ref: a one-shot boolean latch is permanently closed by StrictMode's cleanup
(learned in R7), and a *state* latch lets the second mount fire a second real request —
the test caught exactly that.

Tests: six backend contract tests (length trimming, empty candidates dropped, "do not
fabricate" in the prompt, fallback with a reason, scaffold shape without AI, other users'
projects refused with a readable message, unknown field rejected) and six frontend tests
(auto-load once under StrictMode, click fills and stays editable, another batch, fallback
provenance, retry after failure) plus a page-level test that the chip lands in the answer
box. Backend 519 passed, frontend 286 passed in 44 files, E2E 6/6, ruff/mypy/tsc/eslint clean.
@wu908
wu908 merged commit 77a2ea5 into main Sep 18, 2026
2 checks passed
@wu908
wu908 deleted the feat/ai-field-suggestions branch September 18, 2026 15:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant