Spaces:
Running
Running
File size: 3,601 Bytes
d764c8d | 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 114 115 116 117 118 119 120 | import React, { useState, useRef, useEffect, useCallback } from 'react';
import { ChevronLeft, ChevronRight } from 'lucide-react';
import MessageBubble from './MessageBubble';
const CAROUSEL_BREAKPOINT = 700;
const AdvisorCarousel = ({ messages = [], onReply, onExpand, onClick, onSearchReferences, userAvatarId, userAvatarOptions }) => {
const [activeIndex, setActiveIndex] = useState(0);
const [isCarouselMode, setIsCarouselMode] = useState(false);
const containerRef = useRef(null);
useEffect(() => {
const el = containerRef.current;
if (!el) return;
const observer = new ResizeObserver(entries => {
const width = entries[0].contentRect.width;
setIsCarouselMode(width < CAROUSEL_BREAKPOINT);
});
observer.observe(el);
return () => observer.disconnect();
}, []);
useEffect(() => {
setActiveIndex(0);
}, [messages.length]);
const goPrev = useCallback(() => {
setActiveIndex(i => Math.max(0, i - 1));
}, []);
const goNext = useCallback(() => {
setActiveIndex(i => Math.min(messages.length - 1, i + 1));
}, [messages.length]);
if (messages.length === 1) {
return (
<div className="single-response-wide">
<MessageBubble
message={messages[0]}
onReply={onReply}
onExpand={onExpand}
onClick={onClick}
onSearchReferences={onSearchReferences}
showReplyButton={true}
userAvatarId={userAvatarId}
userAvatarOptions={userAvatarOptions}
/>
</div>
);
}
return (
<div
className={`advisor-carousel ${isCarouselMode ? 'carousel-mode' : 'grid-mode'}`}
ref={containerRef}
>
{isCarouselMode && (
<button
className="carousel-arrow carousel-prev"
onClick={goPrev}
disabled={activeIndex === 0}
aria-label="Previous advisor"
>
<ChevronLeft size={20} />
</button>
)}
<div className="carousel-viewport">
<div
className="carousel-track"
style={isCarouselMode ? { width: `${messages.length * 100}%`, transform: `translateX(-${activeIndex * (100 / messages.length)}%)` } : undefined}
>
{messages.map(message => (
<div key={message.id} className="carousel-slide" style={isCarouselMode ? { width: `${100 / messages.length}%` } : undefined}>
<MessageBubble
message={message}
onReply={onReply}
onExpand={onExpand}
onClick={onClick}
onSearchReferences={onSearchReferences}
showReplyButton={true}
inlineAvatar={true}
userAvatarId={userAvatarId}
userAvatarOptions={userAvatarOptions}
/>
</div>
))}
</div>
</div>
{isCarouselMode && (
<>
<button
className="carousel-arrow carousel-next"
onClick={goNext}
disabled={activeIndex === messages.length - 1}
aria-label="Next advisor"
>
<ChevronRight size={20} />
</button>
<div className="carousel-dots">
{messages.map((m, i) => (
<button
key={m.id}
className={`carousel-dot ${i === activeIndex ? 'active' : ''}`}
onClick={() => setActiveIndex(i)}
aria-label={`Go to advisor ${i + 1}`}
/>
))}
</div>
</>
)}
</div>
);
};
export default AdvisorCarousel;
|