feat: add DocumentPreviewer for chunk of chat reference and remove duplicate \n from record.progress_msg (#97)
* feat: Remove duplicate \n from record.progress_msg * feat: add DocumentPreviewer for chunk of chat reference
This commit is contained in:
@@ -1,8 +1,6 @@
|
||||
.documentContainer {
|
||||
width: 100%;
|
||||
height: calc(100vh - 284px);
|
||||
// overflow-y: auto;
|
||||
// overflow-x: hidden;
|
||||
position: relative;
|
||||
:global(.PdfHighlighter) {
|
||||
overflow-x: hidden;
|
||||
|
||||
@@ -16,7 +16,6 @@ import styles from './index.less';
|
||||
interface IProps {
|
||||
selectedChunkId: string;
|
||||
}
|
||||
|
||||
const HighlightPopup = ({
|
||||
comment,
|
||||
}: {
|
||||
@@ -28,6 +27,7 @@ const HighlightPopup = ({
|
||||
</div>
|
||||
) : null;
|
||||
|
||||
// TODO: merge with DocumentPreviewer
|
||||
const Preview = ({ selectedChunkId }: IProps) => {
|
||||
const url = useGetDocumentUrl();
|
||||
const state = useGetChunkHighlights(selectedChunkId);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { IChunk, IKnowledgeFile } from '@/interfaces/database/knowledge';
|
||||
import { buildChunkHighlights } from '@/utils/documentUtils';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { IHighlight } from 'react-pdf-highlighter';
|
||||
import { useSelector } from 'umi';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
export const useSelectDocumentInfo = () => {
|
||||
const documentInfo: IKnowledgeFile = useSelector(
|
||||
@@ -41,35 +41,7 @@ export const useGetChunkHighlights = (
|
||||
const selectedChunk: IChunk = useGetSelectedChunk(selectedChunkId);
|
||||
|
||||
const highlights: IHighlight[] = useMemo(() => {
|
||||
return Array.isArray(selectedChunk?.positions) &&
|
||||
selectedChunk.positions.every((x) => Array.isArray(x))
|
||||
? selectedChunk?.positions?.map((x) => {
|
||||
const actualPositions = x.map((y, index) =>
|
||||
index !== 0 ? y / 0.7 : y,
|
||||
);
|
||||
const boundingRect = {
|
||||
width: 849,
|
||||
height: 1200,
|
||||
x1: actualPositions[1],
|
||||
x2: actualPositions[2],
|
||||
y1: actualPositions[3],
|
||||
y2: actualPositions[4],
|
||||
};
|
||||
return {
|
||||
id: uuid(),
|
||||
comment: {
|
||||
text: '',
|
||||
emoji: '',
|
||||
},
|
||||
content: { text: selectedChunk.content_with_weight },
|
||||
position: {
|
||||
boundingRect: boundingRect,
|
||||
rects: [boundingRect],
|
||||
pageNumber: x[0],
|
||||
},
|
||||
};
|
||||
})
|
||||
: [];
|
||||
return buildChunkHighlights(selectedChunk);
|
||||
}, [selectedChunk]);
|
||||
|
||||
return highlights;
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
.popoverContentText {
|
||||
white-space: pre-line;
|
||||
max-height: 50vh;
|
||||
overflow: auto;
|
||||
.popoverContentErrorLabel {
|
||||
color: red;
|
||||
}
|
||||
|
||||
@@ -21,6 +21,25 @@ interface IProps {
|
||||
}
|
||||
|
||||
const PopoverContent = ({ record }: IProps) => {
|
||||
const replaceText = (text: string) => {
|
||||
// Remove duplicate \n
|
||||
const nextText = text.replace(/(\n)\1+/g, '$1');
|
||||
|
||||
const replacedText = reactStringReplace(
|
||||
nextText,
|
||||
/(\[ERROR\].+\s)/g,
|
||||
(match, i) => {
|
||||
return (
|
||||
<span key={i} className={styles.popoverContentErrorLabel}>
|
||||
{match}
|
||||
</span>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
return replacedText;
|
||||
};
|
||||
|
||||
const items: DescriptionsProps['items'] = [
|
||||
{
|
||||
key: 'process_begin_at',
|
||||
@@ -35,17 +54,7 @@ const PopoverContent = ({ record }: IProps) => {
|
||||
{
|
||||
key: 'progress_msg',
|
||||
label: 'Progress Msg',
|
||||
children: reactStringReplace(
|
||||
record.progress_msg.trim(),
|
||||
/(\[ERROR\].+\s)/g,
|
||||
(match, i) => {
|
||||
return (
|
||||
<span key={i} className={styles.popoverContentErrorLabel}>
|
||||
{match}
|
||||
</span>
|
||||
);
|
||||
},
|
||||
),
|
||||
children: replaceText(record.progress_msg.trim()),
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -65,7 +65,11 @@ const AssistantSetting = ({ show }: ISegmentedContentProps) => {
|
||||
>
|
||||
<Input placeholder="" />
|
||||
</Form.Item>
|
||||
<Form.Item name={['prompt_config', 'prologue']} label="Set an opener">
|
||||
<Form.Item
|
||||
name={['prompt_config', 'prologue']}
|
||||
label="Set an opener"
|
||||
initialValue={"Hi! I'm your assistant, what can I do for you?"}
|
||||
>
|
||||
<Input.TextArea autoSize={{ minRows: 5 }} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
|
||||
@@ -3,11 +3,21 @@ import { MessageType } from '@/constants/chat';
|
||||
import { useOneNamespaceEffectsLoading } from '@/hooks/storeHooks';
|
||||
import { useSelectUserInfo } from '@/hooks/userSettingHook';
|
||||
import { IReference, Message } from '@/interfaces/database/chat';
|
||||
import { Avatar, Button, Flex, Input, List, Popover, Space } from 'antd';
|
||||
import {
|
||||
Avatar,
|
||||
Button,
|
||||
Drawer,
|
||||
Flex,
|
||||
Input,
|
||||
List,
|
||||
Popover,
|
||||
Space,
|
||||
} from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import { ChangeEventHandler, useCallback, useMemo, useState } from 'react';
|
||||
import reactStringReplace from 'react-string-replace';
|
||||
import {
|
||||
useClickDrawer,
|
||||
useFetchConversationOnMount,
|
||||
useGetFileIcon,
|
||||
useSendMessage,
|
||||
@@ -15,7 +25,9 @@ import {
|
||||
|
||||
import Image from '@/components/image';
|
||||
import NewDocumentLink from '@/components/new-document-link';
|
||||
import DocumentPreviewer from '@/components/pdf-previewer';
|
||||
import { useSelectFileThumbnails } from '@/hooks/knowledgeHook';
|
||||
import { IChunk } from '@/interfaces/database/knowledge';
|
||||
import { InfoCircleOutlined } from '@ant-design/icons';
|
||||
import Markdown from 'react-markdown';
|
||||
import { visitParents } from 'unist-util-visit-parents';
|
||||
@@ -41,15 +53,24 @@ const rehypeWrapReference = () => {
|
||||
const MessageItem = ({
|
||||
item,
|
||||
reference,
|
||||
clickDocumentButton,
|
||||
}: {
|
||||
item: Message;
|
||||
reference: IReference;
|
||||
clickDocumentButton: (documentId: string, chunk: IChunk) => void;
|
||||
}) => {
|
||||
const userInfo = useSelectUserInfo();
|
||||
const fileThumbnails = useSelectFileThumbnails();
|
||||
|
||||
const isAssistant = item.role === MessageType.Assistant;
|
||||
|
||||
const handleDocumentButtonClick = useCallback(
|
||||
(documentId: string, chunk: IChunk) => () => {
|
||||
clickDocumentButton(documentId, chunk);
|
||||
},
|
||||
[clickDocumentButton],
|
||||
);
|
||||
|
||||
const getPopoverContent = useCallback(
|
||||
(chunkIndex: number) => {
|
||||
const chunks = reference?.chunks ?? [];
|
||||
@@ -83,16 +104,19 @@ const MessageItem = ({
|
||||
{documentId && (
|
||||
<Flex gap={'middle'}>
|
||||
<img src={fileThumbnails[documentId]} alt="" />
|
||||
<NewDocumentLink documentId={documentId}>
|
||||
<Button
|
||||
type="link"
|
||||
onClick={handleDocumentButtonClick(documentId, chunkItem)}
|
||||
>
|
||||
{document?.doc_name}
|
||||
</NewDocumentLink>
|
||||
</Button>
|
||||
</Flex>
|
||||
)}
|
||||
</Space>
|
||||
</Flex>
|
||||
);
|
||||
},
|
||||
[reference, fileThumbnails],
|
||||
[reference, fileThumbnails, handleDocumentButtonClick],
|
||||
);
|
||||
|
||||
const renderReference = useCallback(
|
||||
@@ -191,6 +215,8 @@ const ChatContainer = () => {
|
||||
addNewestConversation,
|
||||
} = useFetchConversationOnMount();
|
||||
const { sendMessage } = useSendMessage();
|
||||
const { visible, hideModal, documentId, selectedChunk, clickDocumentButton } =
|
||||
useClickDrawer();
|
||||
|
||||
const loading = useOneNamespaceEffectsLoading('chatModel', [
|
||||
'completeConversation',
|
||||
@@ -210,41 +236,56 @@ const ChatContainer = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<Flex flex={1} className={styles.chatContainer} vertical>
|
||||
<Flex flex={1} vertical className={styles.messageContainer}>
|
||||
<div>
|
||||
{conversation?.message?.map((message) => {
|
||||
const assistantMessages = conversation?.message
|
||||
?.filter((x) => x.role === MessageType.Assistant)
|
||||
.slice(1);
|
||||
const referenceIndex = assistantMessages.findIndex(
|
||||
(x) => x.id === message.id,
|
||||
);
|
||||
const reference = conversation.reference[referenceIndex];
|
||||
return (
|
||||
<MessageItem
|
||||
key={message.id}
|
||||
item={message}
|
||||
reference={reference}
|
||||
></MessageItem>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div ref={ref} />
|
||||
<>
|
||||
<Flex flex={1} className={styles.chatContainer} vertical>
|
||||
<Flex flex={1} vertical className={styles.messageContainer}>
|
||||
<div>
|
||||
{conversation?.message?.map((message) => {
|
||||
const assistantMessages = conversation?.message
|
||||
?.filter((x) => x.role === MessageType.Assistant)
|
||||
.slice(1);
|
||||
const referenceIndex = assistantMessages.findIndex(
|
||||
(x) => x.id === message.id,
|
||||
);
|
||||
const reference = conversation.reference[referenceIndex];
|
||||
return (
|
||||
<MessageItem
|
||||
key={message.id}
|
||||
item={message}
|
||||
reference={reference}
|
||||
clickDocumentButton={clickDocumentButton}
|
||||
></MessageItem>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div ref={ref} />
|
||||
</Flex>
|
||||
<Input
|
||||
size="large"
|
||||
placeholder="Message Resume Assistant..."
|
||||
value={value}
|
||||
suffix={
|
||||
<Button type="primary" onClick={handlePressEnter} loading={loading}>
|
||||
Send
|
||||
</Button>
|
||||
}
|
||||
onPressEnter={handlePressEnter}
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
</Flex>
|
||||
<Input
|
||||
size="large"
|
||||
placeholder="Message Resume Assistant..."
|
||||
value={value}
|
||||
suffix={
|
||||
<Button type="primary" onClick={handlePressEnter} loading={loading}>
|
||||
Send
|
||||
</Button>
|
||||
}
|
||||
onPressEnter={handlePressEnter}
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
</Flex>
|
||||
<Drawer
|
||||
title="Document Previewer"
|
||||
onClose={hideModal}
|
||||
open={visible}
|
||||
width={'50vw'}
|
||||
>
|
||||
<DocumentPreviewer
|
||||
documentId={documentId}
|
||||
chunk={selectedChunk}
|
||||
visible={visible}
|
||||
></DocumentPreviewer>
|
||||
</Drawer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import { fileIconMap } from '@/constants/common';
|
||||
import { useSetModalState } from '@/hooks/commonHooks';
|
||||
import { useOneNamespaceEffectsLoading } from '@/hooks/storeHooks';
|
||||
import { IConversation, IDialog } from '@/interfaces/database/chat';
|
||||
import { IChunk } from '@/interfaces/database/knowledge';
|
||||
import { getFileExtension } from '@/utils';
|
||||
import omit from 'lodash/omit';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
@@ -662,4 +663,28 @@ export const useRenameConversation = () => {
|
||||
};
|
||||
};
|
||||
|
||||
export const useClickDrawer = () => {
|
||||
const { visible, showModal, hideModal } = useSetModalState();
|
||||
const [selectedChunk, setSelectedChunk] = useState<IChunk>({} as IChunk);
|
||||
const [documentId, setDocumentId] = useState<string>('');
|
||||
|
||||
const clickDocumentButton = useCallback(
|
||||
(documentId: string, chunk: IChunk) => {
|
||||
showModal();
|
||||
setSelectedChunk(chunk);
|
||||
setDocumentId(documentId);
|
||||
},
|
||||
[showModal],
|
||||
);
|
||||
|
||||
return {
|
||||
clickDocumentButton,
|
||||
visible,
|
||||
showModal,
|
||||
hideModal,
|
||||
selectedChunk,
|
||||
documentId,
|
||||
};
|
||||
};
|
||||
|
||||
//#endregion
|
||||
|
||||
@@ -50,13 +50,7 @@ const Knowledge = () => {
|
||||
</ModalManager>
|
||||
</Space>
|
||||
</div>
|
||||
<Flex
|
||||
gap="large"
|
||||
wrap="wrap"
|
||||
flex={1}
|
||||
// justify="center"
|
||||
className={styles.knowledgeCardContainer}
|
||||
>
|
||||
<Flex gap={'large'} wrap="wrap" className={styles.knowledgeCardContainer}>
|
||||
{list.length > 0 ? (
|
||||
list.map((item: any) => {
|
||||
return <KnowledgeCard item={item} key={item.name}></KnowledgeCard>;
|
||||
|
||||
Reference in New Issue
Block a user