fix: new message appears in wrong chat window. #1289 (#1571)

### What problem does this PR solve?
fix: new message appears in wrong chat window. #1289

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
This commit is contained in:
balibabu 2024-07-17 17:08:24 +08:00 committed by GitHub
parent 4df75ca84e
commit 06fd35d420
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 639 additions and 627 deletions

View File

@ -214,7 +214,7 @@ export const useSendMessageWithSse = (
[url],
);
return { send, answer, done };
return { send, answer, done, setDone };
};
//#region chat hooks

View File

@ -8,7 +8,7 @@ import { useCallback, useMemo } from 'react';
import { useLocation } from 'umi';
import Toolbar from '../right-toolbar';
import { useFetchAppConf } from '@/hooks/logicHooks';
import { useFetchAppConf } from '@/hooks/logic-hooks';
import { MessageOutlined } from '@ant-design/icons';
import styles from './index.less';

View File

@ -6,7 +6,7 @@ import React from 'react';
import User from '../user';
import { LanguageList } from '@/constants/common';
import { useChangeLanguage } from '@/hooks/logicHooks';
import { useChangeLanguage } from '@/hooks/logic-hooks';
import { useSelector } from 'umi';
import styled from './index.less';

View File

@ -15,7 +15,7 @@ import {
import {
useChangeDocumentParser,
useSetSelectedRecord,
} from '@/hooks/logicHooks';
} from '@/hooks/logic-hooks';
import { useFetchTenantInfo } from '@/hooks/userSettingHook';
import { IKnowledgeFile } from '@/interfaces/database/knowledge';
import { getExtension, isFileUploadDone } from '@/utils/documentUtils';

View File

