diff --git a/frontend/src/app/components/Onboarding/steps/step00_welcomeNudge.ts b/frontend/src/app/components/Onboarding/steps/step00_welcomeNudge.ts index f5b21c15..c892e80b 100644 --- a/frontend/src/app/components/Onboarding/steps/step00_welcomeNudge.ts +++ b/frontend/src/app/components/Onboarding/steps/step00_welcomeNudge.ts @@ -11,11 +11,11 @@ export const welcomeOpenStep: OnboardingStep = { title: 'Welcome', description: '', ops: [ - { kind: 'delay', ms: 900 }, // let the big POP land + breathe - { kind: 'move_to', target: S.newAgentButton }, - { kind: 'popup', text: 'Let me open a chat for you.' }, - { kind: 'delay', ms: 350 }, // quick read, then click promptly - { kind: 'click', target: S.newAgentButton, simulate: true }, // spawns the welcome chat + { kind: 'delay', ms: 700 }, // let the big POP land + { kind: 'popup', text: 'Let me open up a chat for you.' }, // say it first + { kind: 'delay', ms: 900 }, // read, then go click it + { kind: 'move_to', target: S.newAgentButton }, // travel to the chat bubble + { kind: 'click', target: S.newAgentButton, simulate: true }, // click -> spawns the welcome chat { kind: 'outro' }, ], }; diff --git a/frontend/src/app/pages/AgentChat/AgentChat.tsx b/frontend/src/app/pages/AgentChat/AgentChat.tsx index 57f599bb..e7adf9e1 100644 --- a/frontend/src/app/pages/AgentChat/AgentChat.tsx +++ b/frontend/src/app/pages/AgentChat/AgentChat.tsx @@ -1358,14 +1358,17 @@ const AgentChat: React.FC = ({ sessionId: sessionIdProp, onClose )} - {!isDraft && ( + {(!isDraft || session.is_welcome_draft) && ( + // Welcome draft shows just the model so the header isn't bare; real runs add branch + cost. {resolveModelLabel(session.model)} - - {session.branch_name} - + {!isDraft && session.branch_name && ( + + {session.branch_name} + + )} {(() => { if (!(session.cost_usd > 0)) return null; // The SDK reports a per-call $ figure regardless of how diff --git a/frontend/src/app/pages/AgentChat/WelcomeQuickReplies.tsx b/frontend/src/app/pages/AgentChat/WelcomeQuickReplies.tsx index e2d2c6af..9f92c9c3 100644 --- a/frontend/src/app/pages/AgentChat/WelcomeQuickReplies.tsx +++ b/frontend/src/app/pages/AgentChat/WelcomeQuickReplies.tsx @@ -6,11 +6,17 @@ import { ArrowLeft } from 'lucide-react'; import type { ClaudeTokens } from '@/shared/styles/claudeTokens'; import { STARTER_CATEGORIES } from '@/shared/starterCategories'; -const GREETING = "Hi, I'm your AI team. What do you want done?"; +// Reads like a real chat welcome: a heading that types in, then a warm intro that leans on what +// only OpenSwarm can do (act right on your laptop), then the quick-reply chips. No em-dashes. +const HEADING = "Hi, I'm OpenSwarm, your personal AI team."; +const BODY = + "I can do just about anything right on your laptop, so bring me anything: a tough problem, " + + "a half-formed idea, something you need to write. We'll figure it out together. " + + 'Where do you want to start?'; // One-shot typewriter for a fixed string (no infinite loop; stops at the end). startDelayMs // holds the start so the header title can stream first (sequential reveal). -function useTypewriter(text: string, speedMs = 45, startDelayMs = 0): { shown: string; done: boolean } { +function useTypewriter(text: string, speedMs = 38, startDelayMs = 0): { shown: string; done: boolean } { const [shown, setShown] = React.useState(''); React.useEffect(() => { setShown(''); @@ -28,17 +34,15 @@ function useTypewriter(text: string, speedMs = 45, startDelayMs = 0): { shown: s return { shown, done: shown.length >= text.length }; } -// The first-run welcome: the greeting streams in like typing, then the quick-reply chips -// pop in (staggered). Two-level: category -> concrete prompts. Research/Write/Learn -> onPick -// (real run); Build -> onPickBuilder (App Builder). Pure UI; no run until the parent fires. +// First-run welcome. Two-level chips: category -> concrete prompts. Research/Write/Learn -> +// onPick (real run); Build -> onPickBuilder (App Builder). Pure UI; no run until the parent fires. const WelcomeQuickReplies: React.FC<{ c: ClaudeTokens; onPick: (prompt: string) => void; onPickBuilder: (prompt: string) => void; }> = ({ c, onPick, onPickBuilder }) => { - // Sequence: card pops, the header title streams, THEN the greeting types out, THEN the chips - // slide in. Brisk but smooth. - const { shown: greeting, done: greetingDone } = useTypewriter(GREETING, 42, 450); + // Sequence: card pops, header title streams, heading types, THEN body + chips slide in. + const { shown: heading, done: headingDone } = useTypewriter(HEADING, 38, 450); const [expanded, setExpanded] = React.useState(null); const currentCategory = STARTER_CATEGORIES.find((cat) => cat.id === expanded); const isAppBuilder = currentCategory?.target === 'app-builder'; @@ -50,48 +54,36 @@ const WelcomeQuickReplies: React.FC<{ }; return ( - - {/* Greeting bubble, typed out like a real reply. */} - - {greeting} - + + + {heading} + - {/* The chips block slides up + fades in once the greeting finishes (the outer motion.div), - then each chip springs in staggered (inner). */} - {greetingDone && ( + {/* Body + chips slide up + fade in once the heading finishes. */} + {headingDone && ( + + {BODY} + + {expanded === null ? ( - - - pick one, or just type below - - + + pick one, or just type below + {STARTER_CATEGORIES.map((cat, i) => ( setExpanded(cat.id)} - initial={{ opacity: 0, scale: 0.85 }} - animate={{ opacity: 1, scale: 1 }} - transition={{ type: 'spring', stiffness: 480, damping: 24, delay: 0.12 + i * 0.08 }} + initial={{ opacity: 0, scale: 0.86, y: 6 }} + animate={{ opacity: 1, scale: 1, y: 0 }} + transition={{ type: 'spring', stiffness: 360, damping: 24, delay: 0.25 + i * 0.13 }} style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '10px 14px', @@ -137,9 +129,9 @@ const WelcomeQuickReplies: React.FC<{ pick(prompt)} - initial={{ opacity: 0, scale: 0.9 }} + initial={{ opacity: 0, scale: 0.92 }} animate={{ opacity: 1, scale: 1 }} - transition={{ type: 'spring', stiffness: 520, damping: 26, delay: i * 0.05 }} + transition={{ type: 'spring', stiffness: 420, damping: 26, delay: i * 0.06 }} style={{ textAlign: 'left', padding: '9px 14px', diff --git a/frontend/src/app/pages/Dashboard/cards/AgentCard.tsx b/frontend/src/app/pages/Dashboard/cards/AgentCard.tsx index adaaf2be..0bb214c2 100644 --- a/frontend/src/app/pages/Dashboard/cards/AgentCard.tsx +++ b/frontend/src/app/pages/Dashboard/cards/AgentCard.tsx @@ -765,6 +765,12 @@ const AgentCard: React.FC = ({ )} + {/* Welcome chat has no status to show, so name the model instead, otherwise the header reads bare. */} + {session.is_welcome_draft && friendlyModelLabel && ( + + {friendlyModelLabel} + + )} {/* Calm, zero-click signal: the agent recalled or built up memory of this site, so the user feels it getting smarter on its own. */}