import clsx from 'clsx'; import dynamic from 'next/dynamic'; import React, { Fragment, useEffect, useMemo, useRef, useState } from 'react'; import { Link, useNavigate } from 'react-router-dom'; import { DEFAULT_SIDEBAR_WIDTH, MAX_SIDEBAR_WIDTH, MIN_SIDEBAR_WIDTH, NARROW_SIDEBAR_WIDTH, Path, PLUGINS, REPO_URL, } from '../constant'; import AddIcon from '../icons/add.svg'; import ChatGptIcon from '../icons/chatgpt.svg'; import DeleteIcon from '../icons/delete.svg'; import DiscoveryIcon from '../icons/discovery.svg'; import DragIcon from '../icons/drag.svg'; import GithubIcon from '../icons/github.svg'; import MaskIcon from '../icons/mask.svg'; import SettingsIcon from '../icons/settings.svg'; import Locale from '../locales'; import { useAppConfig, useChatStore } from '../store'; import { isIOS, useMobileScreen } from '../utils'; import { IconButton } from './button'; import styles from './home.module.scss'; import { Selector, showConfirm } from './ui-lib'; const ChatList = dynamic(async () => (await import('./chat-list')).ChatList, { loading: () => null, }); export function useHotKey() { const chatStore = useChatStore(); useEffect(() => { const onKeyDown = (e: KeyboardEvent) => { if (e.altKey || e.ctrlKey) { if (e.key === 'ArrowUp') { chatStore.nextSession(-1); } else if (e.key === 'ArrowDown') { chatStore.nextSession(1); } } }; window.addEventListener('keydown', onKeyDown); return () => window.removeEventListener('keydown', onKeyDown); }); } export function useDragSideBar() { const limit = (x: number) => Math.min(MAX_SIDEBAR_WIDTH, x); const config = useAppConfig(); const startX = useRef(0); const startDragWidth = useRef(config.sidebarWidth ?? DEFAULT_SIDEBAR_WIDTH); const lastUpdateTime = useRef(Date.now()); const toggleSideBar = () => { config.update((config) => { if (config.sidebarWidth < MIN_SIDEBAR_WIDTH) { config.sidebarWidth = DEFAULT_SIDEBAR_WIDTH; } else { config.sidebarWidth = NARROW_SIDEBAR_WIDTH; } }); }; const onDragStart = (e: MouseEvent) => { // Remembers the initial width each time the mouse is pressed startX.current = e.clientX; startDragWidth.current = config.sidebarWidth; const dragStartTime = Date.now(); const handleDragMove = (e: MouseEvent) => { if (Date.now() < lastUpdateTime.current + 20) { return; } lastUpdateTime.current = Date.now(); const d = e.clientX - startX.current; const nextWidth = limit(startDragWidth.current + d); config.update((config) => { if (nextWidth < MIN_SIDEBAR_WIDTH) { config.sidebarWidth = NARROW_SIDEBAR_WIDTH; } else { config.sidebarWidth = nextWidth; } }); }; const handleDragEnd = () => { // In useRef the data is non-responsive, so `config.sidebarWidth` can't get the dynamic sidebarWidth window.removeEventListener('pointermove', handleDragMove); window.removeEventListener('pointerup', handleDragEnd); // if user click the drag icon, should toggle the sidebar const shouldFireClick = Date.now() - dragStartTime < 300; if (shouldFireClick) { toggleSideBar(); } }; window.addEventListener('pointermove', handleDragMove); window.addEventListener('pointerup', handleDragEnd); }; const isMobileScreen = useMobileScreen(); const shouldNarrow = !isMobileScreen && config.sidebarWidth < MIN_SIDEBAR_WIDTH; useEffect(() => { const barWidth = shouldNarrow ? NARROW_SIDEBAR_WIDTH : limit(config.sidebarWidth ?? DEFAULT_SIDEBAR_WIDTH); const sideBarWidth = isMobileScreen ? '100vw' : `${barWidth}px`; document.documentElement.style.setProperty('--sidebar-width', sideBarWidth); }, [config.sidebarWidth, isMobileScreen, shouldNarrow]); return { onDragStart, shouldNarrow, }; } export function SideBarContainer(props: { children: React.ReactNode; onDragStart: (e: MouseEvent) => void; shouldNarrow: boolean; className?: string; }) { const isMobileScreen = useMobileScreen(); const isIOSMobile = useMemo( () => isIOS() && isMobileScreen, [isMobileScreen], ); const { children, className, onDragStart, shouldNarrow } = props; return (