feat(content): every field the creator must fill now starts from AI candidates - #133
Merged
Merged
Conversation
…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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
用户要求:「所有需要填的内容都应该先由 AI 给出候选选项,让 AI 进行推荐,然后如果 AI 推荐的不对也需要支持自定义」。创作流程里有六个必填/必答的地方,之前每一个都是从空输入框开始——空框是最难的一步。
候选不是"选项墙"
不是 select、不是选择器:每条候选是一句可以点进去的话,点一下填进输入框,输入框仍然是普通可编辑文本。目的是把"面对空框"变成"改一句话"。
POST /projects/{id}/field-suggestions:入参是字段名、正在回答的问题、以及用户已经写下的内容(所以再取一次是改进他的草稿,而不是另起一句),返回最多 5 条候选,每条带一句推荐理由。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)而不是降级。