Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 | 1x 1x 1x 1x 1x 1x 1x 1x 1x 7x 7x 7x 7x 7x 7x 7x 7x 7x 8x 8x 8x 1x 1x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 21x 21x 21x 21x 21x 7x 7x 7x 7x 7x 7x 21x 21x 21x 21x 21x 21x 21x 21x 7x 7x 7x 7x 7x 7x | import { useEffect } from 'react';
// Constants
import { IMAGES } from '@shared/constants';
// Utils
import { combineClasses } from '@shared/utils';
// Components
import { Skeleton } from '../common';
type ProcessingIndicatorProps = {
className?: string;
};
const indicatorConfig = {
dotDelays: ['0ms', '150ms', '300ms'],
skeletonWidths: ['w-full', 'w-4/5', 'w-3/5'],
};
export const ProcessingIndicator = ({
className = '',
}: ProcessingIndicatorProps) => {
const { dotDelays, skeletonWidths } = indicatorConfig;
useEffect(() => {
const processingIndicators = document?.querySelectorAll(
'.processing-indicator',
);
let count = 0;
processingIndicators.forEach((indicator, index) => {
count++;
const shouldHide = count > 1 && !indicator.classList.contains('hidden');
if (shouldHide) {
processingIndicators[index]?.classList.add('hidden');
}
});
}, [className]);
return (
<div
className={combineClasses(
'processing-indicator flex items-start gap-4',
className,
)}
>
{/* Chat Message Container */}
<div className="flex-1 max-w-cb-message-box">
<div className="bg-white dark:bg-secondary-900 dark:border-secondary-900 border border-frost-250 rounded-lg shadow-md p-4 relative overflow-hidden">
{/* Header with Assistant Avatar and Title */}
<div className="flex items-center gap-2 mb-1.5">
{/* Assistant Avatar */}
<div className="flex-shrink-0 relative size-10">
<div
className={combineClasses(
'size-10 rounded-full flex-center relative m-auto',
'bg-gradient-to-br from-primary-50 to-primary-100 ',
)}
>
<img
src={IMAGES.CHATBOT_PROCESSING}
alt="AI Assistant"
width={28}
height={28}
className="size-7 object-contain"
/>
</div>
</div>
{/* Assistant Title */}
<div className="flex items-center gap-2">
<div className="w-2 h-2 bg-primary-500 rounded-full animate-pulse" />
<span className="capitalize text-xs font-semibold text-primary-700 dark:text-background tracking-wide">
Agility Flash
</span>
</div>
</div>
{/* Thinking Text with typing animation */}
<div className="mb-3">
<span className="inline-flex items-center gap-0.5 text-xs font-medium text-gray-500 dark:text-background">
Thinking
<span className="flex gap-0.5 ml-1">
{dotDelays.map((delay, index) => (
<span
key={`dot-${index}`}
className="size-1 bg-gray-500 rounded-full animate-bounce dark:bg-background"
style={{ animationDelay: delay }}
/>
))}
</span>
</span>
</div>
{/* Skeleton Lines */}
<div className="flex-col space-y-2">
{skeletonWidths.map((width, index) => (
<Skeleton
key={`skeleton-line-${index}`}
className={combineClasses(
'!flex h-3',
'bg-gradient-to-r from-gray-200 via-gray-300 to-gray-200',
width,
)}
/>
))}
</div>
</div>
</div>
</div>
);
};
|