Skip to content

Commit

Permalink
refactor: close ChatGPTNextWeb#643 use react router
Browse files Browse the repository at this point in the history
  • Loading branch information
Yidadaa committed Apr 20, 2023
1 parent ee0f847 commit 693dcf1
Show file tree
Hide file tree
Showing 9 changed files with 231 additions and 171 deletions.
8 changes: 7 additions & 1 deletion app/components/chat-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import {
import { useChatStore } from "../store";

import Locale from "../locales";
import { Link, useNavigate } from "react-router-dom";
import { Path } from "../constant";

export function ChatItem(props: {
onClick?: () => void;
Expand Down Expand Up @@ -59,6 +61,7 @@ export function ChatList() {
state.moveSession,
]);
const chatStore = useChatStore();
const navigate = useNavigate();

const onDragEnd: OnDragEndResponder = (result) => {
const { destination, source } = result;
Expand Down Expand Up @@ -94,7 +97,10 @@ export function ChatList() {
id={item.id}
index={i}
selected={i === selectedIndex}
onClick={() => selectSession(i)}
onClick={() => {
navigate(Path.Chat);
selectSession(i);
}}
onDelete={() => chatStore.deleteSession(i)}
/>
))}
Expand Down
14 changes: 7 additions & 7 deletions app/components/chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ import styles from "./home.module.scss";
import chatStyle from "./chat.module.scss";

import { Input, Modal, showModal } from "./ui-lib";
import { useNavigate } from "react-router-dom";
import { Path } from "../constant";

const Markdown = dynamic(
async () => memo((await import("./markdown")).Markdown),
Expand Down Expand Up @@ -418,10 +420,7 @@ export function ChatActions(props: {
);
}

export function Chat(props: {
showSideBar?: () => void;
sideBarShowing?: boolean;
}) {
export function Chat() {
type RenderMessage = Message & { preview?: boolean };

const chatStore = useChatStore();
Expand All @@ -439,6 +438,7 @@ export function Chat(props: {
const { scrollRef, setAutoScroll, scrollToBottom } = useScrollToBottom();
const [hitBottom, setHitBottom] = useState(false);
const isMobileScreen = useMobileScreen();
const navigate = useNavigate();

const onChatBodyScroll = (e: HTMLElement) => {
const isTouchBottom = e.scrollTop + e.clientHeight >= e.scrollHeight - 20;
Expand Down Expand Up @@ -641,7 +641,7 @@ export function Chat(props: {

// Auto focus
useEffect(() => {
if (props.sideBarShowing && isMobileScreen) return;
if (isMobileScreen) return;
inputRef.current?.focus();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
Expand All @@ -666,7 +666,7 @@ export function Chat(props: {
icon={<ReturnIcon />}
bordered
title={Locale.Chat.Actions.ChatList}
onClick={props?.showSideBar}
onClick={() => navigate(Path.Home)}
/>
</div>
<div className={styles["window-action-button"]}>
Expand Down Expand Up @@ -830,7 +830,7 @@ export function Chat(props: {
setAutoScroll(false);
setTimeout(() => setPromptHints([]), 500);
}}
autoFocus={!props?.sideBarShowing}
autoFocus
rows={inputRows}
/>
<IconButton
Expand Down
206 changes: 48 additions & 158 deletions app/components/home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,32 @@

require("../polyfill");

import { useState, useEffect, useRef } from "react";
import { useState, useEffect } from "react";

import { IconButton } from "./button";
import styles from "./home.module.scss";

import SettingsIcon from "../icons/settings.svg";
import GithubIcon from "../icons/github.svg";
import ChatGptIcon from "../icons/chatgpt.svg";

import BotIcon from "../icons/bot.svg";
import AddIcon from "../icons/add.svg";
import LoadingIcon from "../icons/three-dots.svg";
import CloseIcon from "../icons/close.svg";

import { useChatStore } from "../store";
import { getCSSVar, useMobileScreen } from "../utils";
import Locale from "../locales";
import { Chat } from "./chat";

import dynamic from "next/dynamic";
import { REPO_URL } from "../constant";
import { Path } from "../constant";
import { ErrorBoundary } from "./error";

import {
HashRouter as Router,
Routes,
Route,
useNavigation,
useLocation,
} from "react-router-dom";

export function Loading(props: { noLogo?: boolean }) {
return (
<div className={styles["loading-content"]}>
<div className={styles["loading-content"] + " no-dark"}>
{!props.noLogo && <BotIcon />}
<LoadingIcon />
</div>
Expand All @@ -38,7 +38,7 @@ const Settings = dynamic(async () => (await import("./settings")).Settings, {
loading: () => <Loading noLogo />,
});

const ChatList = dynamic(async () => (await import("./chat-list")).ChatList, {
const SideBar = dynamic(async () => (await import("./sidebar")).SideBar, {
loading: () => <Loading noLogo />,
});

Expand Down Expand Up @@ -73,50 +73,6 @@ function useSwitchTheme() {
}, [config.theme]);
}

function useDragSideBar() {
const limit = (x: number) => Math.min(500, Math.max(220, x));

const chatStore = useChatStore();
const startX = useRef(0);
const startDragWidth = useRef(chatStore.config.sidebarWidth ?? 300);
const lastUpdateTime = useRef(Date.now());

const handleMouseMove = useRef((e: MouseEvent) => {
if (Date.now() < lastUpdateTime.current + 100) {
return;
}
lastUpdateTime.current = Date.now();
const d = e.clientX - startX.current;
const nextWidth = limit(startDragWidth.current + d);
chatStore.updateConfig((config) => (config.sidebarWidth = nextWidth));
});

const handleMouseUp = useRef(() => {
startDragWidth.current = chatStore.config.sidebarWidth ?? 300;
window.removeEventListener("mousemove", handleMouseMove.current);
window.removeEventListener("mouseup", handleMouseUp.current);
});

const onDragMouseDown = (e: MouseEvent) => {
startX.current = e.clientX;

window.addEventListener("mousemove", handleMouseMove.current);
window.addEventListener("mouseup", handleMouseUp.current);
};
const isMobileScreen = useMobileScreen();

useEffect(() => {
const sideBarWidth = isMobileScreen
? "100vw"
: `${limit(chatStore.config.sidebarWidth ?? 300)}px`;
document.documentElement.style.setProperty("--sidebar-width", sideBarWidth);
}, [chatStore.config.sidebarWidth, isMobileScreen]);

return {
onDragMouseDown,
};
}

const useHasHydrated = () => {
const [hasHydrated, setHasHydrated] = useState<boolean>(false);

Expand All @@ -127,130 +83,64 @@ const useHasHydrated = () => {
return hasHydrated;
};

function _Home() {
const [createNewSession, currentIndex, removeSession] = useChatStore(
(state) => [
state.newSession,
state.currentSessionIndex,
state.removeSession,
],
);
const chatStore = useChatStore();
const loading = !useHasHydrated();
const [showSideBar, setShowSideBar] = useState(true);

function WideScreen() {
// setting
const [openSettings, setOpenSettings] = useState(false);
const config = useChatStore((state) => state.config);

// drag side bar
const { onDragMouseDown } = useDragSideBar();
const isMobileScreen = useMobileScreen();

useSwitchTheme();

if (loading) {
return <Loading />;
}

return (
<div
className={`${
config.tightBorder && !isMobileScreen
? styles["tight-container"]
: styles.container
config.tightBorder ? styles["tight-container"] : styles.container
}`}
>
<div
className={styles.sidebar + ` ${showSideBar && styles["sidebar-show"]}`}
>
<div className={styles["sidebar-header"]}>
<div className={styles["sidebar-title"]}>ChatGPT Next</div>
<div className={styles["sidebar-sub-title"]}>
Build your own AI assistant.
</div>
<div className={styles["sidebar-logo"]}>
<ChatGptIcon />
</div>
</div>
<div className={styles.sidebar}>
<SideBar></SideBar>
</div>

<div
className={styles["sidebar-body"]}
onClick={() => {
setOpenSettings(false);
setShowSideBar(false);
}}
>
<ChatList />
</div>
<div className={styles["window-content"]}>
<Routes>
<Route path={Path.Home} element={<Chat />} />
<Route path={Path.Chat} element={<Chat />} />
<Route path={Path.Settings} element={<Settings />} />
</Routes>
</div>
</div>
);
}

<div className={styles["sidebar-tail"]}>
<div className={styles["sidebar-actions"]}>
<div className={styles["sidebar-action"] + " " + styles.mobile}>
<IconButton
icon={<CloseIcon />}
onClick={chatStore.deleteSession}
/>
</div>
<div className={styles["sidebar-action"]}>
<IconButton
icon={<SettingsIcon />}
onClick={() => {
setOpenSettings(true);
setShowSideBar(false);
}}
shadow
/>
</div>
<div className={styles["sidebar-action"]}>
<a href={REPO_URL} target="_blank">
<IconButton icon={<GithubIcon />} shadow />
</a>
</div>
</div>
<div>
<IconButton
icon={<AddIcon />}
text={Locale.Home.NewChat}
onClick={() => {
createNewSession();
setShowSideBar(false);
}}
shadow
/>
</div>
</div>
function MobileScreen() {
const location = useLocation();
const isHome = location.pathname === Path.Home;

<div
className={styles["sidebar-drag"]}
onMouseDown={(e) => onDragMouseDown(e as any)}
></div>
return (
<div className={styles.container}>
<div className={`${styles.sidebar} ${isHome && styles["sidebar-show"]}`}>
<SideBar />
</div>

<div className={styles["window-content"]}>
{openSettings ? (
<Settings
closeSettings={() => {
setOpenSettings(false);
setShowSideBar(true);
}}
/>
) : (
<Chat
key="chat"
showSideBar={() => setShowSideBar(true)}
sideBarShowing={showSideBar}
/>
)}
<Routes>
<Route path={Path.Home} element={null} />
<Route path={Path.Chat} element={<Chat />} />
<Route path={Path.Settings} element={<Settings />} />
</Routes>
</div>
</div>
);
}

export function Home() {
useSwitchTheme();

const isMobileScreen = useMobileScreen();

if (!useHasHydrated()) {
return <Loading />;
}

return (
<ErrorBoundary>
<_Home></_Home>
<Router>{isMobileScreen ? <MobileScreen /> : <WideScreen />}</Router>
</ErrorBoundary>
);
}
10 changes: 6 additions & 4 deletions app/components/settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,11 @@ import { Avatar } from "./chat";
import Locale, { AllLangs, changeLang, getLang } from "../locales";
import { copyToClipboard, getEmojiUrl } from "../utils";
import Link from "next/link";
import { UPDATE_URL } from "../constant";
import { Path, UPDATE_URL } from "../constant";
import { Prompt, SearchService, usePromptStore } from "../store/prompt";
import { ErrorBoundary } from "./error";
import { InputRange } from "./input-range";
import { useNavigate } from "react-router-dom";

function UserPromptModal(props: { onClose?: () => void }) {
const promptStore = usePromptStore();
Expand Down Expand Up @@ -176,7 +177,8 @@ function PasswordInput(props: HTMLProps<HTMLInputElement>) {
);
}

export function Settings(props: { closeSettings: () => void }) {
export function Settings() {
const navigate = useNavigate();
const [showEmojiPicker, setShowEmojiPicker] = useState(false);
const [config, updateConfig, resetConfig, clearAllData, clearSessions] =
useChatStore((state) => [
Expand Down Expand Up @@ -235,7 +237,7 @@ export function Settings(props: { closeSettings: () => void }) {
useEffect(() => {
const keydownEvent = (e: KeyboardEvent) => {
if (e.key === "Escape") {
props.closeSettings();
navigate(Path.Home);
}
};
document.addEventListener("keydown", keydownEvent);
Expand Down Expand Up @@ -290,7 +292,7 @@ export function Settings(props: { closeSettings: () => void }) {
<div className={styles["window-action-button"]}>
<IconButton
icon={<CloseIcon />}
onClick={props.closeSettings}
onClick={() => navigate(Path.Home)}
bordered
title={Locale.Settings.Actions.Close}
/>
Expand Down
Loading

0 comments on commit 693dcf1

Please sign in to comment.