Skip to content

Phase 4.2 — premiumAmicusStrategy + SynthesisPremiumDraft component (streaming output) #1745

@CraigBuckmaster

Description

@CraigBuckmaster

Part of epic #1725. Depends on #1744. Phase 4 — Amicus drafting + Subscription rewrite.

Objective

The premium-with-flag-on path. When GUIDED_STUDY_AMICUS_SYNTHESIS=true and the user has Amicus access, synthesis becomes a streaming Amicus draft with mode-specific output, citations to panels actually opened, and persisted artifact.

Files

Create

  • app/src/services/guidedStudy/synthesis/premiumAmicus.ts
  • app/src/components/guidedStudy/SynthesisPremiumDraft.tsx
  • app/src/services/guidedStudy/synthesis/__tests__/premiumAmicus.test.ts

Modify

  • app/src/services/guidedStudy/synthesis/strategy.tschooseStrategy now returns this when conditions met
  • app/src/screens/StudySessionScreen.tsx — render SynthesisPremiumDraft when strategy.kind === 'premium_amicus'

premiumAmicus.run() behavior

async run(ctx, callbacks) {
  // 1. Build mode-specific system prompt
  const systemPrompt = buildGuidedStudySystemPrompt({
    mode: ctx.plan.mode,
    chapterRef: formatChapterRef(`${ctx.bookId}_${ctx.chapterNum}`),
    captured: ctx.captured,
    panelsOpened: ctx.captured.explore?.panels_opened ?? [],
    modeSpecificContext: extractModeContext(ctx.plan.mode, ctx.captured),
  });

  // 2. Stream via existing streamChat with mode-specific payload
  const fullText = await new Promise<string>((resolve, reject) => {
    let acc = '';
    streamChat({
      threadId: synthesisThreadId(ctx.sessionId),
      userQuery: 'Draft my synthesis.',
      // System prompt is passed via the existing payload (verify the
      // streamChat signature supports this; if not, extend it minimally).
      conversationHistory: [{ role: 'user', content: systemPrompt }],
      // ...standard params from existing askAmicus implementation
      onDelta: (token) => { acc += token; callbacks?.onAmicusDelta?.(token); },
      onComplete: (final) => { callbacks?.onAmicusComplete?.(); resolve(final.text); },
      onError: (err) => { callbacks?.onError?.(err); reject(err); },
    });
  });

  // 3. Parse mode-specific structure out of the response
  const artifact = parseModeArtifact(ctx.plan.mode, fullText, ctx.captured);

  // 4. Persist
  if (ctx.sessionId != null) {
    await setModeArtifact(ctx.sessionId, artifact);
    await setSynthesisStrategy(ctx.sessionId, 'premium_amicus');
    await enqueueReviewItem(ctx.sessionId, artifact);
  }

  return { kind: 'premium_amicus', output: buildAmicusOutputBlocks(fullText, artifact), artifact };
}

Parsing mode artifact from Amicus text

Each mode's prompt asks for a specific structure. The parser is forgiving — it looks for the labels (CLAIM, EVIDENCE, MAIN POINT, etc.) and extracts; missing sections become empty strings.

function parseModeArtifact(
  mode: GuidedStudyMode,
  text: string,
  captured: CapturedInputs,
): ReviewArtifact;

For each mode, define the expected labels and a tolerant regex. Unit-test with realistic Amicus outputs (use fixtures from a recorded prompt run).

SynthesisPremiumDraft component

Renders the streaming text with:

  • Skeleton placeholder while waiting
  • Streaming dots while tokens arrive (use existing StreamingDot from components/amicus/)
  • Final formatted output with citation pills (use existing CitationPill component)
  • "Saved to your study" confirmation banner once parsed
  • "Open in Amicus to ask follow-ups" CTA → leverages existing launchAmicusStudyThread infrastructure

Cap handling

Before streaming, check useAmicusAccess. If canUse === false:

  • reason === 'monthly_cap_reached' → fall back to premiumStructuredStrategy for this session, surface a small banner: "You've hit this month's Amicus limit — your synthesis was saved without the AI draft."
  • reason === 'offline' → same fallback with offline-tinted copy
  • reason === 'disabled_in_settings' → fall back; banner: "Amicus is turned off in settings."

Acceptance

  1. With flag ON and access OK: premium user gets streaming Amicus output, parsed artifact, saved review item.
  2. With flag ON but cap reached: silently falls back to premiumStructured with a banner explanation.
  3. With flag OFF: chooseStrategy never returns this strategy; never called.
  4. Citations link to the panels the user actually opened.
  5. Cancellation works (user navigates away mid-stream → no orphan persistence).
  6. tsc, lint, test clean.

Out of scope

  • Server-side Amicus changes (uses existing streamChat).
  • Cross-session Amicus history-aware nudges (future epic).

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions