diff --git a/skald/src/web.rs b/skald/src/web.rs index 1bb4bf2..79c0634 100644 --- a/skald/src/web.rs +++ b/skald/src/web.rs @@ -128,6 +128,8 @@ pub struct NewStoryForm { word_count_target: String, #[serde(default)] author_slug: String, + #[serde(default)] + fire: String, // "now" = spawn background gen; empty = just queue the seed row } async fn new_story_form(State(state): State>) -> Html { @@ -177,6 +179,39 @@ async fn new_story_create( } } + // Fire generation immediately if requested. No-parent gen path: + // continue_story::run handles parent_story_id=None by using the + // story's own prompt as the LLM context (instead of the parent + // chain's bible/chapters). Audit pass is skipped — there's no + // parent to compare against. So you get a single first-chapter + // gen + cleanup pass and status flows to 'complete'. + if form.fire == "now" { + let database_url = std::env::var("DATABASE_URL") + .unwrap_or_else(|_| "postgresql://skald:skald@localhost:5432/skald".into()); + let author_owned = if author_slug.is_empty() { + None + } else { + Some(author_slug.to_string()) + }; + tokio::spawn(async move { + if let Err(e) = crate::continue_story::run( + &database_url, + id, + author_owned.as_deref(), + None, // direction (no parent — uses story.prompt instead) + target, + 2, // recent_n + true, // skip_audit (no parent → can't audit) + ) + .await + { + tracing::error!(story_id = %id, error = %e, "background new-story task failed"); + } else { + tracing::info!(story_id = %id, "background new-story task succeeded"); + } + }); + } + Ok(Redirect::to(&format!("/stories/{}", id))) } @@ -457,7 +492,11 @@ fn new_story_panel(err: Option<&str>, authors: &[AuthorOpt]) -> Markup { article.form-panel { h1 { "Begin a new saga" } (ornament()) - p.muted { "Create a story row + pick its author. The gen pipeline fires on " code { "skald continue --story " } " or via the continue form once you've drafted the first chapter." } + p.muted { + "Create a story row, pick its author, and optionally fire the gen pipeline " + "immediately. Without " strong { "fire now" } ", the seed row waits for " + code { "skald continue --story " } "." + } @if let Some(e) = err { p.error { (e) } } form method="post" action="/stories/new" { label { "Title" @@ -474,12 +513,16 @@ fn new_story_panel(err: Option<&str>, authors: &[AuthorOpt]) -> Markup { } } } - label { "Seed prompt (optional)" - textarea name="prompt" rows="4" placeholder="What is this saga about? Setting, frame, conflict." {} + label { "Seed prompt" + textarea name="prompt" rows="5" placeholder="What is this saga about? Setting, frame, conflict, vibe. The richer this is, the better the first chapter." {} } - label { "Target word count per chapter (optional)" + label { "Target word count for the first chapter" input type="number" name="word_count_target" min="500" step="500" placeholder="3000"; } + label.checkbox-label { + input type="checkbox" name="fire" value="now"; + span { " fire generation now (background task, ~7 min — no audit pass on the first chapter since there's no parent yet)" } + } button type="submit" { "Create" } } }