From ebb8ab005495acf7d41e100f8527643d0a7e1765 Mon Sep 17 00:00:00 2001 From: ciregenz Date: Wed, 1 Jul 2026 00:46:14 -0700 Subject: [PATCH] [eric] onboarding: fire persona generation the moment a persona is picked (runs in background through name/consent/connect) so the payoff is instant + magical, not a wait --- .../components/Onboarding/flow/OnboardingFlow.tsx | 7 ++++--- .../Onboarding/flow/useOnboardingSuggest.ts | 14 ++++++++------ 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/frontend/src/app/components/Onboarding/flow/OnboardingFlow.tsx b/frontend/src/app/components/Onboarding/flow/OnboardingFlow.tsx index a098329e..67534708 100644 --- a/frontend/src/app/components/Onboarding/flow/OnboardingFlow.tsx +++ b/frontend/src/app/components/Onboarding/flow/OnboardingFlow.tsx @@ -39,9 +39,10 @@ export const OnboardingFlow: React.FC<{ onExit: () => void }> = ({ onExit }) => const onPayoff = step === 'discovering' || step === 'greet' || step === 'task' || step === 'more'; const floor = useMemo(() => demoPayoff(persona), [persona]); - // Personalized payoff from the persona (cheap LLM); status drives the thinking -> stream feel. - const { result: suggest, status: suggestStatus } = useOnboardingSuggest(useCase, name, onPayoff); - // Deeper: background read-only profiling (only if they consented); trumps the persona suggestion. + // Persona generation fires the MOMENT a persona is picked, so it runs in the background through + // name/consent/connect and is already there (instant, no wait) by the time they hit the payoff. + const { result: suggest, status: suggestStatus } = useOnboardingSuggest(useCase); + // Deeper read-only profiling needs connected data, so it can only start after connect (onPayoff). const profile = useOnboardingProfile(name, consent, onPayoff); const profileReady = !!(profile && profile.observation.trim() && profile.options.length > 0); diff --git a/frontend/src/app/components/Onboarding/flow/useOnboardingSuggest.ts b/frontend/src/app/components/Onboarding/flow/useOnboardingSuggest.ts index cb12f52c..c12a9d8b 100644 --- a/frontend/src/app/components/Onboarding/flow/useOnboardingSuggest.ts +++ b/frontend/src/app/components/Onboarding/flow/useOnboardingSuggest.ts @@ -1,6 +1,8 @@ // Generates the payoff content (insight + task + 4 options) from the user's persona via the cheap -// LLM. Exposes status so the payoff can show a brief "thinking" state, then stream the result in. -// Fail-open: on any miss the status goes 'failed' and the caller shows its static floor. +// LLM. Fires as soon as the persona is known (not at the payoff), so it runs in the BACKGROUND while +// the user goes through name/consent/connect and is ready by the time they land. Fail-open: on any +// miss the status goes 'failed' and the caller shows its static floor. Name isn't used here (the task +// content doesn't need it; the greeting adds the name separately), so it never re-fires mid-flow. import { useEffect, useState } from 'react'; import { API_BASE } from '@/shared/config'; @@ -13,18 +15,18 @@ export interface SuggestDto { export type SuggestStatus = 'idle' | 'loading' | 'ready' | 'failed'; -export function useOnboardingSuggest(persona: string, name: string, active: boolean): { result: SuggestDto | null; status: SuggestStatus } { +export function useOnboardingSuggest(persona: string): { result: SuggestDto | null; status: SuggestStatus } { const [result, setResult] = useState(null); const [status, setStatus] = useState('idle'); useEffect(() => { - if (!active || !persona) return; + if (!persona) return; let cancelled = false; setStatus('loading'); fetch(`${API_BASE}/agents/onboarding-suggest`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ persona, name }), + body: JSON.stringify({ persona }), }) .then((r) => (r.ok ? r.json() : null)) .then((data: SuggestDto | null) => { @@ -38,7 +40,7 @@ export function useOnboardingSuggest(persona: string, name: string, active: bool }) .catch(() => { if (!cancelled) setStatus('failed'); }); return () => { cancelled = true; }; - }, [active, persona, name]); + }, [persona]); return { result, status }; }