Signed-off-by: James <james@jan.ai> Co-authored-by: James <james@jan.ai>
This commit is contained in:
parent
74d8c6be3d
commit
82ffcd06f1
@ -1,4 +1,4 @@
|
|||||||
import { Fragment, useState, useEffect } from 'react'
|
import { Fragment } from 'react'
|
||||||
|
|
||||||
import { InferenceEngine } from '@janhq/core'
|
import { InferenceEngine } from '@janhq/core'
|
||||||
import {
|
import {
|
||||||
@ -11,8 +11,11 @@ import {
|
|||||||
Badge,
|
Badge,
|
||||||
} from '@janhq/uikit'
|
} from '@janhq/uikit'
|
||||||
|
|
||||||
|
import { useAtom } from 'jotai'
|
||||||
import { DatabaseIcon, CpuIcon } from 'lucide-react'
|
import { DatabaseIcon, CpuIcon } from 'lucide-react'
|
||||||
|
|
||||||
|
import { showSelectModelModalAtom } from '@/containers/Providers/KeyListener'
|
||||||
|
|
||||||
import { MainViewState } from '@/constants/screens'
|
import { MainViewState } from '@/constants/screens'
|
||||||
|
|
||||||
import { useActiveModel } from '@/hooks/useActiveModel'
|
import { useActiveModel } from '@/hooks/useActiveModel'
|
||||||
@ -23,6 +26,9 @@ export default function CommandListDownloadedModel() {
|
|||||||
const { setMainViewState } = useMainViewState()
|
const { setMainViewState } = useMainViewState()
|
||||||
const { downloadedModels } = useGetDownloadedModels()
|
const { downloadedModels } = useGetDownloadedModels()
|
||||||
const { activeModel, startModel, stopModel } = useActiveModel()
|
const { activeModel, startModel, stopModel } = useActiveModel()
|
||||||
|
const [showSelectModelModal, setShowSelectModelModal] = useAtom(
|
||||||
|
showSelectModelModalAtom
|
||||||
|
)
|
||||||
|
|
||||||
const onModelActionClick = (modelId: string) => {
|
const onModelActionClick = (modelId: string) => {
|
||||||
if (activeModel && activeModel.id === modelId) {
|
if (activeModel && activeModel.id === modelId) {
|
||||||
@ -32,44 +38,29 @@ 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
|
const isNotDownloadedModel = downloadedModels.length === 0
|
||||||
|
|
||||||
if (isNotDownloadedModel) return null
|
if (isNotDownloadedModel) return null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<CommandModal open={open} onOpenChange={setOpen}>
|
<CommandModal
|
||||||
|
open={showSelectModelModal}
|
||||||
|
onOpenChange={setShowSelectModelModal}
|
||||||
|
>
|
||||||
<CommandInput placeholder="Search your model..." />
|
<CommandInput placeholder="Search your model..." />
|
||||||
<CommandList>
|
<CommandList>
|
||||||
<CommandEmpty>No Model found.</CommandEmpty>
|
<CommandEmpty>No Model found.</CommandEmpty>
|
||||||
{!isNotDownloadedModel && (
|
{!isNotDownloadedModel && (
|
||||||
<CommandGroup heading="Your Model">
|
<CommandGroup heading="Your Model">
|
||||||
{downloadedModels
|
{downloadedModels
|
||||||
.filter((model) => {
|
.filter((model) => model.engine === InferenceEngine.nitro)
|
||||||
return model.engine === InferenceEngine.nitro
|
.map((model) => (
|
||||||
})
|
|
||||||
.map((model, i) => {
|
|
||||||
return (
|
|
||||||
<CommandItem
|
<CommandItem
|
||||||
key={i}
|
key={model.id}
|
||||||
value={model.id}
|
value={model.id}
|
||||||
onSelect={() => {
|
onSelect={() => {
|
||||||
onModelActionClick(model.id)
|
onModelActionClick(model.id)
|
||||||
setOpen(false)
|
setShowSelectModelModal(false)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DatabaseIcon
|
<DatabaseIcon
|
||||||
@ -83,15 +74,14 @@ export default function CommandListDownloadedModel() {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</CommandItem>
|
</CommandItem>
|
||||||
)
|
))}
|
||||||
})}
|
|
||||||
</CommandGroup>
|
</CommandGroup>
|
||||||
)}
|
)}
|
||||||
<CommandGroup heading="Find another model">
|
<CommandGroup heading="Find another model">
|
||||||
<CommandItem
|
<CommandItem
|
||||||
onSelect={() => {
|
onSelect={() => {
|
||||||
setMainViewState(MainViewState.Hub)
|
setMainViewState(MainViewState.Hub)
|
||||||
setOpen(false)
|
setShowSelectModelModal(false)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CpuIcon size={16} className="mr-3 text-muted-foreground" />
|
<CpuIcon size={16} className="mr-3 text-muted-foreground" />
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { Fragment, useState, useEffect } from 'react'
|
import { Fragment } from 'react'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
CommandModal,
|
CommandModal,
|
||||||
@ -10,6 +10,7 @@ import {
|
|||||||
CommandList,
|
CommandList,
|
||||||
} from '@janhq/uikit'
|
} from '@janhq/uikit'
|
||||||
|
|
||||||
|
import { useAtom } from 'jotai'
|
||||||
import {
|
import {
|
||||||
MessageCircleIcon,
|
MessageCircleIcon,
|
||||||
SettingsIcon,
|
SettingsIcon,
|
||||||
@ -17,17 +18,14 @@ import {
|
|||||||
MonitorIcon,
|
MonitorIcon,
|
||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
|
|
||||||
|
import { showCommandSearchModalAtom } from '@/containers/Providers/KeyListener'
|
||||||
import ShortCut from '@/containers/Shortcut'
|
import ShortCut from '@/containers/Shortcut'
|
||||||
|
|
||||||
import { MainViewState } from '@/constants/screens'
|
import { MainViewState } from '@/constants/screens'
|
||||||
|
|
||||||
import { useMainViewState } from '@/hooks/useMainViewState'
|
import { useMainViewState } from '@/hooks/useMainViewState'
|
||||||
|
|
||||||
export default function CommandSearch() {
|
const menus = [
|
||||||
const { setMainViewState } = useMainViewState()
|
|
||||||
const [open, setOpen] = useState(false)
|
|
||||||
|
|
||||||
const menus = [
|
|
||||||
{
|
{
|
||||||
name: 'Chat',
|
name: 'Chat',
|
||||||
icon: (
|
icon: (
|
||||||
@ -51,23 +49,13 @@ export default function CommandSearch() {
|
|||||||
state: MainViewState.Settings,
|
state: MainViewState.Settings,
|
||||||
shortcut: <ShortCut menu="," />,
|
shortcut: <ShortCut menu="," />,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
useEffect(() => {
|
export default function CommandSearch() {
|
||||||
const down = (e: KeyboardEvent) => {
|
const { setMainViewState } = useMainViewState()
|
||||||
if (e.key === 'k' && (e.metaKey || e.ctrlKey)) {
|
const [showCommandSearchModal, setShowCommandSearchModal] = useAtom(
|
||||||
e.preventDefault()
|
showCommandSearchModalAtom
|
||||||
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
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
@ -84,7 +72,10 @@ export default function CommandSearch() {
|
|||||||
<ShortCut menu="K" />
|
<ShortCut menu="K" />
|
||||||
</div>
|
</div>
|
||||||
</div> */}
|
</div> */}
|
||||||
<CommandModal open={open} onOpenChange={setOpen}>
|
<CommandModal
|
||||||
|
open={showCommandSearchModal}
|
||||||
|
onOpenChange={setShowCommandSearchModal}
|
||||||
|
>
|
||||||
<CommandInput placeholder="Type a command or search..." />
|
<CommandInput placeholder="Type a command or search..." />
|
||||||
<CommandList>
|
<CommandList>
|
||||||
<CommandEmpty>No results found.</CommandEmpty>
|
<CommandEmpty>No results found.</CommandEmpty>
|
||||||
@ -96,7 +87,7 @@ export default function CommandSearch() {
|
|||||||
value={menu.name}
|
value={menu.name}
|
||||||
onSelect={() => {
|
onSelect={() => {
|
||||||
setMainViewState(menu.state)
|
setMainViewState(menu.state)
|
||||||
setOpen(false)
|
setShowCommandSearchModal(false)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{menu.icon}
|
{menu.icon}
|
||||||
|
|||||||
56
web/containers/Providers/KeyListener.tsx
Normal file
56
web/containers/Providers/KeyListener.tsx
Normal file
@ -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>
|
||||||
|
}
|
||||||
@ -23,6 +23,8 @@ import {
|
|||||||
|
|
||||||
import { instance } from '@/utils/posthog'
|
import { instance } from '@/utils/posthog'
|
||||||
|
|
||||||
|
import KeyListener from './KeyListener'
|
||||||
|
|
||||||
import { extensionManager } from '@/extension'
|
import { extensionManager } from '@/extension'
|
||||||
|
|
||||||
const Providers = (props: PropsWithChildren) => {
|
const Providers = (props: PropsWithChildren) => {
|
||||||
@ -70,17 +72,21 @@ const Providers = (props: PropsWithChildren) => {
|
|||||||
return (
|
return (
|
||||||
<PostHogProvider client={instance}>
|
<PostHogProvider client={instance}>
|
||||||
<JotaiWrapper>
|
<JotaiWrapper>
|
||||||
|
<KeyListener>
|
||||||
<ThemeWrapper>
|
<ThemeWrapper>
|
||||||
{setupCore && activated && (
|
{setupCore && activated && (
|
||||||
<FeatureToggleWrapper>
|
<FeatureToggleWrapper>
|
||||||
<EventListenerWrapper>
|
<EventListenerWrapper>
|
||||||
<TooltipProvider delayDuration={0}>{children}</TooltipProvider>
|
<TooltipProvider delayDuration={0}>
|
||||||
|
{children}
|
||||||
|
</TooltipProvider>
|
||||||
{!isMac && <GPUDriverPrompt />}
|
{!isMac && <GPUDriverPrompt />}
|
||||||
</EventListenerWrapper>
|
</EventListenerWrapper>
|
||||||
<Toaster position="top-right" />
|
<Toaster position="top-right" />
|
||||||
</FeatureToggleWrapper>
|
</FeatureToggleWrapper>
|
||||||
)}
|
)}
|
||||||
</ThemeWrapper>
|
</ThemeWrapper>
|
||||||
|
</KeyListener>
|
||||||
</JotaiWrapper>
|
</JotaiWrapper>
|
||||||
</PostHogProvider>
|
</PostHogProvider>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -7,7 +7,6 @@ import { useAtom, useAtomValue } from 'jotai'
|
|||||||
|
|
||||||
import { debounce } from 'lodash'
|
import { debounce } from 'lodash'
|
||||||
import { StopCircle } from 'lucide-react'
|
import { StopCircle } from 'lucide-react'
|
||||||
import { twMerge } from 'tailwind-merge'
|
|
||||||
|
|
||||||
import LogoMark from '@/containers/Brand/Logo/Mark'
|
import LogoMark from '@/containers/Brand/Logo/Mark'
|
||||||
|
|
||||||
@ -15,6 +14,8 @@ import ModelReload from '@/containers/Loader/ModelReload'
|
|||||||
import ModelStart from '@/containers/Loader/ModelStart'
|
import ModelStart from '@/containers/Loader/ModelStart'
|
||||||
import { currentPromptAtom } from '@/containers/Providers/Jotai'
|
import { currentPromptAtom } from '@/containers/Providers/Jotai'
|
||||||
|
|
||||||
|
import { showLeftSideBarAtom } from '@/containers/Providers/KeyListener'
|
||||||
|
|
||||||
import { MainViewState } from '@/constants/screens'
|
import { MainViewState } from '@/constants/screens'
|
||||||
|
|
||||||
import { useActiveModel } from '@/hooks/useActiveModel'
|
import { useActiveModel } from '@/hooks/useActiveModel'
|
||||||
@ -28,7 +29,7 @@ import ChatBody from '@/screens/Chat/ChatBody'
|
|||||||
|
|
||||||
import ThreadList from '@/screens/Chat/ThreadList'
|
import ThreadList from '@/screens/Chat/ThreadList'
|
||||||
|
|
||||||
import Sidebar, { showRightSideBarAtom } from './Sidebar'
|
import Sidebar from './Sidebar'
|
||||||
|
|
||||||
import { getCurrentChatMessagesAtom } from '@/helpers/atoms/ChatMessage.atom'
|
import { getCurrentChatMessagesAtom } from '@/helpers/atoms/ChatMessage.atom'
|
||||||
|
|
||||||
@ -44,6 +45,7 @@ import { activeThreadStateAtom } from '@/helpers/atoms/Thread.atom'
|
|||||||
const ChatScreen = () => {
|
const ChatScreen = () => {
|
||||||
const activeThread = useAtomValue(activeThreadAtom)
|
const activeThread = useAtomValue(activeThreadAtom)
|
||||||
const { downloadedModels } = useGetDownloadedModels()
|
const { downloadedModels } = useGetDownloadedModels()
|
||||||
|
const showLeftSideBar = useAtomValue(showLeftSideBarAtom)
|
||||||
|
|
||||||
const { activeModel, stateModel } = useActiveModel()
|
const { activeModel, stateModel } = useActiveModel()
|
||||||
const { setMainViewState } = useMainViewState()
|
const { setMainViewState } = useMainViewState()
|
||||||
@ -59,8 +61,6 @@ const ChatScreen = () => {
|
|||||||
const activeThreadId = useAtomValue(getActiveThreadIdAtom)
|
const activeThreadId = useAtomValue(getActiveThreadIdAtom)
|
||||||
const [isWaitingToSend, setIsWaitingToSend] = useAtom(waitingToSendMessage)
|
const [isWaitingToSend, setIsWaitingToSend] = useAtom(waitingToSendMessage)
|
||||||
|
|
||||||
const showing = useAtomValue(showRightSideBarAtom)
|
|
||||||
|
|
||||||
const textareaRef = useRef<HTMLTextAreaElement>(null)
|
const textareaRef = useRef<HTMLTextAreaElement>(null)
|
||||||
const modelRef = useRef(activeModel)
|
const modelRef = useRef(activeModel)
|
||||||
const engineParamsUpdate = useAtomValue(engineParamsUpdateAtom)
|
const engineParamsUpdate = useAtomValue(engineParamsUpdateAtom)
|
||||||
@ -109,17 +109,14 @@ const ChatScreen = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full w-full">
|
<div className="flex h-full w-full">
|
||||||
<div className="flex h-full w-60 flex-shrink-0 flex-col overflow-y-auto border-r border-border bg-background">
|
{/* Left side bar */}
|
||||||
|
{showLeftSideBar ? (
|
||||||
|
<div className="flex h-full w-60 flex-shrink-0 flex-col overflow-y-auto border-r border-border">
|
||||||
<ThreadList />
|
<ThreadList />
|
||||||
</div>
|
</div>
|
||||||
<div
|
) : null}
|
||||||
className={twMerge(
|
|
||||||
'relative flex h-full flex-col overflow-auto bg-background',
|
<div className="relative flex h-full w-full flex-col overflow-auto bg-background">
|
||||||
activeThread && activeThreadId && showing
|
|
||||||
? 'w-[calc(100%-560px)]'
|
|
||||||
: 'w-full'
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<div className="flex h-full w-full flex-col justify-between">
|
<div className="flex h-full w-full flex-col justify-between">
|
||||||
{activeThread ? (
|
{activeThread ? (
|
||||||
<div className="flex h-full w-full overflow-y-auto overflow-x-hidden">
|
<div className="flex h-full w-full overflow-y-auto overflow-x-hidden">
|
||||||
@ -210,8 +207,9 @@ const ChatScreen = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/* Sidebar */}
|
|
||||||
{activeThreadId && activeThread && <Sidebar />}
|
{/* Right side bar */}
|
||||||
|
{activeThread && <Sidebar />}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
|
||||||
import React, { useMemo } from 'react'
|
import React, { useMemo } from 'react'
|
||||||
|
|
||||||
import { Model } from '@janhq/core'
|
import { Model } from '@janhq/core'
|
||||||
@ -32,6 +31,7 @@ const ModelVersionItem: React.FC<Props> = ({ model }) => {
|
|||||||
|
|
||||||
const downloadAtom = useMemo(
|
const downloadAtom = useMemo(
|
||||||
() => atom((get) => get(modelDownloadStateAtom)[model.id ?? '']),
|
() => atom((get) => get(modelDownloadStateAtom)[model.id ?? '']),
|
||||||
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
[model.id]
|
[model.id]
|
||||||
)
|
)
|
||||||
const downloadState = useAtomValue(downloadAtom)
|
const downloadState = useAtomValue(downloadAtom)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user