diff --git a/electron/tests/e2e/navigation.e2e.spec.ts b/electron/tests/e2e/navigation.e2e.spec.ts
index 66924ce78..b599a951c 100644
--- a/electron/tests/e2e/navigation.e2e.spec.ts
+++ b/electron/tests/e2e/navigation.e2e.spec.ts
@@ -2,17 +2,11 @@ import { expect } from '@playwright/test'
import { page, test, TIMEOUT } from '../config/fixtures'
test('renders left navigation panel', async () => {
- const systemMonitorBtn = await page
- .getByTestId('System Monitor')
- .first()
- .isEnabled({
- timeout: TIMEOUT,
- })
const settingsBtn = await page
.getByTestId('Thread')
.first()
.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
await page.getByTestId('Local API Server').first().click({
timeout: TIMEOUT,
diff --git a/web/app/page.tsx b/web/app/page.tsx
index 20b15a235..92d654528 100644
--- a/web/app/page.tsx
+++ b/web/app/page.tsx
@@ -11,7 +11,6 @@ import ExploreModelsScreen from '@/screens/ExploreModels'
import LocalServerScreen from '@/screens/LocalServer'
import SettingsScreen from '@/screens/Settings'
-import SystemMonitorScreen from '@/screens/SystemMonitor'
export default function Page() {
const { mainViewState } = useMainViewState()
@@ -26,10 +25,6 @@ export default function Page() {
children =
break
- case MainViewState.SystemMonitor:
- children =
- break
-
case MainViewState.LocalServer:
children =
break
diff --git a/web/constants/screens.ts b/web/constants/screens.ts
index 6a8adc185..74b441b17 100644
--- a/web/constants/screens.ts
+++ b/web/constants/screens.ts
@@ -3,6 +3,5 @@ export enum MainViewState {
MyModels,
Settings,
Thread,
- SystemMonitor,
LocalServer,
}
diff --git a/web/containers/Layout/BottomBar/SystemItem/index.tsx b/web/containers/Layout/BottomBar/SystemItem/index.tsx
deleted file mode 100644
index 6bb8b645d..000000000
--- a/web/containers/Layout/BottomBar/SystemItem/index.tsx
+++ /dev/null
@@ -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 (
-
-
- {name}
-
-
- {value}
-
-
- )
-}
diff --git a/web/containers/Layout/BottomBar/SystemMonitor/TableActiveModel/index.tsx b/web/containers/Layout/BottomBar/SystemMonitor/TableActiveModel/index.tsx
new file mode 100644
index 000000000..a73ec687f
--- /dev/null
+++ b/web/containers/Layout/BottomBar/SystemMonitor/TableActiveModel/index.tsx
@@ -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 (
+
+
+
+
+
+ {Column.map((col, i) => {
+ return (
+ |
+ {col}
+ |
+ )
+ })}
+
+
+ {activeModel && (
+
+
+
+ | {activeModel.name} |
+ {activeModel.id} |
+
+
+ {toGibibytes(activeModel.metadata.size)}
+
+ |
+
+ v{activeModel.version}
+ |
+
+
+
+
+
+ {serverEnabled && (
+
+
+
+ The API server is running, stop the model will
+ also stop the server
+
+
+
+
+ )}
+
+ |
+
+
+
+ )}
+
+
+
+ )
+}
+
+export default TableActiveModel
diff --git a/web/containers/Layout/BottomBar/SystemMonitor/index.tsx b/web/containers/Layout/BottomBar/SystemMonitor/index.tsx
new file mode 100644
index 000000000..5b7853698
--- /dev/null
+++ b/web/containers/Layout/BottomBar/SystemMonitor/index.tsx
@@ -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(null)
+ const [elementExpand, setElementExpand] = useState(
+ 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 (
+
+ {
+ setSystemMonitorCollapse(!systemMonitorCollapse)
+ setShowFullScreen(false)
+ }}
+ >
+
+ System Monitor
+
+ {systemMonitorCollapse && (
+
+
+
Running Models
+
+ {showFullScreen ? (
+ setShowFullScreen(!showFullScreen)}
+ />
+ ) : (
+ setShowFullScreen(!showFullScreen)}
+ />
+ )}
+ {
+ setSystemMonitorCollapse(false)
+ setShowFullScreen(false)
+ }}
+ />
+
+
+
+
+
+
+
+
+
Memory
+
+ {toGibibytes(usedRam)} of {toGibibytes(totalRam)} used
+
+
+
+
+
+ {ramUtilitized}%
+
+
+
+ {gpus.length > 0 && (
+
+
GPU
+
+
+
+ {calculateUtilization()}%
+
+
+ {gpus.map((gpu, index) => (
+
+
+ {gpu.name}
+
+
+
+ {gpu.utilization}%
+
+
+ {gpu.vram}
+ MB VRAM
+
+
+
+ ))}
+
+ )}
+
+
+
+ )}
+
+ )
+}
+
+export default SystemMonitor
diff --git a/web/containers/Layout/BottomBar/index.tsx b/web/containers/Layout/BottomBar/index.tsx
index 5d6b7e79c..61e984c99 100644
--- a/web/containers/Layout/BottomBar/index.tsx
+++ b/web/containers/Layout/BottomBar/index.tsx
@@ -1,43 +1,21 @@
-import { useEffect } from 'react'
-
import {
- Badge,
- Button,
Tooltip,
TooltipArrow,
TooltipContent,
TooltipTrigger,
} from '@janhq/uikit'
-import { useAtom, useAtomValue, useSetAtom } from 'jotai'
+import { useAtomValue } from 'jotai'
import { FaGithub, FaDiscord } from 'react-icons/fa'
import DownloadingState from '@/containers/Layout/BottomBar/DownloadingState'
-import SystemItem from '@/containers/Layout/BottomBar/SystemItem'
import CommandListDownloadedModel from '@/containers/Layout/TopBar/CommandListDownloadedModel'
import ProgressBar from '@/containers/ProgressBar'
import { appDownloadProgress } from '@/containers/Providers/Jotai'
-import { showSelectModelModalAtom } from '@/containers/Providers/KeyListener'
-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'
+import SystemMonitor from './SystemMonitor'
const menuLinks = [
{
@@ -53,39 +31,7 @@ const menuLinks = [
]
const BottomBar = () => {
- const { activeModel, stateModel } = useActiveModel()
- const { watch, stopWatching } = useGetSystemResources()
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 (
@@ -95,95 +41,11 @@ const BottomBar = () => {
) : null}
-
- {!serverEnabled && (
- setShowSelectModelModal((show) => !show)}
- >
- My Models
-
-
- )}
-
- {stateModel.state === 'start' && stateModel.loading && (
-
- )}
- {stateModel.state === 'stop' && stateModel.loading && (
-
- )}
- {!stateModel.loading &&
- downloadedModels.length !== 0 &&
- activeModel?.id && (
-
- )}
- {downloadedModels.length === 0 &&
- !stateModel.loading &&
- Object.values(downloadStates).length === 0 && (
-
- )}
-
-
-
-
-
- {gpus.length > 0 && (
-
-
-
-
-
-
- {gpus.length > 1 && (
-
-
- {gpus.map((gpu, index) => (
-
-
- {gpu.name}
- {gpu.vram}MB VRAM
-
-
{gpu.utilization}%
-
- ))}
-
-
-
- )}
-
- )}
+
+
{/* VERSION is defined by webpack, please see next.config.js */}
Jan v{VERSION ?? ''}
diff --git a/web/containers/Layout/Ribbon/index.tsx b/web/containers/Layout/Ribbon/index.tsx
index 8e7db604e..8a3c4a3a3 100644
--- a/web/containers/Layout/Ribbon/index.tsx
+++ b/web/containers/Layout/Ribbon/index.tsx
@@ -10,7 +10,6 @@ import { useAtom, useSetAtom } from 'jotai'
import {
MessageCircleIcon,
SettingsIcon,
- MonitorIcon,
LayoutGridIcon,
SquareCodeIcon,
} from 'lucide-react'
@@ -75,16 +74,6 @@ export default function RibbonNav() {
),
state: MainViewState.LocalServer,
},
- {
- name: 'System Monitor',
- icon: (
-
- ),
- state: MainViewState.SystemMonitor,
- },
{
name: 'Settings',
icon: (
diff --git a/web/containers/Layout/TopBar/CommandSearch/index.tsx b/web/containers/Layout/TopBar/CommandSearch/index.tsx
index ec73a7660..17887763e 100644
--- a/web/containers/Layout/TopBar/CommandSearch/index.tsx
+++ b/web/containers/Layout/TopBar/CommandSearch/index.tsx
@@ -38,11 +38,7 @@ const menus = [
icon: ,
state: MainViewState.Hub,
},
- {
- name: 'System Monitor',
- icon: ,
- state: MainViewState.SystemMonitor,
- },
+
{
name: 'Settings',
icon: ,
diff --git a/web/containers/Layout/TopBar/index.tsx b/web/containers/Layout/TopBar/index.tsx
index fdc4b4cdc..525cd97de 100644
--- a/web/containers/Layout/TopBar/index.tsx
+++ b/web/containers/Layout/TopBar/index.tsx
@@ -68,7 +68,7 @@ const TopBar = () => {
}
return (
-
+
{mainViewState !== MainViewState.Thread &&
mainViewState !== MainViewState.LocalServer ? (
diff --git a/web/helpers/atoms/SystemBar.atom.ts b/web/helpers/atoms/SystemBar.atom.ts
index 3c9a48f79..5779ef822 100644
--- a/web/helpers/atoms/SystemBar.atom.ts
+++ b/web/helpers/atoms/SystemBar.atom.ts
@@ -9,3 +9,4 @@ export const ramUtilitizedAtom = atom
(0)
export const gpusAtom = atom[]>([])
export const nvidiaTotalVramAtom = atom(0)
+export const systemMonitorCollapseAtom = atom(false)
diff --git a/web/screens/SystemMonitor/index.tsx b/web/screens/SystemMonitor/index.tsx
deleted file mode 100644
index 3bf8bb35e..000000000
--- a/web/screens/SystemMonitor/index.tsx
+++ /dev/null
@@ -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 (
-
-
-
-
-
-
-
- cpu ({cpuUsage}%)
-
-
- {cpuUsage}% of 100%
-
-
-
-
-
-
-
- ram ({Math.round((usedRam / totalRam) * 100)}%)
-
-
- {toGibibytes(usedRam)} of {toGibibytes(totalRam)} used
-
-
-
-
-
-
- {activeModel && (
-
-
-
- Actively Running Models
-
-
-
-
-
-
- {Column.map((col, i) => {
- return (
- |
- {col}
- |
- )
- })}
-
-
-
-
- |
- {activeModel.name}
- |
- {activeModel.id} |
-
-
- {toGibibytes(activeModel.metadata.size)}
-
- |
-
- v{activeModel.version}
- |
-
-
-
-
-
- {serverEnabled && (
-
-
-
- The API server is running, stop the model will
- also stop the server
-
-
-
-
- )}
-
- |
-
-
-
-
-
- )}
-
-
-
- )
-}