feat: add message popover content

This commit is contained in:
billchen 2024-02-27 16:20:00 +08:00
parent d1417102b6
commit 71ac65960a
8 changed files with 131 additions and 43 deletions

View file

@ -0,0 +1,20 @@
import { api_host } from '@/utils/api';
import React from 'react';
interface IProps extends React.PropsWithChildren {
documentId: string;
}
const NewDocumentLink = ({ children, documentId }: IProps) => {
return (
<a
target="_blank"
href={`${api_host}/document/get/${documentId}`}
rel="noreferrer"
>
{children}
</a>
);
};
export default NewDocumentLink;

View file

@ -71,7 +71,7 @@ export interface IReference {
total: number;
}
interface Docagg {
export interface Docagg {
count: number;
doc_id: string;
doc_name: string;

View file

@ -1,6 +1,5 @@
import Image from '@/components/image';
import { IChunk } from '@/interfaces/database/knowledge';
import { api_host } from '@/utils/api';
import { Card, Checkbox, CheckboxProps, Flex, Popover, Switch } from 'antd';
import { useState } from 'react';
import styles from './index.less';
@ -48,11 +47,6 @@ const ChunkCard = ({
<Image id={item.img_id} className={styles.imagePreview}></Image>
}
>
<img
src={`${api_host}/document/image/${item.img_id}`}
alt=""
className={styles.image}
/>
<Image id={item.img_id} className={styles.image}></Image>
</Popover>
)}

View file

@ -1,6 +1,6 @@
import { ReactComponent as NavigationPointerIcon } from '@/assets/svg/navigation-pointer.svg';
import NewDocumentLink from '@/components/new-document-link';
import { ITestingDocument } from '@/interfaces/database/knowledge';
import { api_host } from '@/utils/api';
import { Table, TableProps } from 'antd';
import { useDispatch, useSelector } from 'umi';
@ -34,13 +34,9 @@ const SelectFiles = ({ handleTesting }: IProps) => {
key: 'view',
width: 50,
render: (_, { doc_id }) => (
<a
href={`${api_host}/document/get/${doc_id}`}
target="_blank"
rel="noreferrer"
>
<NewDocumentLink documentId={doc_id}>
<NavigationPointerIcon />
</a>
</NewDocumentLink>
),
},
];

View file

