* feat: add temporary conversation * feat: add avatar to MessageItem * feat: render message referencetags/v0.1.0
| @@ -25,8 +25,11 @@ | |||
| "react-chat-elements": "^12.0.13", | |||
| "react-i18next": "^14.0.0", | |||
| "react-infinite-scroll-component": "^6.1.0", | |||
| "react-markdown": "^9.0.1", | |||
| "react-string-replace": "^1.1.1", | |||
| "umi": "^4.0.90", | |||
| "umi-request": "^1.4.0", | |||
| "unist-util-visit-parents": "^6.0.1", | |||
| "uuid": "^9.0.1" | |||
| }, | |||
| "devDependencies": { | |||
| @@ -0,0 +1,22 @@ | |||
| import { IUserInfo } from '@/interfaces/database/userSetting'; | |||
| import { useCallback, useEffect } from 'react'; | |||
| import { useDispatch, useSelector } from 'umi'; | |||
| export const useFetchUserInfo = () => { | |||
| const dispatch = useDispatch(); | |||
| const fetchUserInfo = useCallback(() => { | |||
| dispatch({ type: 'settingModel/getUserInfo' }); | |||
| }, [dispatch]); | |||
| useEffect(() => { | |||
| fetchUserInfo(); | |||
| }, [fetchUserInfo]); | |||
| }; | |||
| export const useSelectUserInfo = () => { | |||
| const userInfo: IUserInfo = useSelector( | |||
| (state: any) => state.settingModel.userInfo, | |||
| ); | |||
| return userInfo; | |||
| }; | |||
| @@ -54,7 +54,7 @@ export interface IConversation { | |||
| dialog_id: string; | |||
| id: string; | |||
| message: Message[]; | |||
| reference: any[]; | |||
| reference: IReference[]; | |||
| name: string; | |||
| update_date: string; | |||
| update_time: number; | |||
| @@ -64,3 +64,29 @@ export interface Message { | |||
| content: string; | |||
| role: MessageType; | |||
| } | |||
| export interface IReference { | |||
| chunks: Chunk[]; | |||
| doc_aggs: Docagg[]; | |||
| total: number; | |||
| } | |||
| interface Docagg { | |||
| count: number; | |||
| doc_id: string; | |||
| doc_name: string; | |||
| } | |||
| interface Chunk { | |||
| chunk_id: string; | |||
| content_ltks: string; | |||
| content_with_weight: string; | |||
| doc_id: string; | |||
| docnm_kwd: string; | |||
| img_id: string; | |||
| important_kwd: any[]; | |||
| kb_id: string; | |||
| similarity: number; | |||
| term_similarity: number; | |||
| vector_similarity: number; | |||
| } | |||
| @@ -0,0 +1,21 @@ | |||
| export interface IUserInfo { | |||
| access_token: string; | |||
| avatar?: any; | |||
| color_schema: string; | |||
| create_date: string; | |||
| create_time: number; | |||
| email: string; | |||
| id: string; | |||
| is_active: string; | |||
| is_anonymous: string; | |||
| is_authenticated: string; | |||
| is_superuser: boolean; | |||
| language: string; | |||
| last_login_time: string; | |||
| login_channel: string; | |||
| nickname: string; | |||
| password: string; | |||
| status: string; | |||
| update_date: string; | |||
| update_time: number; | |||
| } | |||
| @@ -19,17 +19,20 @@ const RagHeader = () => { | |||
| const navigate = useNavigate(); | |||
| const { pathname } = useLocation(); | |||
| const tagsData = [ | |||
| { path: '/knowledge', name: 'Knowledge Base', icon: KnowledgeBaseIcon }, | |||
| { path: '/chat', name: 'Chat', icon: StarIon }, | |||
| { path: '/file', name: 'File Management', icon: FileIcon }, | |||
| ]; | |||
| const tagsData = useMemo( | |||
| () => [ | |||
| { path: '/knowledge', name: 'Knowledge Base', icon: KnowledgeBaseIcon }, | |||
| { path: '/chat', name: 'Chat', icon: StarIon }, | |||
| { path: '/file', name: 'File Management', icon: FileIcon }, | |||
| ], | |||
| [], | |||
| ); | |||
| const currentPath = useMemo(() => { | |||
| return ( | |||
| tagsData.find((x) => pathname.startsWith(x.path))?.name || 'knowledge' | |||
| ); | |||
| }, [pathname]); | |||
| }, [pathname, tagsData]); | |||
| const handleChange = (path: string) => { | |||
| navigate(path); | |||
| @@ -48,7 +51,7 @@ const RagHeader = () => { | |||
| > | |||
| <Space size={12}> | |||
| <Logo className={styles.appIcon}></Logo> | |||
| <label className={styles.appName}>Infinity flow</label> | |||
| <label className={styles.appName}>RagFlow</label> | |||
| </Space> | |||
| <Space size={[0, 8]} wrap> | |||
| <Radio.Group | |||
| @@ -1,3 +1,4 @@ | |||
| import { useFetchUserInfo, useSelectUserInfo } from '@/hooks/userSettingHook'; | |||
| import authorizationUtil from '@/utils/authorizationUtil'; | |||
| import type { MenuProps } from 'antd'; | |||
| import { Avatar, Button, Dropdown } from 'antd'; | |||
| @@ -7,6 +8,7 @@ import { history } from 'umi'; | |||
| const App: React.FC = () => { | |||
| const { t } = useTranslation(); | |||
| const userInfo = useSelectUserInfo(); | |||
| const logout = () => { | |||
| authorizationUtil.removeAll(); | |||
| @@ -36,13 +38,18 @@ const App: React.FC = () => { | |||
| ), | |||
| }, | |||
| ]; | |||
| }, []); | |||
| }, [t]); | |||
| useFetchUserInfo(); | |||
| return ( | |||
| <Dropdown menu={{ items }} placement="bottomLeft" arrow> | |||
| <Avatar | |||
| size={32} | |||
| src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png" | |||
| src={ | |||
| userInfo.avatar ?? | |||
| 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png' | |||
| } | |||
| /> | |||
| </Dropdown> | |||
| ); | |||
| @@ -1,11 +1,34 @@ | |||
| .chatContainer { | |||
| padding: 0 24px 24px; | |||
| .messageContainer { | |||
| overflow-y: auto; | |||
| } | |||
| } | |||
| .messageItem { | |||
| .messageItemContent { | |||
| padding: 24px 0; | |||
| .messageItemSection { | |||
| display: inline-block; | |||
| width: 300px; | |||
| } | |||
| .messageItemSectionLeft { | |||
| width: 70%; | |||
| } | |||
| .messageItemSectionRight { | |||
| width: 30%; | |||
| } | |||
| .messageItemContent { | |||
| display: inline-flex; | |||
| gap: 20px; | |||
| } | |||
| .messageItemContentReverse { | |||
| flex-direction: row-reverse; | |||
| } | |||
| .messageText { | |||
| padding: 0 14px; | |||
| background-color: rgba(249, 250, 251, 1); | |||
| } | |||
| .referenceIcon { | |||
| padding: 0 6px; | |||
| } | |||
| } | |||
| @@ -1,17 +1,59 @@ | |||
| import { Button, Flex, Input, Typography } from 'antd'; | |||
| import { ChangeEventHandler, useState } from 'react'; | |||
| import { Message } from '@/interfaces/database/chat'; | |||
| import { ReactComponent as AssistantIcon } from '@/assets/svg/assistant.svg'; | |||
| 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 classNames from 'classnames'; | |||
| import { ChangeEventHandler, useCallback, useMemo, useState } from 'react'; | |||
| import reactStringReplace from 'react-string-replace'; | |||
| import { useFetchConversation, useSendMessage } from '../hooks'; | |||
| import { MessageType } from '@/constants/chat'; | |||
| import { IClientConversation } from '../interface'; | |||
| import { InfoCircleOutlined } from '@ant-design/icons'; | |||
| import Markdown from 'react-markdown'; | |||
| import { visitParents } from 'unist-util-visit-parents'; | |||
| import styles from './index.less'; | |||
| const { Paragraph } = Typography; | |||
| const rehypeWrapReference = () => { | |||
| return function wrapTextTransform(tree: any) { | |||
| visitParents(tree, 'text', (node, ancestors) => { | |||
| if (ancestors.at(-1).tagName !== 'custom-typography') { | |||
| node.type = 'element'; | |||
| node.tagName = 'custom-typography'; | |||
| node.properties = {}; | |||
| node.children = [{ type: 'text', value: node.value }]; | |||
| } | |||
| }); | |||
| }; | |||
| }; | |||
| const MessageItem = ({ item }: { item: Message; references: IReference[] }) => { | |||
| const userInfo = useSelectUserInfo(); | |||
| const popoverContent = useMemo( | |||
| () => ( | |||
| <div> | |||
| <p>Content</p> | |||
| <p>Content</p> | |||
| </div> | |||
| ), | |||
| [], | |||
| ); | |||
| const renderReference = useCallback( | |||
| (text: string) => { | |||
| return reactStringReplace(text, /#{2}\d{1,}\${2}/g, (match, i) => { | |||
| return ( | |||
| <Popover content={popoverContent}> | |||
| <InfoCircleOutlined key={i} className={styles.referenceIcon} /> | |||
| </Popover> | |||
| ); | |||
| }); | |||
| }, | |||
| [popoverContent], | |||
| ); | |||
| const MessageItem = ({ item }: { item: Message }) => { | |||
| return ( | |||
| <div | |||
| className={classNames(styles.messageItem, { | |||
| @@ -19,11 +61,50 @@ const MessageItem = ({ item }: { item: Message }) => { | |||
| [styles.messageItemRight]: item.role === MessageType.User, | |||
| })} | |||
| > | |||
| <span className={styles.messageItemContent}> | |||
| <Paragraph ellipsis={{ tooltip: item.content, rows: 3 }}> | |||
| {item.content} | |||
| </Paragraph> | |||
| </span> | |||
| <section | |||
| className={classNames(styles.messageItemSection, { | |||
| [styles.messageItemSectionLeft]: item.role === MessageType.Assistant, | |||
| [styles.messageItemSectionRight]: item.role === MessageType.User, | |||
| })} | |||
| > | |||
| <div | |||
| className={classNames(styles.messageItemContent, { | |||
| [styles.messageItemContentReverse]: item.role === MessageType.User, | |||
| })} | |||
| > | |||
| {item.role === MessageType.User ? ( | |||
| userInfo.avatar ?? ( | |||
| <Avatar | |||
| size={40} | |||
| src={ | |||
| userInfo.avatar ?? | |||
| 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png' | |||
| } | |||
| /> | |||
| ) | |||
| ) : ( | |||
| <AssistantIcon></AssistantIcon> | |||
| )} | |||
| <Flex vertical gap={8} flex={1}> | |||
| <b> | |||
| {item.role === MessageType.Assistant ? 'Resume Assistant' : 'You'} | |||
| </b> | |||
| <div className={styles.messageText}> | |||
| <Markdown | |||
| rehypePlugins={[rehypeWrapReference]} | |||
| components={ | |||
| { | |||
| 'custom-typography': ({ children }: { children: string }) => | |||
| renderReference(children), | |||
| } as any | |||
| } | |||
| > | |||
| {item.content} | |||
| </Markdown> | |||
| </div> | |||
| </Flex> | |||
| </div> | |||
| </section> | |||
| </div> | |||
| ); | |||
| }; | |||
| @@ -32,9 +113,13 @@ const ChatContainer = () => { | |||
| const [value, setValue] = useState(''); | |||
| const conversation: IClientConversation = useFetchConversation(); | |||
| const { sendMessage } = useSendMessage(); | |||
| const loading = useOneNamespaceEffectsLoading('chatModel', [ | |||
| 'completeConversation', | |||
| 'getConversation', | |||
| ]); | |||
| const handlePressEnter = () => { | |||
| console.info(value); | |||
| setValue(''); | |||
| sendMessage(value); | |||
| }; | |||
| @@ -44,17 +129,23 @@ const ChatContainer = () => { | |||
| return ( | |||
| <Flex flex={1} className={styles.chatContainer} vertical> | |||
| <Flex flex={1} vertical> | |||
| {conversation?.message?.map((message) => ( | |||
| <MessageItem key={message.id} item={message}></MessageItem> | |||
| ))} | |||
| <Flex flex={1} vertical className={styles.messageContainer}> | |||
| <div> | |||
| {conversation?.message?.map((message) => ( | |||
| <MessageItem | |||
| key={message.id} | |||
| item={message} | |||
| references={conversation.reference} | |||
| ></MessageItem> | |||
| ))} | |||
| </div> | |||
| </Flex> | |||
| <Input | |||
| size="large" | |||
| placeholder="Message Resume Assistant..." | |||
| value={value} | |||
| suffix={ | |||
| <Button type="primary" onClick={handlePressEnter}> | |||
| <Button type="primary" onClick={handlePressEnter} loading={loading}> | |||
| Send | |||
| </Button> | |||
| } | |||
| @@ -1,8 +1,8 @@ | |||
| import showDeleteConfirm from '@/components/deleting-confirm'; | |||
| import { MessageType } from '@/constants/chat'; | |||
| import { IDialog } from '@/interfaces/database/chat'; | |||
| import { IConversation, IDialog } from '@/interfaces/database/chat'; | |||
| import omit from 'lodash/omit'; | |||
| import { useCallback, useEffect, useMemo } from 'react'; | |||
| import { useCallback, useEffect, useMemo, useState } from 'react'; | |||
| import { useDispatch, useSearchParams, useSelector } from 'umi'; | |||
| import { v4 as uuid } from 'uuid'; | |||
| import { ChatSearchParams, EmptyConversationId } from './constants'; | |||
| @@ -11,6 +11,8 @@ import { | |||
| IMessage, | |||
| VariableTableDataType, | |||
| } from './interface'; | |||
| import { ChatModelState } from './model'; | |||
| import { isConversationIdNotExist } from './utils'; | |||
| export const useFetchDialogList = () => { | |||
| const dispatch = useDispatch(); | |||
| @@ -137,16 +139,46 @@ export const useRemoveDialog = () => { | |||
| return { onRemoveDialog }; | |||
| }; | |||
| export const useGetChatSearchParams = () => { | |||
| const [currentQueryParameters] = useSearchParams(); | |||
| return { | |||
| dialogId: currentQueryParameters.get(ChatSearchParams.DialogId) || '', | |||
| conversationId: | |||
| currentQueryParameters.get(ChatSearchParams.ConversationId) || '', | |||
| }; | |||
| }; | |||
| export const useSetCurrentConversation = () => { | |||
| const dispatch = useDispatch(); | |||
| const setCurrentConversation = useCallback( | |||
| (currentConversation: IClientConversation) => { | |||
| dispatch({ | |||
| type: 'chatModel/setCurrentConversation', | |||
| payload: currentConversation, | |||
| }); | |||
| }, | |||
| [dispatch], | |||
| ); | |||
| return setCurrentConversation; | |||
| }; | |||
| export const useClickDialogCard = () => { | |||
| const [currentQueryParameters, setSearchParams] = useSearchParams(); | |||
| const newQueryParameters: URLSearchParams = useMemo(() => { | |||
| return new URLSearchParams(currentQueryParameters.toString()); | |||
| }, [currentQueryParameters]); | |||
| return new URLSearchParams(); | |||
| }, []); | |||
| const handleClickDialog = useCallback( | |||
| (dialogId: string) => { | |||
| newQueryParameters.set(ChatSearchParams.DialogId, dialogId); | |||
| // newQueryParameters.set( | |||
| // ChatSearchParams.ConversationId, | |||
| // EmptyConversationId, | |||
| // ); | |||
| setSearchParams(newQueryParameters); | |||
| }, | |||
| [newQueryParameters, setSearchParams], | |||
| @@ -155,16 +187,6 @@ export const useClickDialogCard = () => { | |||
| return { handleClickDialog }; | |||
| }; | |||
| export const useGetChatSearchParams = () => { | |||
| const [currentQueryParameters] = useSearchParams(); | |||
| return { | |||
| dialogId: currentQueryParameters.get(ChatSearchParams.DialogId) || '', | |||
| conversationId: | |||
| currentQueryParameters.get(ChatSearchParams.ConversationId) || '', | |||
| }; | |||
| }; | |||
| export const useSelectFirstDialogOnMount = () => { | |||
| const dialogList = useFetchDialogList(); | |||
| const { dialogId } = useGetChatSearchParams(); | |||
| @@ -182,70 +204,42 @@ export const useSelectFirstDialogOnMount = () => { | |||
| //#region conversation | |||
| export const useFetchConversationList = (dialogId?: string) => { | |||
| const dispatch = useDispatch(); | |||
| const conversationList: any[] = useSelector( | |||
| (state: any) => state.chatModel.conversationList, | |||
| ); | |||
| const fetchConversationList = useCallback(() => { | |||
| if (dialogId) { | |||
| dispatch({ | |||
| type: 'chatModel/listConversation', | |||
| payload: { dialog_id: dialogId }, | |||
| }); | |||
| } | |||
| }, [dispatch, dialogId]); | |||
| useEffect(() => { | |||
| fetchConversationList(); | |||
| }, [fetchConversationList]); | |||
| return conversationList; | |||
| }; | |||
| export const useClickConversationCard = () => { | |||
| const [currentQueryParameters, setSearchParams] = useSearchParams(); | |||
| const newQueryParameters: URLSearchParams = new URLSearchParams( | |||
| currentQueryParameters.toString(), | |||
| ); | |||
| const handleClickConversation = (conversationId: string) => { | |||
| newQueryParameters.set(ChatSearchParams.ConversationId, conversationId); | |||
| setSearchParams(newQueryParameters); | |||
| }; | |||
| return { handleClickConversation }; | |||
| }; | |||
| export const useCreateTemporaryConversation = () => { | |||
| const dispatch = useDispatch(); | |||
| const { dialogId } = useGetChatSearchParams(); | |||
| const { handleClickConversation } = useClickConversationCard(); | |||
| let chatModel = useSelector((state: any) => state.chatModel); | |||
| let currentConversation: Pick< | |||
| const currentConversation: Pick< | |||
| IClientConversation, | |||
| 'id' | 'message' | 'name' | 'dialog_id' | |||
| > = chatModel.currentConversation; | |||
| let conversationList: IClientConversation[] = chatModel.conversationList; | |||
| const createTemporaryConversation = (message: string) => { | |||
| const messages = [...(currentConversation?.message ?? [])]; | |||
| const conversationList: IClientConversation[] = chatModel.conversationList; | |||
| const currentDialog: IDialog = chatModel.currentDialog; | |||
| const setCurrentConversation = useSetCurrentConversation(); | |||
| const createTemporaryConversation = useCallback(() => { | |||
| const firstConversation = conversationList[0]; | |||
| const messages = [...(firstConversation?.message ?? [])]; | |||
| if (messages.some((x) => x.id === EmptyConversationId)) { | |||
| return; | |||
| } | |||
| messages.unshift({ | |||
| messages.push({ | |||
| id: EmptyConversationId, | |||
| content: message, | |||
| content: currentDialog?.prompt_config?.prologue ?? '', | |||
| role: MessageType.Assistant, | |||
| }); | |||
| let nextCurrentConversation = currentConversation; | |||
| // It’s the back-end data. | |||
| if ('id' in currentConversation) { | |||
| currentConversation = { ...currentConversation, message: messages }; | |||
| nextCurrentConversation = { ...currentConversation, message: messages }; | |||
| } else { | |||
| // client data | |||
| currentConversation = { | |||
| nextCurrentConversation = { | |||
| id: EmptyConversationId, | |||
| name: 'New conversation', | |||
| dialog_id: dialogId, | |||
| @@ -255,23 +249,105 @@ export const useCreateTemporaryConversation = () => { | |||
| const nextConversationList = [...conversationList]; | |||
| nextConversationList.push(currentConversation as IClientConversation); | |||
| nextConversationList.unshift( | |||
| nextCurrentConversation as IClientConversation, | |||
| ); | |||
| dispatch({ | |||
| type: 'chatModel/setCurrentConversation', | |||
| payload: currentConversation, | |||
| }); | |||
| setCurrentConversation(nextCurrentConversation as IClientConversation); | |||
| dispatch({ | |||
| type: 'chatModel/setConversationList', | |||
| payload: nextConversationList, | |||
| }); | |||
| handleClickConversation(EmptyConversationId); | |||
| }; | |||
| }, [ | |||
| dispatch, | |||
| currentConversation, | |||
| dialogId, | |||
| setCurrentConversation, | |||
| handleClickConversation, | |||
| conversationList, | |||
| currentDialog, | |||
| ]); | |||
| return { createTemporaryConversation }; | |||
| }; | |||
| export const useFetchConversationList = () => { | |||
| const dispatch = useDispatch(); | |||
| const conversationList: any[] = useSelector( | |||
| (state: any) => state.chatModel.conversationList, | |||
| ); | |||
| const { dialogId } = useGetChatSearchParams(); | |||
| const fetchConversationList = useCallback(async () => { | |||
| if (dialogId) { | |||
| dispatch({ | |||
| type: 'chatModel/listConversation', | |||
| payload: { dialog_id: dialogId }, | |||
| }); | |||
| } | |||
| }, [dispatch, dialogId]); | |||
| useEffect(() => { | |||
| fetchConversationList(); | |||
| }, [fetchConversationList]); | |||
| return conversationList; | |||
| }; | |||
| export const useSelectConversationList = () => { | |||
| const [list, setList] = useState<Array<IConversation>>([]); | |||
| let chatModel: ChatModelState = useSelector((state: any) => state.chatModel); | |||
| const { conversationList, currentDialog } = chatModel; | |||
| const { dialogId } = useGetChatSearchParams(); | |||
| const prologue = currentDialog?.prompt_config?.prologue ?? ''; | |||
| const addTemporaryConversation = useCallback(() => { | |||
| setList(() => { | |||
| const nextList = [ | |||
| { | |||
| id: '', | |||
| name: 'New conversation', | |||
| dialog_id: dialogId, | |||
| message: [ | |||
| { | |||
| content: prologue, | |||
| role: MessageType.Assistant, | |||
| }, | |||
| ], | |||
| } as IConversation, | |||
| ...conversationList, | |||
| ]; | |||
| return nextList; | |||
| }); | |||
| }, [conversationList, dialogId, prologue]); | |||
| useEffect(() => { | |||
| addTemporaryConversation(); | |||
| }, [addTemporaryConversation]); | |||
| return { list, addTemporaryConversation }; | |||
| }; | |||
| export const useClickConversationCard = () => { | |||
| const [currentQueryParameters, setSearchParams] = useSearchParams(); | |||
| const newQueryParameters: URLSearchParams = useMemo( | |||
| () => new URLSearchParams(currentQueryParameters.toString()), | |||
| [currentQueryParameters], | |||
| ); | |||
| const handleClickConversation = useCallback( | |||
| (conversationId: string) => { | |||
| newQueryParameters.set(ChatSearchParams.ConversationId, conversationId); | |||
| setSearchParams(newQueryParameters); | |||
| }, | |||
| [newQueryParameters, setSearchParams], | |||
| ); | |||
| return { handleClickConversation }; | |||
| }; | |||
| export const useSetConversation = () => { | |||
| const dispatch = useDispatch(); | |||
| const { dialogId } = useGetChatSearchParams(); | |||
| @@ -302,17 +378,20 @@ export const useFetchConversation = () => { | |||
| const conversation = useSelector( | |||
| (state: any) => state.chatModel.currentConversation, | |||
| ); | |||
| const setCurrentConversation = useSetCurrentConversation(); | |||
| const fetchConversation = useCallback(() => { | |||
| if (conversationId !== EmptyConversationId && conversationId !== '') { | |||
| dispatch({ | |||
| if (isConversationIdNotExist(conversationId)) { | |||
| dispatch<any>({ | |||
| type: 'chatModel/getConversation', | |||
| payload: { | |||
| conversation_id: conversationId, | |||
| }, | |||
| }); | |||
| } else { | |||
| setCurrentConversation({} as IClientConversation); | |||
| } | |||
| }, [dispatch, conversationId]); | |||
| }, [dispatch, conversationId, setCurrentConversation]); | |||
| useEffect(() => { | |||
| fetchConversation(); | |||
| @@ -347,7 +426,7 @@ export const useSendMessage = () => { | |||
| }; | |||
| const handleSendMessage = async (message: string) => { | |||
| if (conversationId !== EmptyConversationId) { | |||
| if (conversationId !== '') { | |||
| sendMessage(message); | |||
| } else { | |||
| const data = await setConversation(message); | |||
| @@ -18,11 +18,11 @@ import ChatContainer from './chat-container'; | |||
| import { | |||
| useClickConversationCard, | |||
| useClickDialogCard, | |||
| useCreateTemporaryConversation, | |||
| useFetchConversationList, | |||
| useFetchDialog, | |||
| useGetChatSearchParams, | |||
| useRemoveDialog, | |||
| useSelectConversationList, | |||
| useSelectFirstDialogOnMount, | |||
| useSetCurrentDialog, | |||
| } from './hooks'; | |||
| @@ -38,12 +38,10 @@ const Chat = () => { | |||
| const { handleClickDialog } = useClickDialogCard(); | |||
| const { handleClickConversation } = useClickConversationCard(); | |||
| const { dialogId, conversationId } = useGetChatSearchParams(); | |||
| const list = useFetchConversationList(dialogId); | |||
| const { createTemporaryConversation } = useCreateTemporaryConversation(); | |||
| const { list: conversationList, addTemporaryConversation } = | |||
| useSelectConversationList(); | |||
| const selectedDialog = useFetchDialog(dialogId, true); | |||
| const prologue = selectedDialog?.prompt_config?.prologue || ''; | |||
| useFetchDialog(dialogId, true); | |||
| const handleAppCardEnter = (id: string) => () => { | |||
| setActivated(id); | |||
| @@ -69,8 +67,8 @@ const Chat = () => { | |||
| }; | |||
| const handleCreateTemporaryConversation = useCallback(() => { | |||
| createTemporaryConversation(prologue); | |||
| }, [createTemporaryConversation, prologue]); | |||
| addTemporaryConversation(); | |||
| }, [addTemporaryConversation]); | |||
| const items: MenuProps['items'] = [ | |||
| { | |||
| @@ -112,6 +110,8 @@ const Chat = () => { | |||
| return appItems; | |||
| }; | |||
| useFetchConversationList(); | |||
| return ( | |||
| <Flex className={styles.chatWrapper}> | |||
| <Flex className={styles.chatAppWrapper}> | |||
| @@ -171,7 +171,7 @@ const Chat = () => { | |||
| </Flex> | |||
| <Divider></Divider> | |||
| <Flex vertical gap={10} className={styles.chatTitleContent}> | |||
| {list.map((x) => ( | |||
| {conversationList.map((x) => ( | |||
| <Card | |||
| key={x.id} | |||
| hoverable | |||
| @@ -48,10 +48,11 @@ const model: DvaModel<ChatModelState> = { | |||
| }; | |||
| }, | |||
| setCurrentConversation(state, { payload }) { | |||
| const messageList = payload?.message.map((x: Message | IMessage) => ({ | |||
| ...x, | |||
| id: 'id' in x ? x.id : uuid(), | |||
| })); | |||
| const messageList = | |||
| payload?.message?.map((x: Message | IMessage) => ({ | |||
| ...x, | |||
| id: 'id' in x ? x.id : uuid(), | |||
| })) ?? []; | |||
| return { | |||
| ...state, | |||
| currentConversation: { ...payload, message: messageList }, | |||
| @@ -1,4 +1,4 @@ | |||
| import { variableEnabledFieldMap } from './constants'; | |||
| import { EmptyConversationId, variableEnabledFieldMap } from './constants'; | |||
| export const excludeUnEnabledVariables = (values: any) => { | |||
| const unEnabledFields: Array<keyof typeof variableEnabledFieldMap> = | |||
| @@ -10,3 +10,7 @@ export const excludeUnEnabledVariables = (values: any) => { | |||
| (key) => `llm_setting.${variableEnabledFieldMap[key]}`, | |||
| ); | |||
| }; | |||
| export const isConversationIdNotExist = (conversationId: string) => { | |||
| return conversationId !== EmptyConversationId && conversationId !== ''; | |||
| }; | |||
| @@ -1,7 +1,7 @@ | |||
| import { ITenantInfo } from '@/interfaces/database/knowledge'; | |||
| import { IThirdOAIModelCollection as IThirdAiModelCollection } from '@/interfaces/database/llm'; | |||
| import { IUserInfo } from '@/interfaces/database/userSetting'; | |||
| import userService from '@/services/userService'; | |||
| import authorizationUtil from '@/utils/authorizationUtil'; | |||
| import { message } from 'antd'; | |||
| import { Nullable } from 'typings'; | |||
| import { DvaModel } from 'umi'; | |||
| @@ -16,6 +16,7 @@ export interface SettingModelState { | |||
| llmInfo: IThirdAiModelCollection; | |||
| myLlm: any[]; | |||
| factoriesList: any[]; | |||
| userInfo: IUserInfo; | |||
| } | |||
| const model: DvaModel<SettingModelState> = { | |||
| @@ -30,6 +31,7 @@ const model: DvaModel<SettingModelState> = { | |||
| llmInfo: {}, | |||
| myLlm: [], | |||
| factoriesList: [], | |||
| userInfo: {} as IUserInfo, | |||
| }, | |||
| reducers: { | |||
| updateState(state, { payload }) { | |||
| @@ -38,10 +40,11 @@ const model: DvaModel<SettingModelState> = { | |||
| ...payload, | |||
| }; | |||
| }, | |||
| }, | |||
| subscriptions: { | |||
| setup({ dispatch, history }) { | |||
| history.listen((location) => {}); | |||
| setUserInfo(state, { payload }) { | |||
| return { | |||
| ...state, | |||
| userInfo: payload, | |||
| }; | |||
| }, | |||
| }, | |||
| effects: { | |||
| @@ -63,15 +66,17 @@ const model: DvaModel<SettingModelState> = { | |||
| } | |||
| }, | |||
| *getUserInfo({ payload = {} }, { call, put }) { | |||
| const { data, response } = yield call(userService.user_info, payload); | |||
| const { retcode, data: res, retmsg } = data; | |||
| const userInfo = { | |||
| avatar: res.avatar, | |||
| name: res.nickname, | |||
| email: res.email, | |||
| }; | |||
| authorizationUtil.setUserInfo(userInfo); | |||
| const { data } = yield call(userService.user_info, payload); | |||
| const { retcode, data: res } = data; | |||
| // const userInfo = { | |||
| // avatar: res.avatar, | |||
| // name: res.nickname, | |||
| // email: res.email, | |||
| // }; | |||
| // authorizationUtil.setUserInfo(userInfo); | |||
| if (retcode === 0) { | |||
| yield put({ type: 'setUserInfo', payload: res }); | |||
| // localStorage.setItem('userInfo',res.) | |||
| } | |||
| }, | |||