From 0c53579996489363c104abe18d6dddb596101c02 Mon Sep 17 00:00:00 2001 From: Fred Date: Fri, 10 May 2024 14:57:55 +0800 Subject: [PATCH] refactor: init switching to nextjs router --- app/(app)/chat/layout.tsx | 102 +++++++++++++ app/(app)/chat/page.tsx | 137 ++++++++++++++++++ app/(app)/layout.tsx | 21 +++ app/(app)/settings/layout.tsx | 4 + app/(app)/settings/page.tsx | 3 + app/command.ts | 34 +++-- app/components/Modal/index.tsx | 21 ++- app/components/Popover/index.tsx | 40 +++-- app/components/Screen/index.tsx | 20 +-- app/components/auth.tsx | 9 +- app/components/chat-list.tsx | 12 +- app/components/chat.tsx | 6 +- app/components/sidebar.tsx | 12 +- app/config/client.ts | 7 +- .../Chat/components/ChatActions.tsx | 9 +- app/containers/Chat/components/ChatHeader.tsx | 10 +- .../Chat/components/ChatInputPanel.tsx | 7 +- .../Chat/components/ChatMessagePanel.tsx | 14 +- .../Chat/components/SessionItem.tsx | 10 +- app/containers/Chat/index.tsx | 22 +-- app/containers/Sidebar/index.tsx | 22 ++- app/hooks/useDeviceInfo.ts | 44 ++++++ app/hooks/useRelativePosition.ts | 2 +- app/hooks/useWindowSize.ts | 14 +- app/utils.ts | 14 +- 25 files changed, 473 insertions(+), 123 deletions(-) create mode 100644 app/(app)/chat/layout.tsx create mode 100644 app/(app)/chat/page.tsx create mode 100644 app/(app)/layout.tsx create mode 100644 app/(app)/settings/layout.tsx create mode 100644 app/(app)/settings/page.tsx create mode 100644 app/hooks/useDeviceInfo.ts diff --git a/app/(app)/chat/layout.tsx b/app/(app)/chat/layout.tsx new file mode 100644 index 000000000..d2ae81b2e --- /dev/null +++ b/app/(app)/chat/layout.tsx @@ -0,0 +1,102 @@ +"use client"; +import { + DEFAULT_SIDEBAR_WIDTH, + MAX_SIDEBAR_WIDTH, + MIN_SIDEBAR_WIDTH, + Path, +} from "@/app/constant"; +import useDrag from "@/app/hooks/useDrag"; +import useMobileScreen from "@/app/hooks/useMobileScreen"; +import { updateGlobalCSSVars } from "@/app/utils/client"; +import { useRef, useState } from "react"; +import { useAppConfig } from "@/app/store/config"; +import React from "react"; +import { AuthPage } from "@/app/components/auth"; +import { SideBar } from "@/app/containers/Sidebar"; +import Screen from "@/app/components/Screen"; +import { useSwitchTheme } from "@/app/hooks/useSwitchTheme"; +import Chat from "@/app/containers/Chat/ChatPanel"; +export default function Layout({ children }: { children: React.ReactNode }) { + const [showPanel, setShowPanel] = useState(false); + const [externalProps, setExternalProps] = useState({}); + const config = useAppConfig(); + useSwitchTheme(); + const isMobileScreen = useMobileScreen(); + + const startDragWidth = useRef(config.sidebarWidth ?? DEFAULT_SIDEBAR_WIDTH); + // drag side bar + const { onDragStart } = useDrag({ + customToggle: () => { + config.update((config) => { + config.sidebarWidth = DEFAULT_SIDEBAR_WIDTH; + }); + }, + customDragMove: (nextWidth: number) => { + const { menuWidth } = updateGlobalCSSVars(nextWidth); + + document.documentElement.style.setProperty( + "--menu-width", + `${menuWidth}px`, + ); + config.update((config) => { + config.sidebarWidth = nextWidth; + }); + }, + customLimit: (x: number) => + Math.max( + MIN_SIDEBAR_WIDTH, + Math.min(MAX_SIDEBAR_WIDTH, startDragWidth.current + x), + ), + }); + return ( +
+
+ {children} +
+ {!isMobileScreen && ( +
{ + startDragWidth.current = config.sidebarWidth; + onDragStart(e as any); + }} + > +
+   +
+
+ )} +
+ {/* */} + {/* {children} */} + +
+
+ ); +} diff --git a/app/(app)/chat/page.tsx b/app/(app)/chat/page.tsx new file mode 100644 index 000000000..b871f9a1f --- /dev/null +++ b/app/(app)/chat/page.tsx @@ -0,0 +1,137 @@ +"use client"; +import { + DragDropContext, + Droppable, + OnDragEndResponder, +} from "@hello-pangea/dnd"; + +import { useAppConfig, useChatStore } from "@/app/store"; + +import Locale from "@/app/locales"; +// import { useLocation, useNavigate } from "react-router-dom"; +import { useRouter, usePathname } from "next/navigation"; + +import AddIcon from "@/app/icons/addIcon.svg"; +import NextChatTitle from "@/app/icons/nextchatTitle.svg"; + +import Modal from "@/app/components/Modal"; +import SessionItem from "@/app/containers/Chat/components/SessionItem"; + +export default function Page() { + const [sessions, selectedIndex, selectSession, moveSession] = useChatStore( + (state) => [ + state.sessions, + state.currentSessionIndex, + state.selectSession, + state.moveSession, + ], + ); + const config = useAppConfig(); + + const { isMobileScreen } = config; + + const chatStore = useChatStore(); + + const pathname = usePathname(); + const onDragEnd: OnDragEndResponder = (result) => { + const { destination, source } = result; + if (!destination) { + return; + } + + if ( + destination.droppableId === source.droppableId && + destination.index === source.index + ) { + return; + } + moveSession(source.index, destination.index); + }; + + return ( +
+
+
+
+ +
+
{ + // if (config.dontShowMaskSplashScreen) { + // chatStore.newSession(); + // navigate(Path.Chat); + // } else { + // navigate(Path.NewChat); + // } + }} + > + +
+
+
+ Build your own AI assistant. +
+
+ +
+ + + {(provided) => ( +
+ {sessions.map((item, i) => ( + { + // navigate(Path.Chat); + // selectSession(i); + }} + onDelete={async () => { + if ( + await Modal.warn({ + okText: Locale.ChatItem.DeleteOkBtn, + cancelText: Locale.ChatItem.DeleteCancelBtn, + title: Locale.ChatItem.DeleteTitle, + content: Locale.ChatItem.DeleteContent, + }) + ) { + chatStore.deleteSession(i); + } + }} + mask={item.mask} + isMobileScreen={isMobileScreen} + /> + ))} + {provided.placeholder} +
+ )} +
+
+
+
+ ); +} diff --git a/app/(app)/layout.tsx b/app/(app)/layout.tsx new file mode 100644 index 000000000..d859f4b17 --- /dev/null +++ b/app/(app)/layout.tsx @@ -0,0 +1,21 @@ +"use client"; + +import React from "react"; +import { AuthPage } from "@/app/components/auth"; +import { SideBar } from "@/app/containers/Sidebar"; +import Screen from "@/app/components/Screen"; + +export interface MenuWrapperInspectProps { + setExternalProps?: (v: Record) => void; + setShowPanel?: (v: boolean) => void; + showPanel?: boolean; + [k: string]: any; +} + +export default function AppLayout({ children }: { children: React.ReactNode }) { + return ( + } sidebar={}> + {children} + + ); +} diff --git a/app/(app)/settings/layout.tsx b/app/(app)/settings/layout.tsx new file mode 100644 index 000000000..6db9b5fd7 --- /dev/null +++ b/app/(app)/settings/layout.tsx @@ -0,0 +1,4 @@ +import React from "react"; +export default function Layout({ children }: { children: React.ReactNode }) { + return <>{children}; +} diff --git a/app/(app)/settings/page.tsx b/app/(app)/settings/page.tsx new file mode 100644 index 000000000..7d1e0b717 --- /dev/null +++ b/app/(app)/settings/page.tsx @@ -0,0 +1,3 @@ +export default function Page() { + return <>; +} diff --git a/app/command.ts b/app/command.ts index e515e5f0b..11d516d39 100644 --- a/app/command.ts +++ b/app/command.ts @@ -1,5 +1,6 @@ import { useEffect } from "react"; -import { useSearchParams } from "react-router-dom"; +// import { useSearchParams } from "react-router-dom"; +import { useSearchParams } from "next/navigation"; import Locale from "./locales"; type Command = (param: string) => void; @@ -14,22 +15,23 @@ interface Commands { export function useCommand(commands: Commands = {}) { const [searchParams, setSearchParams] = useSearchParams(); - useEffect(() => { - let shouldUpdate = false; - searchParams.forEach((param, name) => { - const commandName = name as keyof Commands; - if (typeof commands[commandName] === "function") { - commands[commandName]!(param); - searchParams.delete(name); - shouldUpdate = true; - } - }); + // fixme: update commands + // useEffect(() => { + // let shouldUpdate = false; + // searchParams.forEach((param, name) => { + // const commandName = name as keyof Commands; + // if (typeof commands[commandName] === "function") { + // commands[commandName]!(param); + // searchParams.delete(name); + // shouldUpdate = true; + // } + // }); - if (shouldUpdate) { - setSearchParams(searchParams); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [searchParams, commands]); + // if (shouldUpdate) { + // setSearchParams(searchParams); + // } + // // eslint-disable-next-line react-hooks/exhaustive-deps + // }, [searchParams, commands]); } interface ChatCommands { diff --git a/app/components/Modal/index.tsx b/app/components/Modal/index.tsx index 9bca53736..71bbefa5f 100644 --- a/app/components/Modal/index.tsx +++ b/app/components/Modal/index.tsx @@ -53,6 +53,8 @@ export interface TriggerProps const baseZIndex = 150; +let div: HTMLDivElement | null = null; + const Modal = (props: ModalProps) => { const { onOk, @@ -78,6 +80,16 @@ const Modal = (props: ModalProps) => { const mergeOpen = visible ?? open; + useLayoutEffect(() => { + const div: HTMLDivElement = document.createElement("div"); + div.id = "confirm-root"; + div.style.height = "0px"; + document.body.appendChild(div); + }, []); + const root = createRoot(div); + const closeModal = () => { + root.unmount(); + }; const handleClose = () => { setOpen(false); onCancel?.(); @@ -121,7 +133,7 @@ const Modal = (props: ModalProps) => { { if (maskCloseble) { @@ -165,7 +177,7 @@ const Modal = (props: ModalProps) => { ${titleClassName} `} > -
+
{title}
{closeble && ( @@ -283,11 +295,6 @@ export const Warn = ({ ); }; -const div = document.createElement("div"); -div.id = "confirm-root"; -div.style.height = "0px"; -document.body.appendChild(div); - Modal.warn = (props: Omit) => { const root = createRoot(div); const closeModal = () => { diff --git a/app/components/Popover/index.tsx b/app/components/Popover/index.tsx index 54491f17a..882dfaad5 100644 --- a/app/components/Popover/index.tsx +++ b/app/components/Popover/index.tsx @@ -1,3 +1,4 @@ +"use client"; import useRelativePosition from "@/app/hooks/useRelativePosition"; import { RefObject, @@ -36,19 +37,6 @@ const ArrowIcon = ({ sibling }: { sibling: RefObject }) => { const baseZIndex = 100; const popoverRootName = "popoverRoot"; -let popoverRoot = document.querySelector( - `#${popoverRootName}`, -) as HTMLDivElement; -if (!popoverRoot) { - popoverRoot = document.createElement("div"); - document.body.appendChild(popoverRoot); - popoverRoot.style.height = "0px"; - popoverRoot.style.width = "100%"; - popoverRoot.style.position = "fixed"; - popoverRoot.style.bottom = "0"; - popoverRoot.style.zIndex = "10000"; - popoverRoot.id = "popover-root"; -} export interface PopoverProps { content?: JSX.Element | string; @@ -65,6 +53,8 @@ export interface PopoverProps { getPopoverPanelRef?: (ref: RefObject) => void; } +let popoverRoot: HTMLDivElement; + export default function Popover(props: PopoverProps) { const { content, @@ -184,6 +174,26 @@ export default function Popover(props: PopoverProps) { const popoverRef = useRef(null); const closeTimer = useRef(0); + useLayoutEffect(() => { + if (popoverRoot) { + return; + } + + popoverRoot = document.querySelector( + `#${popoverRootName}`, + ) as HTMLDivElement; + if (!popoverRoot) { + popoverRoot = document.createElement("div"); + document.body.appendChild(popoverRoot); + popoverRoot.style.height = "0px"; + popoverRoot.style.width = "100%"; + popoverRoot.style.position = "fixed"; + popoverRoot.style.bottom = "0"; + popoverRoot.style.zIndex = "10000"; + popoverRoot.id = "popover-root"; + } + }, []); + useLayoutEffect(() => { getPopoverPanelRef?.(popoverRef); onShow?.(internalShow); @@ -207,6 +217,10 @@ export default function Popover(props: PopoverProps) { window.document.documentElement.style.overflow = "auto"; }; + if (mergedShow) { + return null; + } + return (
isIOS() && isMobileScreen, - [isMobileScreen], - ); - + const { deviceType, systemInfo } = useDeviceInfo(); useListenWinResize(); return ( @@ -59,7 +56,10 @@ export default function Screen(props: ScreenProps) { id={SlotID.AppBody} style={{ // #3016 disable transition on ios mobile screen - transition: isIOSMobile ? "none" : undefined, + transition: + systemInfo === "iOS" && deviceType === "mobile" + ? "none" + : undefined, }} > {props.children} diff --git a/app/components/auth.tsx b/app/components/auth.tsx index 57118349b..8dead2015 100644 --- a/app/components/auth.tsx +++ b/app/components/auth.tsx @@ -1,7 +1,8 @@ +"use client"; import styles from "./auth.module.scss"; import { IconButton } from "./button"; -import { useNavigate } from "react-router-dom"; +import { useRouter } from "next/navigation"; import { Path } from "../constant"; import { useAccessStore } from "../store"; import Locale from "../locales"; @@ -11,11 +12,11 @@ import { useEffect } from "react"; import { getClientConfig } from "../config/client"; export function AuthPage() { - const navigate = useNavigate(); + const router = useRouter(); const accessStore = useAccessStore(); - const goHome = () => navigate(Path.Home); - const goChat = () => navigate(Path.Chat); + const goHome = () => router.push(Path.Home); + const goChat = () => router.push(Path.Chat); const resetAccessCode = () => { accessStore.update((access) => { access.openaiApiKey = ""; diff --git a/app/components/chat-list.tsx b/app/components/chat-list.tsx index 7ef6e7b83..80df69bb5 100644 --- a/app/components/chat-list.tsx +++ b/app/components/chat-list.tsx @@ -12,13 +12,14 @@ import { import { useChatStore } from "../store"; import Locale from "../locales"; -import { Link, useLocation, useNavigate } from "react-router-dom"; +// import { Link, useLocation, useNavigate } from "react-router-dom"; import { Path } from "../constant"; import { MaskAvatar } from "./mask"; import { Mask } from "../store/mask"; import { useRef, useEffect } from "react"; import { showConfirm } from "./ui-lib"; import { useMobileScreen } from "../utils"; +import { usePathname, useRouter } from "next/navigation"; export function ChatItem(props: { onClick?: () => void; @@ -41,14 +42,14 @@ export function ChatItem(props: { } }, [props.selected]); - const { pathname: currentPath } = useLocation(); + const pathname = usePathname(); return ( {(provided) => (
{ const { destination, source } = result; @@ -150,7 +151,8 @@ export function ChatList(props: { narrow?: boolean }) { index={i} selected={i === selectedIndex} onClick={() => { - navigate(Path.Chat); + // navigate(Path.Chat); + router.push(Path.Chat); selectSession(i); }} onDelete={async () => { diff --git a/app/components/chat.tsx b/app/components/chat.tsx index 85df5b9a8..7570a85d9 100644 --- a/app/components/chat.tsx +++ b/app/components/chat.tsx @@ -97,6 +97,7 @@ import { ExportMessageModal } from "./exporter"; import { getClientConfig } from "../config/client"; import { useAllModels } from "../utils/hooks"; import { MultimodalContent } from "../client/api"; +import { useRouter } from "next/navigation"; const Markdown = dynamic(async () => (await import("./markdown")).Markdown, { loading: () => , @@ -428,7 +429,7 @@ export function ChatActions(props: { uploading: boolean; }) { const config = useAppConfig(); - const navigate = useNavigate(); + const router = useRouter(); const chatStore = useChatStore(); // switch themes @@ -543,7 +544,8 @@ export function ChatActions(props: { { - navigate(Path.Masks); + // navigate(Path.Masks); + router.push(Path.Masks); }} text={Locale.Chat.InputActions.Masks} icon={} diff --git a/app/components/sidebar.tsx b/app/components/sidebar.tsx index 69b2e71f8..45bcabaa7 100644 --- a/app/components/sidebar.tsx +++ b/app/components/sidebar.tsx @@ -27,9 +27,9 @@ import { } from "../constant"; import { Link, useNavigate } from "react-router-dom"; -import { isIOS, useMobileScreen } from "../utils"; import dynamic from "next/dynamic"; import { showConfirm, showToast } from "./ui-lib"; +import { useDeviceInfo } from "../hooks/useDeviceInfo"; const ChatList = dynamic(async () => (await import("./chat-list")).ChatList, { loading: () => null, @@ -130,16 +130,11 @@ function useDragSideBar() { export function SideBar(props: { className?: string }) { const chatStore = useChatStore(); - + const { deviceType, systemInfo } = useDeviceInfo(); // drag side bar const { onDragStart, shouldNarrow } = useDragSideBar(); const navigate = useNavigate(); const config = useAppConfig(); - const isMobileScreen = useMobileScreen(); - const isIOSMobile = useMemo( - () => isIOS() && isMobileScreen, - [isMobileScreen], - ); useHotKey(); @@ -150,7 +145,8 @@ export function SideBar(props: { className?: string }) { }`} style={{ // #3016 disable transition on ios mobile screen - transition: isMobileScreen && isIOSMobile ? "none" : undefined, + transition: + deviceType === "mobile" && systemInfo === "iOS" ? "none" : undefined, }} >
diff --git a/app/config/client.ts b/app/config/client.ts index da582a3e8..c2d83ef58 100644 --- a/app/config/client.ts +++ b/app/config/client.ts @@ -3,7 +3,12 @@ import { BuildConfig, getBuildConfig } from "./build"; export function getClientConfig() { if (typeof document !== "undefined") { // client side - return JSON.parse(queryMeta("config")) as BuildConfig; + try { + const config = JSON.parse(queryMeta("config")) as BuildConfig; + return config; + } catch (e) { + return null; + } } if (typeof process !== "undefined") { diff --git a/app/containers/Chat/components/ChatActions.tsx b/app/containers/Chat/components/ChatActions.tsx index a03a9cb6a..68264bd10 100644 --- a/app/containers/Chat/components/ChatActions.tsx +++ b/app/containers/Chat/components/ChatActions.tsx @@ -1,5 +1,3 @@ -import { useNavigate } from "react-router-dom"; - import { ModelType, Theme, useAppConfig } from "@/app/store/config"; import { useChatStore } from "@/app/store/chat"; import { ChatControllerPool } from "@/app/client/controller"; @@ -22,6 +20,7 @@ import AddCircleIcon from "@/app/icons/addCircle.svg"; import Popover from "@/app/components/Popover"; import ModelSelect from "./ModelSelect"; +import { useRouter } from "next/navigation"; export interface Action { onClick?: () => void; @@ -46,7 +45,7 @@ export function ChatActions(props: { className?: string; }) { const config = useAppConfig(); - const navigate = useNavigate(); + const router = useRouter(); const chatStore = useChatStore(); // switch themes @@ -146,7 +145,7 @@ export function ChatActions(props: { }, { onClick: () => { - navigate(Path.Masks); + router.push(Path.Masks); }, text: Locale.Chat.InputActions.Masks, isShow: true, @@ -206,7 +205,7 @@ export function ChatActions(props: { placement="rt" noArrow popoverClassName="border border-chat-actions-popover-mobile rounded-md shadow-chat-actions-popover-mobile w-actions-popover bg-chat-actions-popover-panel-mobile " - className=" cursor-pointer follow-parent-svg default-icon-color" + className="cursor-pointer follow-parent-svg default-icon-color" > diff --git a/app/containers/Chat/components/ChatHeader.tsx b/app/containers/Chat/components/ChatHeader.tsx index 73d30dbaa..c301fc88e 100644 --- a/app/containers/Chat/components/ChatHeader.tsx +++ b/app/containers/Chat/components/ChatHeader.tsx @@ -1,4 +1,4 @@ -import { useNavigate } from "react-router-dom"; +import { useRouter } from "next/navigation"; import Locale from "@/app/locales"; import { Path } from "@/app/constant"; import { DEFAULT_TOPIC, useChatStore } from "@/app/store/chat"; @@ -17,8 +17,8 @@ export interface ChatHeaderProps { export default function ChatHeader(props: ChatHeaderProps) { const { isMobileScreen, setIsEditingMessage, setShowExport } = props; - const navigate = useNavigate(); - + // const navigate = useNavigate(); + const router = useRouter(); const chatStore = useChatStore(); const session = chatStore.currentSession(); @@ -39,8 +39,8 @@ export default function ChatHeader(props: ChatHeaderProps) { {isMobileScreen ? (
navigate(Path.Home)} + className="cursor-pointer follow-parent-svg default-icon-color" + onClick={() => router.push(Path.Home)} >
diff --git a/app/containers/Chat/components/ChatInputPanel.tsx b/app/containers/Chat/components/ChatInputPanel.tsx index bd8148904..24dd8cc37 100644 --- a/app/containers/Chat/components/ChatInputPanel.tsx +++ b/app/containers/Chat/components/ChatInputPanel.tsx @@ -10,6 +10,7 @@ import { ChatCommandPrefix, useChatCommand } from "@/app/command"; import { useChatStore } from "@/app/store/chat"; import { usePromptStore } from "@/app/store/prompt"; import { useAppConfig } from "@/app/store/config"; +import { useRouter } from "next/navigation"; import usePaste from "@/app/hooks/usePaste"; import { ChatActions } from "./ChatActions"; @@ -71,7 +72,7 @@ export default forwardRef( const [promptHints, setPromptHints] = useState([]); const chatStore = useChatStore(); - const navigate = useNavigate(); + const router = useRouter(); const config = useAppConfig(); const { uploadImage } = useUploadImage(attachImages, { @@ -85,7 +86,7 @@ export default forwardRef( // chat commands shortcuts const chatCommands = useChatCommand({ new: () => chatStore.newSession(), - newm: () => navigate(Path.NewChat), + newm: () => router.push(Path.NewChat), prev: () => chatStore.nextSession(-1), next: () => chatStore.nextSession(1), clear: () => @@ -299,7 +300,7 @@ export default forwardRef( }} /> {!isMobileScreen && ( -
+
 
{Locale.Chat.Input(submitKey)} diff --git a/app/containers/Chat/components/ChatMessagePanel.tsx b/app/containers/Chat/components/ChatMessagePanel.tsx index 3beebe831..0a1cb04f6 100644 --- a/app/containers/Chat/components/ChatMessagePanel.tsx +++ b/app/containers/Chat/components/ChatMessagePanel.tsx @@ -1,4 +1,4 @@ -import { Fragment, useMemo } from "react"; +import { Fragment, useEffect, useMemo } from "react"; import { ChatMessage, useChatStore } from "@/app/store/chat"; import { CHAT_PAGE_SIZE } from "@/app/constant"; import Locale from "@/app/locales"; @@ -88,11 +88,13 @@ export default function ChatMessagePanel(props: ChatMessagePanelProps) { ? session.clearContextIndex! + context.length - msgRenderIndex : -1; - if (!MarkdownLoadedCallback) { - MarkdownLoadedCallback = () => { - window.setTimeout(scrollDomToBottom, 100); - }; - } + useEffect(() => { + if (!MarkdownLoadedCallback) { + MarkdownLoadedCallback = () => { + window.setTimeout(scrollDomToBottom, 100); + }; + } + }, [scrollDomToBottom]); const messages = useMemo(() => { const endRenderIndex = Math.min( diff --git a/app/containers/Chat/components/SessionItem.tsx b/app/containers/Chat/components/SessionItem.tsx index 5d5f3bd46..a533bcefd 100644 --- a/app/containers/Chat/components/SessionItem.tsx +++ b/app/containers/Chat/components/SessionItem.tsx @@ -1,11 +1,10 @@ import { Draggable } from "@hello-pangea/dnd"; import Locale from "@/app/locales"; -import { useLocation } from "react-router-dom"; import { Path } from "@/app/constant"; import { Mask } from "@/app/store/mask"; import { useRef, useEffect } from "react"; - +import { usePathname } from "next/navigation"; import DeleteChatIcon from "@/app/icons/deleteChatIcon.svg"; import { getTime } from "@/app/utils"; @@ -36,8 +35,7 @@ export default function SessionItem(props: { }); } }, [props.selected]); - - const { pathname: currentPath } = useLocation(); + const pathname = usePathname(); return ( @@ -51,7 +49,7 @@ export default function SessionItem(props: { md:bg-chat-menu-session-unselected md:border-chat-menu-session-unselected ${ props.selected && - (currentPath === Path.Chat || currentPath === Path.Home) + (pathname === Path.Chat || pathname === Path.Home) ? ` md:!bg-chat-menu-session-selected md:!border-chat-menu-session-selected !bg-chat-menu-session-selected-mobile !border-chat-menu-session-selected-mobile @@ -70,7 +68,7 @@ export default function SessionItem(props: { props.count, )}`} > -
+
diff --git a/app/containers/Chat/index.tsx b/app/containers/Chat/index.tsx index 4d39f1c0d..4d5c789cc 100644 --- a/app/containers/Chat/index.tsx +++ b/app/containers/Chat/index.tsx @@ -7,7 +7,6 @@ import { import { useAppConfig, useChatStore } from "@/app/store"; import Locale from "@/app/locales"; -import { useLocation, useNavigate } from "react-router-dom"; import { Path } from "@/app/constant"; import { useEffect } from "react"; @@ -18,6 +17,7 @@ import MenuLayout from "@/app/components/MenuLayout"; import Panel from "./ChatPanel"; import Modal from "@/app/components/Modal"; import SessionItem from "./components/SessionItem"; +import { usePathname, useRouter } from "next/navigation"; export default MenuLayout(function SessionList(props) { const { setShowPanel } = props; @@ -30,17 +30,16 @@ export default MenuLayout(function SessionList(props) { state.moveSession, ], ); - const navigate = useNavigate(); const config = useAppConfig(); const { isMobileScreen } = config; const chatStore = useChatStore(); - const { pathname: currentPath } = useLocation(); - + const router = useRouter(); + const pathname = usePathname(); useEffect(() => { - setShowPanel?.(currentPath === Path.Chat); - }, [currentPath]); + setShowPanel?.(pathname === Path.Chat); + }, [pathname]); const onDragEnd: OnDragEndResponder = (result) => { const { destination, source } = result; @@ -77,13 +76,15 @@ export default MenuLayout(function SessionList(props) {
{ if (config.dontShowMaskSplashScreen) { chatStore.newSession(); - navigate(Path.Chat); + // navigate(Path.Chat); + router.push(Path.Chat); } else { - navigate(Path.NewChat); + // navigate(Path.NewChat); + router.push(Path.NewChat); } }} > @@ -116,8 +117,9 @@ export default MenuLayout(function SessionList(props) { index={i} selected={i === selectedIndex} onClick={() => { - navigate(Path.Chat); + // navigate(Path.Chat); selectSession(i); + router.push(Path.Chat); }} onDelete={async () => { if ( diff --git a/app/containers/Sidebar/index.tsx b/app/containers/Sidebar/index.tsx index 8a3c53391..dd54aeb7e 100644 --- a/app/containers/Sidebar/index.tsx +++ b/app/containers/Sidebar/index.tsx @@ -14,13 +14,14 @@ import AssistantMobileInactive from "@/app/icons/assistantMobileInactive.svg"; import { useAppConfig } from "@/app/store"; import { Path, REPO_URL } from "@/app/constant"; -import { useNavigate, useLocation } from "react-router-dom"; import useHotKey from "@/app/hooks/useHotKey"; import ActionsBar from "@/app/components/ActionsBar"; +import { usePathname, useRouter } from "next/navigation"; export function SideBar(props: { className?: string }) { - const navigate = useNavigate(); - const loc = useLocation(); + // const navigate = useNavigate(); + const pathname = usePathname(); + const router = useRouter(); const config = useAppConfig(); const { isMobileScreen } = config; @@ -28,8 +29,7 @@ export function SideBar(props: { className?: string }) { useHotKey(); let selectedTab: string; - - switch (loc.pathname) { + switch (pathname) { case Path.Masks: case Path.NewChat: selectedTab = Path.Masks; @@ -40,6 +40,7 @@ export function SideBar(props: { className?: string }) { default: selectedTab = Path.Home; } + console.log("======", selectedTab); return (
(null); + const [deviceType, setDeviceType] = useState(null); + useEffect(() => { + const userAgent = navigator.userAgent.toLowerCase(); + + if (/iphone|ipad|ipod/.test(userAgent)) { + setSystemInfo("iOS"); + } + }, []); + + useEffect(() => { + const onResize = () => { + setDeviceInfo({ + width: window.innerWidth, + height: window.innerHeight, + }); + }; + + if (window.innerWidth < 600) { + setDeviceType("mobile"); + } else { + setDeviceType("desktop"); + } + + window.addEventListener("resize", onResize); + + return () => { + window.removeEventListener("resize", onResize); + }; + }, []); + + return { + windowSize: deviceInfo, + systemInfo, + deviceType, + }; +} diff --git a/app/hooks/useRelativePosition.ts b/app/hooks/useRelativePosition.ts index 90f532be4..edb3718cd 100644 --- a/app/hooks/useRelativePosition.ts +++ b/app/hooks/useRelativePosition.ts @@ -32,7 +32,7 @@ interface Position { } export default function useRelativePosition({ - containerRef = { current: window.document.body }, + containerRef = { current: null }, delay = 100, offsetDistance = 0, }: Options) { diff --git a/app/hooks/useWindowSize.ts b/app/hooks/useWindowSize.ts index a4dbf2ef9..6a3424258 100644 --- a/app/hooks/useWindowSize.ts +++ b/app/hooks/useWindowSize.ts @@ -1,4 +1,4 @@ -import { useLayoutEffect, useRef, useState } from "react"; +import { useEffect, useLayoutEffect, useRef, useState } from "react"; type Size = { width: number; @@ -10,10 +10,14 @@ export function useWindowSize(callback?: (size: Size) => void) { callbackRef.current = callback; - const [size, setSize] = useState({ - width: window.innerWidth, - height: window.innerHeight, - }); + const [size, setSize] = useState({}); + + useEffect(() => { + setSize({ + width: window.innerWidth, + height: window.innerHeight, + }); + }, []); useLayoutEffect(() => { const onResize = () => { diff --git a/app/utils.ts b/app/utils.ts index d42233c7f..c10d6d0e8 100644 --- a/app/utils.ts +++ b/app/utils.ts @@ -291,18 +291,16 @@ export function getMessageImages(message: RequestMessage): string[] { } export function isVisionModel(model: string) { - // Note: This is a better way using the TypeScript feature instead of `&&` or `||` (ts v5.5.0-dev.20240314 I've been using) - const visionKeywords = [ - "vision", - "claude-3", - "gemini-1.5-pro", - ]; + const visionKeywords = ["vision", "claude-3", "gemini-1.5-pro"]; - const isGpt4Turbo = model.includes("gpt-4-turbo") && !model.includes("preview"); + const isGpt4Turbo = + model.includes("gpt-4-turbo") && !model.includes("preview"); - return visionKeywords.some((keyword) => model.includes(keyword)) || isGpt4Turbo; + return ( + visionKeywords.some((keyword) => model.includes(keyword)) || isGpt4Turbo + ); } export function getTime(dateTime: string) {