enhancement: minor ui refinements (#4521)

* enhancement: minor ui refinements

* chore: update test case
This commit is contained in:
Faisal Amir 2025-01-26 20:41:52 +07:00 committed by GitHub
parent 5dc184eed9
commit cdc7d2ba47
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 64 additions and 58 deletions

View File

@ -509,61 +509,61 @@ __metadata:
"@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fassistant-extension%40workspace%3Aassistant-extension": "@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fassistant-extension%40workspace%3Aassistant-extension":
version: 0.1.10 version: 0.1.10
resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=7dd866&locator=%40janhq%2Fassistant-extension%40workspace%3Aassistant-extension" resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=f3025c&locator=%40janhq%2Fassistant-extension%40workspace%3Aassistant-extension"
dependencies: dependencies:
rxjs: "npm:^7.8.1" rxjs: "npm:^7.8.1"
ulidx: "npm:^2.3.0" ulidx: "npm:^2.3.0"
checksum: 10c0/da0eed6e552ce2ff6f52a087e6e221101c3d0c03d92820840ee80c3ca1a17317a66525cb5bf59b6c1e8bd2e36e54763008f97e13000ae339dac49f5682fcfa65 checksum: 10c0/cda3dff029cc6ce8a9ddcd8ac3ff039b783eed9252c1c3f0b3f34a2cf68c00dc2755997b56c3c5796502aa7316b69b57758b15f338e64b4a8ef14b34d23b6c99
languageName: node languageName: node
linkType: hard linkType: hard
"@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fconversational-extension%40workspace%3Aconversational-extension": "@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fconversational-extension%40workspace%3Aconversational-extension":
version: 0.1.10 version: 0.1.10
resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=7dd866&locator=%40janhq%2Fconversational-extension%40workspace%3Aconversational-extension" resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=f3025c&locator=%40janhq%2Fconversational-extension%40workspace%3Aconversational-extension"
dependencies: dependencies:
rxjs: "npm:^7.8.1" rxjs: "npm:^7.8.1"
ulidx: "npm:^2.3.0" ulidx: "npm:^2.3.0"
checksum: 10c0/da0eed6e552ce2ff6f52a087e6e221101c3d0c03d92820840ee80c3ca1a17317a66525cb5bf59b6c1e8bd2e36e54763008f97e13000ae339dac49f5682fcfa65 checksum: 10c0/cda3dff029cc6ce8a9ddcd8ac3ff039b783eed9252c1c3f0b3f34a2cf68c00dc2755997b56c3c5796502aa7316b69b57758b15f338e64b4a8ef14b34d23b6c99
languageName: node languageName: node
linkType: hard linkType: hard
"@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fengine-management-extension%40workspace%3Aengine-management-extension": "@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fengine-management-extension%40workspace%3Aengine-management-extension":
version: 0.1.10 version: 0.1.10
resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=7dd866&locator=%40janhq%2Fengine-management-extension%40workspace%3Aengine-management-extension" resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=f3025c&locator=%40janhq%2Fengine-management-extension%40workspace%3Aengine-management-extension"
dependencies: dependencies:
rxjs: "npm:^7.8.1" rxjs: "npm:^7.8.1"
ulidx: "npm:^2.3.0" ulidx: "npm:^2.3.0"
checksum: 10c0/da0eed6e552ce2ff6f52a087e6e221101c3d0c03d92820840ee80c3ca1a17317a66525cb5bf59b6c1e8bd2e36e54763008f97e13000ae339dac49f5682fcfa65 checksum: 10c0/cda3dff029cc6ce8a9ddcd8ac3ff039b783eed9252c1c3f0b3f34a2cf68c00dc2755997b56c3c5796502aa7316b69b57758b15f338e64b4a8ef14b34d23b6c99
languageName: node languageName: node
linkType: hard linkType: hard
"@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Finference-cortex-extension%40workspace%3Ainference-cortex-extension": "@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Finference-cortex-extension%40workspace%3Ainference-cortex-extension":
version: 0.1.10 version: 0.1.10
resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=7dd866&locator=%40janhq%2Finference-cortex-extension%40workspace%3Ainference-cortex-extension" resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=f3025c&locator=%40janhq%2Finference-cortex-extension%40workspace%3Ainference-cortex-extension"
dependencies: dependencies:
rxjs: "npm:^7.8.1" rxjs: "npm:^7.8.1"
ulidx: "npm:^2.3.0" ulidx: "npm:^2.3.0"
checksum: 10c0/da0eed6e552ce2ff6f52a087e6e221101c3d0c03d92820840ee80c3ca1a17317a66525cb5bf59b6c1e8bd2e36e54763008f97e13000ae339dac49f5682fcfa65 checksum: 10c0/cda3dff029cc6ce8a9ddcd8ac3ff039b783eed9252c1c3f0b3f34a2cf68c00dc2755997b56c3c5796502aa7316b69b57758b15f338e64b4a8ef14b34d23b6c99
languageName: node languageName: node
linkType: hard linkType: hard
"@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fmodel-extension%40workspace%3Amodel-extension": "@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fmodel-extension%40workspace%3Amodel-extension":
version: 0.1.10 version: 0.1.10
resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=7dd866&locator=%40janhq%2Fmodel-extension%40workspace%3Amodel-extension" resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=f3025c&locator=%40janhq%2Fmodel-extension%40workspace%3Amodel-extension"
dependencies: dependencies:
rxjs: "npm:^7.8.1" rxjs: "npm:^7.8.1"
ulidx: "npm:^2.3.0" ulidx: "npm:^2.3.0"
checksum: 10c0/da0eed6e552ce2ff6f52a087e6e221101c3d0c03d92820840ee80c3ca1a17317a66525cb5bf59b6c1e8bd2e36e54763008f97e13000ae339dac49f5682fcfa65 checksum: 10c0/cda3dff029cc6ce8a9ddcd8ac3ff039b783eed9252c1c3f0b3f34a2cf68c00dc2755997b56c3c5796502aa7316b69b57758b15f338e64b4a8ef14b34d23b6c99
languageName: node languageName: node
linkType: hard linkType: hard
"@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fmonitoring-extension%40workspace%3Amonitoring-extension": "@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fmonitoring-extension%40workspace%3Amonitoring-extension":
version: 0.1.10 version: 0.1.10
resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=7dd866&locator=%40janhq%2Fmonitoring-extension%40workspace%3Amonitoring-extension" resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=f3025c&locator=%40janhq%2Fmonitoring-extension%40workspace%3Amonitoring-extension"
dependencies: dependencies:
rxjs: "npm:^7.8.1" rxjs: "npm:^7.8.1"
ulidx: "npm:^2.3.0" ulidx: "npm:^2.3.0"
checksum: 10c0/da0eed6e552ce2ff6f52a087e6e221101c3d0c03d92820840ee80c3ca1a17317a66525cb5bf59b6c1e8bd2e36e54763008f97e13000ae339dac49f5682fcfa65 checksum: 10c0/cda3dff029cc6ce8a9ddcd8ac3ff039b783eed9252c1c3f0b3f34a2cf68c00dc2755997b56c3c5796502aa7316b69b57758b15f338e64b4a8ef14b34d23b6c99
languageName: node languageName: node
linkType: hard linkType: hard

View File

@ -7,9 +7,9 @@ import { twMerge } from 'tailwind-merge'
import { MainViewState } from '@/constants/screens' import { MainViewState } from '@/constants/screens'
import { LEFT_PANEL_WIDTH } from '../LeftPanelContainer' import { leftPanelWidthAtom } from '../LeftPanelContainer'
import { RIGHT_PANEL_WIDTH } from '../RightPanelContainer' import { rightPanelWidthAtom } from '../RightPanelContainer'
import { import {
mainViewStateAtom, mainViewStateAtom,
@ -28,6 +28,8 @@ const CenterPanelContainer = ({ children, isShowStarterScreen }: Props) => {
const showLeftPanel = useAtomValue(showLeftPanelAtom) const showLeftPanel = useAtomValue(showLeftPanelAtom)
const showRightPanel = useAtomValue(showRightPanelAtom) const showRightPanel = useAtomValue(showRightPanelAtom)
const mainViewState = useAtomValue(mainViewStateAtom) const mainViewState = useAtomValue(mainViewStateAtom)
const rightPanelWidth = useAtomValue(rightPanelWidthAtom)
const leftPanelWidth = useAtomValue(leftPanelWidthAtom)
return ( return (
<div <div
@ -36,7 +38,7 @@ const CenterPanelContainer = ({ children, isShowStarterScreen }: Props) => {
maxWidth: matches maxWidth: matches
? '100%' ? '100%'
: mainViewState === MainViewState.Thread && !isShowStarterScreen : mainViewState === MainViewState.Thread && !isShowStarterScreen
? `calc(100% - (${showRightPanel ? Number(localStorage.getItem(RIGHT_PANEL_WIDTH)) : 0}px + ${showLeftPanel ? Number(localStorage.getItem(LEFT_PANEL_WIDTH)) : 0}px))` ? `calc(100% - (${showRightPanel ? rightPanelWidth : 0}px + ${showLeftPanel ? leftPanelWidth : 0}px))`
: '100%', : '100%',
}} }}
> >

View File

@ -1,11 +1,9 @@
import { Fragment } from 'react' import { Fragment } from 'react'
import { Button } from '@janhq/joi' import { Button, Tooltip } from '@janhq/joi'
import { useAtom, useAtomValue, useSetAtom } from 'jotai' import { useAtom, useAtomValue, useSetAtom } from 'jotai'
import { import {
PanelLeftCloseIcon, PanelLeftCloseIcon,
PanelLeftOpenIcon,
PanelRightOpenIcon,
PanelRightCloseIcon, PanelRightCloseIcon,
MinusIcon, MinusIcon,
MenuIcon, MenuIcon,
@ -13,6 +11,8 @@ import {
PaletteIcon, PaletteIcon,
XIcon, XIcon,
PenSquareIcon, PenSquareIcon,
Settings2,
History,
} from 'lucide-react' } from 'lucide-react'
import { twMerge } from 'tailwind-merge' import { twMerge } from 'tailwind-merge'
@ -91,7 +91,10 @@ const TopPanel = () => {
</Button> </Button>
) : ( ) : (
<Button theme="icon" onClick={() => setShowLeftPanel(true)}> <Button theme="icon" onClick={() => setShowLeftPanel(true)}>
<PanelLeftOpenIcon size={16} /> <Tooltip
trigger={<History size={16} />}
content="Threads History"
/>
</Button> </Button>
)} )}
</Fragment> </Fragment>
@ -135,7 +138,10 @@ const TopPanel = () => {
} }
}} }}
> >
<PanelRightOpenIcon size={16} /> <Tooltip
trigger={<Settings2 size={16} />}
content="Thread Settings"
/>
</Button> </Button>
)} )}
</Fragment> </Fragment>

