mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-09-11 12:17:45 +02:00
[eric] onboarding polish: exaggerated cursor pop + longer wait then prompt click; faster greeting, no caret; chips slide in smoothly (no instant pop); title 'First chat with OpenSwarm', no draft label
This commit is contained in:
@@ -120,13 +120,13 @@ const AgenticCursor = forwardRef<AgenticCursorHandle>((_props, ref) => {
|
||||
controls.set({ x: from.x, y: from.y, opacity: 0, scale: 0.3 });
|
||||
setVisible(true);
|
||||
setBurstKey((k) => k + 1); // replay the orange spark on each pop
|
||||
// A little "pop!": scale overshoots past 1 then settles, while opacity snaps in.
|
||||
// An exaggerated "POP!": scale shoots way past 1 then settles back, opacity snaps in.
|
||||
await controls.start({
|
||||
opacity: 1,
|
||||
scale: [0.3, 1.15, 1],
|
||||
scale: [0.1, 1.45, 0.92, 1],
|
||||
transition: {
|
||||
opacity: { duration: 0.16, ease: 'easeOut' },
|
||||
scale: { duration: 0.36, ease: 'easeOut', times: [0, 0.62, 1] },
|
||||
opacity: { duration: 0.14, ease: 'easeOut' },
|
||||
scale: { duration: 0.5, ease: 'easeOut', times: [0, 0.55, 0.8, 1] },
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
@@ -11,10 +11,10 @@ export const welcomeOpenStep: OnboardingStep = {
|
||||
title: 'Welcome',
|
||||
description: '',
|
||||
ops: [
|
||||
{ kind: 'delay', ms: 400 }, // brief beat after the pop + spark
|
||||
{ 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: 650 }, // a moment to read, then click
|
||||
{ kind: 'delay', ms: 350 }, // quick read, then click promptly
|
||||
{ kind: 'click', target: S.newAgentButton, simulate: true }, // spawns the welcome chat
|
||||
{ kind: 'outro' },
|
||||
],
|
||||
|
||||
@@ -36,9 +36,9 @@ const WelcomeQuickReplies: React.FC<{
|
||||
onPick: (prompt: string) => void;
|
||||
onPickBuilder: (prompt: string) => void;
|
||||
}> = ({ c, onPick, onPickBuilder }) => {
|
||||
// Slow + delayed so it reads as a calm sequence: card pops, the header title streams, THEN
|
||||
// the greeting types out unhurried, THEN the chips pop in.
|
||||
const { shown: greeting, done: greetingDone } = useTypewriter(GREETING, 78, 850);
|
||||
// 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);
|
||||
const [expanded, setExpanded] = React.useState<string | null>(null);
|
||||
const currentCategory = STARTER_CATEGORIES.find((cat) => cat.id === expanded);
|
||||
const isAppBuilder = currentCategory?.target === 'app-builder';
|
||||
@@ -65,14 +65,17 @@ const WelcomeQuickReplies: React.FC<{
|
||||
}}
|
||||
>
|
||||
{greeting}
|
||||
{!greetingDone && (
|
||||
<Box component="span" sx={{ ml: '1px', color: c.accent.primary }}>▌</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{/* Chips reveal only once the greeting finishes, each popping in with a stagger. */}
|
||||
{/* The chips block slides up + fades in once the greeting finishes (the outer motion.div),
|
||||
then each chip springs in staggered (inner). */}
|
||||
{greetingDone && (
|
||||
<Box sx={{ width: '100%', alignSelf: 'stretch' }}>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.4, ease: [0.22, 1, 0.36, 1] }}
|
||||
style={{ width: '100%', alignSelf: 'stretch' }}
|
||||
>
|
||||
<AnimatePresence mode="wait" initial={false}>
|
||||
{expanded === null ? (
|
||||
<motion.div key="categories" initial={false} style={{ display: 'flex', flexDirection: 'column' }}>
|
||||
@@ -86,9 +89,9 @@ const WelcomeQuickReplies: React.FC<{
|
||||
<motion.button
|
||||
key={cat.id}
|
||||
onClick={() => setExpanded(cat.id)}
|
||||
initial={{ opacity: 0, scale: 0.82 }}
|
||||
initial={{ opacity: 0, scale: 0.85 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{ type: 'spring', stiffness: 420, damping: 22, delay: 0.25 + i * 0.18 }}
|
||||
transition={{ type: 'spring', stiffness: 480, damping: 24, delay: 0.12 + i * 0.08 }}
|
||||
style={{
|
||||
display: 'flex', alignItems: 'center', gap: 8,
|
||||
padding: '10px 14px',
|
||||
@@ -155,7 +158,7 @@ const WelcomeQuickReplies: React.FC<{
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</Box>
|
||||
</motion.div>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
|
||||
@@ -756,8 +756,9 @@ const AgentCard: React.FC<Props> = ({
|
||||
</Typography>
|
||||
)}
|
||||
</Typewriter>
|
||||
{/* Status speaks only when it needs the user; finished work sits quiet. */}
|
||||
{session.status !== 'completed' && session.status !== 'stopped' && (
|
||||
{/* Status speaks only when it needs the user; finished work sits quiet. The welcome
|
||||
chat hides its 'draft' label so the title reads clean. */}
|
||||
{session.status !== 'completed' && session.status !== 'stopped' && !session.is_welcome_draft && (
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', flexShrink: 0 }}>
|
||||
<Typography sx={{ fontSize: '0.75rem', fontWeight: 500, color: c.text.tertiary, whiteSpace: 'nowrap' }}>
|
||||
{friendlyStatusLabel(session.status)}
|
||||
|
||||
@@ -523,7 +523,7 @@ const agentsSlice = createSlice({
|
||||
const { draftId, mode, setActive, targetDirectory, model, provider, thinkingLevel, seededMessages, welcome, dashboardId } = action.payload;
|
||||
state.sessions[draftId] = {
|
||||
id: draftId,
|
||||
name: 'New chat',
|
||||
name: welcome === true ? 'First chat with OpenSwarm' : 'New chat',
|
||||
status: 'draft',
|
||||
provider: provider || 'anthropic',
|
||||
model: model || 'sonnet',
|
||||
|
||||
Reference in New Issue
Block a user