October 12, 2026 · 7 min · engineering · api · product
.mdBuild an AI Story Generator That Can Stop, Save and Continue Cleanly
Build a streaming AI story generator with separate drafts, explicit acceptance, reliable stop controls, revision checks and honest recovery after disconnects.

To build an AI story generator that stops and continues cleanly, stream into a recoverable draft, not directly into the accepted story. Give each generation attempt its own identity and starting revision. Stop freezes that attempt; Accept commits its text only if the story has not changed. Continue starts a new attempt from the accepted version. A disconnected browser proves none of those operations succeeded.
Part 5 of The 2027 AI Character Business Playbook. Make Stop, Save and Continue dependable before adding more generation. This series turns consumer-character demand into specific experiments and implementation decisions; revenue targets are scenarios, not promised results.
Imagine a weekend fiction app: Mara finds a lantern that contains rain. The writer likes the discovery, but the next paragraph introduces an unwanted villain. They press Stop. The useful result is an editable partial scene beside the unchanged manuscript—not a lost paragraph, an automatically accepted villain, or another generation running unnoticed.
Make saved and accepted mean different things
Use an immutable accepted revision plus separate generation attempts. Saving a draft means the app can recover it; accepting means the writer has promoted it into the story. A model finishing its response does not provide that approval.
- Story revision: the accepted text and revision number, scoped to its authenticated owner. Editing accepted text also creates a new revision.
- Attempt: its identifier, base revision, requested continuation, accumulated draft, last saved chunk sequence and generation outcome.
- Acceptance receipt: the attempt and resulting story revision, committed together so a retry can retrieve the original result.
Keep saved draft text separate from unsaved browser edits. Show Saving, Saved draft or Save unconfirmed according to storage evidence. Let the writer copy an unconfirmed edit. Before acceptance, persist and freeze the reviewed draft version; accepting a stale preview must not silently include words that arrived afterward.
Choose cancellation before choosing stream resumption
The AI SDK Stopping Streams guide documents forwarding an AbortSignal to streamText. Its UI stop helper aborts the client HTTP request; stopping the upstream model also depends on the server runtime propagating that disconnect and the route forwarding the signal. A quiet screen is not proof that generation stopped.
The separate Chatbot Resume Streams guide explains the other design: a resumable producer continues after its viewer disconnects. It needs a dedicated stop endpoint that persists the partial response, cancels active work and clears the matching active-stream reference. Do not combine the ordinary abort path with automatic resumption and assume both semantics survive.
For a first story editor, choose cancellable generation plus saved-draft recovery, without automatic stream resumption. Returning to a saved paragraph is not resuming the original model computation. If uninterrupted background writing becomes a requirement, add the persistent producer and cancellation channel deliberately.
Checkpoint the paragraph before promising it survived
Accumulate text deltas under the attempt identifier and checkpoint them with increasing sequence numbers. Persist before acknowledging a checkpoint as saved. Reject duplicate or out-of-order writes; reconcile a missing sequence rather than guessing its contents. Batching saves is reasonable, but displayed text beyond the last confirmed checkpoint remains unconfirmed.
Do not rely exclusively on onAbort to recover the visible paragraph. The SDK documents its steps argument as completed steps, not every token of an interrupted step. Its generating-text guide provides chunk and error handling; keep the current draft accumulator on that path and record failure separately from completion. Process termination can bypass cleanup callbacks.
Make Stop target one attempt. Persist stop_requested, fence subsequent draft writes, and signal that producer to abort. Only show Stopped when cancellation is acknowledged; a timeout stays Stop unconfirmed until reconciled. A completion arriving after the fence must not reverse it. Freeze the last checkpoint for review, with explicit handling for any unsaved browser text.
Guard acceptance with the revision that produced it
Here is the exact SQLite acceptance update exercised in the local fixture used for this article. It handles append-only continuation, not arbitrary chapter editing. The surrounding transaction first checks ownership and an existing acceptance receipt, requires one updated row, then records that receipt. The attempt is already frozen. This tiny fixture holds only the current revision; a production editor must also archive accepted revisions in the same transaction. This is not an AI SDK endpoint or a complete storage service.
-- SQLite: tested append-only acceptance guard
UPDATE stories
SET accepted_text = accepted_text || (
SELECT draft FROM attempts WHERE id = :attempt
), revision = revision + 1
WHERE id = :story AND owner_id = :owner
AND EXISTS (
SELECT 1 FROM attempts
WHERE id = :attempt AND story_id = stories.id
AND owner_id = :owner
AND base_revision = stories.revision
AND status IN ('stopped', 'complete')
);Bind the owner from the authenticated session, not the request body. A zero-row result refuses acceptance; it does not discard the draft. If another tab changed the manuscript, show both versions and offer an explicit revision or regeneration. Never append stale prose merely because its wording still looks plausible.
The local SQLite fixture passes eight cases, including late chunks after Stop, pending cancellation, stale revisions, duplicate chunks, failed drafts, ownership rejection and lost-receipt retries. The stale-revision test first failed with the revision predicate removed, then passed with it restored. These tests verify local storage decisions, not production concurrency, browser recovery or provider cancellation.
Walk through one interrupted scene
- Revision 12 contains Mara's discovery. Attempt A checkpoints an unfinished continuation. Stop fences A, and an abort acknowledgement freezes its draft; revision 12 remains unchanged.
- The writer reviews and accepts A. The transaction creates revision 13, but the response is lost. Repeating that acceptance returns its saved revision-13 receipt without appending twice.
- Another tab accepts a different continuation and advances the story. An older attempt still based on revision 12 cannot be accepted over the newer version.
- On reopening, fetch the accepted revision and saved attempts separately. Offer Review draft or Start new continuation. Neither label claims the old stream is still alive.
Distinguish a transport retry from a creative retry. Repeating the same request retrieves the same attempt; Try another version creates a new one. Store an owner-scoped request key and reject its reuse with changed inputs. If a model request may have started but its acknowledgement disappeared, reconcile before dispatching another potentially billable request.
Keep a live character separate from the manuscript
An optional character call could let the writer discuss why Mara kept the lantern. Realtime Avatar provides an API, SDK and Studio for live characters; your app still owns text generation, saved chapters and consumer pricing. Supply selected accepted context when opening a call. Do not treat its responses as verbatim manuscript playback or assume the optional post-call transcript webhook is a live text feed. New calls do not inherit automatic persistent story memory.
- Realtime Avatar Calls: server-supplied story context
- Tool calling and optional post-call transcript delivery
Frequently asked questions
Does Stop save the paragraph?
Not by itself. Cancellation and persistence are separate operations. Report the last confirmed checkpoint and retain unconfirmed browser text for copying or retrying the save. Neither becomes accepted story text automatically.
Can Continue pick up halfway through a sentence?
It can request a continuation from a reviewed partial draft, but that is a new generation, not restoration of hidden model state. Let the writer edit the seam and accept the result explicitly.
Should an aborted attempt be called complete?
No. Stopped, failed and completed describe different outcomes. Preserve usable text from each. For a failed draft, allow explicit editing and saving before acceptance rather than changing its generation outcome to successful.
What should I test on the deployed app?
Interrupt mid-paragraph, reload during saving, lose an acceptance response and edit from two tabs. Verify stored text and revisions afterward. Separately confirm that the deployed runtime forwards cancellation and inspect provider usage; a local fixture cannot prove either.
Build the next part of the business
Ready to build? The Next.js guide provides a public starter ZIP. Run it locally with your own server-side key; calls consume credits and the example caps each call at 60 seconds. Its local-only authorization must be replaced with real sign-in, balance checks and persisted session ownership before public deployment. A downloaded example is not yet a tested consumer product.
- Part 1: The $1M ARR AI Companion Playbook for 2027
- Part 2: Character.AI Alternatives for Builders: Consumer App, Agent Framework or Avatar API?
- Part 3: Build an AI Companion App: Make the First Conversation Worth Returning To
- Part 4: Build AI Roleplay Chat with Edits, Rewinds and Separate Story Branches
- Build entrance: the complete local Next.js starter