Skip to content

Commit

Permalink
feat(UI): janhq#1404 make left side bar collapsible by hot key (janhq…
Browse files Browse the repository at this point in the history
…#1420)

Signed-off-by: James <[email protected]>
Co-authored-by: James <[email protected]>
  • Loading branch information
namchuai and James authored Jan 8, 2024
1 parent 74d8c6b commit 82ffcd0
Show file tree
Hide file tree
Showing 6 changed files with 160 additions and 119 deletions.
78 changes: 34 additions & 44 deletions web/containers/Layout/TopBar/CommandListDownloadedModel/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Fragment, useState, useEffect } from 'react'
import { Fragment } from 'react'

import { InferenceEngine } from '@janhq/core'
import {
Expand All @@ -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'
Expand All @@ -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) {
Expand All @@ -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 (
<Fragment>
<CommandModal open={open} onOpenChange={setOpen}>
<CommandModal
open={showSelectModelModal}
onOpenChange={setShowSelectModelModal}
>
<CommandInput placeholder="Search your model..." />
<CommandList>
<CommandEmpty>No Model found.</CommandEmpty>
{!isNotDownloadedModel && (
<CommandGroup heading="Your Model">
{downloadedModels
.filter((model) => {
return model.engine === InferenceEngine.nitro
})
.map((model, i) => {
return (
<CommandItem
key={i}
value={model.id}
onSelect={() => {
onModelActionClick(model.id)
setOpen(false)
}}
>
<DatabaseIcon
size={16}
className="mr-3 text-muted-foreground"
/>
<div className="flex w-full items-center justify-between">
<span>{model.id}</span>
{activeModel && activeModel.id === model.id && (
<Badge themes="secondary">Active</Badge>
)}
</div>
</CommandItem>
)
})}
.filter((model) => model.engine === InferenceEngine.nitro)
.map((model) => (
<CommandItem
key={model.id}
value={model.id}
onSelect={() => {
onModelActionClick(model.id)
setShowSelectModelModal(false)
}}
>
<DatabaseIcon
size={16}
className="mr-3 text-muted-foreground"
/>
<div className="flex w-full items-center justify-between">
<span>{model.id}</span>
{activeModel && activeModel.id === model.id && (
<Badge themes="secondary">Active</Badge>
)}
</div>
</CommandItem>
))}
</CommandGroup>
)}
<CommandGroup heading="Find another model">
<CommandItem
onSelect={() => {
setMainViewState(MainViewState.Hub)
setOpen(false)
setShowSelectModelModal(false)
}}
>
<CpuIcon size={16} className="mr-3 text-muted-foreground" />
Expand Down
83 changes: 37 additions & 46 deletions web/containers/Layout/TopBar/CommandSearch/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Fragment, useState, useEffect } from 'react'
import { Fragment } from 'react'

import {
CommandModal,
Expand All @@ -10,64 +10,52 @@ import {
CommandList,
} from '@janhq/uikit'

import { useAtom } from 'jotai'
import {
MessageCircleIcon,
SettingsIcon,
LayoutGridIcon,
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: (
<MessageCircleIcon size={16} className="mr-3 text-muted-foreground" />
),
state: MainViewState.Thread,
},
{
name: 'Hub',
icon: <LayoutGridIcon size={16} className="mr-3 text-muted-foreground" />,
state: MainViewState.Hub,
},
{
name: 'System Monitor',
icon: <MonitorIcon size={16} className="mr-3 text-muted-foreground" />,
state: MainViewState.SystemMonitor,
},
{
name: 'Settings',
icon: <SettingsIcon size={16} className="mr-3 text-muted-foreground" />,
state: MainViewState.Settings,
shortcut: <ShortCut menu="," />,
},
]

export default function CommandSearch() {
const { setMainViewState } = useMainViewState()
const [open, setOpen] = useState(false)

const menus = [
{
name: 'Chat',
icon: (
<MessageCircleIcon size={16} className="mr-3 text-muted-foreground" />
),
state: MainViewState.Thread,
},
{
name: 'Hub',
icon: <LayoutGridIcon size={16} className="mr-3 text-muted-foreground" />,
state: MainViewState.Hub,
},
{
name: 'System Monitor',
icon: <MonitorIcon size={16} className="mr-3 text-muted-foreground" />,
state: MainViewState.SystemMonitor,
},
{
name: 'Settings',
icon: <SettingsIcon size={16} className="mr-3 text-muted-foreground" />,
state: MainViewState.Settings,
shortcut: <ShortCut menu="," />,
},
]

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 (
<Fragment>
Expand All @@ -84,7 +72,10 @@ export default function CommandSearch() {
<ShortCut menu="K" />
</div>
</div> */}
<CommandModal open={open} onOpenChange={setOpen}>
<CommandModal
open={showCommandSearchModal}
onOpenChange={setShowCommandSearchModal}
>
<CommandInput placeholder="Type a command or search..." />
<CommandList>
<CommandEmpty>No results found.</CommandEmpty>
Expand All @@ -96,7 +87,7 @@ export default function CommandSearch() {
value={menu.name}
onSelect={() => {
setMainViewState(menu.state)
setOpen(false)
setShowCommandSearchModal(false)
}}
>
{menu.icon}
Expand Down
56 changes: 56 additions & 0 deletions web/containers/Providers/KeyListener.tsx
Original file line number Diff line number Diff line change
@@ -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<boolean>(true)
export const showSelectModelModalAtom = atom<boolean>(false)
export const showCommandSearchModalAtom = atom<boolean>(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 <Fragment>{children}</Fragment>
}
28 changes: 17 additions & 11 deletions web/containers/Providers/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import {

import { instance } from '@/utils/posthog'

import KeyListener from './KeyListener'

import { extensionManager } from '@/extension'

const Providers = (props: PropsWithChildren) => {
Expand Down Expand Up @@ -70,17 +72,21 @@ const Providers = (props: PropsWithChildren) => {
return (
<PostHogProvider client={instance}>
<JotaiWrapper>
<ThemeWrapper>
{setupCore && activated && (
<FeatureToggleWrapper>
<EventListenerWrapper>
<TooltipProvider delayDuration={0}>{children}</TooltipProvider>
{!isMac && <GPUDriverPrompt />}
</EventListenerWrapper>
<Toaster position="top-right" />
</FeatureToggleWrapper>
)}
</ThemeWrapper>
<KeyListener>
<ThemeWrapper>
{setupCore && activated && (
<FeatureToggleWrapper>
<EventListenerWrapper>
<TooltipProvider delayDuration={0}>
{children}
</TooltipProvider>
{!isMac && <GPUDriverPrompt />}
</EventListenerWrapper>
<Toaster position="top-right" />
</FeatureToggleWrapper>
)}
</ThemeWrapper>
</KeyListener>
</JotaiWrapper>
</PostHogProvider>
)
Expand Down
Loading

0 comments on commit 82ffcd0

Please sign in to comment.