mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-08 20:42:30 +08:00
### What problem does this PR solve? Feat: Render the uploaded agent message file #3221 ### Type of change - [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
@ -12,28 +12,20 @@ import {
|
|||||||
useState,
|
useState,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
|
|
||||||
import {
|
|
||||||
useFetchDocumentInfosByIds,
|
|
||||||
useFetchDocumentThumbnailsByIds,
|
|
||||||
} from '@/hooks/document-hooks';
|
|
||||||
import { IRegenerateMessage, IRemoveMessageById } from '@/hooks/logic-hooks';
|
import { IRegenerateMessage, IRemoveMessageById } from '@/hooks/logic-hooks';
|
||||||
import { INodeEvent } from '@/hooks/use-send-message';
|
import { INodeEvent } from '@/hooks/use-send-message';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { WorkFlowTimeline } from '@/pages/agent/log-sheet/workFlowTimeline';
|
import { WorkFlowTimeline } from '@/pages/agent/log-sheet/workFlowTimeline';
|
||||||
import { IMessage } from '@/pages/chat/interface';
|
import { IMessage } from '@/pages/chat/interface';
|
||||||
import { getExtension, isImage } from '@/utils/document-util';
|
|
||||||
import { Avatar, Button, Flex, List, Space, Typography } from 'antd';
|
|
||||||
import { isEmpty } from 'lodash';
|
import { isEmpty } from 'lodash';
|
||||||
import FileIcon from '../file-icon';
|
|
||||||
import IndentedTreeModal from '../indented-tree/modal';
|
import IndentedTreeModal from '../indented-tree/modal';
|
||||||
import NewDocumentLink from '../new-document-link';
|
|
||||||
import MarkdownContent from '../next-markdown-content';
|
import MarkdownContent from '../next-markdown-content';
|
||||||
|
import { RAGFlowAvatar } from '../ragflow-avatar';
|
||||||
import { useTheme } from '../theme-provider';
|
import { useTheme } from '../theme-provider';
|
||||||
import { AssistantGroupButton, UserGroupButton } from './group-button';
|
import { AssistantGroupButton, UserGroupButton } from './group-button';
|
||||||
import styles from './index.less';
|
import styles from './index.less';
|
||||||
import { ReferenceDocumentList } from './reference-document-list';
|
import { ReferenceDocumentList } from './reference-document-list';
|
||||||
|
import { UploadedMessageFiles } from './uploaded-message-files';
|
||||||
const { Text } = Typography;
|
|
||||||
|
|
||||||
interface IProps
|
interface IProps
|
||||||
extends Partial<IRemoveMessageById>,
|
extends Partial<IRemoveMessageById>,
|
||||||
@ -79,9 +71,6 @@ function MessageItem({
|
|||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
const isAssistant = item.role === MessageType.Assistant;
|
const isAssistant = item.role === MessageType.Assistant;
|
||||||
const isUser = item.role === MessageType.User;
|
const isUser = item.role === MessageType.User;
|
||||||
const { data: documentList, setDocumentIds } = useFetchDocumentInfosByIds();
|
|
||||||
const { data: documentThumbnails, setDocumentIds: setIds } =
|
|
||||||
useFetchDocumentThumbnailsByIds();
|
|
||||||
const { visible, hideModal, showModal } = useSetModalState();
|
const { visible, hideModal, showModal } = useSetModalState();
|
||||||
const [clickedDocumentId, setClickedDocumentId] = useState('');
|
const [clickedDocumentId, setClickedDocumentId] = useState('');
|
||||||
|
|
||||||
@ -91,29 +80,10 @@ function MessageItem({
|
|||||||
return Object.values(docs);
|
return Object.values(docs);
|
||||||
}, [reference?.doc_aggs]);
|
}, [reference?.doc_aggs]);
|
||||||
|
|
||||||
const handleUserDocumentClick = useCallback(
|
|
||||||
(id: string) => () => {
|
|
||||||
setClickedDocumentId(id);
|
|
||||||
showModal();
|
|
||||||
},
|
|
||||||
[showModal],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleRegenerateMessage = useCallback(() => {
|
const handleRegenerateMessage = useCallback(() => {
|
||||||
regenerateMessage?.(item);
|
regenerateMessage?.(item);
|
||||||
}, [regenerateMessage, item]);
|
}, [regenerateMessage, item]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const ids = item?.doc_ids ?? [];
|
|
||||||
if (ids.length) {
|
|
||||||
setDocumentIds(ids);
|
|
||||||
const documentIds = ids.filter((x) => !(x in documentThumbnails));
|
|
||||||
if (documentIds.length) {
|
|
||||||
setIds(documentIds);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [item.doc_ids, setDocumentIds, setIds, documentThumbnails]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (typeof setCurrentMessageId === 'function') {
|
if (typeof setCurrentMessageId === 'function') {
|
||||||
setCurrentMessageId(item.id);
|
setCurrentMessageId(item.id);
|
||||||
@ -139,15 +109,15 @@ function MessageItem({
|
|||||||
>
|
>
|
||||||
{visibleAvatar &&
|
{visibleAvatar &&
|
||||||
(item.role === MessageType.User ? (
|
(item.role === MessageType.User ? (
|
||||||
<Avatar size={40} src={avatar ?? '/logo.svg'} />
|
<RAGFlowAvatar avatar={avatar ?? '/logo.svg'} />
|
||||||
) : avatarDialog ? (
|
) : avatarDialog ? (
|
||||||
<Avatar size={40} src={avatarDialog} />
|
<RAGFlowAvatar avatar={avatarDialog} />
|
||||||
) : (
|
) : (
|
||||||
<AssistantIcon />
|
<AssistantIcon />
|
||||||
))}
|
))}
|
||||||
|
|
||||||
<Flex vertical gap={8} flex={1}>
|
<section className="flex-col gap-2 flex-1">
|
||||||
<Space>
|
<div className="space-x-1">
|
||||||
{isAssistant ? (
|
{isAssistant ? (
|
||||||
<AssistantGroupButton
|
<AssistantGroupButton
|
||||||
messageId={item.id}
|
messageId={item.id}
|
||||||
@ -171,7 +141,7 @@ function MessageItem({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* <b>{isAssistant ? '' : nickname}</b> */}
|
{/* <b>{isAssistant ? '' : nickname}</b> */}
|
||||||
</Space>
|
</div>
|
||||||
<div
|
<div
|
||||||
className={cn({
|
className={cn({
|
||||||
[theme === 'dark'
|
[theme === 'dark'
|
||||||
@ -208,48 +178,10 @@ function MessageItem({
|
|||||||
canvasId={conversationId}
|
canvasId={conversationId}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{isUser && documentList.length > 0 && (
|
{isUser && (
|
||||||
<List
|
<UploadedMessageFiles files={item.files}></UploadedMessageFiles>
|
||||||
bordered
|
|
||||||
dataSource={documentList}
|
|
||||||
renderItem={(item) => {
|
|
||||||
// TODO:
|
|
||||||
// const fileThumbnail =
|
|
||||||
// documentThumbnails[item.id] || documentThumbnails[item.id];
|
|
||||||
const fileExtension = getExtension(item.name);
|
|
||||||
return (
|
|
||||||
<List.Item>
|
|
||||||
<Flex gap={'small'} align="center">
|
|
||||||
<FileIcon id={item.id} name={item.name}></FileIcon>
|
|
||||||
|
|
||||||
{isImage(fileExtension) ? (
|
|
||||||
<NewDocumentLink
|
|
||||||
documentId={item.id}
|
|
||||||
documentName={item.name}
|
|
||||||
prefix="document"
|
|
||||||
>
|
|
||||||
{item.name}
|
|
||||||
</NewDocumentLink>
|
|
||||||
) : (
|
|
||||||
<Button
|
|
||||||
type={'text'}
|
|
||||||
onClick={handleUserDocumentClick(item.id)}
|
|
||||||
>
|
|
||||||
<Text
|
|
||||||
style={{ maxWidth: '40vw' }}
|
|
||||||
ellipsis={{ tooltip: item.name }}
|
|
||||||
>
|
|
||||||
{item.name}
|
|
||||||
</Text>
|
|
||||||
</Button>
|
|
||||||
)}
|
)}
|
||||||
</Flex>
|
</section>
|
||||||
</List.Item>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Flex>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
{visible && (
|
{visible && (
|
||||||
|
|||||||
@ -0,0 +1,36 @@
|
|||||||
|
import { getExtension } from '@/utils/document-util';
|
||||||
|
import { formatBytes } from '@/utils/file-util';
|
||||||
|
import { memo } from 'react';
|
||||||
|
import SvgIcon from '../svg-icon';
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
files?: File[];
|
||||||
|
}
|
||||||
|
export function InnerUploadedMessageFiles({ files = [] }: IProps) {
|
||||||
|
return (
|
||||||
|
<section className="flex gap-2 pt-2">
|
||||||
|
{files?.map((file, idx) => (
|
||||||
|
<div key={idx} className="flex gap-1 border rounded-md p-1.5">
|
||||||
|
{file.type.startsWith('image/') ? (
|
||||||
|
<img
|
||||||
|
src={URL.createObjectURL(file)}
|
||||||
|
alt={file.name}
|
||||||
|
className="size-10 object-cover"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<SvgIcon
|
||||||
|
name={`file-icon/${getExtension(file.name)}`}
|
||||||
|
width={24}
|
||||||
|
></SvgIcon>
|
||||||
|
)}
|
||||||
|
<div className="text-xs max-w-20">
|
||||||
|
<div className="truncate">{file.name}</div>
|
||||||
|
<p className="text-text-sub-title pt-1">{formatBytes(file.size)}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const UploadedMessageFiles = memo(InnerUploadedMessageFiles);
|
||||||
@ -74,6 +74,7 @@ export interface Message {
|
|||||||
id?: string;
|
id?: string;
|
||||||
audio_binary?: string;
|
audio_binary?: string;
|
||||||
data?: any;
|
data?: any;
|
||||||
|
files?: File[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IReferenceChunk {
|
export interface IReferenceChunk {
|
||||||
|
|||||||
@ -76,7 +76,7 @@ const AgentChatBox = () => {
|
|||||||
useCallback(
|
useCallback(
|
||||||
async (files, options) => {
|
async (files, options) => {
|
||||||
const ret = await uploadCanvasFile({ files, options });
|
const ret = await uploadCanvasFile({ files, options });
|
||||||
appendUploadResponseList(ret.data);
|
appendUploadResponseList(ret.data, files);
|
||||||
},
|
},
|
||||||
[appendUploadResponseList, uploadCanvasFile],
|
[appendUploadResponseList, uploadCanvasFile],
|
||||||
);
|
);
|
||||||
|
|||||||
@ -152,17 +152,21 @@ export function useSetUploadResponseData() {
|
|||||||
const [uploadResponseList, setUploadResponseList] = useState<
|
const [uploadResponseList, setUploadResponseList] = useState<
|
||||||
UploadResponseDataType[]
|
UploadResponseDataType[]
|
||||||
>([]);
|
>([]);
|
||||||
|
const [fileList, setFileList] = useState<File[]>([]);
|
||||||
|
|
||||||
const append = useCallback((data: UploadResponseDataType) => {
|
const append = useCallback((data: UploadResponseDataType, files: File[]) => {
|
||||||
setUploadResponseList((prev) => [...prev, data]);
|
setUploadResponseList((prev) => [...prev, data]);
|
||||||
|
setFileList((pre) => [...pre, ...files]);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const clear = useCallback(() => {
|
const clear = useCallback(() => {
|
||||||
setUploadResponseList([]);
|
setUploadResponseList([]);
|
||||||
|
setFileList([]);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
uploadResponseList,
|
uploadResponseList,
|
||||||
|
fileList,
|
||||||
setUploadResponseList,
|
setUploadResponseList,
|
||||||
appendUploadResponseList: append,
|
appendUploadResponseList: append,
|
||||||
clearUploadResponseList: clear,
|
clearUploadResponseList: clear,
|
||||||
@ -198,6 +202,7 @@ export const useSendAgentMessage = (
|
|||||||
appendUploadResponseList,
|
appendUploadResponseList,
|
||||||
clearUploadResponseList,
|
clearUploadResponseList,
|
||||||
uploadResponseList,
|
uploadResponseList,
|
||||||
|
fileList,
|
||||||
} = useSetUploadResponseData();
|
} = useSetUploadResponseData();
|
||||||
|
|
||||||
const sendMessage = useCallback(
|
const sendMessage = useCallback(
|
||||||
@ -259,18 +264,19 @@ export const useSendAgentMessage = (
|
|||||||
const handlePressEnter = useCallback(() => {
|
const handlePressEnter = useCallback(() => {
|
||||||
if (trim(value) === '') return;
|
if (trim(value) === '') return;
|
||||||
const id = uuid();
|
const id = uuid();
|
||||||
|
const msgBody = {
|
||||||
|
id,
|
||||||
|
content: value.trim(),
|
||||||
|
role: MessageType.User,
|
||||||
|
};
|
||||||
if (done) {
|
if (done) {
|
||||||
setValue('');
|
setValue('');
|
||||||
sendMessage({
|
sendMessage({
|
||||||
message: { id, content: value.trim(), role: MessageType.User },
|
message: msgBody,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
addNewestOneQuestion({
|
addNewestOneQuestion({ ...msgBody, files: fileList });
|
||||||
content: value,
|
}, [value, done, addNewestOneQuestion, fileList, setValue, sendMessage]);
|
||||||
id,
|
|
||||||
role: MessageType.User,
|
|
||||||
});
|
|
||||||
}, [value, done, addNewestOneQuestion, setValue, sendMessage]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const { content, id } = findMessageFromList(answerList);
|
const { content, id } = findMessageFromList(answerList);
|
||||||
|
|||||||
@ -36,8 +36,6 @@ const ChatContainer = () => {
|
|||||||
addEventList,
|
addEventList,
|
||||||
setCurrentMessageId,
|
setCurrentMessageId,
|
||||||
currentEventListWithoutMessageById,
|
currentEventListWithoutMessageById,
|
||||||
clearEventList,
|
|
||||||
currentMessageId,
|
|
||||||
} = useCacheChatLog();
|
} = useCacheChatLog();
|
||||||
const {
|
const {
|
||||||
handlePressEnter,
|
handlePressEnter,
|
||||||
@ -76,7 +74,7 @@ const ChatContainer = () => {
|
|||||||
useCallback(
|
useCallback(
|
||||||
async (files, options) => {
|
async (files, options) => {
|
||||||
const ret = await uploadCanvasFile({ files, options });
|
const ret = await uploadCanvasFile({ files, options });
|
||||||
appendUploadResponseList(ret.data);
|
appendUploadResponseList(ret.data, files);
|
||||||
},
|
},
|
||||||
[appendUploadResponseList, uploadCanvasFile],
|
[appendUploadResponseList, uploadCanvasFile],
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user