feat: chat ui

This commit is contained in:
archer
2023-07-26 11:01:25 +08:00
parent 2b993b926a
commit 248be38939
19 changed files with 153 additions and 54 deletions

View File

@@ -2,7 +2,7 @@
"FeConfig": {
"show_emptyChat": true,
"show_register": true,
"show_appStore": true,
"show_appStore": false,
"show_userDetail": true,
"show_git": true,
"systemTitle": "FastAI",
@@ -35,6 +35,14 @@
"maxTemperature": 1.2,
"price": 3
},
{
"model": "ERNIE-Bot",
"name": "文心一言",
"contextMaxToken": 3000,
"quoteMaxToken": 1500,
"maxTemperature": 1,
"price": 1.2
},
{
"model": "gpt-4",
"name": "FastAI-Plus",

View File

@@ -1,7 +1,16 @@
{
"Cancel": "No",
"Confirm": "Yes",
"Warning": "Warning",
"app": {
"App Detail": "App Detail",
"My Apps": "My Apps"
},
"chat": {
"Confirm to clear history": "Confirm to clear history?",
"New Chat": "New Chat",
"You need to a chat app": "You need to a chat app"
},
"home": {
"Quickly build AI question and answer library": "Quickly build AI question and answer library",
"Start Now": "Start Now",

View File

@@ -1,7 +1,16 @@
{
"Cancel": "取消",
"Confirm": "确认",
"Warning": "提示",
"app": {
"App Detail": "应用详情",
"My Apps": "我的应用"
},
"chat": {
"Confirm to clear history": "确认清空该应用的聊天记录?",
"New Chat": "新对话",
"You need to a chat app": "你需要创建一个应用"
},
"home": {
"Quickly build AI question and answer library": "快速搭建 AI 问答系统",
"Start Now": "立即开始",

View File

@@ -23,12 +23,10 @@ export const getChatHistory = (data: RequestPaging & { appId?: string }) =>
* 删除一条历史记录
*/
export const delChatHistoryById = (chatId: string) => DELETE(`/chat/removeHistory`, { chatId });
/**
* get history quotes
* clear all history by appid
*/
export const getHistoryQuote = (params: { chatId: string; contentId: string }) =>
GET<(QuoteItemType & { _id: string })[]>(`/chat/history/getHistoryQuote`, params);
export const clearChatHistoryByAppId = (appId: string) => DELETE(`/chat/removeHistory`, { appId });
/**
* update history quote status

View File

@@ -37,7 +37,7 @@ const map = {
minus: require('./icons/minus.svg').default,
chatLight: require('./icons/light/chat.svg').default,
chatFill: require('./icons/fill/chat.svg').default,
clearLight: require('./icons/light/clear.svg').default,
clear: require('./icons/light/clear.svg').default,
apiLight: require('./icons/light/appApi.svg').default,
overviewLight: require('./icons/light/overview.svg').default,
settingLight: require('./icons/light/setting.svg').default,

View File

@@ -15,9 +15,9 @@ const Tag = ({ children, colorSchema = 'blue', ...props }: Props) => {
color: 'myBlue.700'
},
green: {
borderColor: '#54cd19',
bg: '#f2fcf2',
color: '#54cd19'
borderColor: '#67c13b',
bg: '#f8fff8',
color: '#67c13b'
},
gray: {
borderColor: '#979797',

View File

@@ -9,8 +9,11 @@ import {
useDisclosure,
Button
} from '@chakra-ui/react';
import { useTranslation } from 'next-i18next';
export const useConfirm = ({ title = 'Warning', content }: { title?: string; content: string }) => {
const { t } = useTranslation();
export const useConfirm = ({ title = '提示', content }: { title?: string; content: string }) => {
const { isOpen, onOpen, onClose } = useDisclosure();
const cancelRef = useRef(null);
const confirmCb = useRef<any>();
@@ -32,7 +35,7 @@ export const useConfirm = ({ title = '提示', content }: { title?: string; cont
<AlertDialogOverlay>
<AlertDialogContent maxW={'min(90vw,400px)'}>
<AlertDialogHeader fontSize="lg" fontWeight="bold">
{title}
{t(title)}
</AlertDialogHeader>
<AlertDialogBody>{content}</AlertDialogBody>
@@ -45,7 +48,7 @@ export const useConfirm = ({ title = '提示', content }: { title?: string; cont
typeof cancelCb.current === 'function' && cancelCb.current();
}}
>
{t('Cancel')}
</Button>
<Button
ml={4}
@@ -54,7 +57,7 @@ export const useConfirm = ({ title = '提示', content }: { title?: string; cont
typeof confirmCb.current === 'function' && confirmCb.current();
}}
>
{t('Confirm')}
</Button>
</AlertDialogFooter>
</AlertDialogContent>

View File

@@ -3,18 +3,31 @@ import { jsonRes } from '@/service/response';
import { connectToDatabase, Chat } from '@/service/mongo';
import { authUser } from '@/service/utils/auth';
/* 获取历史记录 */
type Props = {
chatId?: string;
appId?: string;
};
/* clear chat history */
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
const { chatId } = req.query;
const { chatId, appId } = req.query as Props;
const { userId } = await authUser({ req, authToken: true });
await connectToDatabase();
await Chat.findOneAndRemove({
chatId,
userId
});
if (chatId) {
await Chat.findOneAndRemove({
chatId,
userId
});
}
if (appId) {
await Chat.deleteMany({
appId,
userId
});
}
jsonRes(res);
} catch (err) {

View File

@@ -96,7 +96,7 @@ const ChatTest = (
<IconButton
className="chat"
size={'sm'}
icon={<MyIcon name={'clearLight'} w={'14px'} />}
icon={<MyIcon name={'clear'} w={'14px'} />}
variant={'base'}
borderRadius={'md'}
aria-label={'delete'}

View File

@@ -596,7 +596,7 @@ const ChatTest = ({ appId }: { appId: string }) => {
<IconButton
className="chat"
size={'sm'}
icon={<MyIcon name={'clearLight'} w={'14px'} />}
icon={<MyIcon name={'clear'} w={'14px'} />}
variant={'base'}
borderRadius={'md'}
aria-label={'delete'}

View File

@@ -33,7 +33,7 @@ const MyApps = () => {
const { t } = useTranslation();
const theme = useTheme();
const router = useRouter();
const { myApps, loadMyModels } = useUserStore();
const { myApps, loadMyApps } = useUserStore();
const { openConfirm, ConfirmChild } = useConfirm({
title: '删除提示',
content: '确认删除该应用所有信息?'
@@ -53,7 +53,7 @@ const MyApps = () => {
title: '删除成功',
status: 'success'
});
loadMyModels();
loadMyApps();
} catch (err: any) {
toast({
title: err?.message || '删除失败',
@@ -61,11 +61,11 @@ const MyApps = () => {
});
}
},
[toast, loadMyModels]
[toast, loadMyApps]
);
/* 加载模型 */
useQuery(['loadModels'], loadMyModels, {
useQuery(['loadModels'], loadMyApps, {
refetchOnMount: true
});
@@ -166,7 +166,7 @@ const MyApps = () => {
))}
</Grid>
<ConfirmChild />
{isOpenCreateModal && <CreateModal onClose={onCloseCreateModal} onSuccess={loadMyModels} />}
{isOpenCreateModal && <CreateModal onClose={onCloseCreateModal} onSuccess={loadMyApps} />}
</PageContainer>
);
};

View File

@@ -1,4 +1,4 @@
import React, { useMemo, useState } from 'react';
import React, { useMemo } from 'react';
import {
Box,
Button,
@@ -16,6 +16,8 @@ import { useRouter } from 'next/router';
import Avatar from '@/components/Avatar';
import MyTooltip from '@/components/MyTooltip';
import MyIcon from '@/components/Icon';
import { useTranslation } from 'react-i18next';
import { useConfirm } from '@/hooks/useConfirm';
type HistoryItemType = {
id: string;
@@ -32,6 +34,7 @@ const ChatHistorySlider = ({
activeChatId,
onChangeChat,
onDelHistory,
onClearHistory,
onSetHistoryTop,
onSetCustomTitle
}: {
@@ -42,17 +45,22 @@ const ChatHistorySlider = ({
activeChatId: string;
onChangeChat: (chatId?: string) => void;
onDelHistory: (chatId: string) => void;
onClearHistory: () => void;
onSetHistoryTop?: (e: { chatId: string; top: boolean }) => void;
onSetCustomTitle?: (e: { chatId: string; title: string }) => void;
}) => {
const theme = useTheme();
const router = useRouter();
const { t } = useTranslation();
const { isPc } = useGlobalStore();
// custom title edit
const { onOpenModal, EditModal: EditTitleModal } = useEditInfo({
title: '自定义历史记录标题',
placeholder: '如果设置为空,会自动跟随聊天记录。'
});
const { openConfirm, ConfirmChild } = useConfirm({
content: t('chat.Confirm to clear history')
});
const concatHistory = useMemo<HistoryItemType[]>(
() => (!activeChatId ? [{ id: activeChatId, title: '新对话' }].concat(history) : history),
@@ -70,7 +78,7 @@ const ChatHistorySlider = ({
whiteSpace={'nowrap'}
>
{isPc && (
<MyTooltip label={appId ? '应用详情' : ''} offset={[0, 0]}>
<MyTooltip label={appId ? t('app.App Detail') : ''} offset={[0, 0]}>
<Flex
pt={5}
pb={2}
@@ -92,11 +100,11 @@ const ChatHistorySlider = ({
</Flex>
</MyTooltip>
)}
{/* 新对话 */}
<Box w={'100%'} px={[2, 5]} h={'36px'} my={5}>
{/* btn */}
<Flex w={'100%'} px={[2, 5]} h={'36px'} my={5}>
<Button
variant={'base'}
w={'100%'}
flex={1}
h={'100%'}
color={'myBlue.700'}
borderRadius={'xl'}
@@ -104,9 +112,20 @@ const ChatHistorySlider = ({
overflow={'hidden'}
onClick={() => onChangeChat()}
>
{t('chat.New Chat')}
</Button>
</Box>
<IconButton
ml={3}
h={'100%'}
variant={'base'}
aria-label={''}
borderRadius={'xl'}
onClick={openConfirm(onClearHistory)}
>
<MyIcon name={'clear'} w={'16px'} />
</IconButton>
</Flex>
{/* chat history */}
<Box flex={'1 0 0'} h={0} px={[2, 5]} overflow={'overlay'}>
@@ -230,6 +249,7 @@ const ChatHistorySlider = ({
</Flex>
)}
<EditTitleModal />
<ConfirmChild />
</Flex>
);
};

View File

@@ -8,9 +8,9 @@ import Avatar from '@/components/Avatar';
const SliderApps = ({ appId }: { appId: string }) => {
const router = useRouter();
const { myApps, loadMyModels } = useUserStore();
const { myApps, loadMyApps } = useUserStore();
useQuery(['loadModels'], loadMyModels);
useQuery(['loadModels'], loadMyApps);
return (
<>

View File

@@ -19,6 +19,7 @@ import { useToast } from '@/hooks/useToast';
import { customAlphabet } from 'nanoid';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 12);
import type { ChatHistoryItemType } from '@/types/chat';
import { useTranslation } from 'react-i18next';
import ChatBox, { type ComponentRef, type StartChatFnProps } from '@/components/ChatBox';
import PageContainer from '@/components/PageContainer';
@@ -33,6 +34,7 @@ import { serviceSideProps } from '@/utils/i18n';
const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => {
const router = useRouter();
const theme = useTheme();
const { t } = useTranslation();
const { toast } = useToast();
const ChatBoxRef = useRef<ComponentRef>(null);
@@ -47,10 +49,11 @@ const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => {
loadHistory,
updateHistory,
delHistory,
clearHistory,
chatData,
setChatData
} = useChatStore();
const { myApps, userInfo } = useUserStore();
const { myApps, loadMyApps, userInfo } = useUserStore();
const { isPc } = useGlobalStore();
const { Loading, setIsLoading } = useLoading();
@@ -200,6 +203,26 @@ const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => {
}
});
}
if (!appId) {
(async () => {
const apps = await loadMyApps();
if (apps.length === 0) {
toast({
status: 'error',
title: t('chat.You need to a chat app')
});
router.replace('/app/list');
} else {
router.replace({
query: {
appId: apps[0]._id,
chatId: lastChatId
}
});
}
})();
return;
}
// store id
appId && setLastChatAppId(appId);
@@ -210,15 +233,11 @@ const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => {
return null;
}
if (appId) {
return loadChatInfo({
appId,
chatId,
loading: appId !== chatData.appId
});
}
return null;
return loadChatInfo({
appId,
chatId,
loading: appId !== chatData.appId
});
});
useQuery(['loadHistory', appId], () => (appId ? loadHistory({ appId }) : null));
@@ -268,6 +287,14 @@ const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => {
}
}}
onDelHistory={delHistory}
onClearHistory={() => {
clearHistory(appId);
router.replace({
query: {
appId
}
});
}}
onSetHistoryTop={async (e) => {
try {
await putChatHistory(e);

View File

@@ -99,8 +99,6 @@ const ShareChat = ({ shareId, chatId }: { shareId: string; chatId: string }) =>
const loadAppInfo = useCallback(
async (shareId: string, chatId: string) => {
console.log(shareId, chatId);
if (!shareId) return null;
const history = shareChatHistory.find((item) => item.chatId === chatId) || defaultHistory;
@@ -183,6 +181,14 @@ const ShareChat = ({ shareId, chatId }: { shareId: string; chatId: string }) =>
}
}}
onDelHistory={delOneShareHistoryByChatId}
onClearHistory={() => {
delManyShareChatHistoryByShareId(shareId);
router.replace({
query: {
shareId
}
});
}}
/>
)}

View File

@@ -72,16 +72,16 @@ export const dispatchChatCompletion = async (props: Record<string, any>): Promis
maxToken,
filterMessages
});
// console.log(messages);
// FastGpt temperature range: 1~10
temperature = +(modelConstantsData.maxTemperature * (temperature / 10)).toFixed(2);
temperature = Math.max(temperature, 0.01);
const chatAPI = getOpenAIApi();
const response = await chatAPI.createChatCompletion(
{
model,
temperature: Number(temperature || 0),
temperature,
max_tokens,
messages,
// frequency_penalty: 0.5, // 越大,重复内容越少

View File

@@ -4,12 +4,13 @@ import { immer } from 'zustand/middleware/immer';
import { ChatHistoryItemType } from '@/types/chat';
import type { InitChatResponse } from '@/api/response/chat';
import { delChatHistoryById, getChatHistory } from '@/api/chat';
import { delChatHistoryById, getChatHistory, clearChatHistoryByAppId } from '@/api/chat';
type State = {
history: ChatHistoryItemType[];
loadHistory: (data: { appId?: string }) => Promise<null>;
delHistory(history: string): Promise<void>;
clearHistory(appId: string): Promise<void>;
updateHistory: (history: ChatHistoryItemType) => void;
chatData: InitChatResponse;
setChatData: (e: InitChatResponse | ((e: InitChatResponse) => InitChatResponse)) => void;
@@ -69,6 +70,12 @@ export const useChatStore = create<State>()(
});
await delChatHistoryById(chatId);
},
async clearHistory(appId) {
set((state) => {
state.history = [];
});
await clearChatHistoryByAppId(appId);
},
updateHistory(history) {
const index = get().history.findIndex((item) => item.chatId === history.chatId);
set((state) => {

View File

@@ -29,7 +29,6 @@ export const clientInitData = async (): Promise<InitDateResponse> => {
beianText = res.systemEnv?.beianText;
googleClientVerKey = res.systemEnv?.googleClientVerKey;
baiduTongji = res.systemEnv?.baiduTongji;
console.log(res.feConfigs);
return res;
} catch (error) {

View File

@@ -19,7 +19,7 @@ type State = {
updateUserInfo: (user: UserUpdateParams) => void;
myApps: AppListItemType[];
myCollectionApps: AppListItemType[];
loadMyModels: () => Promise<null>;
loadMyApps: () => Promise<AppListItemType[]>;
appDetail: AppSchema;
loadAppDetail: (id: string, init?: boolean) => Promise<AppSchema>;
updateAppDetail(appId: string, data: AppUpdateParams): Promise<void>;
@@ -63,12 +63,12 @@ export const useUserStore = create<State>()(
},
myApps: [],
myCollectionApps: [],
async loadMyModels() {
async loadMyApps() {
const res = await getMyModels();
set((state) => {
state.myApps = res;
});
return null;
return res;
},
appDetail: defaultApp,
async loadAppDetail(id: string, init = false) {