feat(desktop): add StepDots component and minor onboarding tweaks

- Add StepDots progress indicator to all steps
- Update animations for consistency
- Minor copy and layout adjustments

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Naiyuan Qing 2026-02-11 17:46:56 +08:00
parent 437dc05db0
commit d189b14a15
4 changed files with 93 additions and 96 deletions

View file

@ -42,7 +42,7 @@ export default function PermissionsStep({ onNext }: PermissionsStepProps) {
const [agreed, setAgreed] = useState(false)
return (
<div className="h-full flex items-center justify-center px-6 py-8">
<div className="h-full flex items-center justify-center px-6 py-8 animate-in fade-in duration-300">
<div className="w-full max-w-md space-y-6">
{/* Header */}
<div className="space-y-2">

View file

@ -1,27 +1,34 @@
import { useNavigate } from 'react-router-dom'
import { Button } from '@multica/ui/components/ui/button'
import { Loading } from '@multica/ui/components/ui/loading'
import { ChatView } from '@multica/ui/components/chat-view'
import { Separator } from '@multica/ui/components/ui/separator'
import { HugeiconsIcon } from '@hugeicons/react'
import { ArrowLeft02Icon } from '@hugeicons/core-free-icons'
import { SamplePrompt } from '../../../components/onboarding/sample-prompt'
import {
ArrowLeft02Icon,
ArrowRight01Icon,
Search01Icon,
FolderOpenIcon,
CommandLineIcon,
} from '@hugeicons/core-free-icons'
import { StepDots } from './step-dots'
import { useLocalChat } from '../../../hooks/use-local-chat'
const samplePrompts = [
const tryPrompts = [
{
title: 'Latest AI news',
prompt:
"Search the web for today's top AI news and give me a 3-bullet summary with sources.",
icon: Search01Icon,
title: 'Search the web',
description: "Get today's AI news",
prompt: "Search the web for today's top AI news and give me a 3-bullet summary with sources.",
},
{
title: 'Analyze this project',
prompt:
'Look at the files in my current directory and give me a brief summary of what this project is about.',
icon: FolderOpenIcon,
title: 'Read your files',
description: 'Summarize this directory',
prompt: 'Look at the files in my current directory and give me a brief summary of what this project is about.',
},
{
title: 'Quick task',
prompt:
'Write a one-liner shell command that shows my system info (OS, CPU cores, memory) and run it.',
icon: CommandLineIcon,
title: 'Run a command',
description: 'Show system info',
prompt: 'Write a one-liner shell command that shows my system info (OS, CPU cores, memory) and run it.',
},
]
@ -31,91 +38,81 @@ interface TryItStepProps {
}
export default function TryItStep({ onComplete, onBack }: TryItStepProps) {
const {
agentId,
initError,
messages,
streamingIds,
isLoading,
isLoadingHistory,
isLoadingMore,
hasMore,
error,
pendingApprovals,
sendMessage,
loadMore,
resolveApproval,
} = useLocalChat()
const navigate = useNavigate()
const handlePromptClick = (prompt: string) => {
console.log('[TryItStep] Selected prompt:', prompt)
// TODO: Pass prompt to chat page
onComplete()
navigate('/chat')
}
return (
<div className="h-full flex">
{/* Left column — prompts */}
<div className="flex-1 flex items-center justify-center px-12 py-8">
<div className="max-w-md w-full space-y-6">
<button
onClick={onBack}
className="flex items-center gap-1.5 text-sm text-muted-foreground hover:text-foreground transition-colors"
>
<HugeiconsIcon icon={ArrowLeft02Icon} className="size-4" />
Back
</button>
<div className="h-full flex items-center justify-center px-6 py-8 animate-in fade-in duration-300">
<div className="w-full max-w-md space-y-6">
{/* Back button */}
<button
onClick={onBack}
className="flex items-center gap-1.5 text-sm text-muted-foreground hover:text-foreground transition-colors"
>
<HugeiconsIcon icon={ArrowLeft02Icon} className="size-4" />
Back
</button>
<div className="space-y-2">
<h1 className="text-2xl font-semibold tracking-tight">
Try it out
</h1>
<p className="text-sm text-muted-foreground">
Your agent can search the web, read files, and run commands.
Click a prompt to see it in action.
</p>
</div>
{/* Header */}
<div className="space-y-2">
<h1 className="text-2xl font-semibold tracking-tight">
You're all set 🎉
</h1>
<p className="text-sm text-muted-foreground">
Your agent is ready to help. Try a sample task, or dive right in.
</p>
</div>
<div className="space-y-2">
{samplePrompts.map((sp) => (
<SamplePrompt
key={sp.title}
title={sp.title}
prompt={sp.prompt}
onClick={() => sendMessage(sp.prompt)}
/>
{/* Try prompts */}
<div className="space-y-2">
<p className="text-xs text-muted-foreground">
Quick start
</p>
<div className="rounded-xl border border-border bg-card divide-y divide-border">
{tryPrompts.map((item) => (
<button
key={item.title}
onClick={() => handlePromptClick(item.prompt)}
className="w-full flex items-center justify-between px-4 py-3 hover:bg-accent/50 transition-colors text-left"
>
<div className="flex items-center gap-3">
<div className="flex items-center justify-center size-8 rounded-lg bg-muted shrink-0">
<HugeiconsIcon
icon={item.icon}
className="size-4 text-muted-foreground"
/>
</div>
<div>
<p className="text-sm font-medium">{item.title}</p>
<p className="text-xs text-muted-foreground">
{item.description}
</p>
</div>
</div>
<HugeiconsIcon
icon={ArrowRight01Icon}
className="size-4 text-muted-foreground"
/>
</button>
))}
</div>
<div className="flex items-center justify-between">
<StepDots />
<Button size="lg" onClick={onComplete}>
Open Multica
</Button>
</div>
</div>
</div>
{/* Right column — live chat */}
<div className="flex-1 flex flex-col min-h-0 border-l">
{initError ? (
<div className="flex-1 flex items-center justify-center text-sm text-destructive px-8 text-center">
{initError}
</div>
) : !agentId ? (
<div className="flex-1 flex items-center justify-center gap-2 text-muted-foreground text-sm">
<Loading />
Initializing agent...
</div>
) : (
<ChatView
messages={messages}
streamingIds={streamingIds}
isLoading={isLoading}
isLoadingHistory={isLoadingHistory}
isLoadingMore={isLoadingMore}
hasMore={hasMore}
error={error}
pendingApprovals={pendingApprovals}
sendMessage={sendMessage}
loadMore={loadMore}
resolveApproval={resolveApproval}
/>
)}
<Separator />
{/* Footer */}
<div className="flex items-center justify-between">
<StepDots />
<Button size="sm" onClick={onComplete}>
Go to Multica
</Button>
</div>
</div>
</div>
)

View file

@ -22,7 +22,7 @@ interface WelcomeStepProps {
export default function WelcomeStep({ onStart }: WelcomeStepProps) {
return (
<div className="h-full flex items-center justify-center px-12 py-8">
<div className="h-full flex items-center justify-center px-12 py-8 animate-in fade-in duration-300">
<div className="max-w-md w-full flex flex-col items-center text-center space-y-6">
{/* Brand Title */}
<div className="flex items-center gap-2.5">

View file

@ -31,7 +31,7 @@ export default function OnboardingPage() {
/>
<main
key={currentStep}
className="flex-1 overflow-auto animate-in fade-in duration-300"
className="flex-1 overflow-auto"
>
<WelcomeStep onStart={nextStep} />
</main>
@ -73,7 +73,7 @@ export default function OnboardingPage() {
{/* Step content */}
<main
key={currentStep}
className="flex-1 overflow-auto animate-in fade-in duration-300"
className="flex-1 overflow-auto"
>
{currentStep === 1 && <PermissionsStep onNext={nextStep} />}
{currentStep === 2 && <SetupStep onNext={nextStep} onBack={prevStep} />}