feat: rename conversation and delete conversation and preview reference image and fetch file thumbnails (#79)
* feat: fetch file thumbnails * feat: preview reference image * feat: delete conversation * feat: rename conversation
This commit is contained in:
parent
22da1b1bfe
commit
01ab5b5db1
78
web/src/components/rename-modal/index.tsx
Normal file
78
web/src/components/rename-modal/index.tsx
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
import { Form, Input, Modal } from 'antd';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import { IModalManagerChildrenProps } from '../modal-manager';
|
||||||
|
|
||||||
|
interface IProps extends Omit<IModalManagerChildrenProps, 'showModal'> {
|
||||||
|
loading: boolean;
|
||||||
|
initialName: string;
|
||||||
|
onOk: (name: string) => void;
|
||||||
|
showModal?(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const RenameModal = ({
|
||||||
|
visible,
|
||||||
|
hideModal,
|
||||||
|
loading,
|
||||||
|
initialName,
|
||||||
|
onOk,
|
||||||
|
}: IProps) => {
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
|
||||||
|
type FieldType = {
|
||||||
|
name?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOk = async () => {
|
||||||
|
const ret = await form.validateFields();
|
||||||
|
|
||||||
|
return onOk(ret.name);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
hideModal();
|
||||||
|
};
|
||||||
|
|
||||||
|
const onFinish = (values: any) => {
|
||||||
|
console.log('Success:', values);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onFinishFailed = (errorInfo: any) => {
|
||||||
|
console.log('Failed:', errorInfo);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
form.setFieldValue('name', initialName);
|
||||||
|
}, [initialName, form]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
title="Rename"
|
||||||
|
open={visible}
|
||||||
|
onOk={handleOk}
|
||||||
|
onCancel={handleCancel}
|
||||||
|
okButtonProps={{ loading }}
|
||||||
|
confirmLoading={loading}
|
||||||
|
>
|
||||||
|
<Form
|
||||||
|
name="basic"
|
||||||
|
labelCol={{ span: 4 }}
|
||||||
|
wrapperCol={{ span: 20 }}
|
||||||
|
style={{ maxWidth: 600 }}
|
||||||
|
onFinish={onFinish}
|
||||||
|
onFinishFailed={onFinishFailed}
|
||||||
|
autoComplete="off"
|
||||||
|
form={form}
|
||||||
|
>
|
||||||
|
<Form.Item<FieldType>
|
||||||
|
label="Name"
|
||||||
|
name="name"
|
||||||
|
rules={[{ required: true, message: 'Please input name!' }]}
|
||||||
|
>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RenameModal;
|
||||||
@ -150,3 +150,34 @@ export const useFetchKnowledgeList = (
|
|||||||
|
|
||||||
return list;
|
return list;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useSelectFileThumbnails = () => {
|
||||||
|
const fileThumbnails: Record<string, string> = useSelector(
|
||||||
|
(state: any) => state.kFModel.fileThumbnails,
|
||||||
|
);
|
||||||
|
|
||||||
|
return fileThumbnails;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useFetchFileThumbnails = (docIds?: Array<string>) => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const fileThumbnails = useSelectFileThumbnails();
|
||||||
|
|
||||||
|
const fetchFileThumbnails = useCallback(
|
||||||
|
(docIds: Array<string>) => {
|
||||||
|
dispatch({
|
||||||
|
type: 'kFModel/fetch_document_thumbnails',
|
||||||
|
payload: { doc_ids: docIds.join(',') },
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[dispatch],
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (docIds) {
|
||||||
|
fetchFileThumbnails(docIds);
|
||||||
|
}
|
||||||
|
}, [docIds, fetchFileThumbnails]);
|
||||||
|
|
||||||
|
return { fileThumbnails, fetchFileThumbnails };
|
||||||
|
};
|
||||||
|
|||||||
@ -20,9 +20,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.img {
|
.img {
|
||||||
height: 16px;
|
height: 24px;
|
||||||
width: 16px;
|
width: 24px;
|
||||||
margin-right: 6px;
|
margin-right: 10px;
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
.column {
|
.column {
|
||||||
|
|||||||
@ -22,7 +22,7 @@ import {
|
|||||||
} from 'antd';
|
} from 'antd';
|
||||||
import type { ColumnsType } from 'antd/es/table';
|
import type { ColumnsType } from 'antd/es/table';
|
||||||
import { PaginationProps } from 'antd/lib';
|
import { PaginationProps } from 'antd/lib';
|
||||||
import React, { useEffect, useMemo, useState } from 'react';
|
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { Link, useDispatch, useNavigate, useSelector } from 'umi';
|
import { Link, useDispatch, useNavigate, useSelector } from 'umi';
|
||||||
import CreateEPModal from './createEFileModal';
|
import CreateEPModal from './createEFileModal';
|
||||||
import styles from './index.less';
|
import styles from './index.less';
|
||||||
@ -46,7 +46,7 @@ const KnowledgeFile = () => {
|
|||||||
const [parser_id, setParserId] = useState('0');
|
const [parser_id, setParserId] = useState('0');
|
||||||
let navigate = useNavigate();
|
let navigate = useNavigate();
|
||||||
|
|
||||||
const getKfList = () => {
|
const getKfList = useCallback(() => {
|
||||||
const payload = {
|
const payload = {
|
||||||
kb_id: knowledgeBaseId,
|
kb_id: knowledgeBaseId,
|
||||||
};
|
};
|
||||||
@ -55,7 +55,7 @@ const KnowledgeFile = () => {
|
|||||||
type: 'kFModel/getKfList',
|
type: 'kFModel/getKfList',
|
||||||
payload,
|
payload,
|
||||||
});
|
});
|
||||||
};
|
}, [dispatch, knowledgeBaseId]);
|
||||||
|
|
||||||
const throttledGetDocumentList = () => {
|
const throttledGetDocumentList = () => {
|
||||||
dispatch({
|
dispatch({
|
||||||
@ -64,23 +64,29 @@ const KnowledgeFile = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const setPagination = (pageNumber = 1, pageSize?: number) => {
|
const setPagination = useCallback(
|
||||||
const pagination: Pagination = {
|
(pageNumber = 1, pageSize?: number) => {
|
||||||
current: pageNumber,
|
const pagination: Pagination = {
|
||||||
} as Pagination;
|
current: pageNumber,
|
||||||
if (pageSize) {
|
} as Pagination;
|
||||||
pagination.pageSize = pageSize;
|
if (pageSize) {
|
||||||
}
|
pagination.pageSize = pageSize;
|
||||||
dispatch({
|
}
|
||||||
type: 'kFModel/setPagination',
|
dispatch({
|
||||||
payload: pagination,
|
type: 'kFModel/setPagination',
|
||||||
});
|
payload: pagination,
|
||||||
};
|
});
|
||||||
|
},
|
||||||
|
[dispatch],
|
||||||
|
);
|
||||||
|
|
||||||
const onPageChange: PaginationProps['onChange'] = (pageNumber, pageSize) => {
|
const onPageChange: PaginationProps['onChange'] = useCallback(
|
||||||
setPagination(pageNumber, pageSize);
|
(pageNumber: number, pageSize: number) => {
|
||||||
getKfList();
|
setPagination(pageNumber, pageSize);
|
||||||
};
|
getKfList();
|
||||||
|
},
|
||||||
|
[getKfList, setPagination],
|
||||||
|
);
|
||||||
|
|
||||||
const pagination: PaginationProps = useMemo(() => {
|
const pagination: PaginationProps = useMemo(() => {
|
||||||
return {
|
return {
|
||||||
@ -92,7 +98,7 @@ const KnowledgeFile = () => {
|
|||||||
pageSizeOptions: [1, 2, 10, 20, 50, 100],
|
pageSizeOptions: [1, 2, 10, 20, 50, 100],
|
||||||
onChange: onPageChange,
|
onChange: onPageChange,
|
||||||
};
|
};
|
||||||
}, [total, kFModel.pagination]);
|
}, [total, kFModel.pagination, onPageChange]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (knowledgeBaseId) {
|
if (knowledgeBaseId) {
|
||||||
@ -107,7 +113,7 @@ const KnowledgeFile = () => {
|
|||||||
type: 'kFModel/pollGetDocumentList-stop',
|
type: 'kFModel/pollGetDocumentList-stop',
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}, [knowledgeBaseId]);
|
}, [knowledgeBaseId, dispatch, getKfList]);
|
||||||
|
|
||||||
const handleInputChange = (
|
const handleInputChange = (
|
||||||
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
|
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
|
||||||
@ -129,14 +135,14 @@ const KnowledgeFile = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const showCEFModal = () => {
|
const showCEFModal = useCallback(() => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'kFModel/updateState',
|
type: 'kFModel/updateState',
|
||||||
payload: {
|
payload: {
|
||||||
isShowCEFwModal: true,
|
isShowCEFwModal: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
}, [dispatch]);
|
||||||
|
|
||||||
const actionItems: MenuProps['items'] = useMemo(() => {
|
const actionItems: MenuProps['items'] = useMemo(() => {
|
||||||
return [
|
return [
|
||||||
@ -169,7 +175,7 @@ const KnowledgeFile = () => {
|
|||||||
// disabled: true,
|
// disabled: true,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}, []);
|
}, [knowledgeBaseId, showCEFModal]);
|
||||||
|
|
||||||
const toChunk = (id: string) => {
|
const toChunk = (id: string) => {
|
||||||
navigate(
|
navigate(
|
||||||
@ -187,13 +193,9 @@ const KnowledgeFile = () => {
|
|||||||
title: 'Name',
|
title: 'Name',
|
||||||
dataIndex: 'name',
|
dataIndex: 'name',
|
||||||
key: 'name',
|
key: 'name',
|
||||||
render: (text: any, { id }) => (
|
render: (text: any, { id, thumbnail }) => (
|
||||||
<div className={styles.tochunks} onClick={() => toChunk(id)}>
|
<div className={styles.tochunks} onClick={() => toChunk(id)}>
|
||||||
<img
|
<img className={styles.img} src={thumbnail} alt="" />
|
||||||
className={styles.img}
|
|
||||||
src="https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg"
|
|
||||||
alt=""
|
|
||||||
/>
|
|
||||||
{text}
|
{text}
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
|
|||||||
@ -16,6 +16,7 @@ export interface KFModelState extends BaseState {
|
|||||||
data: IKnowledgeFile[];
|
data: IKnowledgeFile[];
|
||||||
total: number;
|
total: number;
|
||||||
currentRecord: Nullable<IKnowledgeFile>;
|
currentRecord: Nullable<IKnowledgeFile>;
|
||||||
|
fileThumbnails: Record<string, string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const model: DvaModel<KFModelState> = {
|
const model: DvaModel<KFModelState> = {
|
||||||
@ -34,6 +35,7 @@ const model: DvaModel<KFModelState> = {
|
|||||||
current: 1,
|
current: 1,
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
},
|
},
|
||||||
|
fileThumbnails: {} as Record<string, string>,
|
||||||
},
|
},
|
||||||
reducers: {
|
reducers: {
|
||||||
updateState(state, { payload }) {
|
updateState(state, { payload }) {
|
||||||
@ -54,6 +56,9 @@ const model: DvaModel<KFModelState> = {
|
|||||||
setPagination(state, { payload }) {
|
setPagination(state, { payload }) {
|
||||||
return { ...state, pagination: { ...state.pagination, ...payload } };
|
return { ...state, pagination: { ...state.pagination, ...payload } };
|
||||||
},
|
},
|
||||||
|
setFileThumbnails(state, { payload }) {
|
||||||
|
return { ...state, fileThumbnails: payload };
|
||||||
|
},
|
||||||
},
|
},
|
||||||
effects: {
|
effects: {
|
||||||
*createKf({ payload = {} }, { call }) {
|
*createKf({ payload = {} }, { call }) {
|
||||||
@ -201,6 +206,12 @@ const model: DvaModel<KFModelState> = {
|
|||||||
}
|
}
|
||||||
return retcode;
|
return retcode;
|
||||||
},
|
},
|
||||||
|
*fetch_document_thumbnails({ payload = {} }, { call, put }) {
|
||||||
|
const { data } = yield call(kbService.document_thumbnails, payload);
|
||||||
|
if (data.retcode === 0) {
|
||||||
|
yield put({ type: 'setFileThumbnails', payload: data.data });
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
export default model;
|
export default model;
|
||||||
|
|||||||
@ -47,3 +47,7 @@
|
|||||||
.referenceChunkImage {
|
.referenceChunkImage {
|
||||||
width: 10vw;
|
width: 10vw;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.referenceImagePreview {
|
||||||
|
width: 600px;
|
||||||
|
}
|
||||||
|
|||||||
@ -3,21 +3,12 @@ import { MessageType } from '@/constants/chat';
|
|||||||
import { useOneNamespaceEffectsLoading } from '@/hooks/storeHooks';
|
import { useOneNamespaceEffectsLoading } from '@/hooks/storeHooks';
|
||||||
import { useSelectUserInfo } from '@/hooks/userSettingHook';
|
import { useSelectUserInfo } from '@/hooks/userSettingHook';
|
||||||
import { IReference, Message } from '@/interfaces/database/chat';
|
import { IReference, Message } from '@/interfaces/database/chat';
|
||||||
import {
|
import { Avatar, Button, Flex, Input, List, Popover, Space } from 'antd';
|
||||||
Avatar,
|
|
||||||
Button,
|
|
||||||
Flex,
|
|
||||||
Input,
|
|
||||||
List,
|
|
||||||
Popover,
|
|
||||||
Space,
|
|
||||||
Typography,
|
|
||||||
} from 'antd';
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { ChangeEventHandler, useCallback, useMemo, useState } from 'react';
|
import { ChangeEventHandler, useCallback, useMemo, useState } from 'react';
|
||||||
import reactStringReplace from 'react-string-replace';
|
import reactStringReplace from 'react-string-replace';
|
||||||
import {
|
import {
|
||||||
useFetchConversation,
|
useFetchConversationOnMount,
|
||||||
useGetFileIcon,
|
useGetFileIcon,
|
||||||
useScrollToBottom,
|
useScrollToBottom,
|
||||||
useSendMessage,
|
useSendMessage,
|
||||||
@ -26,6 +17,7 @@ import { IClientConversation } from '../interface';
|
|||||||
|
|
||||||
import Image from '@/components/image';
|
import Image from '@/components/image';
|
||||||
import NewDocumentLink from '@/components/new-document-link';
|
import NewDocumentLink from '@/components/new-document-link';
|
||||||
|
import { useSelectFileThumbnails } from '@/hooks/knowledgeHook';
|
||||||
import { InfoCircleOutlined } from '@ant-design/icons';
|
import { InfoCircleOutlined } from '@ant-design/icons';
|
||||||
import Markdown from 'react-markdown';
|
import Markdown from 'react-markdown';
|
||||||
import { visitParents } from 'unist-util-visit-parents';
|
import { visitParents } from 'unist-util-visit-parents';
|
||||||
@ -56,11 +48,10 @@ const MessageItem = ({
|
|||||||
reference: IReference;
|
reference: IReference;
|
||||||
}) => {
|
}) => {
|
||||||
const userInfo = useSelectUserInfo();
|
const userInfo = useSelectUserInfo();
|
||||||
|
const fileThumbnails = useSelectFileThumbnails();
|
||||||
|
|
||||||
const isAssistant = item.role === MessageType.Assistant;
|
const isAssistant = item.role === MessageType.Assistant;
|
||||||
|
|
||||||
const getFileIcon = useGetFileIcon();
|
|
||||||
|
|
||||||
const getPopoverContent = useCallback(
|
const getPopoverContent = useCallback(
|
||||||
(chunkIndex: number) => {
|
(chunkIndex: number) => {
|
||||||
const chunks = reference?.chunks ?? [];
|
const chunks = reference?.chunks ?? [];
|
||||||
@ -75,22 +66,35 @@ const MessageItem = ({
|
|||||||
gap={10}
|
gap={10}
|
||||||
className={styles.referencePopoverWrapper}
|
className={styles.referencePopoverWrapper}
|
||||||
>
|
>
|
||||||
<Image
|
<Popover
|
||||||
id={chunkItem?.img_id}
|
placement="topRight"
|
||||||
className={styles.referenceChunkImage}
|
content={
|
||||||
></Image>
|
<Image
|
||||||
|
id={chunkItem?.img_id}
|
||||||
|
className={styles.referenceImagePreview}
|
||||||
|
></Image>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Image
|
||||||
|
id={chunkItem?.img_id}
|
||||||
|
className={styles.referenceChunkImage}
|
||||||
|
></Image>
|
||||||
|
</Popover>
|
||||||
<Space direction={'vertical'}>
|
<Space direction={'vertical'}>
|
||||||
<div>{chunkItem?.content_with_weight}</div>
|
<div>{chunkItem?.content_with_weight}</div>
|
||||||
{documentId && (
|
{documentId && (
|
||||||
<NewDocumentLink documentId={documentId}>
|
<Flex gap={'middle'}>
|
||||||
{document?.doc_name}
|
<img src={fileThumbnails[documentId]} alt="" />
|
||||||
</NewDocumentLink>
|
<NewDocumentLink documentId={documentId}>
|
||||||
|
{document?.doc_name}
|
||||||
|
</NewDocumentLink>
|
||||||
|
</Flex>
|
||||||
)}
|
)}
|
||||||
</Space>
|
</Space>
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
[reference],
|
[reference, fileThumbnails],
|
||||||
);
|
);
|
||||||
|
|
||||||
const renderReference = useCallback(
|
const renderReference = useCallback(
|
||||||
@ -163,12 +167,13 @@ const MessageItem = ({
|
|||||||
dataSource={referenceDocumentList}
|
dataSource={referenceDocumentList}
|
||||||
renderItem={(item) => (
|
renderItem={(item) => (
|
||||||
<List.Item>
|
<List.Item>
|
||||||
<Typography.Text mark>
|
{/* <SvgIcon name={getFileIcon(item.doc_name)}></SvgIcon> */}
|
||||||
{/* <SvgIcon name={getFileIcon(item.doc_name)}></SvgIcon> */}
|
<Flex gap={'middle'}>
|
||||||
</Typography.Text>
|
<img src={fileThumbnails[item.doc_id]}></img>
|
||||||
<NewDocumentLink documentId={item.doc_id}>
|
<NewDocumentLink documentId={item.doc_id}>
|
||||||
{item.doc_name}
|
{item.doc_name}
|
||||||
</NewDocumentLink>
|
</NewDocumentLink>
|
||||||
|
</Flex>
|
||||||
</List.Item>
|
</List.Item>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@ -182,11 +187,10 @@ const MessageItem = ({
|
|||||||
|
|
||||||
const ChatContainer = () => {
|
const ChatContainer = () => {
|
||||||
const [value, setValue] = useState('');
|
const [value, setValue] = useState('');
|
||||||
const conversation: IClientConversation = useFetchConversation();
|
const conversation: IClientConversation = useFetchConversationOnMount();
|
||||||
const { sendMessage } = useSendMessage();
|
const { sendMessage } = useSendMessage();
|
||||||
const loading = useOneNamespaceEffectsLoading('chatModel', [
|
const loading = useOneNamespaceEffectsLoading('chatModel', [
|
||||||
'completeConversation',
|
'completeConversation',
|
||||||
'getConversation',
|
|
||||||
]);
|
]);
|
||||||
const ref = useScrollToBottom();
|
const ref = useScrollToBottom();
|
||||||
useGetFileIcon();
|
useGetFileIcon();
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
import showDeleteConfirm from '@/components/deleting-confirm';
|
import showDeleteConfirm from '@/components/deleting-confirm';
|
||||||
import { MessageType } from '@/constants/chat';
|
import { MessageType } from '@/constants/chat';
|
||||||
import { fileIconMap } from '@/constants/common';
|
import { fileIconMap } from '@/constants/common';
|
||||||
|
import { useSetModalState } from '@/hooks/commonHooks';
|
||||||
|
import { useOneNamespaceEffectsLoading } from '@/hooks/storeHooks';
|
||||||
import { IConversation, IDialog } from '@/interfaces/database/chat';
|
import { IConversation, IDialog } from '@/interfaces/database/chat';
|
||||||
import { getFileExtension } from '@/utils';
|
import { getFileExtension } from '@/utils';
|
||||||
import omit from 'lodash/omit';
|
import omit from 'lodash/omit';
|
||||||
@ -14,7 +16,7 @@ import {
|
|||||||
VariableTableDataType,
|
VariableTableDataType,
|
||||||
} from './interface';
|
} from './interface';
|
||||||
import { ChatModelState } from './model';
|
import { ChatModelState } from './model';
|
||||||
import { isConversationIdNotExist } from './utils';
|
import { isConversationIdExist } from './utils';
|
||||||
|
|
||||||
export const useFetchDialogList = () => {
|
export const useFetchDialogList = () => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
@ -204,6 +206,24 @@ export const useSelectFirstDialogOnMount = () => {
|
|||||||
return dialogList;
|
return dialogList;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useHandleItemHover = () => {
|
||||||
|
const [activated, setActivated] = useState<string>('');
|
||||||
|
|
||||||
|
const handleItemEnter = (id: string) => {
|
||||||
|
setActivated(id);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleItemLeave = () => {
|
||||||
|
setActivated('');
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
activated,
|
||||||
|
handleItemEnter,
|
||||||
|
handleItemLeave,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
//#region conversation
|
//#region conversation
|
||||||
|
|
||||||
export const useCreateTemporaryConversation = () => {
|
export const useCreateTemporaryConversation = () => {
|
||||||
@ -374,30 +394,50 @@ export const useSetConversation = () => {
|
|||||||
return { setConversation };
|
return { setConversation };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useFetchConversation = () => {
|
export const useSelectCurrentConversation = () => {
|
||||||
const dispatch = useDispatch();
|
const conversation: IClientConversation = useSelector(
|
||||||
const { conversationId } = useGetChatSearchParams();
|
|
||||||
const conversation = useSelector(
|
|
||||||
(state: any) => state.chatModel.currentConversation,
|
(state: any) => state.chatModel.currentConversation,
|
||||||
);
|
);
|
||||||
const setCurrentConversation = useSetCurrentConversation();
|
|
||||||
|
|
||||||
const fetchConversation = useCallback(() => {
|
return conversation;
|
||||||
if (isConversationIdNotExist(conversationId)) {
|
};
|
||||||
dispatch<any>({
|
|
||||||
|
export const useFetchConversation = () => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
const fetchConversation = useCallback(
|
||||||
|
(conversationId: string, needToBeSaved = true) => {
|
||||||
|
return dispatch<any>({
|
||||||
type: 'chatModel/getConversation',
|
type: 'chatModel/getConversation',
|
||||||
payload: {
|
payload: {
|
||||||
|
needToBeSaved,
|
||||||
conversation_id: conversationId,
|
conversation_id: conversationId,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
[dispatch],
|
||||||
|
);
|
||||||
|
|
||||||
|
return fetchConversation;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useFetchConversationOnMount = () => {
|
||||||
|
const { conversationId } = useGetChatSearchParams();
|
||||||
|
const conversation = useSelectCurrentConversation();
|
||||||
|
const setCurrentConversation = useSetCurrentConversation();
|
||||||
|
const fetchConversation = useFetchConversation();
|
||||||
|
|
||||||
|
const fetchConversationOnMount = useCallback(() => {
|
||||||
|
if (isConversationIdExist(conversationId)) {
|
||||||
|
fetchConversation(conversationId);
|
||||||
} else {
|
} else {
|
||||||
setCurrentConversation({} as IClientConversation);
|
setCurrentConversation({} as IClientConversation);
|
||||||
}
|
}
|
||||||
}, [dispatch, conversationId, setCurrentConversation]);
|
}, [fetchConversation, setCurrentConversation, conversationId]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchConversation();
|
fetchConversationOnMount();
|
||||||
}, [fetchConversation]);
|
}, [fetchConversationOnMount]);
|
||||||
|
|
||||||
return conversation;
|
return conversation;
|
||||||
};
|
};
|
||||||
@ -477,4 +517,83 @@ export const useGetFileIcon = () => {
|
|||||||
return getFileIcon;
|
return getFileIcon;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useRemoveConversation = () => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const { dialogId } = useGetChatSearchParams();
|
||||||
|
const { handleClickConversation } = useClickConversationCard();
|
||||||
|
|
||||||
|
const removeConversation = (conversationIds: Array<string>) => async () => {
|
||||||
|
const ret = await dispatch<any>({
|
||||||
|
type: 'chatModel/removeConversation',
|
||||||
|
payload: {
|
||||||
|
dialog_id: dialogId,
|
||||||
|
conversation_ids: conversationIds,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (ret === 0) {
|
||||||
|
handleClickConversation('');
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onRemoveConversation = (conversationIds: Array<string>) => {
|
||||||
|
showDeleteConfirm({ onOk: removeConversation(conversationIds) });
|
||||||
|
};
|
||||||
|
|
||||||
|
return { onRemoveConversation };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useRenameConversation = () => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const [conversation, setConversation] = useState<IClientConversation>(
|
||||||
|
{} as IClientConversation,
|
||||||
|
);
|
||||||
|
const fetchConversation = useFetchConversation();
|
||||||
|
const {
|
||||||
|
visible: conversationRenameVisible,
|
||||||
|
hideModal: hideConversationRenameModal,
|
||||||
|
showModal: showConversationRenameModal,
|
||||||
|
} = useSetModalState();
|
||||||
|
|
||||||
|
const onConversationRenameOk = useCallback(
|
||||||
|
async (name: string) => {
|
||||||
|
const ret = await dispatch<any>({
|
||||||
|
type: 'chatModel/setConversation',
|
||||||
|
payload: { ...conversation, conversation_id: conversation.id, name },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (ret.retcode === 0) {
|
||||||
|
hideConversationRenameModal();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[dispatch, conversation, hideConversationRenameModal],
|
||||||
|
);
|
||||||
|
|
||||||
|
const loading = useOneNamespaceEffectsLoading('chatModel', [
|
||||||
|
'setConversation',
|
||||||
|
]);
|
||||||
|
|
||||||
|
const handleShowConversationRenameModal = useCallback(
|
||||||
|
async (conversationId: string) => {
|
||||||
|
const ret = await fetchConversation(conversationId, false);
|
||||||
|
if (ret.retcode === 0) {
|
||||||
|
setConversation(ret.data);
|
||||||
|
}
|
||||||
|
showConversationRenameModal();
|
||||||
|
},
|
||||||
|
[showConversationRenameModal, fetchConversation],
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
conversationRenameLoading: loading,
|
||||||
|
initialConversationName: conversation.name,
|
||||||
|
onConversationRenameOk,
|
||||||
|
conversationRenameVisible,
|
||||||
|
hideConversationRenameModal,
|
||||||
|
showConversationRenameModal: handleShowConversationRenameModal,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|||||||
@ -11,8 +11,9 @@ import {
|
|||||||
Space,
|
Space,
|
||||||
Tag,
|
Tag,
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
|
import { MenuItemProps } from 'antd/lib/menu/MenuItem';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { useCallback, useState } from 'react';
|
import { useCallback } from 'react';
|
||||||
import ChatConfigurationModal from './chat-configuration-modal';
|
import ChatConfigurationModal from './chat-configuration-modal';
|
||||||
import ChatContainer from './chat-container';
|
import ChatContainer from './chat-container';
|
||||||
import {
|
import {
|
||||||
@ -21,42 +22,88 @@ import {
|
|||||||
useFetchConversationList,
|
useFetchConversationList,
|
||||||
useFetchDialog,
|
useFetchDialog,
|
||||||
useGetChatSearchParams,
|
useGetChatSearchParams,
|
||||||
|
useHandleItemHover,
|
||||||
|
useRemoveConversation,
|
||||||
useRemoveDialog,
|
useRemoveDialog,
|
||||||
|
useRenameConversation,
|
||||||
useSelectConversationList,
|
useSelectConversationList,
|
||||||
useSelectFirstDialogOnMount,
|
useSelectFirstDialogOnMount,
|
||||||
useSetCurrentDialog,
|
useSetCurrentDialog,
|
||||||
} from './hooks';
|
} from './hooks';
|
||||||
|
|
||||||
|
import RenameModal from '@/components/rename-modal';
|
||||||
import styles from './index.less';
|
import styles from './index.less';
|
||||||
|
|
||||||
const Chat = () => {
|
const Chat = () => {
|
||||||
const dialogList = useSelectFirstDialogOnMount();
|
const dialogList = useSelectFirstDialogOnMount();
|
||||||
const [activated, setActivated] = useState<string>('');
|
|
||||||
const { visible, hideModal, showModal } = useSetModalState();
|
const { visible, hideModal, showModal } = useSetModalState();
|
||||||
const { setCurrentDialog, currentDialog } = useSetCurrentDialog();
|
const { setCurrentDialog, currentDialog } = useSetCurrentDialog();
|
||||||
const { onRemoveDialog } = useRemoveDialog();
|
const { onRemoveDialog } = useRemoveDialog();
|
||||||
|
const { onRemoveConversation } = useRemoveConversation();
|
||||||
const { handleClickDialog } = useClickDialogCard();
|
const { handleClickDialog } = useClickDialogCard();
|
||||||
const { handleClickConversation } = useClickConversationCard();
|
const { handleClickConversation } = useClickConversationCard();
|
||||||
const { dialogId, conversationId } = useGetChatSearchParams();
|
const { dialogId, conversationId } = useGetChatSearchParams();
|
||||||
const { list: conversationList, addTemporaryConversation } =
|
const { list: conversationList, addTemporaryConversation } =
|
||||||
useSelectConversationList();
|
useSelectConversationList();
|
||||||
|
const { activated, handleItemEnter, handleItemLeave } = useHandleItemHover();
|
||||||
|
const {
|
||||||
|
activated: conversationActivated,
|
||||||
|
handleItemEnter: handleConversationItemEnter,
|
||||||
|
handleItemLeave: handleConversationItemLeave,
|
||||||
|
} = useHandleItemHover();
|
||||||
|
const {
|
||||||
|
conversationRenameLoading,
|
||||||
|
initialConversationName,
|
||||||
|
onConversationRenameOk,
|
||||||
|
conversationRenameVisible,
|
||||||
|
hideConversationRenameModal,
|
||||||
|
showConversationRenameModal,
|
||||||
|
} = useRenameConversation();
|
||||||
|
|
||||||
useFetchDialog(dialogId, true);
|
useFetchDialog(dialogId, true);
|
||||||
|
|
||||||
const handleAppCardEnter = (id: string) => () => {
|
const handleAppCardEnter = (id: string) => () => {
|
||||||
setActivated(id);
|
handleItemEnter(id);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAppCardLeave = () => {
|
const handleConversationCardEnter = (id: string) => () => {
|
||||||
setActivated('');
|
handleConversationItemEnter(id);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleShowChatConfigurationModal = (dialogId?: string) => () => {
|
const handleShowChatConfigurationModal =
|
||||||
if (dialogId) {
|
(dialogId?: string): any =>
|
||||||
setCurrentDialog(dialogId);
|
(info: any) => {
|
||||||
}
|
info?.domEvent?.preventDefault();
|
||||||
showModal();
|
info?.domEvent?.stopPropagation();
|
||||||
};
|
if (dialogId) {
|
||||||
|
setCurrentDialog(dialogId);
|
||||||
|
}
|
||||||
|
showModal();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRemoveDialog =
|
||||||
|
(dialogId: string): MenuItemProps['onClick'] =>
|
||||||
|
({ domEvent }) => {
|
||||||
|
domEvent.preventDefault();
|
||||||
|
domEvent.stopPropagation();
|
||||||
|
onRemoveDialog([dialogId]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRemoveConversation =
|
||||||
|
(conversationId: string): MenuItemProps['onClick'] =>
|
||||||
|
({ domEvent }) => {
|
||||||
|
domEvent.preventDefault();
|
||||||
|
domEvent.stopPropagation();
|
||||||
|
onRemoveConversation([conversationId]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleShowConversationRenameModal =
|
||||||
|
(conversationId: string): MenuItemProps['onClick'] =>
|
||||||
|
({ domEvent }) => {
|
||||||
|
domEvent.preventDefault();
|
||||||
|
domEvent.stopPropagation();
|
||||||
|
showConversationRenameModal(conversationId);
|
||||||
|
};
|
||||||
|
|
||||||
const handleDialogCardClick = (dialogId: string) => () => {
|
const handleDialogCardClick = (dialogId: string) => () => {
|
||||||
handleClickDialog(dialogId);
|
handleClickDialog(dialogId);
|
||||||
@ -97,7 +144,35 @@ const Chat = () => {
|
|||||||
{ type: 'divider' },
|
{ type: 'divider' },
|
||||||
{
|
{
|
||||||
key: '2',
|
key: '2',
|
||||||
onClick: () => onRemoveDialog([dialogId]),
|
onClick: handleRemoveDialog(dialogId),
|
||||||
|
label: (
|
||||||
|
<Space>
|
||||||
|
<DeleteOutlined />
|
||||||
|
Delete chat
|
||||||
|
</Space>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return appItems;
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildConversationItems = (conversationId: string) => {
|
||||||
|
const appItems: MenuProps['items'] = [
|
||||||
|
{
|
||||||
|
key: '1',
|
||||||
|
onClick: handleShowConversationRenameModal(conversationId),
|
||||||
|
label: (
|
||||||
|
<Space>
|
||||||
|
<EditOutlined />
|
||||||
|
Edit
|
||||||
|
</Space>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{ type: 'divider' },
|
||||||
|
{
|
||||||
|
key: '2',
|
||||||
|
onClick: handleRemoveConversation(conversationId),
|
||||||
label: (
|
label: (
|
||||||
<Space>
|
<Space>
|
||||||
<DeleteOutlined />
|
<DeleteOutlined />
|
||||||
@ -129,7 +204,7 @@ const Chat = () => {
|
|||||||
[styles.chatAppCardSelected]: dialogId === x.id,
|
[styles.chatAppCardSelected]: dialogId === x.id,
|
||||||
})}
|
})}
|
||||||
onMouseEnter={handleAppCardEnter(x.id)}
|
onMouseEnter={handleAppCardEnter(x.id)}
|
||||||
onMouseLeave={handleAppCardLeave}
|
onMouseLeave={handleItemLeave}
|
||||||
onClick={handleDialogCardClick(x.id)}
|
onClick={handleDialogCardClick(x.id)}
|
||||||
>
|
>
|
||||||
<Flex justify="space-between" align="center">
|
<Flex justify="space-between" align="center">
|
||||||
@ -176,11 +251,22 @@ const Chat = () => {
|
|||||||
key={x.id}
|
key={x.id}
|
||||||
hoverable
|
hoverable
|
||||||
onClick={handleConversationCardClick(x.id)}
|
onClick={handleConversationCardClick(x.id)}
|
||||||
|
onMouseEnter={handleConversationCardEnter(x.id)}
|
||||||
|
onMouseLeave={handleConversationItemLeave}
|
||||||
className={classNames(styles.chatTitleCard, {
|
className={classNames(styles.chatTitleCard, {
|
||||||
[styles.chatTitleCardSelected]: x.id === conversationId,
|
[styles.chatTitleCardSelected]: x.id === conversationId,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<div>{x.name}</div>
|
<Flex justify="space-between" align="center">
|
||||||
|
<div>{x.name}</div>
|
||||||
|
{conversationActivated === x.id && x.id !== '' && (
|
||||||
|
<section>
|
||||||
|
<Dropdown menu={{ items: buildConversationItems(x.id) }}>
|
||||||
|
<ChatAppCube className={styles.cubeIcon}></ChatAppCube>
|
||||||
|
</Dropdown>
|
||||||
|
</section>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
</Card>
|
</Card>
|
||||||
))}
|
))}
|
||||||
</Flex>
|
</Flex>
|
||||||
@ -194,6 +280,13 @@ const Chat = () => {
|
|||||||
hideModal={hideModal}
|
hideModal={hideModal}
|
||||||
id={currentDialog.id}
|
id={currentDialog.id}
|
||||||
></ChatConfigurationModal>
|
></ChatConfigurationModal>
|
||||||
|
<RenameModal
|
||||||
|
visible={conversationRenameVisible}
|
||||||
|
hideModal={hideConversationRenameModal}
|
||||||
|
onOk={onConversationRenameOk}
|
||||||
|
initialName={initialConversationName}
|
||||||
|
loading={conversationRenameLoading}
|
||||||
|
></RenameModal>
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { message } from 'antd';
|
|||||||
import { DvaModel } from 'umi';
|
import { DvaModel } from 'umi';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
import { IClientConversation, IMessage } from './interface';
|
import { IClientConversation, IMessage } from './interface';
|
||||||
|
import { getDocumentIdsFromConversionReference } from './utils';
|
||||||
|
|
||||||
export interface ChatModelState {
|
export interface ChatModelState {
|
||||||
name: string;
|
name: string;
|
||||||
@ -109,11 +110,19 @@ const model: DvaModel<ChatModelState> = {
|
|||||||
return data.retcode;
|
return data.retcode;
|
||||||
},
|
},
|
||||||
*getConversation({ payload }, { call, put }) {
|
*getConversation({ payload }, { call, put }) {
|
||||||
const { data } = yield call(chatService.getConversation, payload);
|
const { data } = yield call(chatService.getConversation, {
|
||||||
if (data.retcode === 0) {
|
conversation_id: payload.conversation_id,
|
||||||
|
});
|
||||||
|
if (data.retcode === 0 && payload.needToBeSaved) {
|
||||||
|
yield put({
|
||||||
|
type: 'kFModel/fetch_document_thumbnails',
|
||||||
|
payload: {
|
||||||
|
doc_ids: getDocumentIdsFromConversionReference(data.data),
|
||||||
|
},
|
||||||
|
});
|
||||||
yield put({ type: 'setCurrentConversation', payload: data.data });
|
yield put({ type: 'setCurrentConversation', payload: data.data });
|
||||||
}
|
}
|
||||||
return data.retcode;
|
return data;
|
||||||
},
|
},
|
||||||
*setConversation({ payload }, { call, put }) {
|
*setConversation({ payload }, { call, put }) {
|
||||||
const { data } = yield call(chatService.setConversation, payload);
|
const { data } = yield call(chatService.setConversation, payload);
|
||||||
@ -138,6 +147,19 @@ const model: DvaModel<ChatModelState> = {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
*removeConversation({ payload }, { call, put }) {
|
||||||
|
const { data } = yield call(chatService.removeConversation, {
|
||||||
|
conversation_ids: payload.conversation_ids,
|
||||||
|
});
|
||||||
|
if (data.retcode === 0) {
|
||||||
|
yield put({
|
||||||
|
type: 'listConversation',
|
||||||
|
payload: { dialog_id: payload.dialog_id },
|
||||||
|
});
|
||||||
|
message.success('Deleted successfully !');
|
||||||
|
}
|
||||||
|
return data.retcode;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { IConversation, IReference } from '@/interfaces/database/chat';
|
||||||
import { EmptyConversationId, variableEnabledFieldMap } from './constants';
|
import { EmptyConversationId, variableEnabledFieldMap } from './constants';
|
||||||
|
|
||||||
export const excludeUnEnabledVariables = (values: any) => {
|
export const excludeUnEnabledVariables = (values: any) => {
|
||||||
@ -11,6 +12,23 @@ export const excludeUnEnabledVariables = (values: any) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const isConversationIdNotExist = (conversationId: string) => {
|
export const isConversationIdExist = (conversationId: string) => {
|
||||||
return conversationId !== EmptyConversationId && conversationId !== '';
|
return conversationId !== EmptyConversationId && conversationId !== '';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getDocumentIdsFromConversionReference = (data: IConversation) => {
|
||||||
|
const documentIds = data.reference.reduce(
|
||||||
|
(pre: Array<string>, cur: IReference) => {
|
||||||
|
cur.doc_aggs
|
||||||
|
.map((x) => x.doc_id)
|
||||||
|
.forEach((x) => {
|
||||||
|
if (pre.every((y) => y !== x)) {
|
||||||
|
pre.push(x);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return pre;
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
return documentIds.join(',');
|
||||||
|
};
|
||||||
|
|||||||
@ -11,6 +11,7 @@ const {
|
|||||||
setConversation,
|
setConversation,
|
||||||
completeConversation,
|
completeConversation,
|
||||||
listConversation,
|
listConversation,
|
||||||
|
removeConversation,
|
||||||
} = api;
|
} = api;
|
||||||
|
|
||||||
const methods = {
|
const methods = {
|
||||||
@ -46,6 +47,10 @@ const methods = {
|
|||||||
url: completeConversation,
|
url: completeConversation,
|
||||||
method: 'post',
|
method: 'post',
|
||||||
},
|
},
|
||||||
|
removeConversation: {
|
||||||
|
url: removeConversation,
|
||||||
|
method: 'post',
|
||||||
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
const chatService = registerServer<keyof typeof methods>(methods, request);
|
const chatService = registerServer<keyof typeof methods>(methods, request);
|
||||||
|
|||||||
@ -13,6 +13,7 @@ const {
|
|||||||
document_rm,
|
document_rm,
|
||||||
document_create,
|
document_create,
|
||||||
document_change_parser,
|
document_change_parser,
|
||||||
|
document_thumbnails,
|
||||||
chunk_list,
|
chunk_list,
|
||||||
create_chunk,
|
create_chunk,
|
||||||
set_chunk,
|
set_chunk,
|
||||||
@ -75,6 +76,10 @@ const methods = {
|
|||||||
url: document_change_parser,
|
url: document_change_parser,
|
||||||
method: 'post',
|
method: 'post',
|
||||||
},
|
},
|
||||||
|
document_thumbnails: {
|
||||||
|
url: document_thumbnails,
|
||||||
|
method: 'get',
|
||||||
|
},
|
||||||
// chunk管理
|
// chunk管理
|
||||||
chunk_list: {
|
chunk_list: {
|
||||||
url: chunk_list,
|
url: chunk_list,
|
||||||
|
|||||||
@ -42,6 +42,7 @@ export default {
|
|||||||
document_create: `${api_host}/document/create`,
|
document_create: `${api_host}/document/create`,
|
||||||
document_run: `${api_host}/document/run`,
|
document_run: `${api_host}/document/run`,
|
||||||
document_change_parser: `${api_host}/document/change_parser`,
|
document_change_parser: `${api_host}/document/change_parser`,
|
||||||
|
document_thumbnails: `${api_host}/document/thumbnails`,
|
||||||
|
|
||||||
setDialog: `${api_host}/dialog/set`,
|
setDialog: `${api_host}/dialog/set`,
|
||||||
getDialog: `${api_host}/dialog/get`,
|
getDialog: `${api_host}/dialog/get`,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user