Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 3.8k
improvement(chat): shimmer active subagent and tool labels instead of spinners#5612
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
848b9e813e90f2cba6794ccd0b188bb0808File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,5 @@ | ||
| import { useMemo } from 'react' | ||
| import { PillsRing } from '@sim/emcn' | ||
| import { ShimmerText } from '@/components/ui' | ||
| import { WorkspaceFile } from '@/lib/copilot/generated/tool-catalog-v1' | ||
| import type { ToolCallStatus } from '../../../../types' | ||
| import { getToolIcon, resolveToolDisplayState } from '../../utils' | ||
| @@ -57,10 +57,29 @@ function Hyphen({ className }: { className?: string }) { | ||
| ) | ||
| } | ||
| function CircleOutline({ className }: { className?: string }) { | ||
| return ( | ||
| <svg | ||
| width='16' | ||
| height='16' | ||
| viewBox='0 0 16 16' | ||
| fill='none' | ||
| xmlns='http://www.w3.org/2000/svg' | ||
| className={className} | ||
| > | ||
| <circle cx='8' cy='8' r='6.5' stroke='currentColor' strokeWidth='1.25' /> | ||
| </svg> | ||
| ) | ||
| } | ||
| function StatusIcon({ status, toolName }: { status: ToolCallStatus; toolName: string }) { | ||
| const display = resolveToolDisplayState(status) | ||
| if (display === 'spinner') { | ||
| return <PillsRing className='size-[15px] text-[var(--text-tertiary)]' animate /> | ||
| const Icon = getToolIcon(toolName) | ||
| if (Icon) { | ||
| return <Icon className='size-[15px] text-[var(--text-tertiary)]' /> | ||
| } | ||
| return <CircleOutline className='size-[15px] text-[var(--text-tertiary)]' /> | ||
waleedlatif1 marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. waleedlatif1 marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| } | ||
| if (display === 'cancelled') { | ||
| return <CircleStop className='size-[15px] text-[var(--text-tertiary)]' /> | ||
| @@ -112,14 +131,21 @@ export function ToolCallItem({ toolName, displayTitle, status, streamingArgs }: | ||
| return `${verb} ${unescaped}` | ||
| }, [toolName, streamingArgs]) | ||
| const isExecuting = resolveToolDisplayState(status) === 'spinner' | ||
| const title = liveWorkspaceFileTitle || displayTitle | ||
| return ( | ||
| <div className='flex items-center gap-[8px] pl-[24px]'> | ||
| <div className='flex size-[16px] flex-shrink-0 items-center justify-center'> | ||
| <StatusIcon status={status} toolName={toolName} /> | ||
| </div> | ||
| <span className='text-[13px] text-[var(--text-secondary)]'> | ||
| {liveWorkspaceFileTitle || displayTitle} | ||
| </span> | ||
| {isExecuting ? ( | ||
| <ShimmerText className='text-[13px] [--shimmer-rest:var(--text-secondary)]'> | ||
| {title} | ||
| </ShimmerText> | ||
| ) : ( | ||
| <span className='text-[13px] text-[var(--text-secondary)]'>{title}</span> | ||
| )} | ||
| </div> | ||
| ) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| /** | ||
| * Claude-style text shimmer: the text paints from a gradient with a light band | ||
| * that sweeps across the glyphs via background-clip. This is the single source | ||
| * of truth for the treatment — the ThinkingLoader label composes it. Under | ||
| * reduced motion the sweep is replaced by a gentle opacity pulse in solid ink: | ||
| * the shimmer conveys essential in-progress state, so it needs a vestibular-safe | ||
| * fallback rather than none. Consumers whose resting text is not body ink set | ||
| * `--shimmer-rest` to their resting color. | ||
| */ | ||
| .shimmer { | ||
| background-image: linear-gradient(90deg, #4a4a4a 40%, #b0b0b0 50%, #4a4a4a 60%); | ||
| background-size: 200% 100%; | ||
| -webkit-background-clip: text; | ||
| background-clip: text; | ||
| color: transparent; | ||
| animation: shimmer-sweep 2.2s linear infinite; | ||
| } | ||
| :global(.dark) .shimmer { | ||
| background-image: linear-gradient(90deg, #b9b9b9 40%, #f8f8f8 50%, #b9b9b9 60%); | ||
| } | ||
| @keyframes shimmer-sweep { | ||
| to { | ||
| background-position: 200% 0; | ||
| } | ||
| } | ||
waleedlatif1 marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| @media (prefers-reduced-motion: reduce) { | ||
| .shimmer, | ||
| :global(.dark) .shimmer { | ||
| background-image: none; | ||
| -webkit-background-clip: initial; | ||
| background-clip: initial; | ||
| color: var(--shimmer-rest, var(--text-body)); | ||
| animation: shimmer-pulse 2.2s ease-in-out infinite; | ||
| } | ||
waleedlatif1 marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. waleedlatif1 marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| } | ||
| @keyframes shimmer-pulse { | ||
| 50% { | ||
| opacity: 0.55; | ||
| } | ||
waleedlatif1 marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| import { cn } from '@sim/emcn' | ||
| import styles from '@/components/ui/shimmer-text.module.css' | ||
| interface ShimmerTextProps { | ||
| children: React.ReactNode | ||
| className?: string | ||
| } | ||
| /** | ||
| * Sweeping-highlight shimmer over a text phrase — the same treatment as the | ||
| * ThinkingLoader's "Thinking…" label, reusable on any active/streaming row. | ||
| * Size and weight come from the consumer's className; the gradient replaces | ||
| * the text color, so color classes are ignored while shimmering. | ||
| */ | ||
| export function ShimmerText({ children, className }: ShimmerTextProps) { | ||
| return <span className={cn(styles.shimmer, className)}>{children}</span> | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.