feat: add temporary conversation

This commit is contained in:
billchen 2024-02-26 11:51:35 +08:00
parent 17d751d2d1
commit bc16879a10
7 changed files with 220 additions and 110 deletions

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 30 KiB

View file

@ -1,11 +1,14 @@
.chatContainer {
padding: 0 24px 24px;
.messageContainer {
overflow-y: auto;
}
}
.messageItem {
.messageItemContent {
display: inline-block;
width: 300px;
width: 400px;
}
}

View file

@ -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 (
<div
@ -20,9 +18,7 @@ const MessageItem = ({ item }: { item: Message }) => {
})}
>
<span className={styles.messageItemContent}>
<Paragraph ellipsis={{ tooltip: item.content, rows: 3 }}>
{item.content}
</Paragraph>
<div>{item.content}</div>
</span>
</div>
);
@ -44,10 +40,12 @@ 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}></MessageItem>
))}
</div>
</Flex>
<Input
size="large"

View file

@ -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,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;
// Its 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<Array<IConversation>>([]);
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],
);
// Its 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<any>({
type: 'chatModel/getConversation',
payload: {
conversation_id: conversationId,
},
});
} else {
setCurrentConversation({} as IClientConversation);
}
}, [dispatch, conversationId]);
}, [dispatch, conversationId, setCurrentConversation]);
useEffect(() => {
fetchConversation();

View file

@ -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

View file

@ -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 },

View file

@ -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 !== '';
};