feat: revamp system monitor (#2097)
* feat: revamp system monitor * feat: revamp system monitor ui * remove system monitor page * fix e2e test navigation * added click outside system monitor * update height content system monitor
This commit is contained in:
parent
a859534ab6
commit
56be7742e7
@ -2,17 +2,11 @@ import { expect } from '@playwright/test'
|
|||||||
import { page, test, TIMEOUT } from '../config/fixtures'
|
import { page, test, TIMEOUT } from '../config/fixtures'
|
||||||
|
|
||||||
test('renders left navigation panel', async () => {
|
test('renders left navigation panel', async () => {
|
||||||
const systemMonitorBtn = await page
|
|
||||||
.getByTestId('System Monitor')
|
|
||||||
.first()
|
|
||||||
.isEnabled({
|
|
||||||
timeout: TIMEOUT,
|
|
||||||
})
|
|
||||||
const settingsBtn = await page
|
const settingsBtn = await page
|
||||||
.getByTestId('Thread')
|
.getByTestId('Thread')
|
||||||
.first()
|
.first()
|
||||||
.isEnabled({ timeout: TIMEOUT })
|
.isEnabled({ timeout: TIMEOUT })
|
||||||
expect([systemMonitorBtn, settingsBtn].filter((e) => !e).length).toBe(0)
|
expect([settingsBtn].filter((e) => !e).length).toBe(0)
|
||||||
// Chat section should be there
|
// Chat section should be there
|
||||||
await page.getByTestId('Local API Server').first().click({
|
await page.getByTestId('Local API Server').first().click({
|
||||||
timeout: TIMEOUT,
|
timeout: TIMEOUT,
|
||||||
|
|||||||
@ -11,7 +11,6 @@ import ExploreModelsScreen from '@/screens/ExploreModels'
|
|||||||
|
|
||||||
import LocalServerScreen from '@/screens/LocalServer'
|
import LocalServerScreen from '@/screens/LocalServer'
|
||||||
import SettingsScreen from '@/screens/Settings'
|
import SettingsScreen from '@/screens/Settings'
|
||||||
import SystemMonitorScreen from '@/screens/SystemMonitor'
|
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
const { mainViewState } = useMainViewState()
|
const { mainViewState } = useMainViewState()
|
||||||
@ -26,10 +25,6 @@ export default function Page() {
|
|||||||
children = <SettingsScreen />
|
children = <SettingsScreen />
|
||||||
break
|
break
|
||||||
|
|
||||||
case MainViewState.SystemMonitor:
|
|
||||||
children = <SystemMonitorScreen />
|
|
||||||
break
|
|
||||||
|
|
||||||
case MainViewState.LocalServer:
|
case MainViewState.LocalServer:
|
||||||
children = <LocalServerScreen />
|
children = <LocalServerScreen />
|
||||||
break
|
break
|
||||||
|
|||||||
@ -3,6 +3,5 @@ export enum MainViewState {
|
|||||||
MyModels,
|
MyModels,
|
||||||
Settings,
|
Settings,
|
||||||
Thread,
|
Thread,
|
||||||
SystemMonitor,
|
|
||||||
LocalServer,
|
LocalServer,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,30 +0,0 @@
|
|||||||
import { ReactNode } from 'react'
|
|
||||||
|
|
||||||
import { twMerge } from 'tailwind-merge'
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
name?: string
|
|
||||||
value: string | ReactNode
|
|
||||||
titleBold?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function SystemItem({ name, value, titleBold }: Props) {
|
|
||||||
return (
|
|
||||||
<div className="flex items-center gap-x-1 text-xs">
|
|
||||||
<p
|
|
||||||
className={twMerge(
|
|
||||||
titleBold ? 'font-semibold' : 'text-muted-foreground'
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{name}
|
|
||||||
</p>
|
|
||||||
<span
|
|
||||||
className={twMerge(
|
|
||||||
titleBold ? 'text-muted-foreground' : 'font-semibold'
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{value}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@ -0,0 +1,101 @@
|
|||||||
|
import { Fragment } from 'react'
|
||||||
|
|
||||||
|
import {
|
||||||
|
Tooltip,
|
||||||
|
TooltipTrigger,
|
||||||
|
Button,
|
||||||
|
TooltipPortal,
|
||||||
|
Badge,
|
||||||
|
TooltipContent,
|
||||||
|
TooltipArrow,
|
||||||
|
} from '@janhq/uikit'
|
||||||
|
|
||||||
|
import { useAtom } from 'jotai'
|
||||||
|
|
||||||
|
import { useActiveModel } from '@/hooks/useActiveModel'
|
||||||
|
|
||||||
|
import { toGibibytes } from '@/utils/converter'
|
||||||
|
|
||||||
|
import { serverEnabledAtom } from '@/helpers/atoms/LocalServer.atom'
|
||||||
|
|
||||||
|
const Column = ['Name', 'Model ID', 'Size', 'Version', 'Action']
|
||||||
|
|
||||||
|
const TableActiveModel = () => {
|
||||||
|
const { activeModel, stateModel, stopModel } = useActiveModel()
|
||||||
|
const [serverEnabled, setServerEnabled] = useAtom(serverEnabledAtom)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex-shrink-0 m-4 mr-0 w-2/3">
|
||||||
|
<div className="rounded-lg border border-border shadow-sm overflow-hidden">
|
||||||
|
<table className="w-full px-8">
|
||||||
|
<thead className="w-full border-b border-border bg-secondary">
|
||||||
|
<tr>
|
||||||
|
{Column.map((col, i) => {
|
||||||
|
return (
|
||||||
|
<th
|
||||||
|
key={i}
|
||||||
|
className="px-6 py-2 text-left font-normal last:text-center"
|
||||||
|
>
|
||||||
|
{col}
|
||||||
|
</th>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
{activeModel && (
|
||||||
|
<Fragment>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td className="px-6 py-2 font-bold">{activeModel.name}</td>
|
||||||
|
<td className="px-6 py-2 font-bold">{activeModel.id}</td>
|
||||||
|
<td className="px-6 py-2">
|
||||||
|
<Badge themes="secondary">
|
||||||
|
{toGibibytes(activeModel.metadata.size)}
|
||||||
|
</Badge>
|
||||||
|
</td>
|
||||||
|
<td className="px-6 py-2">
|
||||||
|
<Badge themes="secondary">v{activeModel.version}</Badge>
|
||||||
|
</td>
|
||||||
|
<td className="px-6 py-2 text-center">
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger className="w-full">
|
||||||
|
<Button
|
||||||
|
block
|
||||||
|
themes={
|
||||||
|
stateModel.state === 'stop' ? 'danger' : 'primary'
|
||||||
|
}
|
||||||
|
className="w-16"
|
||||||
|
loading={stateModel.loading}
|
||||||
|
onClick={() => {
|
||||||
|
stopModel()
|
||||||
|
window.core?.api?.stopServer()
|
||||||
|
setServerEnabled(false)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Stop
|
||||||
|
</Button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
{serverEnabled && (
|
||||||
|
<TooltipPortal>
|
||||||
|
<TooltipContent side="top">
|
||||||
|
<span>
|
||||||
|
The API server is running, stop the model will
|
||||||
|
also stop the server
|
||||||
|
</span>
|
||||||
|
<TooltipArrow />
|
||||||
|
</TooltipContent>
|
||||||
|
</TooltipPortal>
|
||||||
|
)}
|
||||||
|
</Tooltip>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</Fragment>
|
||||||
|
)}
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TableActiveModel
|
||||||
188
web/containers/Layout/BottomBar/SystemMonitor/index.tsx
Normal file
188
web/containers/Layout/BottomBar/SystemMonitor/index.tsx
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
import { Fragment, useEffect, useState } from 'react'
|
||||||
|
|
||||||
|
import { Progress } from '@janhq/uikit'
|
||||||
|
import { useAtom, useAtomValue } from 'jotai'
|
||||||
|
import { MonitorIcon, XIcon, ChevronDown, ChevronUp } from 'lucide-react'
|
||||||
|
|
||||||
|
import { twMerge } from 'tailwind-merge'
|
||||||
|
|
||||||
|
import { useClickOutside } from '@/hooks/useClickOutside'
|
||||||
|
import useGetSystemResources from '@/hooks/useGetSystemResources'
|
||||||
|
|
||||||
|
import { toGibibytes } from '@/utils/converter'
|
||||||
|
|
||||||
|
import TableActiveModel from './TableActiveModel'
|
||||||
|
|
||||||
|
import {
|
||||||
|
cpuUsageAtom,
|
||||||
|
gpusAtom,
|
||||||
|
ramUtilitizedAtom,
|
||||||
|
systemMonitorCollapseAtom,
|
||||||
|
totalRamAtom,
|
||||||
|
usedRamAtom,
|
||||||
|
} from '@/helpers/atoms/SystemBar.atom'
|
||||||
|
|
||||||
|
const SystemMonitor = () => {
|
||||||
|
const totalRam = useAtomValue(totalRamAtom)
|
||||||
|
const usedRam = useAtomValue(usedRamAtom)
|
||||||
|
const cpuUsage = useAtomValue(cpuUsageAtom)
|
||||||
|
const gpus = useAtomValue(gpusAtom)
|
||||||
|
const [showFullScreen, setShowFullScreen] = useState(false)
|
||||||
|
const ramUtilitized = useAtomValue(ramUtilitizedAtom)
|
||||||
|
const [systemMonitorCollapse, setSystemMonitorCollapse] = useAtom(
|
||||||
|
systemMonitorCollapseAtom
|
||||||
|
)
|
||||||
|
const [control, setControl] = useState<HTMLDivElement | null>(null)
|
||||||
|
const [elementExpand, setElementExpand] = useState<HTMLDivElement | null>(
|
||||||
|
null
|
||||||
|
)
|
||||||
|
|
||||||
|
const { watch, stopWatching } = useGetSystemResources()
|
||||||
|
useClickOutside(
|
||||||
|
() => {
|
||||||
|
setSystemMonitorCollapse(false)
|
||||||
|
setShowFullScreen(false)
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
[control, elementExpand]
|
||||||
|
)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Watch for resource update
|
||||||
|
watch()
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
stopWatching()
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const calculateUtilization = () => {
|
||||||
|
let sum = 0
|
||||||
|
const util = gpus.map((x) => {
|
||||||
|
return Number(x['utilization'])
|
||||||
|
})
|
||||||
|
util.forEach((num) => {
|
||||||
|
sum += num
|
||||||
|
})
|
||||||
|
return sum
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<div
|
||||||
|
ref={setControl}
|
||||||
|
className={twMerge(
|
||||||
|
'flex items-center gap-x-2 cursor-pointer p-2 rounded-md hover:bg-secondary',
|
||||||
|
systemMonitorCollapse && 'bg-secondary'
|
||||||
|
)}
|
||||||
|
onClick={() => {
|
||||||
|
setSystemMonitorCollapse(!systemMonitorCollapse)
|
||||||
|
setShowFullScreen(false)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<MonitorIcon size={16} />
|
||||||
|
<span className="text-xs font-medium">System Monitor</span>
|
||||||
|
</div>
|
||||||
|
{systemMonitorCollapse && (
|
||||||
|
<div
|
||||||
|
ref={setElementExpand}
|
||||||
|
className={twMerge(
|
||||||
|
'fixed left-16 bottom-12 bg-white w-[calc(100%-64px)] z-50 border-t border-border flex flex-col flex-shrink-0',
|
||||||
|
showFullScreen && 'h-[calc(100%-48px)]'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="h-12 flex items-center border-b border-border px-4 justify-between flex-shrink-0">
|
||||||
|
<h6 className="font-medium">Running Models</h6>
|
||||||
|
<div className="flex items-center gap-x-2 unset-drag">
|
||||||
|
{showFullScreen ? (
|
||||||
|
<ChevronDown
|
||||||
|
size={20}
|
||||||
|
className="text-muted-foreground cursor-pointer"
|
||||||
|
onClick={() => setShowFullScreen(!showFullScreen)}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<ChevronUp
|
||||||
|
size={20}
|
||||||
|
className="text-muted-foreground cursor-pointer"
|
||||||
|
onClick={() => setShowFullScreen(!showFullScreen)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<XIcon
|
||||||
|
size={16}
|
||||||
|
className="text-muted-foreground cursor-pointer"
|
||||||
|
onClick={() => {
|
||||||
|
setSystemMonitorCollapse(false)
|
||||||
|
setShowFullScreen(false)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-4 h-full">
|
||||||
|
<TableActiveModel />
|
||||||
|
<div className="border-l border-border p-4 w-full">
|
||||||
|
<div className="mb-4 pb-4 border-b border-border">
|
||||||
|
<h6 className="font-bold">CPU</h6>
|
||||||
|
<div className="flex items-center gap-x-4">
|
||||||
|
<Progress value={cpuUsage} className="h-2" />
|
||||||
|
<span className="flex-shrink-0 text-muted-foreground">
|
||||||
|
{cpuUsage}%
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="mb-4 pb-4 border-b border-border">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<h6 className="font-bold">Memory</h6>
|
||||||
|
<span className="text-xs text-muted-foreground">
|
||||||
|
{toGibibytes(usedRam)} of {toGibibytes(totalRam)} used
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-x-4">
|
||||||
|
<Progress
|
||||||
|
value={Math.round((usedRam / totalRam) * 100)}
|
||||||
|
className="h-2"
|
||||||
|
/>
|
||||||
|
<span className="flex-shrink-0 text-muted-foreground">
|
||||||
|
{ramUtilitized}%
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{gpus.length > 0 && (
|
||||||
|
<div className="mb-4 pb-4 border-b border-border">
|
||||||
|
<h6 className="font-bold">GPU</h6>
|
||||||
|
<div className="flex items-center gap-x-4">
|
||||||
|
<Progress value={calculateUtilization()} className="h-2" />
|
||||||
|
<span className="flex-shrink-0 text-muted-foreground">
|
||||||
|
{calculateUtilization()}%
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{gpus.map((gpu, index) => (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className="flex items-start justify-between mt-4 gap-4"
|
||||||
|
>
|
||||||
|
<span className="text-muted-foreground font-medium line-clamp-1 w-1/2">
|
||||||
|
{gpu.name}
|
||||||
|
</span>
|
||||||
|
<div className="flex gap-x-2">
|
||||||
|
<span className="font-semibold">
|
||||||
|
{gpu.utilization}%
|
||||||
|
</span>
|
||||||
|
<div>
|
||||||
|
<span className="font-semibold">{gpu.vram}</span>
|
||||||
|
<span>MB VRAM</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Fragment>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SystemMonitor
|
||||||
@ -1,43 +1,21 @@
|
|||||||
import { useEffect } from 'react'
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Badge,
|
|
||||||
Button,
|
|
||||||
Tooltip,
|
Tooltip,
|
||||||
TooltipArrow,
|
TooltipArrow,
|
||||||
TooltipContent,
|
TooltipContent,
|
||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from '@janhq/uikit'
|
} from '@janhq/uikit'
|
||||||
import { useAtom, useAtomValue, useSetAtom } from 'jotai'
|
import { useAtomValue } from 'jotai'
|
||||||
|
|
||||||
import { FaGithub, FaDiscord } from 'react-icons/fa'
|
import { FaGithub, FaDiscord } from 'react-icons/fa'
|
||||||
|
|
||||||
import DownloadingState from '@/containers/Layout/BottomBar/DownloadingState'
|
import DownloadingState from '@/containers/Layout/BottomBar/DownloadingState'
|
||||||
|
|
||||||
import SystemItem from '@/containers/Layout/BottomBar/SystemItem'
|
|
||||||
import CommandListDownloadedModel from '@/containers/Layout/TopBar/CommandListDownloadedModel'
|
import CommandListDownloadedModel from '@/containers/Layout/TopBar/CommandListDownloadedModel'
|
||||||
import ProgressBar from '@/containers/ProgressBar'
|
import ProgressBar from '@/containers/ProgressBar'
|
||||||
|
|
||||||
import { appDownloadProgress } from '@/containers/Providers/Jotai'
|
import { appDownloadProgress } from '@/containers/Providers/Jotai'
|
||||||
|
|
||||||
import { showSelectModelModalAtom } from '@/containers/Providers/KeyListener'
|
import SystemMonitor from './SystemMonitor'
|
||||||
import ShortCut from '@/containers/Shortcut'
|
|
||||||
|
|
||||||
import { MainViewState } from '@/constants/screens'
|
|
||||||
|
|
||||||
import { useActiveModel } from '@/hooks/useActiveModel'
|
|
||||||
|
|
||||||
import { modelDownloadStateAtom } from '@/hooks/useDownloadState'
|
|
||||||
import useGetSystemResources from '@/hooks/useGetSystemResources'
|
|
||||||
import { useMainViewState } from '@/hooks/useMainViewState'
|
|
||||||
|
|
||||||
import { serverEnabledAtom } from '@/helpers/atoms/LocalServer.atom'
|
|
||||||
import { downloadedModelsAtom } from '@/helpers/atoms/Model.atom'
|
|
||||||
import {
|
|
||||||
cpuUsageAtom,
|
|
||||||
gpusAtom,
|
|
||||||
ramUtilitizedAtom,
|
|
||||||
} from '@/helpers/atoms/SystemBar.atom'
|
|
||||||
|
|
||||||
const menuLinks = [
|
const menuLinks = [
|
||||||
{
|
{
|
||||||
@ -53,39 +31,7 @@ const menuLinks = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
const BottomBar = () => {
|
const BottomBar = () => {
|
||||||
const { activeModel, stateModel } = useActiveModel()
|
|
||||||
const { watch, stopWatching } = useGetSystemResources()
|
|
||||||
const progress = useAtomValue(appDownloadProgress)
|
const progress = useAtomValue(appDownloadProgress)
|
||||||
const downloadedModels = useAtomValue(downloadedModelsAtom)
|
|
||||||
const gpus = useAtomValue(gpusAtom)
|
|
||||||
const cpu = useAtomValue(cpuUsageAtom)
|
|
||||||
const ramUtilitized = useAtomValue(ramUtilitizedAtom)
|
|
||||||
|
|
||||||
const { setMainViewState } = useMainViewState()
|
|
||||||
const downloadStates = useAtomValue(modelDownloadStateAtom)
|
|
||||||
const setShowSelectModelModal = useSetAtom(showSelectModelModalAtom)
|
|
||||||
const [serverEnabled] = useAtom(serverEnabledAtom)
|
|
||||||
|
|
||||||
const calculateUtilization = () => {
|
|
||||||
let sum = 0
|
|
||||||
const util = gpus.map((x) => {
|
|
||||||
return Number(x['utilization'])
|
|
||||||
})
|
|
||||||
util.forEach((num) => {
|
|
||||||
sum += num
|
|
||||||
})
|
|
||||||
return sum
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// Watch for resource update
|
|
||||||
watch()
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
stopWatching()
|
|
||||||
}
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed bottom-0 left-16 z-20 flex h-12 w-[calc(100%-64px)] items-center justify-between border-t border-border bg-background/80 px-3">
|
<div className="fixed bottom-0 left-16 z-20 flex h-12 w-[calc(100%-64px)] items-center justify-between border-t border-border bg-background/80 px-3">
|
||||||
@ -95,95 +41,11 @@ const BottomBar = () => {
|
|||||||
<ProgressBar total={100} used={progress} />
|
<ProgressBar total={100} used={progress} />
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{!serverEnabled && (
|
|
||||||
<Badge
|
|
||||||
themes="secondary"
|
|
||||||
className="cursor-pointer rounded-md border-none font-medium"
|
|
||||||
onClick={() => setShowSelectModelModal((show) => !show)}
|
|
||||||
>
|
|
||||||
My Models
|
|
||||||
<ShortCut menu="E" />
|
|
||||||
</Badge>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{stateModel.state === 'start' && stateModel.loading && (
|
|
||||||
<SystemItem
|
|
||||||
titleBold
|
|
||||||
name="Starting"
|
|
||||||
value={stateModel.model || '-'}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{stateModel.state === 'stop' && stateModel.loading && (
|
|
||||||
<SystemItem
|
|
||||||
titleBold
|
|
||||||
name="Stopping"
|
|
||||||
value={stateModel.model || '-'}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{!stateModel.loading &&
|
|
||||||
downloadedModels.length !== 0 &&
|
|
||||||
activeModel?.id && (
|
|
||||||
<SystemItem
|
|
||||||
titleBold
|
|
||||||
name={'Active model'}
|
|
||||||
value={activeModel?.id}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{downloadedModels.length === 0 &&
|
|
||||||
!stateModel.loading &&
|
|
||||||
Object.values(downloadStates).length === 0 && (
|
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
themes="outline"
|
|
||||||
onClick={() => setMainViewState(MainViewState.Hub)}
|
|
||||||
>
|
|
||||||
Download your first model
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<DownloadingState />
|
<DownloadingState />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-x-3">
|
<div className="flex items-center gap-x-3">
|
||||||
<div className="flex items-center gap-x-2">
|
<SystemMonitor />
|
||||||
<SystemItem name="CPU:" value={`${cpu}%`} />
|
|
||||||
<SystemItem name="Mem:" value={`${ramUtilitized}%`} />
|
|
||||||
</div>
|
|
||||||
{gpus.length > 0 && (
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger>
|
|
||||||
<div className="flex items-center">
|
|
||||||
<SystemItem
|
|
||||||
name={`${gpus.length} GPU `}
|
|
||||||
value={`${calculateUtilization()}% `}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</TooltipTrigger>
|
|
||||||
{gpus.length > 1 && (
|
|
||||||
<TooltipContent
|
|
||||||
side="top"
|
|
||||||
sideOffset={10}
|
|
||||||
className="min-w-[240px]"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
{gpus.map((gpu, index) => (
|
|
||||||
<div
|
|
||||||
key={index}
|
|
||||||
className="flex items-center justify-between"
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<span>{gpu.name}</span>
|
|
||||||
<span>{gpu.vram}MB VRAM</span>
|
|
||||||
</div>
|
|
||||||
<span>{gpu.utilization}%</span>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</span>
|
|
||||||
<TooltipArrow />
|
|
||||||
</TooltipContent>
|
|
||||||
)}
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
{/* VERSION is defined by webpack, please see next.config.js */}
|
{/* VERSION is defined by webpack, please see next.config.js */}
|
||||||
<span className="text-xs text-muted-foreground">
|
<span className="text-xs text-muted-foreground">
|
||||||
Jan v{VERSION ?? ''}
|
Jan v{VERSION ?? ''}
|
||||||
|
|||||||
@ -10,7 +10,6 @@ import { useAtom, useSetAtom } from 'jotai'
|
|||||||
import {
|
import {
|
||||||
MessageCircleIcon,
|
MessageCircleIcon,
|
||||||
SettingsIcon,
|
SettingsIcon,
|
||||||
MonitorIcon,
|
|
||||||
LayoutGridIcon,
|
LayoutGridIcon,
|
||||||
SquareCodeIcon,
|
SquareCodeIcon,
|
||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
@ -75,16 +74,6 @@ export default function RibbonNav() {
|
|||||||
),
|
),
|
||||||
state: MainViewState.LocalServer,
|
state: MainViewState.LocalServer,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: 'System Monitor',
|
|
||||||
icon: (
|
|
||||||
<MonitorIcon
|
|
||||||
size={20}
|
|
||||||
className="flex-shrink-0 text-muted-foreground"
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
state: MainViewState.SystemMonitor,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: 'Settings',
|
name: 'Settings',
|
||||||
icon: (
|
icon: (
|
||||||
|
|||||||
@ -38,11 +38,7 @@ const menus = [
|
|||||||
icon: <LayoutGridIcon size={16} className="mr-3 text-muted-foreground" />,
|
icon: <LayoutGridIcon size={16} className="mr-3 text-muted-foreground" />,
|
||||||
state: MainViewState.Hub,
|
state: MainViewState.Hub,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: 'System Monitor',
|
|
||||||
icon: <MonitorIcon size={16} className="mr-3 text-muted-foreground" />,
|
|
||||||
state: MainViewState.SystemMonitor,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: 'Settings',
|
name: 'Settings',
|
||||||
icon: <SettingsIcon size={16} className="mr-3 text-muted-foreground" />,
|
icon: <SettingsIcon size={16} className="mr-3 text-muted-foreground" />,
|
||||||
|
|||||||
@ -68,7 +68,7 @@ const TopBar = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed left-0 top-0 z-50 flex h-12 w-full border-b border-border bg-background/80 backdrop-blur-md">
|
<div className="fixed left-0 top-0 z-20 flex h-12 w-full border-b border-border bg-background/80 backdrop-blur-md">
|
||||||
{mainViewState !== MainViewState.Thread &&
|
{mainViewState !== MainViewState.Thread &&
|
||||||
mainViewState !== MainViewState.LocalServer ? (
|
mainViewState !== MainViewState.LocalServer ? (
|
||||||
<div className="relative left-16 flex w-[calc(100%-64px)] items-center justify-between space-x-4 pl-6 pr-2">
|
<div className="relative left-16 flex w-[calc(100%-64px)] items-center justify-between space-x-4 pl-6 pr-2">
|
||||||
|
|||||||
@ -9,3 +9,4 @@ export const ramUtilitizedAtom = atom<number>(0)
|
|||||||
export const gpusAtom = atom<Record<string, never>[]>([])
|
export const gpusAtom = atom<Record<string, never>[]>([])
|
||||||
|
|
||||||
export const nvidiaTotalVramAtom = atom<number>(0)
|
export const nvidiaTotalVramAtom = atom<number>(0)
|
||||||
|
export const systemMonitorCollapseAtom = atom<boolean>(false)
|
||||||
|
|||||||
@ -1,152 +0,0 @@
|
|||||||
import {
|
|
||||||
ScrollArea,
|
|
||||||
Progress,
|
|
||||||
Badge,
|
|
||||||
Button,
|
|
||||||
Tooltip,
|
|
||||||
TooltipArrow,
|
|
||||||
TooltipContent,
|
|
||||||
TooltipPortal,
|
|
||||||
TooltipTrigger,
|
|
||||||
} from '@janhq/uikit'
|
|
||||||
|
|
||||||
import { useAtom, useAtomValue } from 'jotai'
|
|
||||||
|
|
||||||
import { useActiveModel } from '@/hooks/useActiveModel'
|
|
||||||
|
|
||||||
import { toGibibytes } from '@/utils/converter'
|
|
||||||
|
|
||||||
import { serverEnabledAtom } from '@/helpers/atoms/LocalServer.atom'
|
|
||||||
import {
|
|
||||||
cpuUsageAtom,
|
|
||||||
totalRamAtom,
|
|
||||||
usedRamAtom,
|
|
||||||
} from '@/helpers/atoms/SystemBar.atom'
|
|
||||||
|
|
||||||
const Column = ['Name', 'Model ID', 'Size', 'Version', 'Action']
|
|
||||||
|
|
||||||
export default function SystemMonitorScreen() {
|
|
||||||
const totalRam = useAtomValue(totalRamAtom)
|
|
||||||
const usedRam = useAtomValue(usedRamAtom)
|
|
||||||
const cpuUsage = useAtomValue(cpuUsageAtom)
|
|
||||||
const { activeModel, stateModel, stopModel } = useActiveModel()
|
|
||||||
const [serverEnabled, setServerEnabled] = useAtom(serverEnabledAtom)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex h-full w-full bg-background dark:bg-background">
|
|
||||||
<ScrollArea className="h-full w-full">
|
|
||||||
<div className="h-full p-8" data-testid="testid-system-monitor">
|
|
||||||
<div className="grid grid-cols-2 gap-8 lg:grid-cols-3">
|
|
||||||
<div className="rounded-xl border border-border p-4">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<h4 className="text-base font-bold uppercase">
|
|
||||||
cpu ({cpuUsage}%)
|
|
||||||
</h4>
|
|
||||||
<span className="text-xs text-muted-foreground">
|
|
||||||
{cpuUsage}% of 100%
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className="mt-2">
|
|
||||||
<Progress className="mb-2 h-10 rounded-md" value={cpuUsage} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="rounded-xl border border-border p-4">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<h4 className="text-base font-bold uppercase">
|
|
||||||
ram ({Math.round((usedRam / totalRam) * 100)}%)
|
|
||||||
</h4>
|
|
||||||
<span className="text-xs text-muted-foreground">
|
|
||||||
{toGibibytes(usedRam)} of {toGibibytes(totalRam)} used
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className="mt-2">
|
|
||||||
<Progress
|
|
||||||
className="mb-2 h-10 rounded-md"
|
|
||||||
value={Math.round((usedRam / totalRam) * 100)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{activeModel && (
|
|
||||||
<div className="mt-8 overflow-hidden rounded-xl border border-border shadow-sm">
|
|
||||||
<div className="px-6 py-5">
|
|
||||||
<h4 className="text-base font-medium">
|
|
||||||
Actively Running Models
|
|
||||||
</h4>
|
|
||||||
</div>
|
|
||||||
<div className="relative overflow-x-auto shadow-md">
|
|
||||||
<table className="w-full px-8">
|
|
||||||
<thead className="w-full border-b border-border bg-secondary">
|
|
||||||
<tr>
|
|
||||||
{Column.map((col, i) => {
|
|
||||||
return (
|
|
||||||
<th
|
|
||||||
key={i}
|
|
||||||
className="px-6 py-2 text-left font-normal last:text-center"
|
|
||||||
>
|
|
||||||
{col}
|
|
||||||
</th>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td className="px-6 py-2 font-bold">
|
|
||||||
{activeModel.name}
|
|
||||||
</td>
|
|
||||||
<td className="px-6 py-2 font-bold">{activeModel.id}</td>
|
|
||||||
<td className="px-6 py-2">
|
|
||||||
<Badge themes="secondary">
|
|
||||||
{toGibibytes(activeModel.metadata.size)}
|
|
||||||
</Badge>
|
|
||||||
</td>
|
|
||||||
<td className="px-6 py-2">
|
|
||||||
<Badge themes="secondary">v{activeModel.version}</Badge>
|
|
||||||
</td>
|
|
||||||
<td className="px-6 py-2 text-center">
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger className="w-full">
|
|
||||||
<Button
|
|
||||||
block
|
|
||||||
themes={
|
|
||||||
stateModel.state === 'stop'
|
|
||||||
? 'danger'
|
|
||||||
: 'primary'
|
|
||||||
}
|
|
||||||
className="w-16"
|
|
||||||
loading={stateModel.loading}
|
|
||||||
onClick={() => {
|
|
||||||
stopModel()
|
|
||||||
window.core?.api?.stopServer()
|
|
||||||
setServerEnabled(false)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Stop
|
|
||||||
</Button>
|
|
||||||
</TooltipTrigger>
|
|
||||||
{serverEnabled && (
|
|
||||||
<TooltipPortal>
|
|
||||||
<TooltipContent side="top">
|
|
||||||
<span>
|
|
||||||
The API server is running, stop the model will
|
|
||||||
also stop the server
|
|
||||||
</span>
|
|
||||||
<TooltipArrow />
|
|
||||||
</TooltipContent>
|
|
||||||
</TooltipPortal>
|
|
||||||
)}
|
|
||||||
</Tooltip>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</ScrollArea>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user