@ -1,233 +1,234 @@
import ChunkMethodModal from '@/components/chunk-method-modal';
import SvgIcon from '@/components/svg-icon';
import {
useSelectDocumentList,
useSetDocumentStatus,
} from '@/hooks/documentHooks';
import { useSetSelectedRecord } from '@/hooks/logicHooks';
import { useSelectParserList } from '@/hooks/userSettingHook';
import { IKnowledgeFile } from '@/interfaces/database/knowledge';
import { getExtension } from '@/utils/documentUtils';
import { Divider, Flex, Switch, Table, Typography } from 'antd';
import type { ColumnsType } from 'antd/es/table';
import { useTranslation } from 'react-i18next';
import CreateFileModal from './create-file-modal';
import WebCrawlModal from './web-crawl-modal';
import DocumentToolbar from './document-toolbar';
import {
useChangeDocumentParser,
useCreateEmptyDocument,
useFetchDocumentListOnMount,
useGetPagination,
useGetRowSelection,
useHandleUploadDocument, useHandleWebCrawl,
useNavigateToOtherPage,
useRenameDocument,
} from './hooks';
import ParsingActionCell from './parsing-action-cell';
import ParsingStatusCell from './parsing-status-cell';
import RenameModal from './rename-modal';
import FileUploadModal from '@/components/file-upload-modal';
import { formatDate } from '@/utils/date';
import styles from './index.less';
const { Text } = Typography;
const KnowledgeFile = () => {
const data = useSelectDocumentList();
const { fetchDocumentList } = useFetchDocumentListOnMount();
const parserList = useSelectParserList();
const { pagination } = useGetPagination(fetchDocumentList);
const onChangeStatus = useSetDocumentStatus();
const { toChunk } = useNavigateToOtherPage();
const { currentRecord, setRecord } = useSetSelectedRecord();
const {
renameLoading,
onRenameOk,
renameVisible,
hideRenameModal,
showRenameModal,
} = useRenameDocument(currentRecord.id);
const {
createLoading,
onCreateOk,
createVisible,
hideCreateModal,
showCreateModal,
} = useCreateEmptyDocument();
const {
changeParserLoading,
onChangeParserOk,
changeParserVisible,
hideChangeParserModal,
showChangeParserModal,
} = useChangeDocumentParser(currentRecord.id);
const {
documentUploadVisible,
hideDocumentUploadModal,
showDocumentUploadModal,
onDocumentUploadOk,
documentUploadLoading,
} = useHandleUploadDocument();
const {
webCrawlUploadVisible,
hideWebCrawlUploadModal,
showWebCrawlUploadModal,
onWebCrawlUploadOk,
webCrawlUploadLoading,
} = useHandleWebCrawl();
const { t } = useTranslation('translation', {
keyPrefix: 'knowledgeDetails',
});
const rowSelection = useGetRowSelection();
const columns: ColumnsType<IKnowledgeFile> = [
{
title: t('name'),
dataIndex: 'name',
key: 'name',
fixed: 'left',
render: (text: any, { id, thumbnail, name }) => (
<div className={styles.toChunks} onClick={() => toChunk(id)}>
<Flex gap={10} align="center">
{thumbnail ? (
<img className={styles.img} src={thumbnail} alt="" />
) : (
<SvgIcon
name={`file-icon/${getExtension(name)}`}
width={24}
></SvgIcon>
)}
<Text ellipsis={{ tooltip: text }} className={styles.nameText}>
{text}
</Text>
</Flex>
</div>
),
},
{
title: t('chunkNumber'),
dataIndex: 'chunk_num',
key: 'chunk_num',
},
{
title: t('uploadDate'),
dataIndex: 'create_time',
key: 'create_time',
render(value) {
return formatDate(value);
},
},
{
title: t('chunkMethod'),
dataIndex: 'parser_id',
key: 'parser_id',
render: (text) => {
return parserList.find((x) => x.value === text)?.label;
},
},
{
title: t('enabled'),
key: 'status',
dataIndex: 'status',
render: (_, { status, id }) => (
<>
<Switch
checked={status === '1'}
onChange={(e) => {
onChangeStatus(e, id);
}}
/>
</>
),
},
{
title: t('parsingStatus'),
dataIndex: 'run',
key: 'run',
render: (text, record) => {
return <ParsingStatusCell record={record}></ParsingStatusCell>;
},
},
{
title: t('action'),
key: 'action',
render: (_, record) => (
<ParsingActionCell
setCurrentRecord={setRecord}
showRenameModal={showRenameModal}
showChangeParserModal={showChangeParserModal}
record={record}
></ParsingActionCell>
),
},
];
const finalColumns = columns.map((x) => ({
...x,
className: `${styles.column}`,
}));
return (
<div className={styles.datasetWrapper}>
<h3>{t('dataset')}</h3>
<p>{t('datasetDescription')}</p>
<Divider></Divider>
<DocumentToolbar
selectedRowKeys={rowSelection.selectedRowKeys as string[]}
showCreateModal={showCreateModal}
showWebCrawlModal={showWebCrawlUploadModal}
showDocumentUploadModal={showDocumentUploadModal}
></DocumentToolbar>
<Table
rowKey="id"
columns={finalColumns}
dataSource={data}
// loading={loading}
pagination={pagination}
rowSelection={rowSelection}
className={styles.documentTable}
scroll={{ scrollToFirstRowOnChange: true, x: 1300 }}
/>
<CreateFileModal
visible={createVisible}
hideModal={hideCreateModal}
loading={createLoading}
onOk={onCreateOk}
/>
<ChunkMethodModal
documentId={currentRecord.id}
parserId={currentRecord.parser_id}
parserConfig={currentRecord.parser_config}
documentExtension={getExtension(currentRecord.name)}
onOk={onChangeParserOk}
visible={changeParserVisible}
hideModal={hideChangeParserModal}
loading={changeParserLoading}
/>
<RenameModal
visible={renameVisible}
onOk={onRenameOk}
loading={renameLoading}
hideModal={hideRenameModal}
initialName={currentRecord.name}
></RenameModal>
<FileUploadModal
visible={documentUploadVisible}
hideModal={hideDocumentUploadModal}
loading={documentUploadLoading}
onOk={onDocumentUploadOk}
></FileUploadModal>
<WebCrawlModal
visible={webCrawlUploadVisible}
hideModal={hideWebCrawlUploadModal}
loading={webCrawlUploadLoading}
onOk={onWebCrawlUploadOk}
></WebCrawlModal>
</div>
);
};
export default KnowledgeFile;
import ChunkMethodModal from '@/components/chunk-method-modal';
import SvgIcon from '@/components/svg-icon';
import {
useSelectDocumentList,
useSetDocumentStatus,
} from '@/hooks/documentHooks';
import { useSetSelectedRecord } from '@/hooks/logic-hooks';
import { useSelectParserList } from '@/hooks/userSettingHook';
import { IKnowledgeFile } from '@/interfaces/database/knowledge';
import { getExtension } from '@/utils/documentUtils';
import { Divider, Flex, Switch, Table, Typography } from 'antd';
import type { ColumnsType } from 'antd/es/table';
import { useTranslation } from 'react-i18next';
import CreateFileModal from './create-file-modal';
import DocumentToolbar from './document-toolbar';
import {
useChangeDocumentParser,
useCreateEmptyDocument,
useFetchDocumentListOnMount,
useGetPagination,
useGetRowSelection,
useHandleUploadDocument,
useHandleWebCrawl,
useNavigateToOtherPage,
useRenameDocument,
} from './hooks';
import ParsingActionCell from './parsing-action-cell';
import ParsingStatusCell from './parsing-status-cell';
import RenameModal from './rename-modal';
import WebCrawlModal from './web-crawl-modal';
import FileUploadModal from '@/components/file-upload-modal';
import { formatDate } from '@/utils/date';
import styles from './index.less';
const { Text } = Typography;
const KnowledgeFile = () => {
const data = useSelectDocumentList();
const { fetchDocumentList } = useFetchDocumentListOnMount();
const parserList = useSelectParserList();
const { pagination } = useGetPagination(fetchDocumentList);
const onChangeStatus = useSetDocumentStatus();
const { toChunk } = useNavigateToOtherPage();
const { currentRecord, setRecord } = useSetSelectedRecord();
const {
renameLoading,
onRenameOk,
renameVisible,
hideRenameModal,
showRenameModal,
} = useRenameDocument(currentRecord.id);
const {
createLoading,
onCreateOk,
createVisible,
hideCreateModal,
showCreateModal,
} = useCreateEmptyDocument();
const {
changeParserLoading,
onChangeParserOk,
changeParserVisible,
hideChangeParserModal,
showChangeParserModal,
} = useChangeDocumentParser(currentRecord.id);
const {
documentUploadVisible,
hideDocumentUploadModal,
showDocumentUploadModal,
onDocumentUploadOk,
documentUploadLoading,
} = useHandleUploadDocument();
const {
webCrawlUploadVisible,
hideWebCrawlUploadModal,
showWebCrawlUploadModal,
onWebCrawlUploadOk,
webCrawlUploadLoading,
} = useHandleWebCrawl();
const { t } = useTranslation('translation', {
keyPrefix: 'knowledgeDetails',
});
const rowSelection = useGetRowSelection();
const columns: ColumnsType<IKnowledgeFile> = [
{
title: t('name'),
dataIndex: 'name',
key: 'name',
fixed: 'left',
render: (text: any, { id, thumbnail, name }) => (
<div className={styles.toChunks} onClick={() => toChunk(id)}>
<Flex gap={10} align="center">
{thumbnail ? (
<img className={styles.img} src={thumbnail} alt="" />
) : (
<SvgIcon
name={`file-icon/${getExtension(name)}`}
width={24}
></SvgIcon>
)}
<Text ellipsis={{ tooltip: text }} className={styles.nameText}>
{text}
</Text>
</Flex>
</div>
),
},
{
title: t('chunkNumber'),
dataIndex: 'chunk_num',
key: 'chunk_num',
},
{
title: t('uploadDate'),
dataIndex: 'create_time',
key: 'create_time',
render(value) {
return formatDate(value);
},
},
{
title: t('chunkMethod'),
dataIndex: 'parser_id',
key: 'parser_id',
render: (text) => {
return parserList.find((x) => x.value === text)?.label;
},
},
{
title: t('enabled'),
key: 'status',
dataIndex: 'status',
render: (_, { status, id }) => (
<>
<Switch
checked={status === '1'}
onChange={(e) => {
onChangeStatus(e, id);
}}
/>
</>
),
},
{
title: t('parsingStatus'),
dataIndex: 'run',
key: 'run',
render: (text, record) => {
return <ParsingStatusCell record={record}></ParsingStatusCell>;
},
},
{
title: t('action'),
key: 'action',
render: (_, record) => (
<ParsingActionCell
setCurrentRecord={setRecord}
showRenameModal={showRenameModal}
showChangeParserModal={showChangeParserModal}
record={record}
></ParsingActionCell>
),
},
];
const finalColumns = columns.map((x) => ({
...x,
className: `${styles.column}`,
}));
return (
<div className={styles.datasetWrapper}>
<h3>{t('dataset')}</h3>
<p>{t('datasetDescription')}</p>
<Divider></Divider>
<DocumentToolbar
selectedRowKeys={rowSelection.selectedRowKeys as string[]}
showCreateModal={showCreateModal}
showWebCrawlModal={showWebCrawlUploadModal}
showDocumentUploadModal={showDocumentUploadModal}
></DocumentToolbar>
<Table
rowKey="id"
columns={finalColumns}
dataSource={data}
// loading={loading}
pagination={pagination}
rowSelection={rowSelection}
className={styles.documentTable}
scroll={{ scrollToFirstRowOnChange: true, x: 1300 }}
/>
<CreateFileModal
visible={createVisible}
hideModal={hideCreateModal}
loading={createLoading}
onOk={onCreateOk}
/>
<ChunkMethodModal
documentId={currentRecord.id}
parserId={currentRecord.parser_id}
parserConfig={currentRecord.parser_config}
documentExtension={getExtension(currentRecord.name)}
onOk={onChangeParserOk}
visible={changeParserVisible}
hideModal={hideChangeParserModal}
loading={changeParserLoading}
/>
<RenameModal
visible={renameVisible}
onOk={onRenameOk}
loading={renameLoading}
hideModal={hideRenameModal}
initialName={currentRecord.name}
></RenameModal>
<FileUploadModal
visible={documentUploadVisible}
hideModal={hideDocumentUploadModal}
loading={documentUploadLoading}
onOk={onDocumentUploadOk}
></FileUploadModal>
<WebCrawlModal
visible={webCrawlUploadVisible}
hideModal={hideWebCrawlUploadModal}
loading={webCrawlUploadLoading}
onOk={onWebCrawlUploadOk}
></WebCrawlModal>
</div>
);
};
export default KnowledgeFile;