View File

@ -7,7 +7,7 @@ import {
} from 'react' } from 'react'
import { ScrollArea, useClickOutside, useMediaQuery } from '@janhq/joi' import { ScrollArea, useClickOutside, useMediaQuery } from '@janhq/joi'
import { useAtom, useAtomValue } from 'jotai' import { atom, useAtom, useAtomValue } from 'jotai'
import { twMerge } from 'tailwind-merge' import { twMerge } from 'tailwind-merge'
@ -18,13 +18,12 @@ type Props = PropsWithChildren
const DEFAULT_LEFT_PANEL_WIDTH = 200 const DEFAULT_LEFT_PANEL_WIDTH = 200
export const LEFT_PANEL_WIDTH = 'leftPanelWidth' export const LEFT_PANEL_WIDTH = 'leftPanelWidth'
export const leftPanelWidthAtom = atom(DEFAULT_LEFT_PANEL_WIDTH)
const LeftPanelContainer = ({ children }: Props) => { const LeftPanelContainer = ({ children }: Props) => {
const [leftPanelRef, setLeftPanelRef] = useState<HTMLDivElement | null>(null) const [leftPanelRef, setLeftPanelRef] = useState<HTMLDivElement | null>(null)
const [isResizing, setIsResizing] = useState(false) const [isResizing, setIsResizing] = useState(false)
const [threadLeftPanelWidth, setLeftPanelWidth] = useState( const [leftPanelWidth, setLeftPanelWidth] = useAtom(leftPanelWidthAtom)
Number(localStorage.getItem(LEFT_PANEL_WIDTH)) || DEFAULT_LEFT_PANEL_WIDTH
)
const [showLeftPanel, setShowLeftPanel] = useAtom(showLeftPanelAtom) const [showLeftPanel, setShowLeftPanel] = useAtom(showLeftPanelAtom)
const matches = useMediaQuery('(max-width: 880px)') const matches = useMediaQuery('(max-width: 880px)')
const reduceTransparent = useAtomValue(reduceTransparentAtom) const reduceTransparent = useAtomValue(reduceTransparentAtom)
@ -37,10 +36,12 @@ const LeftPanelContainer = ({ children }: Props) => {
const startResizing = useCallback(() => { const startResizing = useCallback(() => {
setIsResizing(true) setIsResizing(true)
document.body.classList.add('select-none')
}, []) }, [])
const stopResizing = useCallback(() => { const stopResizing = useCallback(() => {
setIsResizing(false) setIsResizing(false)
document.body.classList.remove('select-none')
}, []) }, [])
const resize = useCallback( const resize = useCallback(
@ -69,7 +70,7 @@ const LeftPanelContainer = ({ children }: Props) => {
} }
} }
}, },
[isResizing, leftPanelRef, setShowLeftPanel] [isResizing, leftPanelRef, setLeftPanelWidth, setShowLeftPanel]
) )
useEffect(() => { useEffect(() => {
@ -83,7 +84,7 @@ const LeftPanelContainer = ({ children }: Props) => {
window.removeEventListener('mousemove', resize) window.removeEventListener('mousemove', resize)
window.removeEventListener('mouseup', stopResizing) window.removeEventListener('mouseup', stopResizing)
} }
}, [resize, stopResizing]) }, [resize, setLeftPanelWidth, stopResizing])
return ( return (
<div <div
@ -97,7 +98,7 @@ const LeftPanelContainer = ({ children }: Props) => {
reduceTransparent && reduceTransparent &&
'left-0 border-r border-[hsla(var(--app-border))] bg-[hsla(var(--left-panel-bg))]' 'left-0 border-r border-[hsla(var(--app-border))] bg-[hsla(var(--left-panel-bg))]'
)} )}
style={{ width: showLeftPanel ? threadLeftPanelWidth : 0 }} style={{ width: showLeftPanel ? leftPanelWidth : 0 }}
onMouseDown={(e) => isResizing && e.stopPropagation()} onMouseDown={(e) => isResizing && e.stopPropagation()}
> >
<ScrollArea className="h-full w-full"> <ScrollArea className="h-full w-full">

