* feat: fixed the issue where the next button would float above the file list on the file upload page. * feat: fixed the issue where the last message would keep loading if the backend reported an error during chat. * fix: fixed the issue that the prompt word for registering an account is not in Englishtags/v0.1.0
| @@ -8,6 +8,7 @@ | |||
| } | |||
| .footer { | |||
| text-align: right; | |||
| padding-top: 16px; | |||
| .nextButton { | |||
| background-color: @purple; | |||
| } | |||
| @@ -17,8 +18,9 @@ | |||
| padding-top: 60px; | |||
| } | |||
| .uploader { | |||
| display: block; | |||
| height: 200px; | |||
| :global(.ant-upload) { | |||
| height: 126px; | |||
| } | |||
| } | |||
| .hiddenUploader { | |||
| :global(.ant-upload-drag) { | |||
| @@ -29,6 +31,17 @@ | |||
| font-size: 40px; | |||
| align-items: end; | |||
| } | |||
| .uploaderButton { | |||
| padding: 10px; | |||
| vertical-align: middle; | |||
| height: 40px; | |||
| } | |||
| .uploaderIcon { | |||
| svg { | |||
| width: 20px; | |||
| height: 20px; | |||
| } | |||
| } | |||
| .deleteIcon { | |||
| font-size: 20px; | |||
| } | |||
| @@ -1,5 +1,6 @@ | |||
| import { ReactComponent as SelectFilesEndIcon } from '@/assets/svg/select-files-end.svg'; | |||
| import { ReactComponent as SelectFilesStartIcon } from '@/assets/svg/select-files-start.svg'; | |||
| import { KnowledgeRouteKey } from '@/constants/knowledge'; | |||
| import { | |||
| useDeleteDocumentById, | |||
| useFetchKnowledgeDetail, | |||
| @@ -10,14 +11,14 @@ import { | |||
| useFetchTenantInfo, | |||
| useSelectParserList, | |||
| } from '@/hooks/userSettingHook'; | |||
| import uploadService from '@/services/uploadService'; | |||
| import { isFileUploadDone } from '@/utils/documentUtils'; | |||
| import { | |||
| ArrowLeftOutlined, | |||
| CloudUploadOutlined, | |||
| DeleteOutlined, | |||
| EditOutlined, | |||
| FileDoneOutlined, | |||
| InboxOutlined, | |||
| } from '@ant-design/icons'; | |||
| import { | |||
| Button, | |||
| @@ -43,8 +44,6 @@ import { | |||
| } from 'react'; | |||
| import { Link, useDispatch, useNavigate } from 'umi'; | |||
| import { KnowledgeRouteKey } from '@/constants/knowledge'; | |||
| import { isFileUploadDone } from '@/utils/documentUtils'; | |||
| import styles from './index.less'; | |||
| const { Dragger } = Upload; | |||
| @@ -290,9 +289,9 @@ const KnowledgeUploadFile = () => { | |||
| [styles.hiddenUploader]: !isUpload, | |||
| })} | |||
| > | |||
| <p className="ant-upload-drag-icon"> | |||
| <InboxOutlined /> | |||
| </p> | |||
| <Button className={styles.uploaderButton}> | |||
| <CloudUploadOutlined className={styles.uploaderIcon} /> | |||
| </Button> | |||
| <p className="ant-upload-text"> | |||
| Click or drag file to this area to upload | |||
| </p> | |||
| @@ -27,7 +27,7 @@ const ChunkTitle = ({ item }: { item: ITestingChunk }) => { | |||
| {similarityList.map((x) => ( | |||
| <Space key={x.field}> | |||
| <span className={styles.similarityCircle}> | |||
| {((item[x.field] as number) * 100).toFixed(2)}% | |||
| {((item[x.field] as number) * 100).toFixed(2)} | |||
| </span> | |||
| <span className={styles.similarityText}>{x.label}</span> | |||
| </Space> | |||
| @@ -4,7 +4,6 @@ import NewDocumentLink from '@/components/new-document-link'; | |||
| import DocumentPreviewer from '@/components/pdf-previewer'; | |||
| import { MessageType } from '@/constants/chat'; | |||
| import { useSelectFileThumbnails } from '@/hooks/knowledgeHook'; | |||
| import { useOneNamespaceEffectsLoading } from '@/hooks/storeHooks'; | |||
| import { useSelectUserInfo } from '@/hooks/userSettingHook'; | |||
| import { IReference, Message } from '@/interfaces/database/chat'; | |||
| import { IChunk } from '@/interfaces/database/knowledge'; | |||
| @@ -21,7 +20,7 @@ import { | |||
| Space, | |||
| } from 'antd'; | |||
| import classNames from 'classnames'; | |||
| import { ChangeEventHandler, useCallback, useMemo, useState } from 'react'; | |||
| import { useCallback, useMemo } from 'react'; | |||
| import Markdown from 'react-markdown'; | |||
| import reactStringReplace from 'react-string-replace'; | |||
| import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; | |||
| @@ -243,35 +242,19 @@ const MessageItem = ({ | |||
| }; | |||
| const ChatContainer = () => { | |||
| const [value, setValue] = useState(''); | |||
| const { | |||
| ref, | |||
| currentConversation: conversation, | |||
| addNewestConversation, | |||
| removeLatestMessage, | |||
| } = useFetchConversationOnMount(); | |||
| const { sendMessage } = useSendMessage(); | |||
| const { handleInputChange, handlePressEnter, value, loading } = | |||
| useSendMessage(conversation, addNewestConversation, removeLatestMessage); | |||
| const { visible, hideModal, documentId, selectedChunk, clickDocumentButton } = | |||
| useClickDrawer(); | |||
| const loading = useOneNamespaceEffectsLoading('chatModel', [ | |||
| 'completeConversation', | |||
| ]); | |||
| useGetFileIcon(); | |||
| const handlePressEnter = () => { | |||
| if (!loading) { | |||
| setValue(''); | |||
| addNewestConversation(value); | |||
| sendMessage(value.trim()); | |||
| } | |||
| }; | |||
| const handleInputChange: ChangeEventHandler<HTMLInputElement> = (e) => { | |||
| const value = e.target.value; | |||
| const nextValue = value.replaceAll('\\n', '\n').replaceAll('\\t', '\t'); | |||
| setValue(nextValue); | |||
| }; | |||
| return ( | |||
| <> | |||
| <Flex flex={1} className={styles.chatContainer} vertical> | |||
| @@ -7,7 +7,14 @@ 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'; | |||
| import { | |||
| ChangeEventHandler, | |||
| useCallback, | |||
| useEffect, | |||
| useMemo, | |||
| useRef, | |||
| useState, | |||
| } from 'react'; | |||
| import { useDispatch, useSearchParams, useSelector } from 'umi'; | |||
| import { v4 as uuid } from 'uuid'; | |||
| import { ChatSearchParams } from './constants'; | |||
| @@ -432,6 +439,16 @@ export const useSelectCurrentConversation = () => { | |||
| }); | |||
| }, []); | |||
| const removeLatestMessage = useCallback(() => { | |||
| setCurrentConversation((pre) => { | |||
| const nextMessages = pre.message.slice(0, -2); | |||
| return { | |||
| ...pre, | |||
| message: nextMessages, | |||
| }; | |||
| }); | |||
| }, []); | |||
| const addPrologue = useCallback(() => { | |||
| if (dialogId !== '' && conversationId === '') { | |||
| const prologue = dialog.prompt_config?.prologue; | |||
| @@ -459,7 +476,7 @@ export const useSelectCurrentConversation = () => { | |||
| setCurrentConversation(conversation); | |||
| }, [conversation]); | |||
| return { currentConversation, addNewestConversation }; | |||
| return { currentConversation, addNewestConversation, removeLatestMessage }; | |||
| }; | |||
| export const useFetchConversation = () => { | |||
| @@ -501,7 +518,7 @@ export const useScrollToBottom = (currentConversation: IClientConversation) => { | |||
| export const useFetchConversationOnMount = () => { | |||
| const { conversationId } = useGetChatSearchParams(); | |||
| const fetchConversation = useFetchConversation(); | |||
| const { currentConversation, addNewestConversation } = | |||
| const { currentConversation, addNewestConversation, removeLatestMessage } = | |||
| useSelectCurrentConversation(); | |||
| const ref = useScrollToBottom(currentConversation); | |||
| @@ -515,16 +532,45 @@ export const useFetchConversationOnMount = () => { | |||
| fetchConversationOnMount(); | |||
| }, [fetchConversationOnMount]); | |||
| return { currentConversation, addNewestConversation, ref }; | |||
| return { | |||
| currentConversation, | |||
| addNewestConversation, | |||
| ref, | |||
| removeLatestMessage, | |||
| }; | |||
| }; | |||
| export const useHandleMessageInputChange = () => { | |||
| const [value, setValue] = useState(''); | |||
| const handleInputChange: ChangeEventHandler<HTMLInputElement> = (e) => { | |||
| const value = e.target.value; | |||
| const nextValue = value.replaceAll('\\n', '\n').replaceAll('\\t', '\t'); | |||
| setValue(nextValue); | |||
| }; | |||
| return { | |||
| handleInputChange, | |||
| value, | |||
| setValue, | |||
| }; | |||
| }; | |||
| export const useSendMessage = () => { | |||
| export const useSendMessage = ( | |||
| conversation: IClientConversation, | |||
| addNewestConversation: (message: string) => void, | |||
| removeLatestMessage: () => void, | |||
| ) => { | |||
| const loading = useOneNamespaceEffectsLoading('chatModel', [ | |||
| 'completeConversation', | |||
| ]); | |||
| const dispatch = useDispatch(); | |||
| const { setConversation } = useSetConversation(); | |||
| const { conversationId } = useGetChatSearchParams(); | |||
| const conversation: IClientConversation = useSelector( | |||
| (state: any) => state.chatModel.currentConversation, | |||
| ); | |||
| const { handleInputChange, value, setValue } = useHandleMessageInputChange(); | |||
| // const conversation: IClientConversation = useSelector( | |||
| // (state: any) => state.chatModel.currentConversation, | |||
| // ); | |||
| const fetchConversation = useFetchConversation(); | |||
| const { handleClickConversation } = useClickConversationCard(); | |||
| @@ -549,10 +595,15 @@ export const useSendMessage = () => { | |||
| if (retcode === 0) { | |||
| if (id) { | |||
| // new conversation | |||
| handleClickConversation(id); | |||
| } else { | |||
| fetchConversation(conversationId); | |||
| } | |||
| } else { | |||
| // cancel loading | |||
| setValue(message); | |||
| removeLatestMessage(); | |||
| } | |||
| }, | |||
| [ | |||
| @@ -561,6 +612,8 @@ export const useSendMessage = () => { | |||
| conversationId, | |||
| fetchConversation, | |||
| handleClickConversation, | |||
| removeLatestMessage, | |||
| setValue, | |||
| ], | |||
| ); | |||
| @@ -579,7 +632,20 @@ export const useSendMessage = () => { | |||
| [conversationId, setConversation, sendMessage], | |||
| ); | |||
| return { sendMessage: handleSendMessage }; | |||
| const handlePressEnter = () => { | |||
| if (!loading) { | |||
| setValue(''); | |||
| addNewestConversation(value); | |||
| handleSendMessage(value.trim()); | |||
| } | |||
| }; | |||
| return { | |||
| handlePressEnter, | |||
| handleInputChange, | |||
| value, | |||
| loading, | |||
| }; | |||
| }; | |||
| export const useGetFileIcon = () => { | |||
| @@ -115,7 +115,11 @@ const Login = () => { | |||
| label="Password" | |||
| rules={[{ required: true, message: 'Please input value' }]} | |||
| > | |||
| <Input.Password size="large" placeholder="Please input value" /> | |||
| <Input.Password | |||
| size="large" | |||
| placeholder="Please input value" | |||
| onPressEnter={onCheck} | |||
| /> | |||
| </Form.Item> | |||
| {title === 'login' && ( | |||
| <Form.Item name="remember" valuePropName="checked"> | |||
| @@ -51,7 +51,7 @@ const model: DvaModel<LoginModelState> = { | |||
| console.log(); | |||
| const { retcode } = data; | |||
| if (retcode === 0) { | |||
| message.success('注册成功!'); | |||
| message.success('Registered!'); | |||
| } | |||
| return retcode; | |||
| }, | |||