View File

@ -15,7 +15,10 @@ import ModelSetting from './model-setting';
import PromptEngine from './prompt-engine';
import { useTranslate } from '@/hooks/commonHooks';
import { useFetchLlmModelOnVisible, useFetchModelId } from '@/hooks/logicHooks';
import {
useFetchLlmModelOnVisible,
useFetchModelId,
} from '@/hooks/logic-hooks';
import { getBase64FromUploadFileList } from '@/utils/fileUtil';
import { removeUselessFieldsFromValues } from '@/utils/form';
import styles from './index.less';

View File

@ -23,7 +23,7 @@ import {
useShowDeleteConfirm,
useTranslate,
} from '@/hooks/commonHooks';
import { useSendMessageWithSse } from '@/hooks/logicHooks';
import { useSendMessageWithSse } from '@/hooks/logic-hooks';
import { useOneNamespaceEffectsLoading } from '@/hooks/storeHooks';
import {
IAnswer,
@ -552,7 +552,7 @@ export const useSendMessage = (
const { handleInputChange, value, setValue } = useHandleMessageInputChange();
const { handleClickConversation } = useClickConversationCard();
const { send, answer, done } = useSendMessageWithSse();
const { send, answer, done, setDone } = useSendMessageWithSse();
const sendMessage = useCallback(
async (message: string, id?: string) => {
@ -609,11 +609,19 @@ export const useSendMessage = (
);
useEffect(() => {
// #1289
if (answer.answer && answer?.conversationId === conversationId) {
addNewestAnswer(answer);
}
}, [answer, addNewestAnswer, conversationId]);
useEffect(() => {
// #1289 switch to another conversion window when the last conversion answer doesn't finish.
if (conversationId) {
setDone(true);
}
}, [setDone, conversationId]);
const handlePressEnter = useCallback(() => {
if (trim(value) === '') return;

View File

@ -1,381 +1,381 @@
import { ReactComponent as ChatAppCube } from '@/assets/svg/chat-app-cube.svg';
import RenameModal from '@/components/rename-modal';
import {
CloudOutlined,
DeleteOutlined,
EditOutlined,
PlusOutlined,
} from '@ant-design/icons';
import {
Avatar,
Button,
Card,
Divider,
Dropdown,
Flex,
MenuProps,
Space,
Spin,
Tag,
Typography,
} from 'antd';
import { MenuItemProps } from 'antd/lib/menu/MenuItem';
import classNames from 'classnames';
import { useCallback } from 'react';
import ChatConfigurationModal from './chat-configuration-modal';
import ChatContainer from './chat-container';
import {
useClickConversationCard,
useClickDialogCard,
useDeleteConversation,
useDeleteDialog,
useEditDialog,
useFetchConversationListOnMount,
useFetchDialogOnMount,
useGetChatSearchParams,
useHandleItemHover,
useRenameConversation,
useSelectConversationListLoading,
useSelectDerivedConversationList,
useSelectDialogListLoading,
useSelectFirstDialogOnMount,
} from './hooks';
import { useSetModalState, useTranslate } from '@/hooks/commonHooks';
import { useSetSelectedRecord } from '@/hooks/logicHooks';
import { IDialog } from '@/interfaces/database/chat';
import ChatOverviewModal from './chat-overview-modal';
import styles from './index.less';
const { Text } = Typography;
const Chat = () => {
const dialogList = useSelectFirstDialogOnMount();
const { onRemoveDialog } = useDeleteDialog();
const { onRemoveConversation } = useDeleteConversation();
const { handleClickDialog } = useClickDialogCard();
const { handleClickConversation } = useClickConversationCard();
const { dialogId, conversationId } = useGetChatSearchParams();
const { list: conversationList, addTemporaryConversation } =
useSelectDerivedConversationList();
const { activated, handleItemEnter, handleItemLeave } = useHandleItemHover();
const {
activated: conversationActivated,
handleItemEnter: handleConversationItemEnter,
handleItemLeave: handleConversationItemLeave,
} = useHandleItemHover();
const {
conversationRenameLoading,
initialConversationName,
onConversationRenameOk,
conversationRenameVisible,
hideConversationRenameModal,
showConversationRenameModal,
} = useRenameConversation();
const {
dialogSettingLoading,
initialDialog,
onDialogEditOk,
dialogEditVisible,
clearDialog,
hideDialogEditModal,
showDialogEditModal,
} = useEditDialog();
const dialogLoading = useSelectDialogListLoading();
const conversationLoading = useSelectConversationListLoading();
const { t } = useTranslate('chat');
const {
visible: overviewVisible,
hideModal: hideOverviewModal,
showModal: showOverviewModal,
} = useSetModalState();
const { currentRecord, setRecord } = useSetSelectedRecord<IDialog>();
useFetchDialogOnMount(dialogId, true);
const handleAppCardEnter = (id: string) => () => {
handleItemEnter(id);
};
const handleConversationCardEnter = (id: string) => () => {
handleConversationItemEnter(id);
};
const handleShowChatConfigurationModal =
(dialogId?: string): any =>
(info: any) => {
info?.domEvent?.preventDefault();
info?.domEvent?.stopPropagation();
showDialogEditModal(dialogId);
};
const handleRemoveDialog =
(dialogId: string): MenuItemProps['onClick'] =>
({ domEvent }) => {
domEvent.preventDefault();
domEvent.stopPropagation();
onRemoveDialog([dialogId]);
};
const handleShowOverviewModal =
(dialog: IDialog): any =>
(info: any) => {
info?.domEvent?.preventDefault();
info?.domEvent?.stopPropagation();
setRecord(dialog);
showOverviewModal();
};
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) => () => {
handleClickDialog(dialogId);
};
const handleConversationCardClick = (dialogId: string) => () => {
handleClickConversation(dialogId);
};
const handleCreateTemporaryConversation = useCallback(() => {
addTemporaryConversation();
}, [addTemporaryConversation]);
const items: MenuProps['items'] = [
{
key: '1',
onClick: handleCreateTemporaryConversation,
label: (
<Space>
<PlusOutlined />
{t('newChat')}
</Space>
),
},
];
const buildAppItems = (dialog: IDialog) => {
const dialogId = dialog.id;
const appItems: MenuProps['items'] = [
{
key: '1',
onClick: handleShowChatConfigurationModal(dialogId),
label: (
<Space>
<EditOutlined />
{t('edit', { keyPrefix: 'common' })}
</Space>
),
},
{ type: 'divider' },
{
key: '2',
onClick: handleRemoveDialog(dialogId),
label: (
<Space>
<DeleteOutlined />
{t('delete', { keyPrefix: 'common' })}
</Space>
),
},
{ type: 'divider' },
{
key: '3',
onClick: handleShowOverviewModal(dialog),
label: (
<Space>
<CloudOutlined />
{t('overview')}
</Space>
),
},
];
return appItems;
};
const buildConversationItems = (conversationId: string) => {
const appItems: MenuProps['items'] = [
{
key: '1',
onClick: handleShowConversationRenameModal(conversationId),
label: (
<Space>
<EditOutlined />
{t('rename', { keyPrefix: 'common' })}
</Space>
),
},
{ type: 'divider' },
{
key: '2',
onClick: handleRemoveConversation(conversationId),
label: (
<Space>
<DeleteOutlined />
{t('delete', { keyPrefix: 'common' })}
</Space>
),
},
];
return appItems;
};
useFetchConversationListOnMount();
return (
<Flex className={styles.chatWrapper}>
<Flex className={styles.chatAppWrapper}>
<Flex flex={1} vertical>
<Button type="primary" onClick={handleShowChatConfigurationModal()}>
{t('createAssistant')}
</Button>
<Divider></Divider>
<Flex className={styles.chatAppContent} vertical gap={10}>
<Spin spinning={dialogLoading} wrapperClassName={styles.chatSpin}>
{dialogList.map((x) => (
<Card
key={x.id}
hoverable
className={classNames(styles.chatAppCard, {
[styles.chatAppCardSelected]: dialogId === x.id,
})}
onMouseEnter={handleAppCardEnter(x.id)}
onMouseLeave={handleItemLeave}
onClick={handleDialogCardClick(x.id)}
>
<Flex justify="space-between" align="center">
<Space size={15}>
<Avatar src={x.icon} shape={'square'} />
<section>
<b>
<Text
ellipsis={{ tooltip: x.name }}
style={{ width: 130 }}
>
{x.name}
</Text>
</b>
<div>{x.description}</div>
</section>
</Space>
{activated === x.id && (
<section>
<Dropdown menu={{ items: buildAppItems(x) }}>
<ChatAppCube
className={styles.cubeIcon}
></ChatAppCube>
</Dropdown>
</section>
)}
</Flex>
</Card>
))}
</Spin>
</Flex>
</Flex>
</Flex>
<Divider type={'vertical'} className={styles.divider}></Divider>
<Flex className={styles.chatTitleWrapper}>
<Flex flex={1} vertical>
<Flex
justify={'space-between'}
align="center"
className={styles.chatTitle}
>
<Space>
<b>{t('chat')}</b>
<Tag>{conversationList.length}</Tag>
</Space>
<Dropdown menu={{ items }}>
{/* <FormOutlined /> */}
<PlusOutlined />
</Dropdown>
</Flex>
<Divider></Divider>
<Flex vertical gap={10} className={styles.chatTitleContent}>
<Spin
spinning={conversationLoading}
wrapperClassName={styles.chatSpin}
>
{conversationList.map((x) => (
<Card
key={x.id}
hoverable
onClick={handleConversationCardClick(x.id)}
onMouseEnter={handleConversationCardEnter(x.id)}
onMouseLeave={handleConversationItemLeave}
className={classNames(styles.chatTitleCard, {
[styles.chatTitleCardSelected]: x.id === conversationId,
})}
>
<Flex justify="space-between" align="center">
<div>
<Text
ellipsis={{ tooltip: x.name }}
style={{ width: 150 }}
>
{x.name}
</Text>
</div>
{conversationActivated === x.id && x.id !== '' && (
<section>
<Dropdown
menu={{ items: buildConversationItems(x.id) }}
>
<ChatAppCube
className={styles.cubeIcon}
></ChatAppCube>
</Dropdown>
</section>
)}
</Flex>
</Card>
))}
</Spin>
</Flex>
</Flex>
</Flex>
<Divider type={'vertical'} className={styles.divider}></Divider>
<ChatContainer></ChatContainer>
<ChatConfigurationModal
visible={dialogEditVisible}
initialDialog={initialDialog}
showModal={showDialogEditModal}
hideModal={hideDialogEditModal}
loading={dialogSettingLoading}
onOk={onDialogEditOk}
clearDialog={clearDialog}
></ChatConfigurationModal>
<RenameModal
visible={conversationRenameVisible}
hideModal={hideConversationRenameModal}
onOk={onConversationRenameOk}
initialName={initialConversationName}
loading={conversationRenameLoading}
></RenameModal>
<ChatOverviewModal
visible={overviewVisible}
hideModal={hideOverviewModal}
dialog={currentRecord}
></ChatOverviewModal>
</Flex>
);
};
export default Chat;
import { ReactComponent as ChatAppCube } from '@/assets/svg/chat-app-cube.svg';
import RenameModal from '@/components/rename-modal';
import {
CloudOutlined,
DeleteOutlined,
EditOutlined,
PlusOutlined,
} from '@ant-design/icons';
import {
Avatar,
Button,
Card,
Divider,
Dropdown,
Flex,
MenuProps,
Space,
Spin,
Tag,
Typography,
} from 'antd';
import { MenuItemProps } from 'antd/lib/menu/MenuItem';
import classNames from 'classnames';
import { useCallback } from 'react';
import ChatConfigurationModal from './chat-configuration-modal';
import ChatContainer from './chat-container';
import {
useClickConversationCard,
useClickDialogCard,
useDeleteConversation,
useDeleteDialog,
useEditDialog,
useFetchConversationListOnMount,
useFetchDialogOnMount,
useGetChatSearchParams,
useHandleItemHover,
useRenameConversation,
useSelectConversationListLoading,
useSelectDerivedConversationList,
useSelectDialogListLoading,
useSelectFirstDialogOnMount,
} from './hooks';
import { useSetModalState, useTranslate } from '@/hooks/commonHooks';
import { useSetSelectedRecord } from '@/hooks/logic-hooks';
import { IDialog } from '@/interfaces/database/chat';
import ChatOverviewModal from './chat-overview-modal';
import styles from './index.less';
const { Text } = Typography;
const Chat = () => {
const dialogList = useSelectFirstDialogOnMount();
const { onRemoveDialog } = useDeleteDialog();
const { onRemoveConversation } = useDeleteConversation();
const { handleClickDialog } = useClickDialogCard();
const { handleClickConversation } = useClickConversationCard();
const { dialogId, conversationId } = useGetChatSearchParams();
const { list: conversationList, addTemporaryConversation } =
useSelectDerivedConversationList();
const { activated, handleItemEnter, handleItemLeave } = useHandleItemHover();
const {
activated: conversationActivated,
handleItemEnter: handleConversationItemEnter,
handleItemLeave: handleConversationItemLeave,
} = useHandleItemHover();
const {
conversationRenameLoading,
initialConversationName,
onConversationRenameOk,
conversationRenameVisible,
hideConversationRenameModal,
showConversationRenameModal,
} = useRenameConversation();
const {
dialogSettingLoading,
initialDialog,
onDialogEditOk,
dialogEditVisible,
clearDialog,
hideDialogEditModal,
showDialogEditModal,
} = useEditDialog();
const dialogLoading = useSelectDialogListLoading();
const conversationLoading = useSelectConversationListLoading();
const { t } = useTranslate('chat');
const {
visible: overviewVisible,
hideModal: hideOverviewModal,
showModal: showOverviewModal,
} = useSetModalState();
const { currentRecord, setRecord } = useSetSelectedRecord<IDialog>();
useFetchDialogOnMount(dialogId, true);
const handleAppCardEnter = (id: string) => () => {
handleItemEnter(id);
};
const handleConversationCardEnter = (id: string) => () => {
handleConversationItemEnter(id);
};
const handleShowChatConfigurationModal =
(dialogId?: string): any =>
(info: any) => {
info?.domEvent?.preventDefault();
info?.domEvent?.stopPropagation();
showDialogEditModal(dialogId);
};
const handleRemoveDialog =
(dialogId: string): MenuItemProps['onClick'] =>
({ domEvent }) => {
domEvent.preventDefault();
domEvent.stopPropagation();
onRemoveDialog([dialogId]);
};
const handleShowOverviewModal =
(dialog: IDialog): any =>
(info: any) => {
info?.domEvent?.preventDefault();
info?.domEvent?.stopPropagation();
setRecord(dialog);
showOverviewModal();
};
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) => () => {
handleClickDialog(dialogId);
};
const handleConversationCardClick = (dialogId: string) => () => {
handleClickConversation(dialogId);
};
const handleCreateTemporaryConversation = useCallback(() => {
addTemporaryConversation();
}, [addTemporaryConversation]);
const items: MenuProps['items'] = [
{
key: '1',
onClick: handleCreateTemporaryConversation,
label: (
<Space>
<PlusOutlined />
{t('newChat')}
</Space>
),
},
];
const buildAppItems = (dialog: IDialog) => {
const dialogId = dialog.id;
const appItems: MenuProps['items'] = [
{
key: '1',
onClick: handleShowChatConfigurationModal(dialogId),
label: (
<Space>
<EditOutlined />
{t('edit', { keyPrefix: 'common' })}
</Space>
),
},
{ type: 'divider' },
{
key: '2',
onClick: handleRemoveDialog(dialogId),
label: (
<Space>
<DeleteOutlined />
{t('delete', { keyPrefix: 'common' })}
</Space>
),
},
{ type: 'divider' },
{
key: '3',
onClick: handleShowOverviewModal(dialog),
label: (
<Space>
<CloudOutlined />
{t('overview')}
</Space>
),
},
];
return appItems;
};
const buildConversationItems = (conversationId: string) => {
const appItems: MenuProps['items'] = [
{
key: '1',
onClick: handleShowConversationRenameModal(conversationId),
label: (
<Space>
<EditOutlined />
{t('rename', { keyPrefix: 'common' })}
</Space>
),
},
{ type: 'divider' },
{
key: '2',
onClick: handleRemoveConversation(conversationId),
label: (
<Space>
<DeleteOutlined />
{t('delete', { keyPrefix: 'common' })}
</Space>
),
},
];
return appItems;
};
useFetchConversationListOnMount();
return (
<Flex className={styles.chatWrapper}>
<Flex className={styles.chatAppWrapper}>
<Flex flex={1} vertical>
<Button type="primary" onClick={handleShowChatConfigurationModal()}>
{t('createAssistant')}
</Button>
<Divider></Divider>
<Flex className={styles.chatAppContent} vertical gap={10}>
<Spin spinning={dialogLoading} wrapperClassName={styles.chatSpin}>
{dialogList.map((x) => (
<Card
key={x.id}
hoverable
className={classNames(styles.chatAppCard, {
[styles.chatAppCardSelected]: dialogId === x.id,
})}
onMouseEnter={handleAppCardEnter(x.id)}
onMouseLeave={handleItemLeave}
onClick={handleDialogCardClick(x.id)}
>
<Flex justify="space-between" align="center">
<Space size={15}>
<Avatar src={x.icon} shape={'square'} />
<section>
<b>
<Text
ellipsis={{ tooltip: x.name }}
style={{ width: 130 }}
>
{x.name}
</Text>
</b>
<div>{x.description}</div>
</section>
</Space>
{activated === x.id && (
<section>
<Dropdown menu={{ items: buildAppItems(x) }}>
<ChatAppCube
className={styles.cubeIcon}
></ChatAppCube>
</Dropdown>
</section>
)}
</Flex>
</Card>
))}
</Spin>
</Flex>
</Flex>
</Flex>
<Divider type={'vertical'} className={styles.divider}></Divider>
<Flex className={styles.chatTitleWrapper}>
<Flex flex={1} vertical>
<Flex
justify={'space-between'}
align="center"
className={styles.chatTitle}
>
<Space>
<b>{t('chat')}</b>
<Tag>{conversationList.length}</Tag>
</Space>
<Dropdown menu={{ items }}>
{/* <FormOutlined /> */}
<PlusOutlined />
</Dropdown>
</Flex>
<Divider></Divider>
<Flex vertical gap={10} className={styles.chatTitleContent}>
<Spin
spinning={conversationLoading}
wrapperClassName={styles.chatSpin}
>
{conversationList.map((x) => (
<Card
key={x.id}
hoverable
onClick={handleConversationCardClick(x.id)}
onMouseEnter={handleConversationCardEnter(x.id)}
onMouseLeave={handleConversationItemLeave}
className={classNames(styles.chatTitleCard, {
[styles.chatTitleCardSelected]: x.id === conversationId,
})}
>
<Flex justify="space-between" align="center">
<div>
<Text
ellipsis={{ tooltip: x.name }}
style={{ width: 150 }}
>
{x.name}
</Text>
</div>
{conversationActivated === x.id && x.id !== '' && (
<section>
<Dropdown
menu={{ items: buildConversationItems(x.id) }}
>
<ChatAppCube
className={styles.cubeIcon}
></ChatAppCube>
</Dropdown>
</section>
)}
</Flex>
</Card>
))}
</Spin>
</Flex>
</Flex>
</Flex>
<Divider type={'vertical'} className={styles.divider}></Divider>
<ChatContainer></ChatContainer>
<ChatConfigurationModal
visible={dialogEditVisible}
initialDialog={initialDialog}
showModal={showDialogEditModal}
hideModal={hideDialogEditModal}
loading={dialogSettingLoading}
onOk={onDialogEditOk}
clearDialog={clearDialog}
></ChatConfigurationModal>
<RenameModal
visible={conversationRenameVisible}
hideModal={hideConversationRenameModal}
onOk={onConversationRenameOk}
initialName={initialConversationName}
loading={conversationRenameLoading}
></RenameModal>
<ChatOverviewModal
visible={overviewVisible}
hideModal={hideOverviewModal}
dialog={currentRecord}
></ChatOverviewModal>
</Flex>
);
};
export default Chat;

View File

@ -3,7 +3,7 @@ import {
useCreateSharedConversation,
useFetchSharedConversation,
} from '@/hooks/chatHooks';
import { useSendMessageWithSse } from '@/hooks/logicHooks';
import { useSendMessageWithSse } from '@/hooks/logic-hooks';
import { useOneNamespaceEffectsLoading } from '@/hooks/storeHooks';
import { IAnswer } from '@/interfaces/database/chat';
import api from '@/utils/api';

View File

@ -10,7 +10,7 @@ import {
useSelectParentFolderList,
useUploadFile,
} from '@/hooks/fileManagerHooks';
import { useGetPagination, useSetPagination } from '@/hooks/logicHooks';
import { useGetPagination, useSetPagination } from '@/hooks/logic-hooks';
import { useOneNamespaceEffectsLoading } from '@/hooks/storeHooks';
import { IFile } from '@/interfaces/database/file-manager';
import { PaginationProps } from 'antd';

View File

@ -4,7 +4,7 @@ import {
useHandleMessageInputChange,
useScrollToBottom,
useSendMessageWithSse,
} from '@/hooks/logicHooks';
} from '@/hooks/logic-hooks';
import { IAnswer } from '@/interfaces/database/chat';
import { IMessage } from '@/pages/chat/interface';
import api from '@/utils/api';

View File

@ -18,7 +18,7 @@ import {
ModelVariableType,
settledModelVariableMap,
} from '@/constants/knowledge';
import { useFetchModelId, useSendMessageWithSse } from '@/hooks/logicHooks';
import { useFetchModelId, useSendMessageWithSse } from '@/hooks/logic-hooks';
import { Variable } from '@/interfaces/database/chat';
import api from '@/utils/api';
import { useDebounceEffect } from 'ahooks';

View File

@ -1,7 +1,7 @@
import { IModalManagerChildrenProps } from '@/components/modal-manager';
import { useTranslate } from '@/hooks/commonHooks';
import { useFetchFlowTemplates } from '@/hooks/flow-hooks';
import { useSelectItem } from '@/hooks/logicHooks';
import { useSelectItem } from '@/hooks/logic-hooks';
import { Card, Flex, Form, Input, Modal, Space, Typography } from 'antd';
import classNames from 'classnames';
import { useEffect } from 'react';

View File

@ -32,7 +32,7 @@ import {
import { LanguageList } from '@/constants/common';
import { useTranslate } from '@/hooks/commonHooks';
import { useChangeLanguage } from '@/hooks/logicHooks';
import { useChangeLanguage } from '@/hooks/logic-hooks';
import parentStyles from '../index.less';
import styles from './index.less';