242 lines
6.6 KiB
TypeScript

import type { FC } from "react";
import "antd/dist/reset.css"; // 引入 antd 的样式
import { useEffect, useState } from "react";
import { useAsyncEffect } from "ahooks";
import { useThemeContext } from "@/app/components/chat/embedded-chatbot/theme/theme-context";
import { ChatWithHistoryContext, useChatWithHistoryContext } from "./context";
import { useChatWithHistory } from "./hooks";
import Sidebar from "./sidebar";
import HeaderInMobile from "./header-in-mobile";
import ConfigPanel from "./config-panel";
import ChatWrapper from "./chat-wrapper";
import type { InstalledApp } from "@/models/explore";
import Loading from "@/app/components/base/loading";
import useBreakpoints, { MediaType } from "@/hooks/use-breakpoints";
import { useRouter } from "next/navigation";
import AppUnavailable from "@/app/components/base/app-unavailable";
type ChatWithHistoryProps = {
className?: string;
userStr?: string;
};
const ChatWithHistory: FC<ChatWithHistoryProps> = ({ className, userStr }) => {
const {
appInfoError,
appData,
appInfoLoading,
appPrevChatTree,
showConfigPanelBeforeChat,
appChatListDataLoading,
chatShouldReloadKey,
isMobile,
themeBuilder,
} = useChatWithHistoryContext();
const chatReady = !showConfigPanelBeforeChat || !!appPrevChatTree.length;
const customConfig = appData?.custom_config;
const site = appData?.site;
useEffect(() => {
themeBuilder?.buildTheme(
site?.chat_color_theme,
site?.chat_color_theme_inverted
);
if (site) {
if (customConfig) document.title = `${site.title}`;
else document.title = `${site.title} - Powered by Dify`;
}
}, [site, customConfig, themeBuilder]);
if (appInfoLoading) {
return <Loading type="app" />;
}
if (appInfoError) {
return <AppUnavailable />;
}
return (
<div
className={`h-full flex bg-white ${className} ${isMobile && "flex-col"}`}
>
{!isMobile && <Sidebar userStr={userStr} />}
{isMobile && <HeaderInMobile />}
<div
className={`grow overflow-hidden ${
showConfigPanelBeforeChat &&
!appPrevChatTree.length &&
"flex items-center justify-center"
}`}
>
{showConfigPanelBeforeChat &&
!appChatListDataLoading &&
!appPrevChatTree.length && (
<div
className={`flex w-full items-center justify-center h-full ${
isMobile && "px-4"
}`}
>
<ConfigPanel />
</div>
)}
{appChatListDataLoading && chatReady && <Loading type="app" />}
{chatReady && !appChatListDataLoading && (
<ChatWrapper key={chatShouldReloadKey} />
)}
</div>
</div>
);
};
export type ChatWithHistoryWrapProps = {
installedAppInfo?: InstalledApp;
className?: string;
userStr?: string;
};
const ChatWithHistoryWrap: FC<ChatWithHistoryWrapProps> = ({
installedAppInfo,
className,
userStr,
}) => {
const media = useBreakpoints();
const isMobile = media === MediaType.mobile;
const themeBuilder = useThemeContext();
const {
appInfoError,
appInfoLoading,
appData,
appParams,
appMeta,
appChatListDataLoading,
currentConversationId,
currentConversationItem,
appPrevChatTree,
pinnedConversationList,
conversationList,
showConfigPanelBeforeChat,
newConversationInputs,
newConversationInputsRef,
handleNewConversationInputsChange,
inputsForms,
handleNewConversation,
handleStartChat,
handleChangeConversation,
handlePinConversation,
handleUnpinConversation,
handleDeleteConversation,
conversationRenaming,
handleRenameConversation,
handleNewConversationCompleted,
chatShouldReloadKey,
isInstalledApp,
appId,
handleFeedback,
currentChatInstanceRef,
feedbackModal,
} = useChatWithHistory(installedAppInfo);
return (
<ChatWithHistoryContext.Provider
value={{
appInfoError,
appInfoLoading,
appData,
appParams,
appMeta,
appChatListDataLoading,
currentConversationId,
currentConversationItem,
appPrevChatTree,
pinnedConversationList,
conversationList,
showConfigPanelBeforeChat,
newConversationInputs,
newConversationInputsRef,
handleNewConversationInputsChange,
inputsForms,
handleNewConversation,
handleStartChat,
handleChangeConversation,
handlePinConversation,
handleUnpinConversation,
handleDeleteConversation,
conversationRenaming,
handleRenameConversation,
handleNewConversationCompleted,
chatShouldReloadKey,
isMobile,
isInstalledApp,
appId,
handleFeedback,
currentChatInstanceRef,
themeBuilder,
}}
>
<ChatWithHistory className={className} userStr={userStr} />
{feedbackModal} {/* 确保 feedbackModal 正确渲染 */}
</ChatWithHistoryContext.Provider>
);
};
const ChatWithHistoryWrapWithCheckToken: FC<ChatWithHistoryWrapProps> = ({
installedAppInfo,
className,
}) => {
const [initialized, setInitialized] = useState(false);
const [appUnavailable, setAppUnavailable] = useState<boolean>(false);
const [resCode, setResCode] = useState<number>(200);
const router = useRouter();
const [userName, setUserName] = useState("");
const [nickName, setNickName] = useState("");
useAsyncEffect(async () => {
if (!initialized) {
if (!installedAppInfo) {
const accessToken = localStorage.getItem("token");
if (!accessToken) {
router.replace("/login");
} else {
fetch(`/dev-api/getInfo`, {
method: "GET",
headers: {
Authorization: `Bearer ${accessToken}`,
},
})
.then((res) => res.json())
.then((data) => {
if (data.code !== 200) {
localStorage.removeItem("token");
router.replace("/login");
} else {
setUserName(data.user.userName);
setNickName(data.user.nickName);
}
})
.catch(() => {
setResCode(500);
setAppUnavailable(true);
});
}
}
setInitialized(true);
}
}, []);
if (!initialized) return null;
if (appUnavailable) return <AppUnavailable code={resCode} />;
return (
<ChatWithHistoryWrap
installedAppInfo={installedAppInfo}
className={className}
userStr={`${nickName}[${userName}]`}
/>
);
};
export default ChatWithHistoryWrapWithCheckToken;