diff --git a/web/src/assets/svg/assistant.svg b/web/src/assets/svg/assistant.svg new file mode 100644 index 000000000..43446a186 --- /dev/null +++ b/web/src/assets/svg/assistant.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/web/src/pages/chat/chat-container/index.less b/web/src/pages/chat/chat-container/index.less index ff6eec79c..7b9a25493 100644 --- a/web/src/pages/chat/chat-container/index.less +++ b/web/src/pages/chat/chat-container/index.less @@ -1,11 +1,14 @@ .chatContainer { padding: 0 24px 24px; + .messageContainer { + overflow-y: auto; + } } .messageItem { .messageItemContent { display: inline-block; - width: 300px; + width: 400px; } } diff --git a/web/src/pages/chat/chat-container/index.tsx b/web/src/pages/chat/chat-container/index.tsx index b58b52f65..265509e61 100644 --- a/web/src/pages/chat/chat-container/index.tsx +++ b/web/src/pages/chat/chat-container/index.tsx @@ -1,4 +1,4 @@ -import { Button, Flex, Input, Typography } from 'antd'; +import { Button, Flex, Input } from 'antd'; import { ChangeEventHandler, useState } from 'react'; import { Message } from '@/interfaces/database/chat'; @@ -9,8 +9,6 @@ import { MessageType } from '@/constants/chat'; import { IClientConversation } from '../interface'; import styles from './index.less'; -const { Paragraph } = Typography; - const MessageItem = ({ item }: { item: Message }) => { return (
{ })} > - - {item.content} - +
{item.content}
); @@ -44,10 +40,12 @@ const ChatContainer = () => { return ( - - {conversation?.message?.map((message) => ( - - ))} + +
+ {conversation?.message?.map((message) => ( + + ))} +
{ const dispatch = useDispatch(); @@ -137,24 +139,6 @@ export const useRemoveDialog = () => { return { onRemoveDialog }; }; -export const useClickDialogCard = () => { - const [currentQueryParameters, setSearchParams] = useSearchParams(); - - const newQueryParameters: URLSearchParams = useMemo(() => { - return new URLSearchParams(currentQueryParameters.toString()); - }, [currentQueryParameters]); - - const handleClickDialog = useCallback( - (dialogId: string) => { - newQueryParameters.set(ChatSearchParams.DialogId, dialogId); - setSearchParams(newQueryParameters); - }, - [newQueryParameters, setSearchParams], - ); - - return { handleClickDialog }; -}; - export const useGetChatSearchParams = () => { const [currentQueryParameters] = useSearchParams(); @@ -165,6 +149,44 @@ export const useGetChatSearchParams = () => { }; }; +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(); + }, []); + + const handleClickDialog = useCallback( + (dialogId: string) => { + newQueryParameters.set(ChatSearchParams.DialogId, dialogId); + // newQueryParameters.set( + // ChatSearchParams.ConversationId, + // EmptyConversationId, + // ); + setSearchParams(newQueryParameters); + }, + [newQueryParameters, setSearchParams], + ); + + return { handleClickDialog }; +}; + export const useSelectFirstDialogOnMount = () => { const dialogList = useFetchDialogList(); const { dialogId } = useGetChatSearchParams(); @@ -182,13 +204,83 @@ export const useSelectFirstDialogOnMount = () => { //#region conversation -export const useFetchConversationList = (dialogId?: string) => { +export const useCreateTemporaryConversation = () => { + const dispatch = useDispatch(); + const { dialogId } = useGetChatSearchParams(); + const { handleClickConversation } = useClickConversationCard(); + let chatModel = useSelector((state: any) => state.chatModel); + + const currentConversation: Pick< + IClientConversation, + 'id' | 'message' | 'name' | 'dialog_id' + > = chatModel.currentConversation; + + 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.push({ + id: EmptyConversationId, + content: currentDialog?.prompt_config?.prologue ?? '', + role: MessageType.Assistant, + }); + + let nextCurrentConversation = currentConversation; + + // It’s the back-end data. + if ('id' in currentConversation) { + nextCurrentConversation = { ...currentConversation, message: messages }; + } else { + // client data + nextCurrentConversation = { + id: EmptyConversationId, + name: 'New conversation', + dialog_id: dialogId, + message: messages, + }; + } + + const nextConversationList = [...conversationList]; + + nextConversationList.unshift( + nextCurrentConversation as IClientConversation, + ); + + 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(() => { + const fetchConversationList = useCallback(async () => { if (dialogId) { dispatch({ type: 'chatModel/listConversation', @@ -204,72 +296,56 @@ export const useFetchConversationList = (dialogId?: string) => { return conversationList; }; -export const useClickConversationCard = () => { - const [currentQueryParameters, setSearchParams] = useSearchParams(); - const newQueryParameters: URLSearchParams = new URLSearchParams( - currentQueryParameters.toString(), - ); +export const useSelectConversationList = () => { + const [list, setList] = useState>([]); + let chatModel: ChatModelState = useSelector((state: any) => state.chatModel); + const { conversationList, currentDialog } = chatModel; + const { dialogId } = useGetChatSearchParams(); + const prologue = currentDialog?.prompt_config?.prologue ?? ''; - const handleClickConversation = (conversationId: string) => { - newQueryParameters.set(ChatSearchParams.ConversationId, conversationId); - setSearchParams(newQueryParameters); - }; + 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]); - return { handleClickConversation }; + useEffect(() => { + addTemporaryConversation(); + }, [addTemporaryConversation]); + + return { list, addTemporaryConversation }; }; -export const useCreateTemporaryConversation = () => { - const dispatch = useDispatch(); - const { dialogId } = useGetChatSearchParams(); - const { handleClickConversation } = useClickConversationCard(); - let chatModel = useSelector((state: any) => state.chatModel); - let currentConversation: Pick< - IClientConversation, - 'id' | 'message' | 'name' | 'dialog_id' - > = chatModel.currentConversation; - let conversationList: IClientConversation[] = chatModel.conversationList; +export const useClickConversationCard = () => { + const [currentQueryParameters, setSearchParams] = useSearchParams(); + const newQueryParameters: URLSearchParams = useMemo( + () => new URLSearchParams(currentQueryParameters.toString()), + [currentQueryParameters], + ); - const createTemporaryConversation = (message: string) => { - const messages = [...(currentConversation?.message ?? [])]; - if (messages.some((x) => x.id === EmptyConversationId)) { - return; - } - messages.unshift({ - id: EmptyConversationId, - content: message, - role: MessageType.Assistant, - }); + const handleClickConversation = useCallback( + (conversationId: string) => { + newQueryParameters.set(ChatSearchParams.ConversationId, conversationId); + setSearchParams(newQueryParameters); + }, + [newQueryParameters, setSearchParams], + ); - // It’s the back-end data. - if ('id' in currentConversation) { - currentConversation = { ...currentConversation, message: messages }; - } else { - // client data - currentConversation = { - id: EmptyConversationId, - name: 'New conversation', - dialog_id: dialogId, - message: messages, - }; - } - - const nextConversationList = [...conversationList]; - - nextConversationList.push(currentConversation as IClientConversation); - - dispatch({ - type: 'chatModel/setCurrentConversation', - payload: currentConversation, - }); - - dispatch({ - type: 'chatModel/setConversationList', - payload: nextConversationList, - }); - handleClickConversation(EmptyConversationId); - }; - - return { createTemporaryConversation }; + return { handleClickConversation }; }; export const useSetConversation = () => { @@ -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({ type: 'chatModel/getConversation', payload: { conversation_id: conversationId, }, }); + } else { + setCurrentConversation({} as IClientConversation); } - }, [dispatch, conversationId]); + }, [dispatch, conversationId, setCurrentConversation]); useEffect(() => { fetchConversation(); diff --git a/web/src/pages/chat/index.tsx b/web/src/pages/chat/index.tsx index 33b20e44c..4e3124f32 100644 --- a/web/src/pages/chat/index.tsx +++ b/web/src/pages/chat/index.tsx @@ -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 ( @@ -171,7 +171,7 @@ const Chat = () => { - {list.map((x) => ( + {conversationList.map((x) => ( = { }; }, 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 }, diff --git a/web/src/pages/chat/utils.ts b/web/src/pages/chat/utils.ts index 997dc3755..cbec9e2c7 100644 --- a/web/src/pages/chat/utils.ts +++ b/web/src/pages/chat/utils.ts @@ -1,4 +1,4 @@ -import { variableEnabledFieldMap } from './constants'; +import { EmptyConversationId, variableEnabledFieldMap } from './constants'; export const excludeUnEnabledVariables = (values: any) => { const unEnabledFields: Array = @@ -10,3 +10,7 @@ export const excludeUnEnabledVariables = (values: any) => { (key) => `llm_setting.${variableEnabledFieldMap[key]}`, ); }; + +export const isConversationIdNotExist = (conversationId: string) => { + return conversationId !== EmptyConversationId && conversationId !== ''; +};