mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-02-08 03:25:06 +08:00
### What problem does this PR solve? Feat: An image carousel is displayed at the bottom of the agent's chat messages. #12076 ### Type of change - [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
@ -36,6 +36,7 @@ import { Button } from '../ui/button';
|
||||
import { AssistantGroupButton, UserGroupButton } from './group-button';
|
||||
import styles from './index.less';
|
||||
import { ReferenceDocumentList } from './reference-document-list';
|
||||
import { ReferenceImageList } from './reference-image-list';
|
||||
import { UploadedMessageFiles } from './uploaded-message-files';
|
||||
|
||||
interface IProps
|
||||
@ -295,6 +296,13 @@ function MessageItem({
|
||||
|
||||
{renderContent()}
|
||||
|
||||
{isAssistant && (
|
||||
<ReferenceImageList
|
||||
referenceChunks={reference?.chunks}
|
||||
messageContent={messageContent}
|
||||
></ReferenceImageList>
|
||||
)}
|
||||
|
||||
{isAssistant && referenceDocuments.length > 0 && (
|
||||
<ReferenceDocumentList
|
||||
list={referenceDocuments}
|
||||
|
||||
@ -7,22 +7,34 @@ import {
|
||||
CarouselPrevious,
|
||||
} from '@/components/ui/carousel';
|
||||
import { IReferenceChunk } from '@/interfaces/database/chat';
|
||||
import { useResponsive } from 'ahooks';
|
||||
import { isPlainObject } from 'lodash';
|
||||
import { useMemo } from 'react';
|
||||
import { extractNumbersFromMessageContent } from './utils';
|
||||
|
||||
type IProps = {
|
||||
referenceChunks: IReferenceChunk[];
|
||||
referenceChunks?: IReferenceChunk[] | Record<string, IReferenceChunk>;
|
||||
messageContent: string;
|
||||
};
|
||||
|
||||
function ImageCarousel({
|
||||
imageIds,
|
||||
hideButtons,
|
||||
}: {
|
||||
hideButtons?: boolean;
|
||||
imageIds: string[];
|
||||
}) {
|
||||
type ImageItem = {
|
||||
id: string;
|
||||
index: number;
|
||||
};
|
||||
|
||||
const getButtonVisibilityClass = (imageCount: number) => {
|
||||
const map: Record<number, string> = {
|
||||
1: 'hidden',
|
||||
2: '@sm:hidden',
|
||||
3: '@md:hidden',
|
||||
4: '@lg:hidden',
|
||||
5: '@lg:hidden',
|
||||
};
|
||||
return map[imageCount] || (imageCount >= 6 ? '@2xl:hidden' : '');
|
||||
};
|
||||
|
||||
function ImageCarousel({ images }: { images: ImageItem[] }) {
|
||||
const buttonVisibilityClass = getButtonVisibilityClass(images.length);
|
||||
|
||||
return (
|
||||
<Carousel
|
||||
className="w-full"
|
||||
@ -31,22 +43,27 @@ function ImageCarousel({
|
||||
}}
|
||||
>
|
||||
<CarouselContent>
|
||||
{imageIds.map((imageId, index) => (
|
||||
<CarouselItem key={index} className="md:basis-1/2 2xl:basis-1/6">
|
||||
{images.map(({ id, index }) => (
|
||||
<CarouselItem
|
||||
key={index}
|
||||
className="
|
||||
basis-full
|
||||
@sm:basis-1/2
|
||||
@md:basis-1/3
|
||||
@lg:basis-1/4
|
||||
@2xl:basis-1/6
|
||||
"
|
||||
>
|
||||
<Image
|
||||
id={imageId}
|
||||
id={id}
|
||||
className="h-40 w-full"
|
||||
label={`Fig. ${(index + 1).toString()}`}
|
||||
/>
|
||||
</CarouselItem>
|
||||
))}
|
||||
</CarouselContent>
|
||||
{!hideButtons && (
|
||||
<>
|
||||
<CarouselPrevious />
|
||||
<CarouselNext />
|
||||
</>
|
||||
)}
|
||||
<CarouselPrevious className={buttonVisibilityClass} />
|
||||
<CarouselNext className={buttonVisibilityClass} />
|
||||
</Carousel>
|
||||
);
|
||||
}
|
||||
@ -55,30 +72,38 @@ 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 allChunkIndexes = extractNumbersFromMessageContent(messageContent);
|
||||
const images = useMemo(() => {
|
||||
if (Array.isArray(referenceChunks)) {
|
||||
return referenceChunks
|
||||
.map((chunk, idx) => ({ id: chunk.image_id, index: idx }))
|
||||
.filter((item, idx) => allChunkIndexes.includes(idx) && item.id);
|
||||
}
|
||||
|
||||
const responsive = useResponsive();
|
||||
if (isPlainObject(referenceChunks)) {
|
||||
return Object.entries(referenceChunks || {}).reduce<ImageItem[]>(
|
||||
(pre, [idx, chunk]) => {
|
||||
if (allChunkIndexes.includes(Number(idx)) && chunk.image_id) {
|
||||
return pre.concat({ id: chunk.image_id, index: Number(idx) });
|
||||
}
|
||||
return pre;
|
||||
},
|
||||
[],
|
||||
);
|
||||
}
|
||||
|
||||
const { isMd, is2xl } = useMemo(() => {
|
||||
return {
|
||||
isMd: responsive.md,
|
||||
is2xl: responsive['2xl'],
|
||||
};
|
||||
}, [responsive]);
|
||||
return [];
|
||||
}, [allChunkIndexes, referenceChunks]);
|
||||
|
||||
// If there are few images, hide the previous/next buttons.
|
||||
const hideButtons = is2xl ? imageCount <= 6 : isMd ? imageCount <= 2 : false;
|
||||
const imageCount = images?.length || 0;
|
||||
|
||||
if (imageCount === 0) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return <ImageCarousel imageIds={imageIds} hideButtons={hideButtons} />;
|
||||
return (
|
||||
<section className="@container w-full">
|
||||
<ImageCarousel images={images} />
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user