From 248be3893910516cdba6c7e7f6685c0b8adee7cc Mon Sep 17 00:00:00 2001 From: archer <545436317@qq.com> Date: Wed, 26 Jul 2023 11:01:25 +0800 Subject: [PATCH] feat: chat ui --- client/data/config.json | 10 +++- client/public/locales/en/common.json | 9 ++++ client/public/locales/zh/common.json | 9 ++++ client/src/api/chat.ts | 6 +-- client/src/components/Icon/index.tsx | 2 +- client/src/components/Tag/index.tsx | 6 +-- client/src/hooks/useConfirm.tsx | 11 +++-- client/src/pages/api/chat/removeHistory.ts | 25 +++++++--- .../components/AdEdit/components/ChatTest.tsx | 2 +- .../app/detail/components/BasicEdit/index.tsx | 2 +- client/src/pages/app/list/index.tsx | 10 ++-- .../chat/components/ChatHistorySlider.tsx | 34 +++++++++++--- .../src/pages/chat/components/SliderApps.tsx | 4 +- client/src/pages/chat/index.tsx | 47 +++++++++++++++---- client/src/pages/chat/share.tsx | 10 +++- .../src/service/moduleDispatch/chat/oneapi.ts | 4 +- client/src/store/chat.ts | 9 +++- client/src/store/static.ts | 1 - client/src/store/user.ts | 6 +-- 19 files changed, 153 insertions(+), 54 deletions(-) diff --git a/client/data/config.json b/client/data/config.json index 78af9bff9..cf4c2dda4 100644 --- a/client/data/config.json +++ b/client/data/config.json @@ -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", diff --git a/client/public/locales/en/common.json b/client/public/locales/en/common.json index 9f7bb3653..80a7aeacf 100644 --- a/client/public/locales/en/common.json +++ b/client/public/locales/en/common.json @@ -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", diff --git a/client/public/locales/zh/common.json b/client/public/locales/zh/common.json index ace76730a..89c4e9d6e 100644 --- a/client/public/locales/zh/common.json +++ b/client/public/locales/zh/common.json @@ -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": "立即开始", diff --git a/client/src/api/chat.ts b/client/src/api/chat.ts index a675cb732..b3d428bc0 100644 --- a/client/src/api/chat.ts +++ b/client/src/api/chat.ts @@ -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 diff --git a/client/src/components/Icon/index.tsx b/client/src/components/Icon/index.tsx index 3c0046499..b387ba4e4 100644 --- a/client/src/components/Icon/index.tsx +++ b/client/src/components/Icon/index.tsx @@ -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, diff --git a/client/src/components/Tag/index.tsx b/client/src/components/Tag/index.tsx index 648945c51..7ab97e115 100644 --- a/client/src/components/Tag/index.tsx +++ b/client/src/components/Tag/index.tsx @@ -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', diff --git a/client/src/hooks/useConfirm.tsx b/client/src/hooks/useConfirm.tsx index cdd6345e9..bf0f56e64 100644 --- a/client/src/hooks/useConfirm.tsx +++ b/client/src/hooks/useConfirm.tsx @@ -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(); @@ -32,7 +35,7 @@ export const useConfirm = ({ title = '提示', content }: { title?: string; cont - {title} + {t(title)} {content} @@ -45,7 +48,7 @@ export const useConfirm = ({ title = '提示', content }: { title?: string; cont typeof cancelCb.current === 'function' && cancelCb.current(); }} > - 取消 + {t('Cancel')} diff --git a/client/src/pages/api/chat/removeHistory.ts b/client/src/pages/api/chat/removeHistory.ts index 2b440a91f..83195bbd5 100644 --- a/client/src/pages/api/chat/removeHistory.ts +++ b/client/src/pages/api/chat/removeHistory.ts @@ -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) { diff --git a/client/src/pages/app/detail/components/AdEdit/components/ChatTest.tsx b/client/src/pages/app/detail/components/AdEdit/components/ChatTest.tsx index 8c8b7dc68..92862aece 100644 --- a/client/src/pages/app/detail/components/AdEdit/components/ChatTest.tsx +++ b/client/src/pages/app/detail/components/AdEdit/components/ChatTest.tsx @@ -96,7 +96,7 @@ const ChatTest = ( } + icon={} variant={'base'} borderRadius={'md'} aria-label={'delete'} diff --git a/client/src/pages/app/detail/components/BasicEdit/index.tsx b/client/src/pages/app/detail/components/BasicEdit/index.tsx index e2ef15f11..a955ab420 100644 --- a/client/src/pages/app/detail/components/BasicEdit/index.tsx +++ b/client/src/pages/app/detail/components/BasicEdit/index.tsx @@ -596,7 +596,7 @@ const ChatTest = ({ appId }: { appId: string }) => { } + icon={} variant={'base'} borderRadius={'md'} aria-label={'delete'} diff --git a/client/src/pages/app/list/index.tsx b/client/src/pages/app/list/index.tsx index b76b88c69..d85c099a6 100644 --- a/client/src/pages/app/list/index.tsx +++ b/client/src/pages/app/list/index.tsx @@ -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 = () => { ))} - {isOpenCreateModal && } + {isOpenCreateModal && } ); }; diff --git a/client/src/pages/chat/components/ChatHistorySlider.tsx b/client/src/pages/chat/components/ChatHistorySlider.tsx index 24c963e68..420684348 100644 --- a/client/src/pages/chat/components/ChatHistorySlider.tsx +++ b/client/src/pages/chat/components/ChatHistorySlider.tsx @@ -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( () => (!activeChatId ? [{ id: activeChatId, title: '新对话' }].concat(history) : history), @@ -70,7 +78,7 @@ const ChatHistorySlider = ({ whiteSpace={'nowrap'} > {isPc && ( - + )} - {/* 新对话 */} - + {/* btn */} + - + + + + + {/* chat history */} @@ -230,6 +249,7 @@ const ChatHistorySlider = ({ )} + ); }; diff --git a/client/src/pages/chat/components/SliderApps.tsx b/client/src/pages/chat/components/SliderApps.tsx index 091c9f8b1..45322e051 100644 --- a/client/src/pages/chat/components/SliderApps.tsx +++ b/client/src/pages/chat/components/SliderApps.tsx @@ -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 ( <> diff --git a/client/src/pages/chat/index.tsx b/client/src/pages/chat/index.tsx index 349338356..2270fad73 100644 --- a/client/src/pages/chat/index.tsx +++ b/client/src/pages/chat/index.tsx @@ -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(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); diff --git a/client/src/pages/chat/share.tsx b/client/src/pages/chat/share.tsx index 52cdacdbc..66877c321 100644 --- a/client/src/pages/chat/share.tsx +++ b/client/src/pages/chat/share.tsx @@ -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 + } + }); + }} /> )} diff --git a/client/src/service/moduleDispatch/chat/oneapi.ts b/client/src/service/moduleDispatch/chat/oneapi.ts index bdf834dcf..23919a5d8 100644 --- a/client/src/service/moduleDispatch/chat/oneapi.ts +++ b/client/src/service/moduleDispatch/chat/oneapi.ts @@ -72,16 +72,16 @@ export const dispatchChatCompletion = async (props: Record): 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, // 越大,重复内容越少 diff --git a/client/src/store/chat.ts b/client/src/store/chat.ts index f51d9c359..b32e0689b 100644 --- a/client/src/store/chat.ts +++ b/client/src/store/chat.ts @@ -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; delHistory(history: string): Promise; + clearHistory(appId: string): Promise; updateHistory: (history: ChatHistoryItemType) => void; chatData: InitChatResponse; setChatData: (e: InitChatResponse | ((e: InitChatResponse) => InitChatResponse)) => void; @@ -69,6 +70,12 @@ export const useChatStore = create()( }); 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) => { diff --git a/client/src/store/static.ts b/client/src/store/static.ts index 0239be228..7a54b2963 100644 --- a/client/src/store/static.ts +++ b/client/src/store/static.ts @@ -29,7 +29,6 @@ export const clientInitData = async (): Promise => { beianText = res.systemEnv?.beianText; googleClientVerKey = res.systemEnv?.googleClientVerKey; baiduTongji = res.systemEnv?.baiduTongji; - console.log(res.feConfigs); return res; } catch (error) { diff --git a/client/src/store/user.ts b/client/src/store/user.ts index dd4005af2..4793c4792 100644 --- a/client/src/store/user.ts +++ b/client/src/store/user.ts @@ -19,7 +19,7 @@ type State = { updateUserInfo: (user: UserUpdateParams) => void; myApps: AppListItemType[]; myCollectionApps: AppListItemType[]; - loadMyModels: () => Promise; + loadMyApps: () => Promise; appDetail: AppSchema; loadAppDetail: (id: string, init?: boolean) => Promise; updateAppDetail(appId: string, data: AppUpdateParams): Promise; @@ -63,12 +63,12 @@ export const useUserStore = create()( }, 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) {