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'; import SideBar from '@/components/SideBar'; import { GPTMessages2Chats } from '@fastgpt/global/core/chat/adapt'; import ChatBox from '@/components/core/chat/ChatContainer/ChatBox'; import type { StartChatFnProps } from '@/components/core/chat/ChatContainer/type'; import PageContainer from '@/components/PageContainer'; import ChatHeader from './components/ChatHeader'; import ChatHistorySlider from './components/ChatHistorySlider'; import { serviceSideProps } from '@/web/common/utils/i18n'; import { useTranslation } from 'next-i18next'; 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'; import { addLog } from '@fastgpt/service/common/system/log'; import { connectToDatabase } from '@/service/mongo'; import NextHead from '@/components/common/NextHead'; import { useContextSelector } from 'use-context-selector'; import ChatContextProvider, { ChatContext } from '@/web/core/chat/context/chatContext'; 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 { 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; shareId: string; authToken: string; customUid: string; showRawSource: boolean; showNodeStatus: boolean; }; const OutLink = (props: Props) => { const { t } = useTranslation(); const router = useRouter(); const { showRawSource, showNodeStatus } = props; const { shareId = '', showHistory = '1', showHead = '1', authToken, customUid, ...customVariables } = router.query as { shareId: 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 isChatRecordsLoaded = useContextSelector(ChatRecordContext, (v) => v.isChatRecordsLoaded); const initSign = useRef(false); const { data, loading } = useRequest2( async () => { const shareId = outLinkAuthData.shareId; const outLinkUid = outLinkAuthData.outLinkUid; if (!outLinkUid || !shareId || forbidLoadChat.current) return; const res = await getInitOutLinkChatInfo({ chatId, shareId, outLinkUid }); setChatBoxData(res); resetVariables({ variables: res.variables }); return res; }, { manual: false, refreshDeps: [shareId, outLinkAuthData, chatId], onError(e: any) { if (chatId) { onChangeChatId(''); } }, onFinally() { forbidLoadChat.current = false; } } ); useEffect(() => { if (initSign.current === false && data && isChatRecordsLoaded) { initSign.current = true; if (window !== top) { window.top?.postMessage({ type: 'shareChatReady' }, '*'); } } }, [data, isChatRecordsLoaded]); const startChat = useCallback( async ({ messages, controller, generatingMessage, variables, responseChatItemId }: StartChatFnProps) => { const completionChatId = chatId || getNanoid(); const histories = messages.slice(-1); //post message to report chat start window.top?.postMessage( { type: 'shareChatStart', data: { question: histories[0]?.content } }, '*' ); const { responseText, responseData } = await streamFetch({ data: { messages: histories, variables: { ...variables, ...customVariables }, responseChatItemId, chatId: completionChatId, ...outLinkAuthData }, onMessage: generatingMessage, abortCtrl: controller }); const newTitle = getChatTitleFromChatMessage(GPTMessages2Chats(histories)[0]); // new chat if (completionChatId !== chatId) { onChangeChatId(completionChatId, true); } onUpdateHistoryTitle({ chatId: completionChatId, newTitle }); // update chat window setChatBoxData((state) => ({ ...state, title: newTitle })); // hook message window.top?.postMessage( { type: 'shareChatFinish', data: { question: histories[0]?.content, answer: responseText } }, '*' ); return { responseText, responseData, isNewChat: forbidLoadChat.current }; }, [ chatId, customVariables, outLinkAuthData, onUpdateHistoryTitle, setChatBoxData, forbidLoadChat, onChangeChatId ] ); // window init const [isEmbed, setIdEmbed] = useState(true); useMount(() => { setIdEmbed(window !== top); }); const RenderHistoryList = useMemo(() => { const Children = ( ); if (showHistory !== '1') return null; return isPc ? ( {Children} ) : ( {Children} ); }, [isOpenSlider, isPc, onCloseSlider, showHistory, t]); return ( <> {RenderHistoryList} {/* chat container */} {/* header */} {showHead === '1' ? ( ) : null} {/* chat box */} {isPlugin ? ( onChangeChatId(getNanoid())} onStartChat={startChat} /> ) : ( )} ); }; const Render = (props: Props) => { const { shareId, authToken, customUid, appId } = props; const { localUId, loaded } = useShareChatStore(); const { source, chatId, setSource, setAppId, setOutLinkAuthData } = useChatStore(); 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(() => { setSource('share'); }); // 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 ? ( ) : ( ); }; export default React.memo(Render); export async function getServerSideProps(context: any) { const shareId = context?.query?.shareId || ''; const authToken = context?.query?.authToken || ''; const customUid = context?.query?.customUid || ''; const app = await (async () => { try { await connectToDatabase(); const app = (await MongoOutLink.findOne( { shareId }, 'appId showRawSource showNodeStatus' ) .populate('appId', 'name avatar intro') .lean()) as OutLinkWithAppType; return app; } catch (error) { addLog.error('getServerSideProps', error); return undefined; } })(); return { props: { appId: String(app?.appId?._id) ?? '', appName: app?.appId?.name ?? 'AI', appAvatar: app?.appId?.avatar ?? '', appIntro: app?.appId?.intro ?? 'AI', showRawSource: app?.showRawSource ?? false, showNodeStatus: app?.showNodeStatus ?? false, shareId: shareId ?? '', authToken: authToken ?? '', customUid, ...(await serviceSideProps(context, ['file', 'app', 'chat', 'workflow'])) } }; }