Files
zclaw_openfang/desktop/src/lib/use-cold-start.ts
iven 722d8a3a9e
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
fix(ui): UX 文案优化 — 区分新/老用户 + 去政务化 + 友好提示
- FirstConversationPrompt: 新用户显示"欢迎开始!",老用户"欢迎回来!"
- use-cold-start: 冷启动问候语改为通用语言,去掉政务场景特定文案
- LoginPage: 添加"忘记密码?请联系管理员重置"提示
- connectionStore: 错误提示改为用户友好的"暂时没有可用的 AI 模型"
2026-04-11 02:56:19 +08:00

207 lines
5.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 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<ColdStartPhase>(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;