Feat: Images referenced in chat messages are displayed as a carousel at the bottom of the message. #12076 (#12207)

### What problem does this PR solve?
Feat: Images referenced in chat messages are displayed as a carousel at
the bottom of the message. #12076

### Type of change


- [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
balibabu
2025-12-25 15:54:07 +08:00
committed by GitHub
parent a3ceb7a944
commit f6217bb990
8 changed files with 161 additions and 92 deletions

View File

@ -1,5 +1,6 @@
import { Card, CardContent } from '@/components/ui/card';
import { Docagg } from '@/interfaces/database/chat';
import { middleEllipsis } from '@/utils/common-util';
import FileIcon from '../file-icon';
import NewDocumentLink from '../new-document-link';
@ -17,7 +18,7 @@ export function ReferenceDocumentList({ list }: { list: Docagg[] }) {
link={item.url}
className="text-text-sub-title-invert"
>
{item.doc_name}
{middleEllipsis(item.doc_name)}
</NewDocumentLink>
</CardContent>
</Card>

View File

@ -0,0 +1,84 @@
import Image from '@/components/image';
import {
Carousel,
CarouselContent,
CarouselItem,
CarouselNext,
CarouselPrevious,
} from '@/components/ui/carousel';
import { IReferenceChunk } from '@/interfaces/database/chat';
import { useResponsive } from 'ahooks';
import { useMemo } from 'react';
import { extractNumbersFromMessageContent } from './utils';
type IProps = {
referenceChunks: IReferenceChunk[];
messageContent: string;
};
function ImageCarousel({
imageIds,
hideButtons,
}: {
hideButtons?: boolean;
imageIds: string[];
}) {
return (
<Carousel
className="w-full"
opts={{
align: 'start',
}}
>
<CarouselContent>
{imageIds.map((imageId, index) => (
<CarouselItem key={index} className="md:basis-1/2 2xl:basis-1/6">
<Image
id={imageId}
className="h-40 w-full"
label={`Fig. ${(index + 1).toString()}`}
/>
</CarouselItem>
))}
</CarouselContent>
{!hideButtons && (
<>
<CarouselPrevious />
<CarouselNext />
</>
)}
</Carousel>
);
}
export function ReferenceImageList({
referenceChunks,
messageContent,
}: IProps) {
const imageIds = useMemo(() => {
return referenceChunks
.filter((_, idx) =>
extractNumbersFromMessageContent(messageContent).includes(idx),
)
.map((chunk) => chunk.image_id);
}, [messageContent, referenceChunks]);
const imageCount = imageIds.length;
const responsive = useResponsive();
const { isMd, is2xl } = useMemo(() => {
return {
isMd: responsive.md,
is2xl: responsive['2xl'],
};
}, [responsive]);
// If there are few images, hide the previous/next buttons.
const hideButtons = is2xl ? imageCount <= 6 : isMd ? imageCount <= 2 : false;
if (imageCount === 0) {
return <></>;
}
return <ImageCarousel imageIds={imageIds} hideButtons={hideButtons} />;
}

View File

@ -0,0 +1,16 @@
import { currentReg } from '@/utils/chat';
export const extractNumbersFromMessageContent = (content: string) => {
const matches = content.match(currentReg);
if (matches) {
const list = matches
.map((match) => {
const numMatch = match.match(/\[ID:(\d+)\]/);
return numMatch ? parseInt(numMatch[1], 10) : null;
})
.filter((num) => num !== null) as number[];
return list;
}
return [];
};