View File

@ -44,11 +44,6 @@ export default function ModelReload() {
Reloading model {stateModel.model?.id} Reloading model {stateModel.model?.id}
</span> </span>
</div> </div>
<div className="my-4 mb-2 text-center">
<span className="text-[hsla(var(--text-secondary)]">
Model is reloading to apply new changes.
</span>
</div>
</div> </div>
) )
} }

View File

@ -4,8 +4,6 @@ import { PropsWithChildren } from 'react'
import { Toaster } from 'react-hot-toast' import { Toaster } from 'react-hot-toast'
import { SWRConfig } from 'swr'
import EventListener from '@/containers/Providers/EventListener' import EventListener from '@/containers/Providers/EventListener'
import JotaiWrapper from '@/containers/Providers/Jotai' import JotaiWrapper from '@/containers/Providers/Jotai'

View File

@ -1,9 +1,11 @@
import '@testing-library/jest-dom' import '@testing-library/jest-dom'
import { waitFor } from '@testing-library/react'
import React from 'react' import React from 'react'
import { render, fireEvent } from '@testing-library/react' import { render, fireEvent } from '@testing-library/react'
import RightPanelContainer from './index' import RightPanelContainer, { rightPanelWidthAtom } from './index'
import { useAtom } from 'jotai' import { showRightPanelAtom } from '@/helpers/atoms/App.atom'
import { reduceTransparentAtom } from '@/helpers/atoms/Setting.atom'
// Mocking ResizeObserver // Mocking ResizeObserver
class ResizeObserver { class ResizeObserver {
@ -34,24 +36,24 @@ jest.mock('jotai', () => {
const originalModule = jest.requireActual('jotai') const originalModule = jest.requireActual('jotai')
return { return {
...originalModule, ...originalModule,
useAtom: jest.fn(), useAtomValue: jest.fn((atom) => {
useAtomValue: jest.fn(), if (atom === reduceTransparentAtom) return false
if (atom === showRightPanelAtom) return true
}),
useAtom: jest.fn((atom) => {
if (atom === rightPanelWidthAtom) return [280, jest.fn()]
if (atom === showRightPanelAtom) return [true, mockSetShowRightPanel]
return [null, jest.fn()]
}),
} }
}) })
const mockSetShowRightPanel = jest.fn() const mockSetShowRightPanel = jest.fn()
const mockShowRightPanel = true // Change this to test the panel visibility
beforeEach(() => { beforeEach(() => {
// Setting up the localStorage mock // Setting up the localStorage mock
localStorage.clear() localStorage.clear()
localStorage.setItem('rightPanelWidth', '280') // Setting a default width localStorage.setItem('rightPanelWidth', '280') // Setting a default width
// Mocking the atom behavior
;(useAtom as jest.Mock).mockImplementation(() => [
mockShowRightPanel,
mockSetShowRightPanel,
])
}) })
describe('RightPanelContainer', () => { describe('RightPanelContainer', () => {
@ -66,12 +68,15 @@ describe('RightPanelContainer', () => {
expect(getByText('Child Content')).toBeInTheDocument() expect(getByText('Child Content')).toBeInTheDocument()
}) })
it('initializes width from localStorage', () => { it('initializes width from localStorage', async () => {
const { container } = render(<RightPanelContainer />) const { container } = render(<RightPanelContainer />)
// Check the width from localStorage is applied
const rightPanel = container.firstChild as HTMLDivElement const rightPanel = container.firstChild as HTMLDivElement
expect(rightPanel.style.width).toBe('280px') // Width from localStorage
// Wait for the width to be applied
await waitFor(() => {
expect(rightPanel.style.width).toBe('280px') // Correct width from localStorage
})
}) })
it('changes width on resizing', () => { it('changes width on resizing', () => {

View File

@ -7,7 +7,7 @@ import {
} from 'react' } from 'react'
import { ScrollArea, useClickOutside, useMediaQuery } from '@janhq/joi' import { ScrollArea, useClickOutside, useMediaQuery } from '@janhq/joi'
import { useAtom, useAtomValue } from 'jotai' import { atom, useAtom, useAtomValue } from 'jotai'
import { twMerge } from 'tailwind-merge' import { twMerge } from 'tailwind-merge'
@ -19,11 +19,11 @@ type Props = PropsWithChildren
const DEFAULT_RIGHT_PANEL_WIDTH = 280 const DEFAULT_RIGHT_PANEL_WIDTH = 280
export const RIGHT_PANEL_WIDTH = 'rightPanelWidth' export const RIGHT_PANEL_WIDTH = 'rightPanelWidth'
export const rightPanelWidthAtom = atom(DEFAULT_RIGHT_PANEL_WIDTH)
const RightPanelContainer = ({ children }: Props) => { const RightPanelContainer = ({ children }: Props) => {
const [isResizing, setIsResizing] = useState(false) const [isResizing, setIsResizing] = useState(false)
const [threadRightPanelWidth, setRightPanelWidth] = useState( const [rightPanelWidth, setRightPanelWidth] = useAtom(rightPanelWidthAtom)
Number(localStorage.getItem(RIGHT_PANEL_WIDTH)) || DEFAULT_RIGHT_PANEL_WIDTH
)
const [rightPanelRef, setRightPanelRef] = useState<HTMLDivElement | null>( const [rightPanelRef, setRightPanelRef] = useState<HTMLDivElement | null>(
null null
) )
@ -40,10 +40,12 @@ const RightPanelContainer = ({ children }: Props) => {
const startResizing = useCallback(() => { const startResizing = useCallback(() => {
setIsResizing(true) setIsResizing(true)
document.body.classList.add('select-none')
}, []) }, [])
const stopResizing = useCallback(() => { const stopResizing = useCallback(() => {
setIsResizing(false) setIsResizing(false)
document.body.classList.remove('select-none')
}, []) }, [])
const resize = useCallback( const resize = useCallback(
@ -72,7 +74,7 @@ const RightPanelContainer = ({ children }: Props) => {
} }
} }
}, },
[isResizing, rightPanelRef, setShowRightPanel] [isResizing, rightPanelRef, setRightPanelWidth, setShowRightPanel]
) )
useEffect(() => { useEffect(() => {
@ -86,7 +88,7 @@ const RightPanelContainer = ({ children }: Props) => {
window.removeEventListener('mousemove', resize) window.removeEventListener('mousemove', resize)
window.removeEventListener('mouseup', stopResizing) window.removeEventListener('mouseup', stopResizing)
} }
}, [resize, stopResizing]) }, [resize, setRightPanelWidth, stopResizing])
return ( return (
<div <div
@ -100,7 +102,7 @@ const RightPanelContainer = ({ children }: Props) => {
reduceTransparent && reduceTransparent &&
'border-l border-[hsla(var(--app-border))] bg-[hsla(var(--right-panel-bg))]' 'border-l border-[hsla(var(--app-border))] bg-[hsla(var(--right-panel-bg))]'
)} )}
style={{ width: showRightPanel ? threadRightPanelWidth : 0 }} style={{ width: showRightPanel ? rightPanelWidth : 0 }}
onMouseDown={(e) => isResizing && e.preventDefault()} onMouseDown={(e) => isResizing && e.preventDefault()}
> >
<ScrollArea className="h-full w-full"> <ScrollArea className="h-full w-full">

View File

@ -10,7 +10,6 @@ import { useConfigurations } from '@/hooks/useConfigurations'
import { import {
ignoreSslAtom, ignoreSslAtom,
proxyAtom, proxyAtom,
proxyEnabledAtom,
verifyProxySslAtom, verifyProxySslAtom,
verifyProxyHostSslAtom, verifyProxyHostSslAtom,
verifyPeerSslAtom, verifyPeerSslAtom,
@ -21,7 +20,6 @@ import {
} from '@/helpers/atoms/AppConfig.atom' } from '@/helpers/atoms/AppConfig.atom'
const ProxySettings = ({ onBack }: { onBack: () => void }) => { const ProxySettings = ({ onBack }: { onBack: () => void }) => {
const [proxyEnabled] = useAtom(proxyEnabledAtom)
const [proxy, setProxy] = useAtom(proxyAtom) const [proxy, setProxy] = useAtom(proxyAtom)
const [noProxy, setNoProxy] = useAtom(noProxyAtom) const [noProxy, setNoProxy] = useAtom(noProxyAtom)
const [partialProxy, setPartialProxy] = useState<string>(proxy) const [partialProxy, setPartialProxy] = useState<string>(proxy)

View File

@ -2,7 +2,6 @@ import React from 'react'
import { InferenceEngine } from '@janhq/core' import { InferenceEngine } from '@janhq/core'
import { ScrollArea } from '@janhq/joi' import { ScrollArea } from '@janhq/joi'
import { useAtomValue } from 'jotai'
import { useGetEngines } from '@/hooks/useEngineManagement' import { useGetEngines } from '@/hooks/useEngineManagement'