feat: deliverables 3-6 — cold start, simple mode UI, bridge tests, docs
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

Deliverable 3 — Cold Start Flow:
- New: use-cold-start.ts — cold start detection + greeting management
- Default Chinese greeting for hospital admin users
- Phase tracking: idle → greeting_sent → waiting_response → completed

Deliverable 4 — Simple Mode UI:
- New: uiModeStore.ts — 'simple'|'professional' mode with localStorage
- New: SimpleTopBar.tsx — minimal top bar with mode toggle
- Modified: App.tsx — dual layout rendering based on UI mode
- Modified: ChatArea.tsx — compact prop hides advanced controls
- Default: 'simple' mode for zero-barrier first experience

Deliverable 5 — Tauri Bridge Integration Tests:
- New: tauri-bridge.integration.test.ts — 14 test cases
- Covers: cold start, chat flow, persistence, memory, butler, UI mode, e2e
- 14/14 passing

Deliverable 6 — Release Documentation:
- New: installation-guide.md — user-facing install guide (Chinese, no jargon)
- New: hospital-deployment.md — IT admin deployment guide (Docker, GPO, SCCM)
This commit is contained in:
iven
2026-04-09 09:51:56 +08:00
parent ffaee49d67
commit e6937e1e5f
8 changed files with 2031 additions and 4 deletions

View File

@@ -0,0 +1,206 @@
/**
* 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;