Feat: Modify the data structure of the chunk in the conversation #3909 (#3955)

### What problem does this PR solve?

Feat: Modify the data structure of the chunk in the conversation #3909

### Type of change


- [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
balibabu
2024-12-10 16:36:16 +08:00
committed by GitHub
parent 03f00c9e6f
commit fc4e644e5f
27 changed files with 304 additions and 202 deletions

View File

@ -29,18 +29,20 @@ import {
useSelectDerivedConversationList,
} from './hooks';
import EmbedModal from '@/components/api-service/embed-modal';
import { useShowEmbedModal } from '@/components/api-service/hooks';
import SvgIcon from '@/components/svg-icon';
import { useTheme } from '@/components/theme-provider';
import { SharedFrom } from '@/constants/chat';
import {
useClickConversationCard,
useClickDialogCard,
useFetchNextDialogList,
useGetChatSearchParams,
} from '@/hooks/chat-hooks';
import { useSetModalState, useTranslate } from '@/hooks/common-hooks';
import { useTranslate } from '@/hooks/common-hooks';
import { useSetSelectedRecord } from '@/hooks/logic-hooks';
import { IDialog } from '@/interfaces/database/chat';
import ChatIdModal from './chat-id-modal';
import styles from './index.less';
const { Text } = Typography;
@ -82,13 +84,10 @@ const Chat = () => {
showDialogEditModal,
} = useEditDialog();
const { t } = useTranslate('chat');
const {
visible: overviewVisible,
hideModal: hideOverviewModal,
showModal: showOverviewModal,
} = useSetModalState();
const { currentRecord, setRecord } = useSetSelectedRecord<IDialog>();
const [controller, setController] = useState(new AbortController());
const { showEmbedModal, hideEmbedModal, embedVisible, beta } =
useShowEmbedModal();
const handleAppCardEnter = (id: string) => () => {
handleItemEnter(id);
@ -120,7 +119,7 @@ const Chat = () => {
info?.domEvent?.preventDefault();
info?.domEvent?.stopPropagation();
setRecord(dialog);
showOverviewModal();
showEmbedModal();
};
const handleRemoveConversation =
@ -193,7 +192,7 @@ const Chat = () => {
label: (
<Space>
<KeyOutlined />
{t('overview')}
{t('publish', { keyPrefix: 'flow' })}
</Space>
),
},
@ -374,14 +373,16 @@ const Chat = () => {
initialName={initialConversationName}
loading={conversationRenameLoading}
></RenameModal>
{overviewVisible && (
<ChatIdModal
visible={overviewVisible}
hideModal={hideOverviewModal}
id={currentRecord.id}
name={currentRecord.name}
idKey="dialogId"
></ChatIdModal>
{embedVisible && (
<EmbedModal
visible={embedVisible}
hideModal={hideEmbedModal}
token={currentRecord.id}
form={SharedFrom.Chat}
beta={beta}
isAgent={false}
></EmbedModal>
)}
</Flex>
);

View File

@ -1,7 +1,6 @@
import Image from '@/components/image';
import SvgIcon from '@/components/svg-icon';
import { IReference } from '@/interfaces/database/chat';
import { IChunk } from '@/interfaces/database/knowledge';
import { IReference, IReferenceChunk } from '@/interfaces/database/chat';
import { getExtension } from '@/utils/document-util';
import { InfoCircleOutlined } from '@ant-design/icons';
import { Button, Flex, Popover, Space } from 'antd';
@ -11,6 +10,7 @@ import Markdown from 'react-markdown';
import reactStringReplace from 'react-string-replace';
import SyntaxHighlighter from 'react-syntax-highlighter';
import rehypeKatex from 'rehype-katex';
import rehypeRaw from 'rehype-raw';
import remarkGfm from 'remark-gfm';
import remarkMath from 'remark-math';
import { visitParents } from 'unist-util-visit-parents';
@ -36,7 +36,7 @@ const MarkdownContent = ({
content: string;
loading: boolean;
reference: IReference;
clickDocumentButton?: (documentId: string, chunk: IChunk) => void;
clickDocumentButton?: (documentId: string, chunk: IReferenceChunk) => void;
}) => {
const { t } = useTranslation();
const { setDocumentIds, data: fileThumbnails } =
@ -54,7 +54,7 @@ const MarkdownContent = ({
}, [reference, setDocumentIds]);
const handleDocumentButtonClick = useCallback(
(documentId: string, chunk: IChunk, isPdf: boolean) => () => {
(documentId: string, chunk: IReferenceChunk, isPdf: boolean) => () => {
if (!isPdf) {
return;
}
@ -85,15 +85,15 @@ const MarkdownContent = ({
const chunks = reference?.chunks ?? [];
const chunkItem = chunks[chunkIndex];
const document = reference?.doc_aggs?.find(
(x) => x?.doc_id === chunkItem?.doc_id,
(x) => x?.doc_id === chunkItem?.document_id,
);
const documentId = document?.doc_id;
const fileThumbnail = documentId ? fileThumbnails[documentId] : '';
const fileExtension = documentId ? getExtension(document?.doc_name) : '';
const imageId = chunkItem?.img_id;
const imageId = chunkItem?.image_id;
return (
<Flex
key={chunkItem?.chunk_id}
key={chunkItem?.id}
gap={10}
className={styles.referencePopoverWrapper}
>
@ -116,7 +116,7 @@ const MarkdownContent = ({
<Space direction={'vertical'}>
<div
dangerouslySetInnerHTML={{
__html: DOMPurify.sanitize(chunkItem?.content_with_weight),
__html: DOMPurify.sanitize(chunkItem?.content ?? ''),
}}
className={styles.chunkContentText}
></div>
@ -176,7 +176,7 @@ const MarkdownContent = ({
return (
<Markdown
rehypePlugins={[rehypeWrapReference, rehypeKatex]}
rehypePlugins={[rehypeWrapReference, rehypeKatex, rehypeRaw]}
remarkPlugins={[remarkGfm, remarkMath]}
components={
{

View File

@ -1,21 +1,23 @@
import MessageInput from '@/components/message-input';
import MessageItem from '@/components/message-item';
import { useClickDrawer } from '@/components/pdf-drawer/hooks';
import { MessageType, SharedFrom } from '@/constants/chat';
import { useFetchNextSharedConversation } from '@/hooks/chat-hooks';
import { useSendButtonDisabled } from '@/pages/chat/hooks';
import { Flex, Spin } from 'antd';
import { forwardRef } from 'react';
import {
useCreateSharedConversationOnMount,
useGetSharedChatSearchParams,
useSendSharedMessage,
} from '../shared-hooks';
import { buildMessageItemReference } from '../utils';
import PdfDrawer from '@/components/pdf-drawer';
import styles from './index.less';
const ChatContainer = () => {
const { conversationId } = useCreateSharedConversationOnMount();
const { data } = useFetchNextSharedConversation(conversationId);
const { from, sharedId: conversationId } = useGetSharedChatSearchParams();
const { visible, hideModal, documentId, selectedChunk, clickDocumentButton } =
useClickDrawer();
const {
handlePressEnter,
@ -25,9 +27,13 @@ const ChatContainer = () => {
loading,
ref,
derivedMessages,
} = useSendSharedMessage(conversationId);
hasError,
} = useSendSharedMessage();
const sendDisabled = useSendButtonDisabled(value);
const { from } = useGetSharedChatSearchParams();
if (!conversationId) {
return <div>empty</div>;
}
return (
<>
@ -44,7 +50,7 @@ const ChatContainer = () => {
reference={buildMessageItemReference(
{
message: derivedMessages,
reference: data?.data?.reference,
reference: [],
},
message,
)}
@ -54,6 +60,7 @@ const ChatContainer = () => {
derivedMessages?.length - 1 === i
}
index={i}
clickDocumentButton={clickDocumentButton}
></MessageItem>
);
})}
@ -65,7 +72,7 @@ const ChatContainer = () => {
<MessageInput
isShared
value={value}
disabled={false}
disabled={hasError}
sendDisabled={sendDisabled}
conversationId={conversationId}
onInputChange={handleInputChange}
@ -75,6 +82,14 @@ const ChatContainer = () => {
showUploadIcon={from === SharedFrom.Chat}
></MessageInput>
</Flex>
{visible && (
<PdfDrawer
visible={visible}
hideModal={hideModal}
documentId={documentId}
chunk={selectedChunk}
></PdfDrawer>
)}
</>
);
};

View File

@ -1,83 +1,41 @@
import { MessageType, SharedFrom } from '@/constants/chat';
import {
useCreateNextSharedConversation,
useFetchNextSharedConversation,
} from '@/hooks/chat-hooks';
import { useCreateNextSharedConversation } from '@/hooks/chat-hooks';
import {
useSelectDerivedMessages,
useSendMessageWithSse,
} from '@/hooks/logic-hooks';
import { Message } from '@/interfaces/database/chat';
import api from '@/utils/api';
import { message } from 'antd';
import { get } from 'lodash';
import trim from 'lodash/trim';
import { useCallback, useEffect, useState } from 'react';
import { useSearchParams } from 'umi';
import { v4 as uuid } from 'uuid';
import { useHandleMessageInputChange } from './hooks';
export const useCreateSharedConversationOnMount = () => {
const [currentQueryParameters] = useSearchParams();
const [conversationId, setConversationId] = useState('');
const { createSharedConversation: createConversation } =
useCreateNextSharedConversation();
const sharedId = currentQueryParameters.get('shared_id');
const userId = currentQueryParameters.get('user_id');
const setConversation = useCallback(async () => {
if (sharedId) {
const data = await createConversation(userId ?? undefined);
const id = data.data?.id;
if (id) {
setConversationId(id);
}
}
}, [createConversation, sharedId, userId]);
useEffect(() => {
setConversation();
}, [setConversation]);
return { conversationId };
};
export const useSelectNextSharedMessages = (conversationId: string) => {
const { data, loading } = useFetchNextSharedConversation(conversationId);
const {
derivedMessages,
ref,
setDerivedMessages,
addNewestAnswer,
addNewestQuestion,
removeLatestMessage,
} = useSelectDerivedMessages();
useEffect(() => {
setDerivedMessages(data?.data?.message);
}, [setDerivedMessages, data]);
return {
derivedMessages,
addNewestAnswer,
addNewestQuestion,
removeLatestMessage,
loading,
ref,
setDerivedMessages,
};
};
const isCompletionError = (res: any) =>
res && (res?.response.status !== 200 || res?.data?.code !== 0);
export const useSendButtonDisabled = (value: string) => {
return trim(value) === '';
};
export const useSendSharedMessage = (conversationId: string) => {
export const useGetSharedChatSearchParams = () => {
const [searchParams] = useSearchParams();
return {
from: searchParams.get('from') as SharedFrom,
sharedId: searchParams.get('shared_id'),
};
};
export const useSendSharedMessage = () => {
const { from, sharedId: conversationId } = useGetSharedChatSearchParams();
const { createSharedConversation: setConversation } =
useCreateNextSharedConversation();
const { handleInputChange, value, setValue } = useHandleMessageInputChange();
const { send, answer, done } = useSendMessageWithSse(
api.completeExternalConversation,
`/api/v1/${from === SharedFrom.Agent ? 'agentbots' : 'chatbots'}/${conversationId}/completions`,
);
const {
derivedMessages,
@ -85,24 +43,25 @@ export const useSendSharedMessage = (conversationId: string) => {
removeLatestMessage,
addNewestAnswer,
addNewestQuestion,
loading,
} = useSelectNextSharedMessages(conversationId);
} = useSelectDerivedMessages();
const [hasError, setHasError] = useState(false);
const sendMessage = useCallback(
async (message: Message, id?: string) => {
const res = await send({
conversation_id: id ?? conversationId,
quote: false,
messages: [...(derivedMessages ?? []), message],
quote: true,
question: message.content,
session_id: get(derivedMessages, '0.session_id'),
});
if (res && (res?.response.status !== 200 || res?.data?.code !== 0)) {
if (isCompletionError(res)) {
// cancel loading
setValue(message.content);
removeLatestMessage();
}
},
[conversationId, derivedMessages, removeLatestMessage, setValue, send],
[send, conversationId, derivedMessages, setValue, removeLatestMessage],
);
const handleSendMessage = useCallback(
@ -120,6 +79,18 @@ export const useSendSharedMessage = (conversationId: string) => {
[conversationId, setConversation, sendMessage],
);
const fetchSessionId = useCallback(async () => {
const ret = await send({ question: '' });
if (isCompletionError(ret)) {
message.error(ret?.data.message);
setHasError(true);
}
}, [send]);
useEffect(() => {
fetchSessionId();
}, [fetchSessionId, send]);
useEffect(() => {
if (answer.answer) {
addNewestAnswer(answer);
@ -154,16 +125,8 @@ export const useSendSharedMessage = (conversationId: string) => {
value,
sendLoading: !done,
ref,
loading,
loading: false,
derivedMessages,
};
};
export const useGetSharedChatSearchParams = () => {
const [searchParams] = useSearchParams();
return {
from: searchParams.get('from') as SharedFrom,
sharedId: searchParams.get('shared_id'),
hasError,
};
};

View File

@ -1,13 +1,15 @@
import ChatOverviewModal from '@/components/api-service/chat-overview-modal';
import { useSetModalState, useTranslate } from '@/hooks/common-hooks';
import EmbedModal from '@/components/api-service/embed-modal';
import { useShowEmbedModal } from '@/components/api-service/hooks';
import { SharedFrom } from '@/constants/chat';
import { useTranslate } from '@/hooks/common-hooks';
import { useFetchFlow } from '@/hooks/flow-hooks';
import { ArrowLeftOutlined } from '@ant-design/icons';
import { Button, Flex, Space } from 'antd';
import { useCallback } from 'react';
import { Link, useParams } from 'umi';
import FlowIdModal from '../flow-id-modal';
import {
useGetBeginNodeDataQuery,
useGetBeginNodeDataQueryIsEmpty,
useSaveGraph,
useSaveGraphBeforeOpeningDebugDrawer,
useWatchAgentChange,
@ -25,15 +27,16 @@ const FlowHeader = ({ showChatDrawer, chatDrawerVisible }: IProps) => {
const { handleRun } = useSaveGraphBeforeOpeningDebugDrawer(showChatDrawer);
const { data } = useFetchFlow();
const { t } = useTranslate('flow');
const {
visible: overviewVisible,
hideModal: hideOverviewModal,
// showModal: showOverviewModal,
} = useSetModalState();
const { visible, hideModal, showModal } = useSetModalState();
const { id } = useParams();
const time = useWatchAgentChange(chatDrawerVisible);
const getBeginNodeDataQuery = useGetBeginNodeDataQuery();
const { showEmbedModal, hideEmbedModal, embedVisible, beta } =
useShowEmbedModal();
const isBeginNodeDataQueryEmpty = useGetBeginNodeDataQueryIsEmpty();
const handleShowEmbedModal = useCallback(() => {
showEmbedModal();
}, [showEmbedModal]);
const handleRunAgent = useCallback(() => {
const query: BeginQuery[] = getBeginNodeDataQuery();
@ -70,23 +73,25 @@ const FlowHeader = ({ showChatDrawer, chatDrawerVisible }: IProps) => {
<Button type="primary" onClick={() => saveGraph()}>
<b>{t('save')}</b>
</Button>
{/* <Button type="primary" onClick={showOverviewModal} disabled>
<Button
type="primary"
onClick={handleShowEmbedModal}
disabled={!isBeginNodeDataQueryEmpty}
>
<b>{t('publish')}</b>
</Button> */}
<Button type="primary" onClick={showModal}>
<b>Agent ID</b>
</Button>
</Space>
</Flex>
{overviewVisible && (
<ChatOverviewModal
visible={overviewVisible}
hideModal={hideOverviewModal}
id={id!}
idKey="canvasId"
></ChatOverviewModal>
{embedVisible && (
<EmbedModal
visible={embedVisible}
hideModal={hideEmbedModal}
token={id!}
form={SharedFrom.Agent}
beta={beta}
isAgent
></EmbedModal>
)}
{visible && <FlowIdModal hideModal={hideModal}></FlowIdModal>}
</>
);
};

View File

@ -474,6 +474,20 @@ export const useGetBeginNodeDataQuery = () => {
return getBeginNodeDataQuery;
};
export const useGetBeginNodeDataQueryIsEmpty = () => {
const [isBeginNodeDataQueryEmpty, setIsBeginNodeDataQueryEmpty] =
useState(false);
const getBeginNodeDataQuery = useGetBeginNodeDataQuery();
const nodes = useGraphStore((state) => state.nodes);
useEffect(() => {
const query: BeginQuery[] = getBeginNodeDataQuery();
setIsBeginNodeDataQueryEmpty(query.length === 0);
}, [getBeginNodeDataQuery, nodes]);
return isBeginNodeDataQueryEmpty;
};
export const useSaveGraphBeforeOpeningDebugDrawer = (show: () => void) => {
const { saveGraph, loading } = useSaveGraph();
const { resetFlow } = useResetFlow();