Skip to content

feat(ai-create): let users rate the draft the AI produced#193

Open
dantaspaulo wants to merge 2 commits into
trypostit:mainfrom
dantaspaulo:feat/content-rating
Open

feat(ai-create): let users rate the draft the AI produced#193
dantaspaulo wants to merge 2 commits into
trypostit:mainfrom
dantaspaulo:feat/content-rating

Conversation

@dantaspaulo

Copy link
Copy Markdown
Contributor

Stacked on #191 — the review screen this control sits on comes from that PR. The first commit here is #191's; review only the second.

Problem

There is no signal for whether an AI generation was actually any good. Publishing tells you nothing: a user can publish something mediocre, or abandon a great draft. So there is no way to tell whether a prompt change, a new template or a model swap made generations better or worse.

Change

A small 1–5 star control under the review blocks. It is optional, never blocks anything, and answers 204 so the UI can fire and forget.

  • A rating tied to a specific item is idempotent — rating again updates it rather than inserting a second row. That is enforced by a unique index, not just in the application. Standalone ratings stay possible, since NULLs are distinct in a unique index.
  • rateable_type is validated against the registered morph aliases, so an arbitrary type cannot blow up later when the relation is resolved.
  • The control follows the WAI-ARIA radiogroup pattern: arrows move focus and selection, Enter or click submits.

The table is polymorphic, so the same control can be dropped anywhere else you want a quality signal later.

Tests

Five feature tests: recording, authentication, the 1–5 bound, the morph-map guard, and idempotency. The full suite passes (2643), Pint and ESLint are clean.

Notes

  • Translations follow the same approach as feat(ai-create): review the generated copy before spending image credits #191: en and pt-BR written properly, the other thirteen locales carrying the English strings as an untranslated fallback so the parity test passes.
  • The original version of this also snapshotted a "brand completeness" score alongside each rating, to correlate output quality with how well the workspace brand was filled in. That depended on a helper that does not exist here, so I dropped it — the rating is just the rating. Happy to add something equivalent if you would find that correlation useful.

## Problem

AI creation is one shot: the wizard sends the prompt and the job writes the
copy AND renders the images in a single pass. The user only sees the result
once everything has been generated and the post exists.

That is expensive and frustrating. Images are the costly part of a
generation, so a weak headline, an off-tone caption or a carousel whose
slides arrived in the wrong order all cost a full image render before the
user can react — and fixing it means starting over and paying again.

## Change

Generation is split into two phases with a review step in between.

**Phase A** (`PreparePostContent`) writes only the structured text — the same
generator and humanizer as before, no images — and stores it on a new
`AiPostDraft`. **The review screen** shows that text as editable blocks:
headline, body, caption, photo keywords, and for carousels a card per slide
that can be reordered by dragging. Edits autosave. **Phase B** reuses
`StreamPostCreation` with the approved structure, so it skips text generation
and renders the images from exactly what the user signed off on.

Nothing about the one-shot path changes: `StreamPostCreation` without
`preparedStructured` behaves exactly as before.

## How

- `PostContentComposer` — the "generate then humanize" step extracted from
  `StreamPostCreation` verbatim, so both phases share one implementation.
  Behaviour is unchanged; this is a pure extraction.
- `AiPostDraft` + `DraftStatus` (`preparing/ready/generating/completed/failed`)
  hold the draft between phases. `generate` refuses a draft that is not
  `ready`, so a double click cannot create a duplicate post or bill images twice.
- `PostContentPrepared` broadcasts on `user.{id}.ai-draft.{draftId}` when the
  text lands; the review screen also polls every 4s so a dropped websocket
  cannot strand the user on the skeleton.
- `creationStatus` gives the loading screen the same fallback for phase B.

## Tests

15 tests covering the flow end to end: draft creation and job dispatch, the
cross-workspace social account guard, rendering and authorization of the
review screen, generate persisting the reviewed structure and dispatching
phase B, the replay guard, autosave (persists while `ready`, no-op otherwise),
validation, and `PreparePostContent` marking the draft ready while preserving
the fields the humanizer does not return.

The full suite passes (2638 tests).
There is no signal today for whether an AI generation was actually any good.
Publishing tells you nothing — a user can publish something mediocre, or
abandon a great draft — so there is no way to tell whether a prompt change or
a template tweak made generations better or worse.

This adds a small 1-5 star control under the review blocks. Rating is
optional, never blocks anything, and answers 204 so the UI can fire and
forget. A rating tied to a specific item is idempotent — rating again updates
it rather than creating a second row, enforced by a unique index and not just
in the application. Standalone ratings stay possible, since NULLs are distinct
in a unique index.

`rateable_type` is validated against the registered morph aliases, so an
arbitrary type cannot blow up later when the relation is resolved.

The control follows the WAI-ARIA radiogroup pattern: arrow keys move focus and
selection, Enter or click submits.

Five feature tests cover recording, auth, the 1-5 bound, the morph-map guard
and idempotency.
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