/** * useColdStart - Cold start state management hook * * Detects first-time users and manages the cold start greeting flow. * Reuses the onboarding completion key to determine if user is new. * * Flow: idle -> greeting_sent -> waiting_response -> completed */ import { useState, useEffect, useCallback } from 'react'; import { createLogger } from './logger'; const log = createLogger('useColdStart'); // Reuse the same key from use-onboarding.ts const ONBOARDING_COMPLETED_KEY = 'zclaw-onboarding-completed'; // Cold start state persisted to localStorage const COLD_START_STATE_KEY = 'zclaw-cold-start-state'; // Re-export UserProfile for consumers that need it export type { UserProfile } from './use-onboarding'; // === Types === export type ColdStartPhase = 'idle' | 'greeting_sent' | 'waiting_response' | 'completed'; export interface ColdStartState { isColdStart: boolean; phase: ColdStartPhase; greetingSent: boolean; markGreetingSent: () => void; markWaitingResponse: () => void; markCompleted: () => void; getGreetingMessage: (agentName?: string, agentEmoji?: string) => string; } // === Default Greeting === const DEFAULT_GREETING_BODY = '我可以帮您处理写作、研究、数据分析、内容生成等各类任务。\n\n请告诉我您需要什么帮助?'; const FALLBACK_GREETING = '您好!我是您的工作助手。我可以帮您处理写作、研究、数据分析、内容生成等各类任务。请告诉我您需要什么帮助?'; // === Persistence Helpers === interface PersistedColdStart { phase: ColdStartPhase; } function loadPersistedPhase(): ColdStartPhase { try { const raw = localStorage.getItem(COLD_START_STATE_KEY); if (raw) { const parsed = JSON.parse(raw) as PersistedColdStart; if (parsed && typeof parsed.phase === 'string') { return parsed.phase; } } } catch (err) { log.warn('Failed to read cold start state:', err); } return 'idle'; } function persistPhase(phase: ColdStartPhase): void { try { const data: PersistedColdStart = { phase }; localStorage.setItem(COLD_START_STATE_KEY, JSON.stringify(data)); } catch (err) { log.warn('Failed to persist cold start state:', err); } } // === Greeting Builder === function buildGreeting(agentName?: string, agentEmoji?: string): string { if (!agentName) { return FALLBACK_GREETING; } const emoji = agentEmoji ? ` ${agentEmoji}` : ''; return `您好!我是${agentName}${emoji}\n\n${DEFAULT_GREETING_BODY}`; } // === Hook === /** * Hook to manage cold start state for first-time users. * * A user is considered "cold start" when they have not completed onboarding * AND have not yet gone through the greeting flow. * * Usage: * ```tsx * const { isColdStart, phase, markGreetingSent, getGreetingMessage } = useColdStart(); * * if (isColdStart && phase === 'idle') { * const msg = getGreetingMessage(agent.name, agent.emoji); * sendMessage(msg); * markGreetingSent(); * } * ``` */ export function useColdStart(): ColdStartState { const [phase, setPhase] = useState(loadPersistedPhase); const [isColdStart, setIsColdStart] = useState(false); // Determine cold start status on mount useEffect(() => { try { const onboardingCompleted = localStorage.getItem(ONBOARDING_COMPLETED_KEY); const isNewUser = onboardingCompleted !== 'true'; if (isNewUser && phase !== 'completed') { setIsColdStart(true); } else { setIsColdStart(false); // If onboarding is completed but phase is not completed, // force phase to completed to avoid stuck states if (phase !== 'completed') { setPhase('completed'); persistPhase('completed'); } } } catch (err) { log.warn('Failed to check cold start status:', err); setIsColdStart(false); } }, [phase]); const markGreetingSent = useCallback(() => { const nextPhase: ColdStartPhase = 'greeting_sent'; setPhase(nextPhase); persistPhase(nextPhase); log.debug('Cold start: greeting sent'); }, []); const markWaitingResponse = useCallback(() => { const nextPhase: ColdStartPhase = 'waiting_response'; setPhase(nextPhase); persistPhase(nextPhase); log.debug('Cold start: waiting for user response'); }, []); const markCompleted = useCallback(() => { const nextPhase: ColdStartPhase = 'completed'; setPhase(nextPhase); persistPhase(nextPhase); setIsColdStart(false); log.debug('Cold start: completed'); }, []); const getGreetingMessage = useCallback( (agentName?: string, agentEmoji?: string): string => { return buildGreeting(agentName, agentEmoji); }, [], ); return { isColdStart, phase, greetingSent: phase === 'greeting_sent' || phase === 'waiting_response' || phase === 'completed', markGreetingSent, markWaitingResponse, markCompleted, getGreetingMessage, }; } // === Non-hook Accessor === /** * Get cold start state without React hook (for use outside components). */ export function getColdStartState(): { isColdStart: boolean; phase: ColdStartPhase } { try { const onboardingCompleted = localStorage.getItem(ONBOARDING_COMPLETED_KEY); const isNewUser = onboardingCompleted !== 'true'; const phase = loadPersistedPhase(); return { isColdStart: isNewUser && phase !== 'completed', phase, }; } catch (err) { log.warn('Failed to get cold start state:', err); return { isColdStart: false, phase: 'completed' }; } } /** * Reset cold start state (for testing or debugging). */ export function resetColdStartState(): void { try { localStorage.removeItem(COLD_START_STATE_KEY); log.debug('Cold start state reset'); } catch (err) { log.warn('Failed to reset cold start state:', err); } } export default useColdStart;