[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

This commit is contained in:
ciregenz
2026-07-01 00:46:14 -07:00
parent ce782adb83
commit ebb8ab0054
2 changed files with 12 additions and 9 deletions
@@ -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);
@@ -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<SuggestDto | null>(null);
const [status, setStatus] = useState<SuggestStatus>('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 };
}