mirror of
https://github.com/labring/FastGPT.git
synced 2025-07-24 22:03:54 +00:00
feat: chat ui
This commit is contained in:
@@ -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",
|
||||
|
@@ -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",
|
||||
|
@@ -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": "立即开始",
|
||||
|
@@ -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
|
||||
|
@@ -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,
|
||||
|
@@ -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',
|
||||
|
@@ -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>
|
||||
|
@@ -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) {
|
||||
|
@@ -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'}
|
||||
|
@@ -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'}
|
||||
|
@@ -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>
|
||||
);
|
||||
};
|
||||
|
@@ -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>
|
||||
);
|
||||
};
|
||||
|
@@ -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 (
|
||||
<>
|
||||
|
@@ -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);
|
||||
|
@@ -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
|
||||
}
|
||||
});
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
@@ -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, // 越大,重复内容越少
|
||||
|
@@ -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) => {
|
||||
|
@@ -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) {
|
||||
|
@@ -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) {
|
||||
|
Reference in New Issue
Block a user