@ -2,7 +2,7 @@ import { useKnowledgeBaseId } from '@/hooks/knowledgeHook';
import { useSecondPathName, useThirdPathName } from '@/hooks/routeHook';
import { Breadcrumb } from 'antd';
import { ItemType } from 'antd/es/breadcrumb/Breadcrumb';
import { useEffect, useMemo } from 'react';
import { useCallback, useEffect, useMemo } from 'react';
import { Link, Outlet, useDispatch, useLocation, useNavigate } from 'umi';
import Siderbar from './components/knowledge-sidebar';
import {
@ -25,9 +25,9 @@ const KnowledgeAdding = () => {
const datasetActiveKey: KnowledgeDatasetRouteKey =
useThirdPathName() as KnowledgeDatasetRouteKey;
const gotoList = () => {
const gotoList = useCallback(() => {
navigate('/knowledge');
};
}, [navigate]);
const breadcrumbItems: ItemType[] = useMemo(() => {
const items: ItemType[] = [
@ -54,7 +54,7 @@ const KnowledgeAdding = () => {
}
return items;
}, [activeKey, datasetActiveKey]);
}, [activeKey, datasetActiveKey, gotoList, knowledgeBaseId]);
useEffect(() => {
const search: string = location.search.slice(1);
@ -71,7 +71,7 @@ const KnowledgeAdding = () => {
...map,
},
});
}, [location]);
}, [location, dispatch]);
return (
<>

View file

@ -39,3 +39,11 @@
.messageItemRight {
text-align: right;
}
.referencePopoverWrapper {
width: 50vw;
}
.referenceChunkImage {
width: 10vw;
}

View file

@ -3,18 +3,33 @@ 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, Popover } from 'antd';
import {
Avatar,
Button,
Flex,
Input,
List,
Popover,
Space,
Typography,
} from 'antd';
import classNames from 'classnames';
import { ChangeEventHandler, useCallback, useMemo, useState } from 'react';
import reactStringReplace from 'react-string-replace';
import { useFetchConversation, useSendMessage } from '../hooks';
import { IClientConversation } from '../interface';
import Image from '@/components/image';
import NewDocumentLink from '@/components/new-document-link';
import { InfoCircleOutlined } from '@ant-design/icons';
import Markdown from 'react-markdown';
import { visitParents } from 'unist-util-visit-parents';
import styles from './index.less';
const reg = /(#{2}\d+\${2})/g;
const getChunkIndex = (match: string) => Number(match.slice(2, 3));
const rehypeWrapReference = () => {
return function wrapTextTransform(tree: any) {
visitParents(tree, 'text', (node, ancestors) => {
@ -28,32 +43,67 @@ const rehypeWrapReference = () => {
};
};
const MessageItem = ({ item }: { item: Message; references: IReference[] }) => {
const MessageItem = ({
item,
reference,
}: {
item: Message;
reference: IReference;
}) => {
const userInfo = useSelectUserInfo();
const popoverContent = useMemo(
() => (
<div>
<p>Content</p>
<p>Content</p>
</div>
),
[],
const isAssistant = item.role === MessageType.Assistant;
const getPopoverContent = useCallback(
(chunkIndex: number) => {
const chunks = reference?.chunks ?? [];
const chunkItem = chunks[chunkIndex];
const document = reference.doc_aggs.find(
(x) => x.doc_id === chunkItem.doc_id,
);
const documentId = document?.doc_id;
return (
<Flex
key={chunkItem.chunk_id}
gap={10}
className={styles.referencePopoverWrapper}
>
<Image
id={chunkItem.img_id}
className={styles.referenceChunkImage}
></Image>
<Space direction={'vertical'}>
<div>{chunkItem.content_with_weight}</div>
{documentId && (
<NewDocumentLink documentId={documentId}>
{document?.doc_name}
</NewDocumentLink>
)}
</Space>
</Flex>
);
},
[reference],
);
const renderReference = useCallback(
(text: string) => {
return reactStringReplace(text, /#{2}\d{1,}\${2}/g, (match, i) => {
return reactStringReplace(text, reg, (match, i) => {
const chunkIndex = getChunkIndex(match);
return (
<Popover content={popoverContent}>
<Popover content={getPopoverContent(chunkIndex)}>
<InfoCircleOutlined key={i} className={styles.referenceIcon} />
</Popover>
);
});
},
[popoverContent],
[getPopoverContent],
);
const referenceDocumentList = useMemo(() => {
return reference?.doc_aggs ?? [];
}, [reference?.doc_aggs]);
return (
<div
className={classNames(styles.messageItem, {
@ -86,9 +136,7 @@ const MessageItem = ({ item }: { item: Message; references: IReference[] }) => {
<AssistantIcon></AssistantIcon>
)}
<Flex vertical gap={8} flex={1}>
<b>
{item.role === MessageType.Assistant ? 'Resume Assistant' : 'You'}
</b>
<b>{isAssistant ? 'Resume Assistant' : 'You'}</b>
<div className={styles.messageText}>
<Markdown
rehypePlugins={[rehypeWrapReference]}
@ -102,6 +150,20 @@ const MessageItem = ({ item }: { item: Message; references: IReference[] }) => {
{item.content}
</Markdown>
</div>
{isAssistant && referenceDocumentList.length > 0 && (
<List
bordered
dataSource={referenceDocumentList}
renderItem={(item) => (
<List.Item>
<Typography.Text mark>[ITEM]</Typography.Text>
<NewDocumentLink documentId={item.doc_id}>
{item.doc_name}
</NewDocumentLink>
</List.Item>
)}
/>
)}
</Flex>
</div>
</section>
@ -131,13 +193,22 @@ const ChatContainer = () => {
<Flex flex={1} className={styles.chatContainer} vertical>
<Flex flex={1} vertical className={styles.messageContainer}>
<div>
{conversation?.message?.map((message) => (
<MessageItem
key={message.id}
item={message}
references={conversation.reference}
></MessageItem>
))}
{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>
</Flex>
<Input

View file

@ -107,7 +107,6 @@ request.interceptors.request.use((url: string, options: any) => {
* */
request.interceptors.response.use(async (response: any, request) => {
console.log(response, request);
const data: ResponseType = await response.clone().json();
// response 拦截