V4.8.14 dev (#3234)

* feat: rewrite chat context (#3176)

* feat: add app auto execute (#3115)

* feat: add app auto execute

* auto exec configtion

* chatting animation

* change icon

* fix

* fix

* fix link

* feat: add chat context to all chatbox

* perf: loading ui

---------

Co-authored-by: heheer <heheer@sealos.io>

* app auto exec (#3179)

* add chat records loaded state (#3184)

* perf: chat store reset storage (#3186)

* perf: chat store reset storage

* perf: auto exec code

* chore: workflow ui (#3175)

* chore: workflow ui

* fix

* change icon color config

* change popover to mymenu

* 4.8.14 test (#3189)

* update doc

* fix: token check

* perf: icon button

* update doc

* feat: share page support configuration Whether to allow the original view (#3194)

* update doc

* perf: fix index (#3206)

* perf: i18n

* perf: Add service entry (#3226)

* 4.8.14 test (#3228)

* fix: ai log

* fix: text splitter

* fix: reference unselect & user form description & simple to advance (#3229)

* fix: reference unselect & user form description & simple to advance

* change abort position

* perf

* perf: code (#3232)

* perf: code

* update doc

* fix: create btn permission (#3233)

* update doc

* fix: refresh chatbox listener

* perf: check invalid reference

* perf: check invalid reference

* update doc

* fix: ui props

---------

Co-authored-by: heheer <heheer@sealos.io>
This commit is contained in:
Archer
2024-11-26 12:02:58 +08:00
committed by GitHub
parent 7e1d31b5a9
commit 8aa6b53760
221 changed files with 3831 additions and 2737 deletions

View File

@@ -14,6 +14,8 @@ import { useContextSelector } from 'use-context-selector';
import { ChatContext } from '@/web/core/chat/context/chatContext';
import MyBox from '@fastgpt/web/components/common/MyBox';
import { formatTimeToChatTime } from '@fastgpt/global/common/string/time';
import { ChatItemContext } from '@/web/core/chat/context/chatItemContext';
import { useChatStore } from '@/web/core/chat/context/useChatStore';
type HistoryItemType = {
id: string;
@@ -23,25 +25,7 @@ type HistoryItemType = {
updateTime: Date;
};
const ChatHistorySlider = ({
appId,
appName,
appAvatar,
confirmClearText,
onDelHistory,
onClearHistory,
onSetHistoryTop,
onSetCustomTitle
}: {
appId?: string;
appName: string;
appAvatar: string;
confirmClearText: string;
onDelHistory: (e: { chatId: string }) => void;
onClearHistory: () => void;
onSetHistoryTop?: (e: { chatId: string; top: boolean }) => void;
onSetCustomTitle?: (e: { chatId: string; title: string }) => void;
}) => {
const ChatHistorySlider = ({ confirmClearText }: { confirmClearText: string }) => {
const theme = useTheme();
const router = useRouter();
const isUserChatPage = router.pathname === '/chat';
@@ -51,13 +35,17 @@ const ChatHistorySlider = ({
const { isPc } = useSystem();
const { userInfo } = useUserStore();
const {
onChangeChatId,
chatId: activeChatId,
isLoading,
ScrollData,
histories
} = useContextSelector(ChatContext, (v) => v);
const { appId, chatId: activeChatId } = useChatStore();
const onChangeChatId = useContextSelector(ChatContext, (v) => v.onChangeChatId);
const isLoading = useContextSelector(ChatContext, (v) => v.isLoading);
const ScrollData = useContextSelector(ChatContext, (v) => v.ScrollData);
const histories = useContextSelector(ChatContext, (v) => v.histories);
const onDelHistory = useContextSelector(ChatContext, (v) => v.onDelHistory);
const onClearHistory = useContextSelector(ChatContext, (v) => v.onClearHistories);
const onUpdateHistory = useContextSelector(ChatContext, (v) => v.onUpdateHistory);
const appName = useContextSelector(ChatItemContext, (v) => v.chatBoxData?.app.name);
const appAvatar = useContextSelector(ChatItemContext, (v) => v.chatBoxData?.app.avatar);
const concatHistory = useMemo(() => {
const formatHistories: HistoryItemType[] = histories.map((item) => {
@@ -169,14 +157,13 @@ const ChatHistorySlider = ({
size={'mdSquare'}
aria-label={''}
borderRadius={'50%'}
icon={<MyIcon name={'common/clearLight'} w={'16px'} />}
onClick={() =>
openConfirm(() => {
onClearHistory();
})()
}
>
<MyIcon name={'common/clearLight'} w={'16px'} />
</IconButton>
/>
)}
</Flex>
@@ -249,45 +236,38 @@ const ChatHistorySlider = ({
menuList={[
{
children: [
...(onSetHistoryTop
? [
{
label: item.top
? t('common:core.chat.Unpin')
: t('common:core.chat.Pin'),
icon: 'core/chat/setTopLight',
onClick: () => {
onSetHistoryTop({
chatId: item.id,
top: !item.top
});
}
}
]
: []),
...(onSetCustomTitle
? [
{
label: t('common:common.Custom Title'),
icon: 'common/customTitleLight',
onClick: () => {
onOpenModal({
defaultVal: item.customTitle || item.title,
onSuccess: (e) =>
onSetCustomTitle({
chatId: item.id,
title: e
})
});
}
}
]
: []),
{
label: item.top
? t('common:core.chat.Unpin')
: t('common:core.chat.Pin'),
icon: 'core/chat/setTopLight',
onClick: () => {
onUpdateHistory({
chatId: item.id,
top: !item.top
});
}
},
{
label: t('common:common.Custom Title'),
icon: 'common/customTitleLight',
onClick: () => {
onOpenModal({
defaultVal: item.customTitle || item.title,
onSuccess: (e) =>
onUpdateHistory({
chatId: item.id,
customTitle: e
})
});
}
},
{
label: t('common:common.Delete'),
icon: 'delete',
onClick: () => {
onDelHistory({ chatId: item.id });
onDelHistory(item.id);
if (item.id === activeChatId) {
onChangeChatId();
}

View File

@@ -6,12 +6,16 @@ import { Box, Grid, Stack } from '@chakra-ui/react';
import { useTranslation } from 'next-i18next';
import { PluginRunBoxTabEnum } from '@/components/core/chat/ChatContainer/PluginRunBox/constants';
import LightRowTabs from '@fastgpt/web/components/common/Tabs/LightRowTabs';
import { ChatItemContext } from '@/web/core/chat/context/chatItemContext';
import { useContextSelector } from 'use-context-selector';
const CustomPluginRunBox = (props: PluginRunBoxProps) => {
const { tab, setTab } = props;
const { isPc } = useSystem();
const { t } = useTranslation();
const tab = useContextSelector(ChatItemContext, (v) => v.pluginRunTab);
const setTab = useContextSelector(ChatItemContext, (v) => v.setPluginRunTab);
useEffect(() => {
if (isPc && tab === PluginRunBoxTabEnum.input) {
setTab(PluginRunBoxTabEnum.output);
@@ -24,7 +28,7 @@ const CustomPluginRunBox = (props: PluginRunBoxProps) => {
<Box color={'myGray.900'} mb={5}>
{t('common:common.Input')}
</Box>
<PluginRunBox {...props} tab={PluginRunBoxTabEnum.input} />
<PluginRunBox {...props} showTab={PluginRunBoxTabEnum.input} />
</Box>
<Stack px={3} py={4} h={'100%'} alignItems={'flex-start'} w={'100%'} overflow={'auto'}>
<Box display={'inline-block'}>

View File

@@ -41,7 +41,6 @@ const SliderApps = ({ apps, activeAppId }: { apps: AppListItemType[]; activeAppI
router.replace({
query: {
...router.query,
chatId: '',
appId
}
});

View File

@@ -1,10 +1,10 @@
import React, { useCallback, useMemo, useState } from 'react';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import NextHead from '@/components/common/NextHead';
import { useRouter } from 'next/router';
import { delChatRecordById, getInitChatInfo } from '@/web/core/chat/api';
import { getInitChatInfo } from '@/web/core/chat/api';
import { Box, Flex, Drawer, DrawerOverlay, DrawerContent, useTheme } from '@chakra-ui/react';
import { streamFetch } from '@/web/common/api/fetch';
import { useChatStore } from '@/web/core/chat/context/storeChat';
import { useChatStore } from '@/web/core/chat/context/useChatStore';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { useTranslation } from 'next-i18next';
@@ -21,103 +21,75 @@ import { GPTMessages2Chats } from '@fastgpt/global/core/chat/adapt';
import { getMyApps } from '@/web/core/app/api';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { useCreation, useMount } from 'ahooks';
import { useMount } from 'ahooks';
import { getNanoid } from '@fastgpt/global/common/string/tools';
import { defaultChatData, GetChatTypeEnum } from '@/global/core/chat/constants';
import ChatContextProvider, { ChatContext } from '@/web/core/chat/context/chatContext';
import { AppListItemType } from '@fastgpt/global/core/app/type';
import { useContextSelector } from 'use-context-selector';
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
import dynamic from 'next/dynamic';
import { useChat } from '@/components/core/chat/ChatContainer/useChat';
import ChatBox from '@/components/core/chat/ChatContainer/ChatBox';
import { useSystem } from '@fastgpt/web/hooks/useSystem';
import { InitChatResponse } from '@/global/core/chat/api';
import { ChatSourceEnum } from '@fastgpt/global/core/chat/constants';
import ChatItemContextProvider, { ChatItemContext } from '@/web/core/chat/context/chatItemContext';
import ChatRecordContextProvider, {
ChatRecordContext
} from '@/web/core/chat/context/chatRecordContext';
import { InitChatResponse } from '@/global/core/chat/api';
const CustomPluginRunBox = dynamic(() => import('./components/CustomPluginRunBox'));
type Props = { appId: string; chatId: string };
const Chat = ({
appId,
chatId,
myApps
}: Props & {
myApps: AppListItemType[];
}) => {
const Chat = ({ myApps }: { myApps: AppListItemType[] }) => {
const router = useRouter();
const theme = useTheme();
const { t } = useTranslation();
const { userInfo } = useUserStore();
const { isPc } = useSystem();
const { setLastChatAppId } = useChatStore();
const { userInfo } = useUserStore();
const { setLastChatAppId, chatId, appId, outLinkAuthData } = useChatStore();
const {
onUpdateHistory,
onClearHistories,
onDelHistory,
isOpenSlider,
onCloseSlider,
forbidLoadChat,
onChangeChatId,
onUpdateHistoryTitle
} = useContextSelector(ChatContext, (v) => v);
const isOpenSlider = useContextSelector(ChatContext, (v) => v.isOpenSlider);
const onCloseSlider = useContextSelector(ChatContext, (v) => v.onCloseSlider);
const forbidLoadChat = useContextSelector(ChatContext, (v) => v.forbidLoadChat);
const onChangeChatId = useContextSelector(ChatContext, (v) => v.onChangeChatId);
const onUpdateHistoryTitle = useContextSelector(ChatContext, (v) => v.onUpdateHistoryTitle);
const params = useCreation(() => {
return {
chatId,
appId,
type: GetChatTypeEnum.normal
};
}, [appId, chatId]);
const {
ChatBoxRef,
variablesForm,
pluginRunTab,
setPluginRunTab,
resetVariables,
chatRecords,
ScrollData,
setChatRecords,
totalRecordsCount
} = useChat(params);
const resetVariables = useContextSelector(ChatItemContext, (v) => v.resetVariables);
const isPlugin = useContextSelector(ChatItemContext, (v) => v.isPlugin);
const setChatBoxData = useContextSelector(ChatItemContext, (v) => v.setChatBoxData);
// get chat app info
const [chatData, setChatData] = useState<InitChatResponse>(defaultChatData);
const isPlugin = chatData.app.type === AppTypeEnum.plugin;
const chatRecords = useContextSelector(ChatRecordContext, (v) => v.chatRecords);
const totalRecordsCount = useContextSelector(ChatRecordContext, (v) => v.totalRecordsCount);
// Load chat init data
const [chatData, setChatData] = useState<InitChatResponse>(defaultChatData);
const { loading: isLoading } = useRequest2(
async () => {
if (!appId || forbidLoadChat.current) return;
const res = await getInitChatInfo({ appId, chatId });
res.userAvatar = userInfo?.avatar;
setChatData(res);
setChatBoxData(res);
// reset chat variables
resetVariables({
variables: res.variables
});
setLastChatAppId(appId);
},
{
manual: false,
refreshDeps: [appId, chatId],
onError(e: any) {
setLastChatAppId('');
// reset all chat tore
if (e?.code === 501) {
setLastChatAppId('');
router.replace('/app/list');
} else {
router.replace({
query: {
...router.query,
appId: myApps[0]?._id,
chatId: ''
appId: myApps[0]?._id
}
});
}
@@ -136,7 +108,6 @@ const Chat = ({
generatingMessage,
variables
}: StartChatFnProps) => {
const completionChatId = chatId || getNanoid();
// Just send a user prompt
const histories = messages.slice(-1);
const { responseText, responseData } = await streamFetch({
@@ -145,7 +116,7 @@ const Chat = ({
variables,
responseChatItemId,
appId,
chatId: completionChatId
chatId
},
onMessage: generatingMessage,
abortCtrl: controller
@@ -154,10 +125,7 @@ const Chat = ({
const newTitle = getChatTitleFromChatMessage(GPTMessages2Chats(histories)[0]);
// new chat
if (completionChatId !== chatId && controller.signal.reason !== 'leave') {
onChangeChatId(completionChatId, true);
}
onUpdateHistoryTitle({ chatId: completionChatId, newTitle });
onUpdateHistoryTitle({ chatId, newTitle });
// update chat window
setChatData((state) => ({
...state,
@@ -166,32 +134,13 @@ const Chat = ({
return { responseText, responseData, isNewChat: forbidLoadChat.current };
},
[chatId, appId, onUpdateHistoryTitle, forbidLoadChat, onChangeChatId]
[chatId, appId, onUpdateHistoryTitle, forbidLoadChat]
);
const loading = isLoading;
const RenderHistorySlider = useMemo(() => {
const Children = (
<ChatHistorySlider
confirmClearText={t('common:core.chat.Confirm to clear history')}
appId={appId}
appName={chatData.app.name}
appAvatar={chatData.app.avatar}
onDelHistory={(e) => onDelHistory({ ...e, appId })}
onClearHistory={() => {
onClearHistories({ appId });
}}
onSetHistoryTop={(e) => {
onUpdateHistory({ ...e, appId });
}}
onSetCustomTitle={async (e) => {
onUpdateHistory({
appId,
chatId: e.chatId,
customTitle: e.title
});
}}
/>
<ChatHistorySlider confirmClearText={t('common:core.chat.Confirm to clear history')} />
);
return isPc || !appId ? (
@@ -208,18 +157,7 @@ const Chat = ({
<DrawerContent maxWidth={'75vw'}>{Children}</DrawerContent>
</Drawer>
);
}, [
appId,
chatData.app.avatar,
chatData.app.name,
isOpenSlider,
isPc,
onClearHistories,
onCloseSlider,
onDelHistory,
onUpdateHistory,
t
]);
}, [appId, isOpenSlider, isPc, onCloseSlider, t]);
return (
<Flex h={'100%'}>
@@ -257,33 +195,20 @@ const Chat = ({
<Box flex={'1 0 0'} bg={'white'}>
{isPlugin ? (
<CustomPluginRunBox
pluginInputs={chatData.app.pluginInputs}
variablesForm={variablesForm}
histories={chatRecords}
setHistories={setChatRecords}
appId={chatData.appId}
chatConfig={chatData.app.chatConfig}
tab={pluginRunTab}
setTab={setPluginRunTab}
appId={appId}
chatId={chatId}
outLinkAuthData={outLinkAuthData}
onNewChat={() => onChangeChatId(getNanoid())}
onStartChat={onStartChat}
/>
) : (
<ChatBox
ScrollData={ScrollData}
ref={ChatBoxRef}
chatHistories={chatRecords}
setChatHistories={setChatRecords}
variablesForm={variablesForm}
showEmptyIntro
appAvatar={chatData.app.avatar}
userAvatar={userInfo?.avatar}
chatConfig={chatData.app?.chatConfig}
feedbackType={'user'}
onStartChat={onStartChat}
onDelMessage={({ contentId }) => delChatRecordById({ contentId, appId, chatId })}
appId={appId}
chatId={chatId}
outLinkAuthData={outLinkAuthData}
showEmptyIntro
feedbackType={'user'}
onStartChat={onStartChat}
chatType={'chat'}
showRawSource
showNodeStatus
@@ -297,13 +222,12 @@ const Chat = ({
);
};
const Render = (props: Props) => {
const Render = (props: { appId: string }) => {
const { appId } = props;
const { t } = useTranslation();
const { toast } = useToast();
const router = useRouter();
const { lastChatAppId, lastChatId } = useChatStore();
const { source, chatId, lastChatAppId, setSource, setAppId } = useChatStore();
const { data: myApps = [], runAsync: loadMyApps } = useRequest2(
() => getMyApps({ getRecentlyChat: true }),
@@ -316,16 +240,6 @@ const Render = (props: Props) => {
useMount(async () => {
// pc: redirect to latest model chat
if (!appId) {
if (lastChatAppId) {
return router.replace({
query: {
...router.query,
appId: lastChatAppId,
chatId: lastChatId
}
});
}
const apps = await loadMyApps();
if (apps.length === 0) {
toast({
@@ -337,27 +251,45 @@ const Render = (props: Props) => {
router.replace({
query: {
...router.query,
appId: apps[0]._id,
chatId: ''
appId: lastChatAppId || apps[0]._id
}
});
}
}
setSource('online');
});
// Watch appId
useEffect(() => {
setAppId(appId);
}, [appId, setAppId]);
const providerParams = useMemo(() => ({ appId, source: ChatSourceEnum.online }), [appId]);
return (
<ChatContextProvider params={providerParams}>
<Chat {...props} myApps={myApps} />
</ChatContextProvider>
const chatHistoryProviderParams = useMemo(
() => ({ appId, source: ChatSourceEnum.online }),
[appId]
);
const chatRecordProviderParams = useMemo(() => {
return {
appId,
type: GetChatTypeEnum.normal,
chatId: chatId
};
}, [appId, chatId]);
return source === ChatSourceEnum.online ? (
<ChatContextProvider params={chatHistoryProviderParams}>
<ChatItemContextProvider>
<ChatRecordContextProvider params={chatRecordProviderParams}>
<Chat myApps={myApps} />
</ChatRecordContextProvider>
</ChatItemContextProvider>
</ChatContextProvider>
) : null;
};
export async function getServerSideProps(context: any) {
return {
props: {
appId: context?.query?.appId || '',
chatId: context?.query?.chatId || '',
...(await serviceSideProps(context, ['file', 'app', 'chat', 'workflow']))
}
};

View File

@@ -1,4 +1,4 @@
import React, { useCallback, useMemo, useRef, useState } from 'react';
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useRouter } from 'next/router';
import { Box, Flex, Drawer, DrawerOverlay, DrawerContent } from '@chakra-ui/react';
import { streamFetch } from '@/web/common/api/fetch';
@@ -13,7 +13,7 @@ import ChatHeader from './components/ChatHeader';
import ChatHistorySlider from './components/ChatHistorySlider';
import { serviceSideProps } from '@/web/common/utils/i18n';
import { useTranslation } from 'next-i18next';
import { delChatRecordById, getInitOutLinkChatInfo } from '@/web/core/chat/api';
import { getInitOutLinkChatInfo } from '@/web/core/chat/api';
import { getChatTitleFromChatMessage } from '@fastgpt/global/core/chat/utils';
import { MongoOutLink } from '@fastgpt/service/support/outLink/schema';
import { OutLinkWithAppType } from '@fastgpt/global/support/outLink/type';
@@ -26,16 +26,21 @@ import { InitChatResponse } from '@/global/core/chat/api';
import { defaultChatData, GetChatTypeEnum } from '@/global/core/chat/constants';
import { useMount } from 'ahooks';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
import { useChat } from '@/components/core/chat/ChatContainer/useChat';
import { getNanoid } from '@fastgpt/global/common/string/tools';
import dynamic from 'next/dynamic';
import { useSystem } from '@fastgpt/web/hooks/useSystem';
import { useShareChatStore } from '@/web/core/chat/storeShareChat';
import ChatItemContextProvider, { ChatItemContext } from '@/web/core/chat/context/chatItemContext';
import ChatRecordContextProvider, {
ChatRecordContext
} from '@/web/core/chat/context/chatRecordContext';
import { useChatStore } from '@/web/core/chat/context/useChatStore';
import { ChatSourceEnum } from '@fastgpt/global/core/chat/constants';
const CustomPluginRunBox = dynamic(() => import('./components/CustomPluginRunBox'));
type Props = {
appId: string;
appName: string;
appIntro: string;
appAvatar: string;
@@ -46,17 +51,12 @@ type Props = {
showNodeStatus: boolean;
};
const OutLink = (
props: Props & {
outLinkUid: string;
}
) => {
const OutLink = (props: Props) => {
const { t } = useTranslation();
const router = useRouter();
const { outLinkUid, showRawSource, showNodeStatus } = props;
const { showRawSource, showNodeStatus } = props;
const {
shareId = '',
chatId = '',
showHistory = '1',
showHead = '1',
authToken,
@@ -64,49 +64,69 @@ const OutLink = (
...customVariables
} = router.query as {
shareId: string;
chatId: string;
showHistory: '0' | '1';
showHead: '0' | '1';
authToken: string;
[key: string]: string;
};
const { isPc } = useSystem();
const { outLinkAuthData, appId, chatId } = useChatStore();
const isOpenSlider = useContextSelector(ChatContext, (v) => v.isOpenSlider);
const onCloseSlider = useContextSelector(ChatContext, (v) => v.onCloseSlider);
const forbidLoadChat = useContextSelector(ChatContext, (v) => v.forbidLoadChat);
const onChangeChatId = useContextSelector(ChatContext, (v) => v.onChangeChatId);
const onUpdateHistoryTitle = useContextSelector(ChatContext, (v) => v.onUpdateHistoryTitle);
const resetVariables = useContextSelector(ChatItemContext, (v) => v.resetVariables);
const isPlugin = useContextSelector(ChatItemContext, (v) => v.isPlugin);
const setChatBoxData = useContextSelector(ChatItemContext, (v) => v.setChatBoxData);
const chatRecords = useContextSelector(ChatRecordContext, (v) => v.chatRecords);
const totalRecordsCount = useContextSelector(ChatRecordContext, (v) => v.totalRecordsCount);
const initSign = useRef(false);
const [isEmbed, setIdEmbed] = useState(true);
const [chatData, setChatData] = useState<InitChatResponse>(defaultChatData);
const { loading: isLoading } = useRequest2(
async () => {
const shareId = outLinkAuthData.shareId;
const outLinkUid = outLinkAuthData.outLinkUid;
if (!outLinkUid || !shareId || forbidLoadChat.current) return;
const {
onUpdateHistoryTitle,
onUpdateHistory,
onClearHistories,
onDelHistory,
isOpenSlider,
onCloseSlider,
forbidLoadChat,
onChangeChatId
} = useContextSelector(ChatContext, (v) => v);
const res = await getInitOutLinkChatInfo({
chatId,
shareId,
outLinkUid
});
const params = useMemo(() => {
return {
chatId,
shareId,
outLinkUid,
appId: chatData.appId,
type: GetChatTypeEnum.outLink
};
}, [chatData.appId, chatId, outLinkUid, shareId]);
const {
ChatBoxRef,
variablesForm,
pluginRunTab,
setPluginRunTab,
resetVariables,
chatRecords,
ScrollData,
setChatRecords,
totalRecordsCount
} = useChat(params);
setChatData(res);
setChatBoxData(res);
resetVariables({
variables: res.variables
});
},
{
manual: false,
refreshDeps: [shareId, outLinkAuthData, chatId],
onSuccess() {
// send init message
if (!initSign.current) {
initSign.current = true;
if (window !== top) {
window.top?.postMessage({ type: 'shareChatReady' }, '*');
}
}
},
onError(e: any) {
if (chatId) {
onChangeChatId('');
}
},
onFinally() {
forbidLoadChat.current = false;
}
}
);
const startChat = useCallback(
async ({
@@ -138,10 +158,8 @@ const OutLink = (
...customVariables
},
responseChatItemId,
shareId,
chatId: completionChatId,
appType: chatData.app.type,
outLinkUid
...outLinkAuthData
},
onMessage: generatingMessage,
abortCtrl: controller
@@ -175,57 +193,11 @@ const OutLink = (
return { responseText, responseData, isNewChat: forbidLoadChat.current };
},
[
chatId,
customVariables,
shareId,
chatData.app.type,
outLinkUid,
onUpdateHistoryTitle,
forbidLoadChat,
onChangeChatId
]
);
const { loading: isLoading } = useRequest2(
async () => {
if (!shareId || !outLinkUid || forbidLoadChat.current) return;
const res = await getInitOutLinkChatInfo({
chatId,
shareId,
outLinkUid
});
setChatData(res);
resetVariables({
variables: res.variables
});
},
{
manual: false,
refreshDeps: [shareId, outLinkUid, chatId],
onSuccess() {
// send init message
if (!initSign.current) {
initSign.current = true;
if (window !== top) {
window.top?.postMessage({ type: 'shareChatReady' }, '*');
}
}
},
onError(e: any) {
if (chatId) {
onChangeChatId('');
}
},
onFinally() {
forbidLoadChat.current = false;
}
}
[chatId, customVariables, outLinkAuthData, onUpdateHistoryTitle, forbidLoadChat, onChangeChatId]
);
// window init
const [isEmbed, setIdEmbed] = useState(true);
useMount(() => {
setIdEmbed(window !== top);
});
@@ -233,32 +205,7 @@ const OutLink = (
const RenderHistoryList = useMemo(() => {
const Children = (
<ChatHistorySlider
appName={chatData.app.name}
appAvatar={chatData.app.avatar}
confirmClearText={t('common:core.chat.Confirm to clear share chat history')}
onDelHistory={({ chatId }) =>
onDelHistory({ appId: chatData.appId, chatId, shareId, outLinkUid })
}
onClearHistory={() => {
onClearHistories({ shareId, outLinkUid });
}}
onSetHistoryTop={(e) => {
onUpdateHistory({
...e,
appId: chatData.appId,
shareId,
outLinkUid
});
}}
onSetCustomTitle={(e) => {
onUpdateHistory({
appId: chatData.appId,
chatId: e.chatId,
customTitle: e.title,
shareId,
outLinkUid
});
}}
/>
);
@@ -280,21 +227,7 @@ const OutLink = (
</DrawerContent>
</Drawer>
);
}, [
chatData.app.avatar,
chatData.app.name,
chatData.appId,
isOpenSlider,
isPc,
onClearHistories,
onCloseSlider,
onDelHistory,
onUpdateHistory,
outLinkUid,
shareId,
showHistory,
t
]);
}, [isOpenSlider, isPc, onCloseSlider, showHistory, t]);
const loading = isLoading;
@@ -329,43 +262,21 @@ const OutLink = (
) : null}
{/* chat box */}
<Box flex={1} bg={'white'}>
{chatData.app.type === AppTypeEnum.plugin ? (
{isPlugin ? (
<CustomPluginRunBox
pluginInputs={chatData.app.pluginInputs}
variablesForm={variablesForm}
histories={chatRecords}
setHistories={setChatRecords}
appId={chatData.appId}
tab={pluginRunTab}
setTab={setPluginRunTab}
appId={appId}
chatId={chatId}
outLinkAuthData={outLinkAuthData}
onNewChat={() => onChangeChatId(getNanoid())}
onStartChat={startChat}
/>
) : (
<ChatBox
ScrollData={ScrollData}
ref={ChatBoxRef}
chatHistories={chatRecords}
setChatHistories={setChatRecords}
variablesForm={variablesForm}
appAvatar={chatData.app.avatar}
userAvatar={chatData.userAvatar}
chatConfig={chatData.app?.chatConfig}
appId={appId}
chatId={chatId}
outLinkAuthData={outLinkAuthData}
feedbackType={'user'}
onStartChat={startChat}
onDelMessage={({ contentId }) =>
delChatRecordById({
contentId,
appId: chatData.appId,
chatId,
shareId,
outLinkUid
})
}
appId={chatData.appId}
chatId={chatId}
shareId={shareId}
outLinkUid={outLinkUid}
chatType="share"
showRawSource={showRawSource}
showNodeStatus={showNodeStatus}
@@ -380,29 +291,57 @@ const OutLink = (
};
const Render = (props: Props) => {
const { shareId, authToken, customUid } = props;
const { shareId, authToken, customUid, appId } = props;
const { localUId, loaded } = useShareChatStore();
const { source, chatId, setSource, setAppId, setOutLinkAuthData } = useChatStore();
const [isLoaded, setIsLoaded] = useState(false);
const contextParams = useMemo(() => {
const chatHistoryProviderParams = useMemo(() => {
return { shareId, outLinkUid: authToken || customUid || localUId };
}, [authToken, customUid, localUId, shareId]);
const chatRecordProviderParams = useMemo(() => {
return {
appId,
shareId,
outLinkUid: chatHistoryProviderParams.outLinkUid,
chatId,
type: GetChatTypeEnum.outLink
};
}, [appId, chatHistoryProviderParams.outLinkUid, chatId, shareId]);
useMount(() => {
setIsLoaded(true);
});
const systemLoaded = isLoaded && loaded && contextParams.outLinkUid;
return (
<>
{systemLoaded ? (
<ChatContextProvider params={contextParams}>
<OutLink {...props} outLinkUid={contextParams.outLinkUid} />;
</ChatContextProvider>
) : (
<NextHead title="Loading..." />
)}
</>
setSource('share');
});
const systemLoaded = isLoaded && loaded && chatHistoryProviderParams.outLinkUid;
// Set outLinkAuthData
useEffect(() => {
setOutLinkAuthData({
shareId,
outLinkUid: chatHistoryProviderParams.outLinkUid
});
return () => {
setOutLinkAuthData({});
};
}, [chatHistoryProviderParams.outLinkUid, setOutLinkAuthData, shareId]);
// Watch appId
useEffect(() => {
setAppId(appId);
}, [appId, setAppId]);
return source === ChatSourceEnum.share ? (
<ChatContextProvider params={chatHistoryProviderParams}>
<ChatItemContextProvider>
<ChatRecordContextProvider params={chatRecordProviderParams}>
<OutLink {...props} />
</ChatRecordContextProvider>
</ChatItemContextProvider>
</ChatContextProvider>
) : (
<NextHead title={props.appName} desc={props.appIntro} icon={props.appAvatar} />
);
};
@@ -433,6 +372,7 @@ export async function getServerSideProps(context: any) {
return {
props: {
appId: String(app?.appId?._id) ?? '',
appName: app?.appId?.name ?? 'AI',
appAvatar: app?.appId?.avatar ?? '',
appIntro: app?.appId?.intro ?? 'AI',

View File

@@ -1,9 +1,8 @@
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import NextHead from '@/components/common/NextHead';
import { delChatRecordById, getTeamChatInfo } from '@/web/core/chat/api';
import { getTeamChatInfo } from '@/web/core/chat/api';
import { useRouter } from 'next/router';
import { Box, Flex, Drawer, DrawerOverlay, DrawerContent, useTheme } from '@chakra-ui/react';
import { useToast } from '@fastgpt/web/hooks/useToast';
import SideBar from '@/components/SideBar';
import PageContainer from '@/components/PageContainer';
import { getMyTokensApps } from '@/web/core/chat/api';
@@ -25,10 +24,16 @@ import { InitChatResponse } from '@/global/core/chat/api';
import { defaultChatData, GetChatTypeEnum } from '@/global/core/chat/constants';
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
import { getNanoid } from '@fastgpt/global/common/string/tools';
import { useChat } from '@/components/core/chat/ChatContainer/useChat';
import dynamic from 'next/dynamic';
import { useSystem } from '@fastgpt/web/hooks/useSystem';
import ChatItemContextProvider, { ChatItemContext } from '@/web/core/chat/context/chatItemContext';
import ChatRecordContextProvider, {
ChatRecordContext
} from '@/web/core/chat/context/chatRecordContext';
import { useChatStore } from '@/web/core/chat/context/useChatStore';
import { useMount } from 'ahooks';
import { ChatSourceEnum } from '@fastgpt/global/core/chat/constants';
const CustomPluginRunBox = dynamic(() => import('./components/CustomPluginRunBox'));
type Props = { appId: string; chatId: string; teamId: string; teamToken: string };
@@ -38,51 +43,59 @@ const Chat = ({ myApps }: { myApps: AppListItemType[] }) => {
const router = useRouter();
const {
teamId = '',
appId = '',
chatId = '',
appId: appIdQuery = '',
teamToken,
...customVariables
} = router.query as Props & {
[key: string]: string;
};
const { toast } = useToast();
const theme = useTheme();
const { isPc } = useSystem();
const { outLinkAuthData, appId, chatId } = useChatStore();
const isOpenSlider = useContextSelector(ChatContext, (v) => v.isOpenSlider);
const onCloseSlider = useContextSelector(ChatContext, (v) => v.onCloseSlider);
const forbidLoadChat = useContextSelector(ChatContext, (v) => v.forbidLoadChat);
const onChangeChatId = useContextSelector(ChatContext, (v) => v.onChangeChatId);
const onUpdateHistoryTitle = useContextSelector(ChatContext, (v) => v.onUpdateHistoryTitle);
const resetVariables = useContextSelector(ChatItemContext, (v) => v.resetVariables);
const setChatBoxData = useContextSelector(ChatItemContext, (v) => v.setChatBoxData);
const chatRecords = useContextSelector(ChatRecordContext, (v) => v.chatRecords);
const totalRecordsCount = useContextSelector(ChatRecordContext, (v) => v.totalRecordsCount);
// get chat app info
const [chatData, setChatData] = useState<InitChatResponse>(defaultChatData);
const { loading: isLoading } = useRequest2(
async () => {
if (!appId || forbidLoadChat.current) return;
const {
onUpdateHistoryTitle,
onUpdateHistory,
onClearHistories,
onDelHistory,
isOpenSlider,
onCloseSlider,
forbidLoadChat,
onChangeChatId
} = useContextSelector(ChatContext, (v) => v);
const res = await getTeamChatInfo({ teamId, appId, chatId, teamToken });
const params = useMemo(() => {
return {
appId,
chatId,
teamId,
teamToken,
type: GetChatTypeEnum.team
};
}, [appId, chatId, teamId, teamToken]);
const {
ChatBoxRef,
variablesForm,
pluginRunTab,
setPluginRunTab,
resetVariables,
chatRecords,
ScrollData,
setChatRecords,
totalRecordsCount
} = useChat(params);
setChatData(res);
setChatBoxData(res);
// reset chat records
resetVariables({
variables: res.variables
});
},
{
manual: false,
refreshDeps: [teamId, teamToken, appId, chatId],
onError(e: any) {
console.log(e);
if (chatId) {
onChangeChatId();
}
},
onFinally() {
forbidLoadChat.current = false;
}
}
);
const startChat = useCallback(
async ({
@@ -143,58 +156,9 @@ const Chat = ({ myApps }: { myApps: AppListItemType[] }) => {
]
);
// get chat app info
const { loading: isLoading } = useRequest2(
async () => {
if (!appId || forbidLoadChat.current) return;
const res = await getTeamChatInfo({ teamId, appId, chatId, teamToken });
setChatData(res);
// reset chat records
resetVariables({
variables: res.variables
});
},
{
manual: false,
refreshDeps: [teamId, teamToken, appId, chatId],
onError(e: any) {
console.log(e);
if (chatId) {
onChangeChatId('');
}
},
onFinally() {
forbidLoadChat.current = false;
}
}
);
const RenderHistoryList = useMemo(() => {
const Children = (
<ChatHistorySlider
appId={appId}
appName={chatData.app.name}
appAvatar={chatData.app.avatar}
confirmClearText={t('common:core.chat.Confirm to clear history')}
onDelHistory={(e) => onDelHistory({ ...e, appId, teamId, teamToken })}
onClearHistory={() => {
onClearHistories({ appId, teamId, teamToken });
}}
onSetHistoryTop={(e) => {
onUpdateHistory({ ...e, teamId, teamToken, appId });
}}
onSetCustomTitle={async (e) => {
onUpdateHistory({
appId,
chatId: e.chatId,
customTitle: e.title,
teamId,
teamToken
});
}}
/>
<ChatHistorySlider confirmClearText={t('common:core.chat.Confirm to clear history')} />
);
return isPc || !appId ? (
@@ -211,20 +175,7 @@ const Chat = ({ myApps }: { myApps: AppListItemType[] }) => {
<DrawerContent maxWidth={'75vw'}>{Children}</DrawerContent>
</Drawer>
);
}, [
appId,
chatData.app.avatar,
chatData.app.name,
isOpenSlider,
isPc,
onClearHistories,
onCloseSlider,
onDelHistory,
onUpdateHistory,
t,
teamId,
teamToken
]);
}, [appId, isOpenSlider, isPc, onCloseSlider, t]);
const loading = isLoading;
@@ -261,41 +212,19 @@ const Chat = ({ myApps }: { myApps: AppListItemType[] }) => {
<Box flex={1}>
{chatData.app.type === AppTypeEnum.plugin ? (
<CustomPluginRunBox
pluginInputs={chatData.app.pluginInputs}
variablesForm={variablesForm}
histories={chatRecords}
setHistories={setChatRecords}
appId={chatData.appId}
tab={pluginRunTab}
setTab={setPluginRunTab}
appId={appId}
chatId={chatId}
outLinkAuthData={outLinkAuthData}
onNewChat={() => onChangeChatId(getNanoid())}
onStartChat={startChat}
/>
) : (
<ChatBox
ref={ChatBoxRef}
ScrollData={ScrollData}
chatHistories={chatRecords}
setChatHistories={setChatRecords}
variablesForm={variablesForm}
appAvatar={chatData.app.avatar}
userAvatar={chatData.userAvatar}
chatConfig={chatData.app?.chatConfig}
appId={appId}
chatId={chatId}
outLinkAuthData={outLinkAuthData}
feedbackType={'user'}
onStartChat={startChat}
onDelMessage={({ contentId }) =>
delChatRecordById({
contentId,
appId: chatData.appId,
chatId,
teamId,
teamToken
})
}
appId={chatData.appId}
chatId={chatId}
teamId={teamId}
teamToken={teamToken}
chatType="team"
showRawSource
showNodeStatus
@@ -311,9 +240,8 @@ const Chat = ({ myApps }: { myApps: AppListItemType[] }) => {
const Render = (props: Props) => {
const { teamId, appId, teamToken } = props;
const { t } = useTranslation();
const { toast } = useToast();
const router = useRouter();
const { source, chatId, setSource, setAppId, setOutLinkAuthData } = useChatStore();
const { data: myApps = [], runAsync: loadMyApps } = useRequest2(
async () => {
@@ -323,34 +251,61 @@ const Render = (props: Props) => {
return [];
},
{
manual: false
manual: true
}
);
// 初始化聊天框
useEffect(() => {
(async () => {
if (appId || myApps.length === 0) return;
useMount(async () => {
setSource('team');
router.replace({
query: {
...router.query,
appId: myApps[0]._id,
chatId: ''
}
});
})();
}, [appId, loadMyApps, myApps, router, t, toast]);
const apps = await loadMyApps();
if (appId || apps.length === 0) return;
router.replace({
query: {
...router.query,
appId: apps[0]._id
}
});
});
// Watch appId
useEffect(() => {
setAppId(appId);
}, [appId, setAppId]);
useEffect(() => {
setOutLinkAuthData({
teamId,
teamToken
});
return () => {
setOutLinkAuthData({});
};
}, [teamId, teamToken, setOutLinkAuthData]);
const contextParams = useMemo(() => {
return { teamId, appId, teamToken };
}, [teamId, appId, teamToken]);
const chatRecordProviderParams = useMemo(() => {
return {
appId,
chatId,
teamId,
teamToken,
type: GetChatTypeEnum.team
};
}, [appId, chatId, teamId, teamToken]);
return (
return source === ChatSourceEnum.team ? (
<ChatContextProvider params={contextParams}>
<Chat {...props} myApps={myApps} />
<ChatItemContextProvider>
<ChatRecordContextProvider params={chatRecordProviderParams}>
<Chat {...props} myApps={myApps} />
</ChatRecordContextProvider>
</ChatItemContextProvider>
</ChatContextProvider>
);
) : null;
};
export async function getServerSideProps(context: any) {