* feat: add temporary conversation * feat: add avatar to MessageItem * feat: render message referencetags/v0.1.0
| "react-chat-elements": "^12.0.13", | "react-chat-elements": "^12.0.13", | ||||
| "react-i18next": "^14.0.0", | "react-i18next": "^14.0.0", | ||||
| "react-infinite-scroll-component": "^6.1.0", | "react-infinite-scroll-component": "^6.1.0", | ||||
| "react-markdown": "^9.0.1", | |||||
| "react-string-replace": "^1.1.1", | |||||
| "umi": "^4.0.90", | "umi": "^4.0.90", | ||||
| "umi-request": "^1.4.0", | "umi-request": "^1.4.0", | ||||
| "unist-util-visit-parents": "^6.0.1", | |||||
| "uuid": "^9.0.1" | "uuid": "^9.0.1" | ||||
| }, | }, | ||||
| "devDependencies": { | "devDependencies": { |
| 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; | |||||
| }; |
| dialog_id: string; | dialog_id: string; | ||||
| id: string; | id: string; | ||||
| message: Message[]; | message: Message[]; | ||||
| reference: any[]; | |||||
| reference: IReference[]; | |||||
| name: string; | name: string; | ||||
| update_date: string; | update_date: string; | ||||
| update_time: number; | update_time: number; | ||||
| content: string; | content: string; | ||||
| role: MessageType; | 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; | |||||
| } |
| 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; | |||||
| } |
| const navigate = useNavigate(); | const navigate = useNavigate(); | ||||
| const { pathname } = useLocation(); | 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(() => { | const currentPath = useMemo(() => { | ||||
| return ( | return ( | ||||
| tagsData.find((x) => pathname.startsWith(x.path))?.name || 'knowledge' | tagsData.find((x) => pathname.startsWith(x.path))?.name || 'knowledge' | ||||
| ); | ); | ||||
| }, [pathname]); | |||||
| }, [pathname, tagsData]); | |||||
| const handleChange = (path: string) => { | const handleChange = (path: string) => { | ||||
| navigate(path); | navigate(path); | ||||
| > | > | ||||
| <Space size={12}> | <Space size={12}> | ||||
| <Logo className={styles.appIcon}></Logo> | <Logo className={styles.appIcon}></Logo> | ||||
| <label className={styles.appName}>Infinity flow</label> | |||||
| <label className={styles.appName}>RagFlow</label> | |||||
| </Space> | </Space> | ||||
| <Space size={[0, 8]} wrap> | <Space size={[0, 8]} wrap> | ||||
| <Radio.Group | <Radio.Group |
| import { useFetchUserInfo, useSelectUserInfo } from '@/hooks/userSettingHook'; | |||||
| import authorizationUtil from '@/utils/authorizationUtil'; | import authorizationUtil from '@/utils/authorizationUtil'; | ||||
| import type { MenuProps } from 'antd'; | import type { MenuProps } from 'antd'; | ||||
| import { Avatar, Button, Dropdown } from 'antd'; | import { Avatar, Button, Dropdown } from 'antd'; | ||||
| const App: React.FC = () => { | const App: React.FC = () => { | ||||
| const { t } = useTranslation(); | const { t } = useTranslation(); | ||||
| const userInfo = useSelectUserInfo(); | |||||
| const logout = () => { | const logout = () => { | ||||
| authorizationUtil.removeAll(); | authorizationUtil.removeAll(); | ||||
| ), | ), | ||||
| }, | }, | ||||
| ]; | ]; | ||||
| }, []); | |||||
| }, [t]); | |||||
| useFetchUserInfo(); | |||||
| return ( | return ( | ||||
| <Dropdown menu={{ items }} placement="bottomLeft" arrow> | <Dropdown menu={{ items }} placement="bottomLeft" arrow> | ||||
| <Avatar | <Avatar | ||||
| size={32} | size={32} | ||||
| src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png" | |||||
| src={ | |||||
| userInfo.avatar ?? | |||||
| 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png' | |||||
| } | |||||
| /> | /> | ||||
| </Dropdown> | </Dropdown> | ||||
| ); | ); |
| .chatContainer { | .chatContainer { | ||||
| padding: 0 24px 24px; | padding: 0 24px 24px; | ||||
| .messageContainer { | |||||
| overflow-y: auto; | |||||
| } | |||||
| } | } | ||||
| .messageItem { | .messageItem { | ||||
| .messageItemContent { | |||||
| padding: 24px 0; | |||||
| .messageItemSection { | |||||
| display: inline-block; | 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; | |||||
| } | } | ||||
| } | } | ||||
| 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 classNames from 'classnames'; | ||||
| import { ChangeEventHandler, useCallback, useMemo, useState } from 'react'; | |||||
| import reactStringReplace from 'react-string-replace'; | |||||
| import { useFetchConversation, useSendMessage } from '../hooks'; | import { useFetchConversation, useSendMessage } from '../hooks'; | ||||
| import { MessageType } from '@/constants/chat'; | |||||
| import { IClientConversation } from '../interface'; | 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'; | 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 ( | return ( | ||||
| <div | <div | ||||
| className={classNames(styles.messageItem, { | className={classNames(styles.messageItem, { | ||||
| [styles.messageItemRight]: item.role === MessageType.User, | [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> | </div> | ||||
| ); | ); | ||||
| }; | }; | ||||
| const [value, setValue] = useState(''); | const [value, setValue] = useState(''); | ||||
| const conversation: IClientConversation = useFetchConversation(); | const conversation: IClientConversation = useFetchConversation(); | ||||
| const { sendMessage } = useSendMessage(); | const { sendMessage } = useSendMessage(); | ||||
| const loading = useOneNamespaceEffectsLoading('chatModel', [ | |||||
| 'completeConversation', | |||||
| 'getConversation', | |||||
| ]); | |||||
| const handlePressEnter = () => { | const handlePressEnter = () => { | ||||
| console.info(value); | |||||
| setValue(''); | |||||
| sendMessage(value); | sendMessage(value); | ||||
| }; | }; | ||||
| return ( | return ( | ||||
| <Flex flex={1} className={styles.chatContainer} vertical> | <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> | </Flex> | ||||
| <Input | <Input | ||||
| size="large" | size="large" | ||||
| placeholder="Message Resume Assistant..." | placeholder="Message Resume Assistant..." | ||||
| value={value} | value={value} | ||||
| suffix={ | suffix={ | ||||
| <Button type="primary" onClick={handlePressEnter}> | |||||
| <Button type="primary" onClick={handlePressEnter} loading={loading}> | |||||
| Send | Send | ||||
| </Button> | </Button> | ||||
| } | } |
| import showDeleteConfirm from '@/components/deleting-confirm'; | import showDeleteConfirm from '@/components/deleting-confirm'; | ||||
| import { MessageType } from '@/constants/chat'; | import { MessageType } from '@/constants/chat'; | ||||
| import { IDialog } from '@/interfaces/database/chat'; | |||||
| import { IConversation, IDialog } from '@/interfaces/database/chat'; | |||||
| import omit from 'lodash/omit'; | 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 { useDispatch, useSearchParams, useSelector } from 'umi'; | ||||
| import { v4 as uuid } from 'uuid'; | import { v4 as uuid } from 'uuid'; | ||||
| import { ChatSearchParams, EmptyConversationId } from './constants'; | import { ChatSearchParams, EmptyConversationId } from './constants'; | ||||
| IMessage, | IMessage, | ||||
| VariableTableDataType, | VariableTableDataType, | ||||
| } from './interface'; | } from './interface'; | ||||
| import { ChatModelState } from './model'; | |||||
| import { isConversationIdNotExist } from './utils'; | |||||
| export const useFetchDialogList = () => { | export const useFetchDialogList = () => { | ||||
| const dispatch = useDispatch(); | const dispatch = useDispatch(); | ||||
| return { onRemoveDialog }; | 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 = () => { | export const useClickDialogCard = () => { | ||||
| const [currentQueryParameters, setSearchParams] = useSearchParams(); | const [currentQueryParameters, setSearchParams] = useSearchParams(); | ||||
| const newQueryParameters: URLSearchParams = useMemo(() => { | const newQueryParameters: URLSearchParams = useMemo(() => { | ||||
| return new URLSearchParams(currentQueryParameters.toString()); | |||||
| }, [currentQueryParameters]); | |||||
| return new URLSearchParams(); | |||||
| }, []); | |||||
| const handleClickDialog = useCallback( | const handleClickDialog = useCallback( | ||||
| (dialogId: string) => { | (dialogId: string) => { | ||||
| newQueryParameters.set(ChatSearchParams.DialogId, dialogId); | newQueryParameters.set(ChatSearchParams.DialogId, dialogId); | ||||
| // newQueryParameters.set( | |||||
| // ChatSearchParams.ConversationId, | |||||
| // EmptyConversationId, | |||||
| // ); | |||||
| setSearchParams(newQueryParameters); | setSearchParams(newQueryParameters); | ||||
| }, | }, | ||||
| [newQueryParameters, setSearchParams], | [newQueryParameters, setSearchParams], | ||||
| return { handleClickDialog }; | return { handleClickDialog }; | ||||
| }; | }; | ||||
| export const useGetChatSearchParams = () => { | |||||
| const [currentQueryParameters] = useSearchParams(); | |||||
| return { | |||||
| dialogId: currentQueryParameters.get(ChatSearchParams.DialogId) || '', | |||||
| conversationId: | |||||
| currentQueryParameters.get(ChatSearchParams.ConversationId) || '', | |||||
| }; | |||||
| }; | |||||
| export const useSelectFirstDialogOnMount = () => { | export const useSelectFirstDialogOnMount = () => { | ||||
| const dialogList = useFetchDialogList(); | const dialogList = useFetchDialogList(); | ||||
| const { dialogId } = useGetChatSearchParams(); | const { dialogId } = useGetChatSearchParams(); | ||||
| //#region conversation | //#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 = () => { | export const useCreateTemporaryConversation = () => { | ||||
| const dispatch = useDispatch(); | const dispatch = useDispatch(); | ||||
| const { dialogId } = useGetChatSearchParams(); | const { dialogId } = useGetChatSearchParams(); | ||||
| const { handleClickConversation } = useClickConversationCard(); | const { handleClickConversation } = useClickConversationCard(); | ||||
| let chatModel = useSelector((state: any) => state.chatModel); | let chatModel = useSelector((state: any) => state.chatModel); | ||||
| let currentConversation: Pick< | |||||
| const currentConversation: Pick< | |||||
| IClientConversation, | IClientConversation, | ||||
| 'id' | 'message' | 'name' | 'dialog_id' | 'id' | 'message' | 'name' | 'dialog_id' | ||||
| > = chatModel.currentConversation; | > = 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)) { | if (messages.some((x) => x.id === EmptyConversationId)) { | ||||
| return; | return; | ||||
| } | } | ||||
| messages.unshift({ | |||||
| messages.push({ | |||||
| id: EmptyConversationId, | id: EmptyConversationId, | ||||
| content: message, | |||||
| content: currentDialog?.prompt_config?.prologue ?? '', | |||||
| role: MessageType.Assistant, | role: MessageType.Assistant, | ||||
| }); | }); | ||||
| let nextCurrentConversation = currentConversation; | |||||
| // It’s the back-end data. | // It’s the back-end data. | ||||
| if ('id' in currentConversation) { | if ('id' in currentConversation) { | ||||
| currentConversation = { ...currentConversation, message: messages }; | |||||
| nextCurrentConversation = { ...currentConversation, message: messages }; | |||||
| } else { | } else { | ||||
| // client data | // client data | ||||
| currentConversation = { | |||||
| nextCurrentConversation = { | |||||
| id: EmptyConversationId, | id: EmptyConversationId, | ||||
| name: 'New conversation', | name: 'New conversation', | ||||
| dialog_id: dialogId, | dialog_id: dialogId, | ||||
| const nextConversationList = [...conversationList]; | const nextConversationList = [...conversationList]; | ||||
| nextConversationList.push(currentConversation as IClientConversation); | |||||
| nextConversationList.unshift( | |||||
| nextCurrentConversation as IClientConversation, | |||||
| ); | |||||
| dispatch({ | |||||
| type: 'chatModel/setCurrentConversation', | |||||
| payload: currentConversation, | |||||
| }); | |||||
| setCurrentConversation(nextCurrentConversation as IClientConversation); | |||||
| dispatch({ | dispatch({ | ||||
| type: 'chatModel/setConversationList', | type: 'chatModel/setConversationList', | ||||
| payload: nextConversationList, | payload: nextConversationList, | ||||
| }); | }); | ||||
| handleClickConversation(EmptyConversationId); | handleClickConversation(EmptyConversationId); | ||||
| }; | |||||
| }, [ | |||||
| dispatch, | |||||
| currentConversation, | |||||
| dialogId, | |||||
| setCurrentConversation, | |||||
| handleClickConversation, | |||||
| conversationList, | |||||
| currentDialog, | |||||
| ]); | |||||
| return { createTemporaryConversation }; | 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 = () => { | export const useSetConversation = () => { | ||||
| const dispatch = useDispatch(); | const dispatch = useDispatch(); | ||||
| const { dialogId } = useGetChatSearchParams(); | const { dialogId } = useGetChatSearchParams(); | ||||
| const conversation = useSelector( | const conversation = useSelector( | ||||
| (state: any) => state.chatModel.currentConversation, | (state: any) => state.chatModel.currentConversation, | ||||
| ); | ); | ||||
| const setCurrentConversation = useSetCurrentConversation(); | |||||
| const fetchConversation = useCallback(() => { | const fetchConversation = useCallback(() => { | ||||
| if (conversationId !== EmptyConversationId && conversationId !== '') { | |||||
| dispatch({ | |||||
| if (isConversationIdNotExist(conversationId)) { | |||||
| dispatch<any>({ | |||||
| type: 'chatModel/getConversation', | type: 'chatModel/getConversation', | ||||
| payload: { | payload: { | ||||
| conversation_id: conversationId, | conversation_id: conversationId, | ||||
| }, | }, | ||||
| }); | }); | ||||
| } else { | |||||
| setCurrentConversation({} as IClientConversation); | |||||
| } | } | ||||
| }, [dispatch, conversationId]); | |||||
| }, [dispatch, conversationId, setCurrentConversation]); | |||||
| useEffect(() => { | useEffect(() => { | ||||
| fetchConversation(); | fetchConversation(); | ||||
| }; | }; | ||||
| const handleSendMessage = async (message: string) => { | const handleSendMessage = async (message: string) => { | ||||
| if (conversationId !== EmptyConversationId) { | |||||
| if (conversationId !== '') { | |||||
| sendMessage(message); | sendMessage(message); | ||||
| } else { | } else { | ||||
| const data = await setConversation(message); | const data = await setConversation(message); |
| import { | import { | ||||
| useClickConversationCard, | useClickConversationCard, | ||||
| useClickDialogCard, | useClickDialogCard, | ||||
| useCreateTemporaryConversation, | |||||
| useFetchConversationList, | useFetchConversationList, | ||||
| useFetchDialog, | useFetchDialog, | ||||
| useGetChatSearchParams, | useGetChatSearchParams, | ||||
| useRemoveDialog, | useRemoveDialog, | ||||
| useSelectConversationList, | |||||
| useSelectFirstDialogOnMount, | useSelectFirstDialogOnMount, | ||||
| useSetCurrentDialog, | useSetCurrentDialog, | ||||
| } from './hooks'; | } from './hooks'; | ||||
| const { handleClickDialog } = useClickDialogCard(); | const { handleClickDialog } = useClickDialogCard(); | ||||
| const { handleClickConversation } = useClickConversationCard(); | const { handleClickConversation } = useClickConversationCard(); | ||||
| const { dialogId, conversationId } = useGetChatSearchParams(); | 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) => () => { | const handleAppCardEnter = (id: string) => () => { | ||||
| setActivated(id); | setActivated(id); | ||||
| }; | }; | ||||
| const handleCreateTemporaryConversation = useCallback(() => { | const handleCreateTemporaryConversation = useCallback(() => { | ||||
| createTemporaryConversation(prologue); | |||||
| }, [createTemporaryConversation, prologue]); | |||||
| addTemporaryConversation(); | |||||
| }, [addTemporaryConversation]); | |||||
| const items: MenuProps['items'] = [ | const items: MenuProps['items'] = [ | ||||
| { | { | ||||
| return appItems; | return appItems; | ||||
| }; | }; | ||||
| useFetchConversationList(); | |||||
| return ( | return ( | ||||
| <Flex className={styles.chatWrapper}> | <Flex className={styles.chatWrapper}> | ||||
| <Flex className={styles.chatAppWrapper}> | <Flex className={styles.chatAppWrapper}> | ||||
| </Flex> | </Flex> | ||||
| <Divider></Divider> | <Divider></Divider> | ||||
| <Flex vertical gap={10} className={styles.chatTitleContent}> | <Flex vertical gap={10} className={styles.chatTitleContent}> | ||||
| {list.map((x) => ( | |||||
| {conversationList.map((x) => ( | |||||
| <Card | <Card | ||||
| key={x.id} | key={x.id} | ||||
| hoverable | hoverable |
| }; | }; | ||||
| }, | }, | ||||
| setCurrentConversation(state, { payload }) { | 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 { | return { | ||||
| ...state, | ...state, | ||||
| currentConversation: { ...payload, message: messageList }, | currentConversation: { ...payload, message: messageList }, |
| import { variableEnabledFieldMap } from './constants'; | |||||
| import { EmptyConversationId, variableEnabledFieldMap } from './constants'; | |||||
| export const excludeUnEnabledVariables = (values: any) => { | export const excludeUnEnabledVariables = (values: any) => { | ||||
| const unEnabledFields: Array<keyof typeof variableEnabledFieldMap> = | const unEnabledFields: Array<keyof typeof variableEnabledFieldMap> = | ||||
| (key) => `llm_setting.${variableEnabledFieldMap[key]}`, | (key) => `llm_setting.${variableEnabledFieldMap[key]}`, | ||||
| ); | ); | ||||
| }; | }; | ||||
| export const isConversationIdNotExist = (conversationId: string) => { | |||||
| return conversationId !== EmptyConversationId && conversationId !== ''; | |||||
| }; |
| import { ITenantInfo } from '@/interfaces/database/knowledge'; | import { ITenantInfo } from '@/interfaces/database/knowledge'; | ||||
| import { IThirdOAIModelCollection as IThirdAiModelCollection } from '@/interfaces/database/llm'; | import { IThirdOAIModelCollection as IThirdAiModelCollection } from '@/interfaces/database/llm'; | ||||
| import { IUserInfo } from '@/interfaces/database/userSetting'; | |||||
| import userService from '@/services/userService'; | import userService from '@/services/userService'; | ||||
| import authorizationUtil from '@/utils/authorizationUtil'; | |||||
| import { message } from 'antd'; | import { message } from 'antd'; | ||||
| import { Nullable } from 'typings'; | import { Nullable } from 'typings'; | ||||
| import { DvaModel } from 'umi'; | import { DvaModel } from 'umi'; | ||||
| llmInfo: IThirdAiModelCollection; | llmInfo: IThirdAiModelCollection; | ||||
| myLlm: any[]; | myLlm: any[]; | ||||
| factoriesList: any[]; | factoriesList: any[]; | ||||
| userInfo: IUserInfo; | |||||
| } | } | ||||
| const model: DvaModel<SettingModelState> = { | const model: DvaModel<SettingModelState> = { | ||||
| llmInfo: {}, | llmInfo: {}, | ||||
| myLlm: [], | myLlm: [], | ||||
| factoriesList: [], | factoriesList: [], | ||||
| userInfo: {} as IUserInfo, | |||||
| }, | }, | ||||
| reducers: { | reducers: { | ||||
| updateState(state, { payload }) { | updateState(state, { payload }) { | ||||
| ...payload, | ...payload, | ||||
| }; | }; | ||||
| }, | }, | ||||
| }, | |||||
| subscriptions: { | |||||
| setup({ dispatch, history }) { | |||||
| history.listen((location) => {}); | |||||
| setUserInfo(state, { payload }) { | |||||
| return { | |||||
| ...state, | |||||
| userInfo: payload, | |||||
| }; | |||||
| }, | }, | ||||
| }, | }, | ||||
| effects: { | effects: { | ||||
| } | } | ||||
| }, | }, | ||||
| *getUserInfo({ payload = {} }, { call, put }) { | *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) { | if (retcode === 0) { | ||||
| yield put({ type: 'setUserInfo', payload: res }); | |||||
| // localStorage.setItem('userInfo',res.) | // localStorage.setItem('userInfo',res.) | ||||
| } | } | ||||
| }, | }, |