Skip to content

feat(ai-create): show the draft's viral potential on the review screen#192

Open
dantaspaulo wants to merge 2 commits into
trypostit:mainfrom
dantaspaulo:feat/ai-viral-score
Open

feat(ai-create): show the draft's viral potential on the review screen#192
dantaspaulo wants to merge 2 commits into
trypostit:mainfrom
dantaspaulo:feat/ai-viral-score

Conversation

@dantaspaulo

Copy link
Copy Markdown
Contributor

Stacked on #191 — that PR adds the review screen this gauge lives on. The first commit here is #191's; review only the second one, or wait until #191 lands and this rebases to a single commit.

Problem

On the review screen a user reads their draft cold. There is no signal for whether the hook actually hooks, whether a call to action is present, or whether the post is the right length — the things that decide if it performs.

Change

An instant gauge above the editable blocks, reading the real signals of the copy: hook length and tension (a question or a number), a call to action, word count, scannability (paragraph breaks), hashtags, attached media, carousel length and cross-posting reach. It recalculates live as the user edits, costs no credits and makes no AI call.

It is deterministic — the same content always scores the same, so the number never looks random between renders.

Please read before merging

The score is deliberately compressed into an 80–97 band with only positive labels (good / great / exceptional). It is a motivational device, not an objective measurement: the weakest possible draft still scores 80 and reads as "good". There is also a small stable per-content jitter so the number does not land on round values.

That is a product decision from where this came from, and one you may well not want in yours. The factors and weights are honest — only the output range is squeezed. Setting VIRAL_MIN to 0 and adding a weak band turns it into a straight 0–100 measurement that can genuinely say "this draft is weak". Say the word and I will send that version instead — I would rather you decide this knowingly than inherit a number that looks like analysis and is partly encouragement.

Known limitation

The CTA detector matches English, Portuguese and Spanish cues only, so the cta factor (weight 4 of 30) never fires for the other twelve locales. Worth either extending per locale or moving the cue list into the translation files — happy to do either.

Tests

None here: the project has no JavaScript test runner. viralScore.ts is pure, deterministic logic and is exactly the kind of thing worth unit testing — #130 introduces Vitest, and as soon as that lands I will send the tests for this. If you prefer, I can bring the Vitest setup into this PR instead.

Pint, ESLint and vue-tsc are clean on the touched files.

## 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).
Reading a draft cold gives no sense of whether it will actually perform. This
adds an instant, credit-free gauge above the editable blocks that reads the
real signals of the copy — hook length and tension, a call to action, word
count, scannability, hashtags, attached media and carousel length — and
recalculates live as the user edits.

It is deterministic: the same content always scores the same, so the number
never looks random, and no AI call is involved.

IMPORTANT, please read before merging: the score is deliberately mapped into
an 80-97 band with only positive labels (good / great / exceptional). It is a
motivational device, not an objective measurement — the weakest possible draft
still scores 80 and reads as "good". That is a product choice, and one you may
well not want in your product. The factors and their weights are honest; only
the output range is compressed. Changing VIRAL_MIN to 0 and adding a weak band
turns it into a straight 0-100 measurement, and I am happy to send that
instead.

Also note the CTA detector recognises English, Portuguese and Spanish cues
only, so the `cta` factor never fires for the other twelve locales.
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