diff --git a/web/containers/Layout/TopBar/CommandListDownloadedModel/index.tsx b/web/containers/Layout/TopBar/CommandListDownloadedModel/index.tsx index c9ccfdfbb..fbb003780 100644 --- a/web/containers/Layout/TopBar/CommandListDownloadedModel/index.tsx +++ b/web/containers/Layout/TopBar/CommandListDownloadedModel/index.tsx @@ -1,4 +1,4 @@ -import { Fragment, useState, useEffect } from 'react' +import { Fragment } from 'react' import { InferenceEngine } from '@janhq/core' import { @@ -11,8 +11,11 @@ import { Badge, } from '@janhq/uikit' +import { useAtom } from 'jotai' import { DatabaseIcon, CpuIcon } from 'lucide-react' +import { showSelectModelModalAtom } from '@/containers/Providers/KeyListener' + import { MainViewState } from '@/constants/screens' import { useActiveModel } from '@/hooks/useActiveModel' @@ -23,6 +26,9 @@ export default function CommandListDownloadedModel() { const { setMainViewState } = useMainViewState() const { downloadedModels } = useGetDownloadedModels() const { activeModel, startModel, stopModel } = useActiveModel() + const [showSelectModelModal, setShowSelectModelModal] = useAtom( + showSelectModelModalAtom + ) const onModelActionClick = (modelId: string) => { if (activeModel && activeModel.id === modelId) { @@ -32,66 +38,50 @@ export default function CommandListDownloadedModel() { } } - const [open, setOpen] = useState(false) - - useEffect(() => { - const down = (e: KeyboardEvent) => { - if (e.key === 'e' && (e.metaKey || e.ctrlKey)) { - e.preventDefault() - setOpen((open) => !open) - } - } - document.addEventListener('keydown', down) - return () => document.removeEventListener('keydown', down) - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []) - const isNotDownloadedModel = downloadedModels.length === 0 - if (isNotDownloadedModel) return null return ( - + No Model found. {!isNotDownloadedModel && ( {downloadedModels - .filter((model) => { - return model.engine === InferenceEngine.nitro - }) - .map((model, i) => { - return ( - { - onModelActionClick(model.id) - setOpen(false) - }} - > - -
- {model.id} - {activeModel && activeModel.id === model.id && ( - Active - )} -
-
- ) - })} + .filter((model) => model.engine === InferenceEngine.nitro) + .map((model) => ( + { + onModelActionClick(model.id) + setShowSelectModelModal(false) + }} + > + +
+ {model.id} + {activeModel && activeModel.id === model.id && ( + Active + )} +
+
+ ))}
)} { setMainViewState(MainViewState.Hub) - setOpen(false) + setShowSelectModelModal(false) }} > diff --git a/web/containers/Layout/TopBar/CommandSearch/index.tsx b/web/containers/Layout/TopBar/CommandSearch/index.tsx index bea248a3c..ec73a7660 100644 --- a/web/containers/Layout/TopBar/CommandSearch/index.tsx +++ b/web/containers/Layout/TopBar/CommandSearch/index.tsx @@ -1,4 +1,4 @@ -import { Fragment, useState, useEffect } from 'react' +import { Fragment } from 'react' import { CommandModal, @@ -10,6 +10,7 @@ import { CommandList, } from '@janhq/uikit' +import { useAtom } from 'jotai' import { MessageCircleIcon, SettingsIcon, @@ -17,57 +18,44 @@ import { MonitorIcon, } from 'lucide-react' +import { showCommandSearchModalAtom } from '@/containers/Providers/KeyListener' import ShortCut from '@/containers/Shortcut' import { MainViewState } from '@/constants/screens' import { useMainViewState } from '@/hooks/useMainViewState' +const menus = [ + { + name: 'Chat', + icon: ( + + ), + state: MainViewState.Thread, + }, + { + name: 'Hub', + icon: , + state: MainViewState.Hub, + }, + { + name: 'System Monitor', + icon: , + state: MainViewState.SystemMonitor, + }, + { + name: 'Settings', + icon: , + state: MainViewState.Settings, + shortcut: , + }, +] + export default function CommandSearch() { const { setMainViewState } = useMainViewState() - const [open, setOpen] = useState(false) - - const menus = [ - { - name: 'Chat', - icon: ( - - ), - state: MainViewState.Thread, - }, - { - name: 'Hub', - icon: , - state: MainViewState.Hub, - }, - { - name: 'System Monitor', - icon: , - state: MainViewState.SystemMonitor, - }, - { - name: 'Settings', - icon: , - state: MainViewState.Settings, - shortcut: , - }, - ] - - useEffect(() => { - const down = (e: KeyboardEvent) => { - if (e.key === 'k' && (e.metaKey || e.ctrlKey)) { - e.preventDefault() - setOpen((open) => !open) - } - if (e.key === ',' && (e.metaKey || e.ctrlKey)) { - e.preventDefault() - setMainViewState(MainViewState.Settings) - } - } - document.addEventListener('keydown', down) - return () => document.removeEventListener('keydown', down) - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []) + const [showCommandSearchModal, setShowCommandSearchModal] = useAtom( + showCommandSearchModalAtom + ) return ( @@ -84,7 +72,10 @@ export default function CommandSearch() { */} - + No results found. @@ -96,7 +87,7 @@ export default function CommandSearch() { value={menu.name} onSelect={() => { setMainViewState(menu.state) - setOpen(false) + setShowCommandSearchModal(false) }} > {menu.icon} diff --git a/web/containers/Providers/KeyListener.tsx b/web/containers/Providers/KeyListener.tsx new file mode 100644 index 000000000..00bcb8c92 --- /dev/null +++ b/web/containers/Providers/KeyListener.tsx @@ -0,0 +1,56 @@ +'use client' + +import { Fragment, ReactNode, useEffect } from 'react' + +import { atom, useSetAtom } from 'jotai' + +import { MainViewState } from '@/constants/screens' + +import { useMainViewState } from '@/hooks/useMainViewState' + +type Props = { + children: ReactNode +} + +export const showLeftSideBarAtom = atom(true) +export const showSelectModelModalAtom = atom(false) +export const showCommandSearchModalAtom = atom(false) + +export default function KeyListener({ children }: Props) { + const setShowLeftSideBar = useSetAtom(showLeftSideBarAtom) + const setShowSelectModelModal = useSetAtom(showSelectModelModalAtom) + const { setMainViewState } = useMainViewState() + const showCommandSearchModal = useSetAtom(showCommandSearchModalAtom) + + useEffect(() => { + const onKeyDown = (e: KeyboardEvent) => { + e.preventDefault() + const prefixKey = isMac ? e.metaKey : e.ctrlKey + + if (e.key === 'b' && prefixKey) { + setShowLeftSideBar((showLeftSideBar) => !showLeftSideBar) + return + } + + if (e.key === 'e' && prefixKey) { + setShowSelectModelModal((show) => !show) + return + } + + if (e.key === ',' && prefixKey) { + setMainViewState(MainViewState.Settings) + return + } + + if (e.key === 'k' && prefixKey) { + showCommandSearchModal((show) => !show) + return + } + } + document.addEventListener('keydown', onKeyDown) + return () => document.removeEventListener('keydown', onKeyDown) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) + + return {children} +} diff --git a/web/containers/Providers/index.tsx b/web/containers/Providers/index.tsx index 82a855fce..d5daca4a0 100644 --- a/web/containers/Providers/index.tsx +++ b/web/containers/Providers/index.tsx @@ -23,6 +23,8 @@ import { import { instance } from '@/utils/posthog' +import KeyListener from './KeyListener' + import { extensionManager } from '@/extension' const Providers = (props: PropsWithChildren) => { @@ -70,17 +72,21 @@ const Providers = (props: PropsWithChildren) => { return ( - - {setupCore && activated && ( - - - {children} - {!isMac && } - - - - )} - + + + {setupCore && activated && ( + + + + {children} + + {!isMac && } + + + + )} + + ) diff --git a/web/screens/Chat/index.tsx b/web/screens/Chat/index.tsx index 0b58a2b6d..2e2e6ffec 100644 --- a/web/screens/Chat/index.tsx +++ b/web/screens/Chat/index.tsx @@ -7,7 +7,6 @@ import { useAtom, useAtomValue } from 'jotai' import { debounce } from 'lodash' import { StopCircle } from 'lucide-react' -import { twMerge } from 'tailwind-merge' import LogoMark from '@/containers/Brand/Logo/Mark' @@ -15,6 +14,8 @@ import ModelReload from '@/containers/Loader/ModelReload' import ModelStart from '@/containers/Loader/ModelStart' import { currentPromptAtom } from '@/containers/Providers/Jotai' +import { showLeftSideBarAtom } from '@/containers/Providers/KeyListener' + import { MainViewState } from '@/constants/screens' import { useActiveModel } from '@/hooks/useActiveModel' @@ -28,7 +29,7 @@ import ChatBody from '@/screens/Chat/ChatBody' import ThreadList from '@/screens/Chat/ThreadList' -import Sidebar, { showRightSideBarAtom } from './Sidebar' +import Sidebar from './Sidebar' import { getCurrentChatMessagesAtom } from '@/helpers/atoms/ChatMessage.atom' @@ -44,6 +45,7 @@ import { activeThreadStateAtom } from '@/helpers/atoms/Thread.atom' const ChatScreen = () => { const activeThread = useAtomValue(activeThreadAtom) const { downloadedModels } = useGetDownloadedModels() + const showLeftSideBar = useAtomValue(showLeftSideBarAtom) const { activeModel, stateModel } = useActiveModel() const { setMainViewState } = useMainViewState() @@ -59,8 +61,6 @@ const ChatScreen = () => { const activeThreadId = useAtomValue(getActiveThreadIdAtom) const [isWaitingToSend, setIsWaitingToSend] = useAtom(waitingToSendMessage) - const showing = useAtomValue(showRightSideBarAtom) - const textareaRef = useRef(null) const modelRef = useRef(activeModel) const engineParamsUpdate = useAtomValue(engineParamsUpdateAtom) @@ -109,17 +109,14 @@ const ChatScreen = () => { return (
-
- -
-
+ {/* Left side bar */} + {showLeftSideBar ? ( +
+ +
+ ) : null} + +
{activeThread ? (
@@ -210,8 +207,9 @@ const ChatScreen = () => {
- {/* Sidebar */} - {activeThreadId && activeThread && } + + {/* Right side bar */} + {activeThread && }
) } diff --git a/web/screens/ExploreModels/ModelVersionItem/index.tsx b/web/screens/ExploreModels/ModelVersionItem/index.tsx index 244f07734..50d71b161 100644 --- a/web/screens/ExploreModels/ModelVersionItem/index.tsx +++ b/web/screens/ExploreModels/ModelVersionItem/index.tsx @@ -1,4 +1,3 @@ -/* eslint-disable react-hooks/exhaustive-deps */ import React, { useMemo } from 'react' import { Model } from '@janhq/core' @@ -32,6 +31,7 @@ const ModelVersionItem: React.FC = ({ model }) => { const downloadAtom = useMemo( () => atom((get) => get(modelDownloadStateAtom)[model.id ?? '']), + /* eslint-disable react-hooks/exhaustive-deps */ [model.id] ) const downloadState = useAtomValue(downloadAtom)