feat: add message popover content
This commit is contained in:
parent
d1417102b6
commit
71ac65960a
8 changed files with 131 additions and 43 deletions
20
web/src/components/new-document-link.tsx
Normal file
20
web/src/components/new-document-link.tsx
Normal 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;
|
||||
|
|
@ -71,7 +71,7 @@ export interface IReference {
|
|||
total: number;
|
||||
}
|
||||
|
||||
interface Docagg {
|
||||
export interface Docagg {
|
||||
count: number;
|
||||
doc_id: string;
|
||||
doc_name: string;
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<>
|
||||
|
|
|
|||
|
|
@ -39,3 +39,11 @@
|
|||
.messageItemRight {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.referencePopoverWrapper {
|
||||
width: 50vw;
|
||||
}
|
||||
|
||||
.referenceChunkImage {
|
||||
width: 10vw;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 拦截
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue