chore: intial new window system monitor
This commit is contained in:
parent
81c4dc516b
commit
2812a8978a
14
src-tauri/capabilities/system-monitor-window.json
Normal file
14
src-tauri/capabilities/system-monitor-window.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"$schema": "../gen/schemas/desktop-schema.json",
|
||||
"identifier": "system-monitor-window",
|
||||
"description": "enables permissions for the system monitor window",
|
||||
"windows": ["system-monitor-window"],
|
||||
"permissions": [
|
||||
"core:default",
|
||||
"core:window:allow-start-dragging",
|
||||
"core:window:allow-set-theme",
|
||||
"log:default",
|
||||
"core:webview:allow-create-webview-window",
|
||||
"core:window:allow-set-focus"
|
||||
]
|
||||
}
|
||||
@ -17,5 +17,6 @@ export const route = {
|
||||
},
|
||||
hub: '/hub',
|
||||
localApiServerlogs: '/local-api-server/logs',
|
||||
systemMonitor: '/system-monitor',
|
||||
threadsDetail: '/threads/$threadId',
|
||||
}
|
||||
|
||||
@ -37,7 +37,7 @@ export const useChat = () => {
|
||||
const provider = useMemo(() => {
|
||||
return getProviderByName(selectedProvider)
|
||||
}, [selectedProvider, getProviderByName])
|
||||
|
||||
|
||||
const getCurrentThread = useCallback(async () => {
|
||||
let currentThread = retrieveThread()
|
||||
if (!currentThread) {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { clsx, type ClassValue } from 'clsx'
|
||||
import { type ClassValue, clsx } from 'clsx'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
@ -131,4 +131,14 @@ export const toGigabytes = (
|
||||
} else {
|
||||
return input + (options?.hideUnit ? '' : 'B')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function formatMegaBytes(mb: number) {
|
||||
const tb = mb / (1024 * 1024)
|
||||
if (tb >= 1) {
|
||||
return `${tb.toFixed(2)} TB`
|
||||
} else {
|
||||
const gb = mb / 1024
|
||||
return `${gb.toFixed(2)} GB`
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,6 +11,7 @@
|
||||
// Import Routes
|
||||
|
||||
import { Route as rootRoute } from './routes/__root'
|
||||
import { Route as SystemMonitorImport } from './routes/system-monitor'
|
||||
import { Route as HubImport } from './routes/hub'
|
||||
import { Route as AssistantImport } from './routes/assistant'
|
||||
import { Route as IndexImport } from './routes/index'
|
||||
@ -29,6 +30,12 @@ import { Route as SettingsProvidersProviderNameImport } from './routes/settings/
|
||||
|
||||
// Create/Update Routes
|
||||
|
||||
const SystemMonitorRoute = SystemMonitorImport.update({
|
||||
id: '/system-monitor',
|
||||
path: '/system-monitor',
|
||||
getParentRoute: () => rootRoute,
|
||||
} as any)
|
||||
|
||||
const HubRoute = HubImport.update({
|
||||
id: '/hub',
|
||||
path: '/hub',
|
||||
@ -145,6 +152,13 @@ declare module '@tanstack/react-router' {
|
||||
preLoaderRoute: typeof HubImport
|
||||
parentRoute: typeof rootRoute
|
||||
}
|
||||
'/system-monitor': {
|
||||
id: '/system-monitor'
|
||||
path: '/system-monitor'
|
||||
fullPath: '/system-monitor'
|
||||
preLoaderRoute: typeof SystemMonitorImport
|
||||
parentRoute: typeof rootRoute
|
||||
}
|
||||
'/local-api-server/logs': {
|
||||
id: '/local-api-server/logs'
|
||||
path: '/local-api-server/logs'
|
||||
@ -238,6 +252,7 @@ export interface FileRoutesByFullPath {
|
||||
'/': typeof IndexRoute
|
||||
'/assistant': typeof AssistantRoute
|
||||
'/hub': typeof HubRoute
|
||||
'/system-monitor': typeof SystemMonitorRoute
|
||||
'/local-api-server/logs': typeof LocalApiServerLogsRoute
|
||||
'/settings/appearance': typeof SettingsAppearanceRoute
|
||||
'/settings/extensions': typeof SettingsExtensionsRoute
|
||||
@ -256,6 +271,7 @@ export interface FileRoutesByTo {
|
||||
'/': typeof IndexRoute
|
||||
'/assistant': typeof AssistantRoute
|
||||
'/hub': typeof HubRoute
|
||||
'/system-monitor': typeof SystemMonitorRoute
|
||||
'/local-api-server/logs': typeof LocalApiServerLogsRoute
|
||||
'/settings/appearance': typeof SettingsAppearanceRoute
|
||||
'/settings/extensions': typeof SettingsExtensionsRoute
|
||||
@ -275,6 +291,7 @@ export interface FileRoutesById {
|
||||
'/': typeof IndexRoute
|
||||
'/assistant': typeof AssistantRoute
|
||||
'/hub': typeof HubRoute
|
||||
'/system-monitor': typeof SystemMonitorRoute
|
||||
'/local-api-server/logs': typeof LocalApiServerLogsRoute
|
||||
'/settings/appearance': typeof SettingsAppearanceRoute
|
||||
'/settings/extensions': typeof SettingsExtensionsRoute
|
||||
@ -295,6 +312,7 @@ export interface FileRouteTypes {
|
||||
| '/'
|
||||
| '/assistant'
|
||||
| '/hub'
|
||||
| '/system-monitor'
|
||||
| '/local-api-server/logs'
|
||||
| '/settings/appearance'
|
||||
| '/settings/extensions'
|
||||
@ -312,6 +330,7 @@ export interface FileRouteTypes {
|
||||
| '/'
|
||||
| '/assistant'
|
||||
| '/hub'
|
||||
| '/system-monitor'
|
||||
| '/local-api-server/logs'
|
||||
| '/settings/appearance'
|
||||
| '/settings/extensions'
|
||||
@ -329,6 +348,7 @@ export interface FileRouteTypes {
|
||||
| '/'
|
||||
| '/assistant'
|
||||
| '/hub'
|
||||
| '/system-monitor'
|
||||
| '/local-api-server/logs'
|
||||
| '/settings/appearance'
|
||||
| '/settings/extensions'
|
||||
@ -348,6 +368,7 @@ export interface RootRouteChildren {
|
||||
IndexRoute: typeof IndexRoute
|
||||
AssistantRoute: typeof AssistantRoute
|
||||
HubRoute: typeof HubRoute
|
||||
SystemMonitorRoute: typeof SystemMonitorRoute
|
||||
LocalApiServerLogsRoute: typeof LocalApiServerLogsRoute
|
||||
SettingsAppearanceRoute: typeof SettingsAppearanceRoute
|
||||
SettingsExtensionsRoute: typeof SettingsExtensionsRoute
|
||||
@ -366,6 +387,7 @@ const rootRouteChildren: RootRouteChildren = {
|
||||
IndexRoute: IndexRoute,
|
||||
AssistantRoute: AssistantRoute,
|
||||
HubRoute: HubRoute,
|
||||
SystemMonitorRoute: SystemMonitorRoute,
|
||||
LocalApiServerLogsRoute: LocalApiServerLogsRoute,
|
||||
SettingsAppearanceRoute: SettingsAppearanceRoute,
|
||||
SettingsExtensionsRoute: SettingsExtensionsRoute,
|
||||
@ -393,6 +415,7 @@ export const routeTree = rootRoute
|
||||
"/",
|
||||
"/assistant",
|
||||
"/hub",
|
||||
"/system-monitor",
|
||||
"/local-api-server/logs",
|
||||
"/settings/appearance",
|
||||
"/settings/extensions",
|
||||
@ -416,6 +439,9 @@ export const routeTree = rootRoute
|
||||
"/hub": {
|
||||
"filePath": "hub.tsx"
|
||||
},
|
||||
"/system-monitor": {
|
||||
"filePath": "system-monitor.tsx"
|
||||
},
|
||||
"/local-api-server/logs": {
|
||||
"filePath": "local-api-server/logs.tsx"
|
||||
},
|
||||
|
||||
@ -18,9 +18,6 @@ export const Route = createRootRoute({
|
||||
const AppLayout = () => {
|
||||
return (
|
||||
<Fragment>
|
||||
<ExtensionProvider>
|
||||
<DataProvider />
|
||||
</ExtensionProvider>
|
||||
<KeyboardShortcutsProvider />
|
||||
<main className="relative h-svh text-sm antialiased select-none bg-app">
|
||||
{/* Fake absolute panel top to enable window drag */}
|
||||
@ -62,13 +59,17 @@ const LogsLayout = () => {
|
||||
function RootLayout() {
|
||||
const router = useRouterState()
|
||||
const isLocalAPIServerLogsRoute =
|
||||
router.location.pathname === route.localApiServerlogs
|
||||
router.location.pathname === route.localApiServerlogs ||
|
||||
router.location.pathname === route.systemMonitor
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<ThemeProvider />
|
||||
<AppearanceProvider />
|
||||
<ToasterProvider />
|
||||
<ExtensionProvider>
|
||||
<DataProvider />
|
||||
</ExtensionProvider>
|
||||
{isLocalAPIServerLogsRoute ? <LogsLayout /> : <AppLayout />}
|
||||
{/* <TanStackRouterDevtools position="bottom-right" /> */}
|
||||
</Fragment>
|
||||
|
||||
@ -24,23 +24,18 @@ import {
|
||||
useSortable,
|
||||
} from '@dnd-kit/sortable'
|
||||
import { CSS } from '@dnd-kit/utilities'
|
||||
import { IconGripVertical } from '@tabler/icons-react'
|
||||
import {
|
||||
IconGripVertical,
|
||||
IconDeviceDesktopAnalytics,
|
||||
} from '@tabler/icons-react'
|
||||
import { getHardwareInfo } from '@/services/hardware'
|
||||
import { WebviewWindow } from '@tauri-apps/api/webviewWindow'
|
||||
import { formatMegaBytes } from '@/lib/utils'
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export const Route = createFileRoute(route.settings.hardware as any)({
|
||||
component: Hardware,
|
||||
})
|
||||
// Format bytes to a human-readable format
|
||||
function formatMegaBytes(mb: number) {
|
||||
const tb = mb / (1024 * 1024)
|
||||
if (tb >= 1) {
|
||||
return `${tb.toFixed(2)} TB`
|
||||
} else {
|
||||
const gb = mb / 1024
|
||||
return `${gb.toFixed(2)} GB`
|
||||
}
|
||||
}
|
||||
|
||||
function SortableGPUItem({ gpu, index }: { gpu: GPU; index: number }) {
|
||||
const {
|
||||
@ -171,7 +166,54 @@ function Hardware() {
|
||||
return (
|
||||
<div className="flex flex-col h-full">
|
||||
<HeaderPage>
|
||||
<h1 className="font-medium">{t('common.settings')}</h1>
|
||||
<div className="flex items-center gap-2 justify-between w-full pr-3">
|
||||
<h1 className="font-medium">{t('common.settings')}</h1>
|
||||
<div
|
||||
className="flex items-center gap-1 hover:bg-main-view-fg/8 px-1.5 py-0.5 rounded relative z-10 cursor-pointer"
|
||||
onClick={async () => {
|
||||
try {
|
||||
// Check if system monitor window already exists
|
||||
const existingWindow = await WebviewWindow.getByLabel(
|
||||
'system-monitor-window'
|
||||
)
|
||||
|
||||
if (existingWindow) {
|
||||
// If window exists, focus it
|
||||
await existingWindow.setFocus()
|
||||
console.log('Focused existing system monitor window')
|
||||
} else {
|
||||
// Create a new system monitor window
|
||||
const monitorWindow = new WebviewWindow(
|
||||
'system-monitor-window',
|
||||
{
|
||||
url: route.systemMonitor,
|
||||
title: 'System Monitor - Jan',
|
||||
width: 900,
|
||||
height: 600,
|
||||
resizable: true,
|
||||
center: true,
|
||||
}
|
||||
)
|
||||
|
||||
// Listen for window creation
|
||||
monitorWindow.once('tauri://created', () => {
|
||||
console.log('System monitor window created')
|
||||
})
|
||||
|
||||
// Listen for window errors
|
||||
monitorWindow.once('tauri://error', (e) => {
|
||||
console.error('Error creating system monitor window:', e)
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to open system monitor window:', error)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<IconDeviceDesktopAnalytics className="text-main-view-fg/50 size-5" />
|
||||
<p>System monitor</p>
|
||||
</div>
|
||||
</div>
|
||||
</HeaderPage>
|
||||
<div className="flex h-full w-full">
|
||||
<SettingsMenu />
|
||||
|
||||
@ -115,7 +115,13 @@ function LocalAPIServer() {
|
||||
Start an OpenAI-compatible local HTTP server.
|
||||
</p>
|
||||
</div>
|
||||
<Button onClick={toggleAPIServer}>
|
||||
<Button
|
||||
onClick={toggleAPIServer}
|
||||
variant={
|
||||
serverStatus === 'running' ? 'destructive' : 'default'
|
||||
}
|
||||
size="sm"
|
||||
>
|
||||
{`${serverStatus === 'running' ? 'Stop' : 'Start'}`} Server
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
237
web-app/src/routes/system-monitor.tsx
Normal file
237
web-app/src/routes/system-monitor.tsx
Normal file
@ -0,0 +1,237 @@
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
import { useEffect } from 'react'
|
||||
import { useHardware } from '@/hooks/useHardware'
|
||||
import { getHardwareInfo } from '@/services/hardware'
|
||||
import { Progress } from '@/components/ui/progress'
|
||||
import type { HardwareData } from '@/hooks/useHardware'
|
||||
import { route } from '@/constants/routes'
|
||||
import { formatMegaBytes } from '@/lib/utils'
|
||||
import { IconDeviceDesktopAnalytics } from '@tabler/icons-react'
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export const Route = createFileRoute(route.systemMonitor as any)({
|
||||
component: SystemMonitor,
|
||||
})
|
||||
|
||||
function SystemMonitor() {
|
||||
const { hardwareData, setHardwareData, updateCPUUsage, updateRAMAvailable } =
|
||||
useHardware()
|
||||
|
||||
useEffect(() => {
|
||||
// Initial data fetch
|
||||
getHardwareInfo().then((data) => {
|
||||
setHardwareData(data as unknown as HardwareData)
|
||||
})
|
||||
|
||||
// Set up interval for real-time updates
|
||||
const intervalId = setInterval(() => {
|
||||
getHardwareInfo().then((data) => {
|
||||
setHardwareData(data as unknown as HardwareData)
|
||||
updateCPUUsage(data.cpu.usage)
|
||||
updateRAMAvailable(data.ram.available)
|
||||
})
|
||||
}, 5000)
|
||||
|
||||
return () => clearInterval(intervalId)
|
||||
}, [setHardwareData, updateCPUUsage, updateRAMAvailable])
|
||||
|
||||
// Calculate RAM usage percentage
|
||||
const ramUsagePercentage =
|
||||
((hardwareData.ram.total - hardwareData.ram.available) /
|
||||
hardwareData.ram.total) *
|
||||
100
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full bg-main-view overflow-y-auto p-6">
|
||||
<div className="flex items-center mb-4 gap-2">
|
||||
<IconDeviceDesktopAnalytics className="text-main-view-fg/80 size-6" />
|
||||
<h1 className="text-xl font-bold text-main-view-fg">System Monitor</h1>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{/* CPU Usage Card */}
|
||||
<div className="bg-main-view-fg/2 rounded-lg p-6 shadow-sm">
|
||||
<h2 className="text-base font-semibold text-main-view-fg mb-4">
|
||||
CPU Usage
|
||||
</h2>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-main-view-fg/70">Model</span>
|
||||
<span className="text-main-view-fg">
|
||||
{hardwareData.cpu.model}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-main-view-fg/70">Cores</span>
|
||||
<span className="text-main-view-fg">
|
||||
{hardwareData.cpu.cores}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-main-view-fg/70">Architecture</span>
|
||||
<span className="text-main-view-fg">{hardwareData.cpu.arch}</span>
|
||||
</div>
|
||||
<div className="mt-4">
|
||||
<div className="flex justify-between items-center mb-2">
|
||||
<span className="text-main-view-fg/70">Current Usage</span>
|
||||
<span className="text-main-view-fg font-bold">
|
||||
{hardwareData.cpu.usage.toFixed(2)}%
|
||||
</span>
|
||||
</div>
|
||||
<Progress value={hardwareData.cpu.usage} className="h-3 w-full" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* RAM Usage Card */}
|
||||
<div className="bg-main-view-fg/2 rounded-lg p-6 shadow-sm">
|
||||
<h2 className="text-base font-semibold text-main-view-fg mb-4">
|
||||
Memory Usage
|
||||
</h2>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-main-view-fg/70">Total RAM</span>
|
||||
<span className="text-main-view-fg">
|
||||
{formatMegaBytes(hardwareData.ram.total)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-main-view-fg/70">Available RAM</span>
|
||||
<span className="text-main-view-fg">
|
||||
{formatMegaBytes(hardwareData.ram.available)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-main-view-fg/70">Used RAM</span>
|
||||
<span className="text-main-view-fg">
|
||||
{formatMegaBytes(
|
||||
hardwareData.ram.total - hardwareData.ram.available
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="mt-4">
|
||||
<div className="flex justify-between items-center mb-2">
|
||||
<span className="text-main-view-fg/70">Current Usage</span>
|
||||
<span className="text-main-view-fg font-bold">
|
||||
{ramUsagePercentage.toFixed(2)}%
|
||||
</span>
|
||||
</div>
|
||||
<Progress value={ramUsagePercentage} className="h-3 w-full" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Current Active Model Section */}
|
||||
<div className="mt-6 bg-main-view-fg/2 rounded-lg p-6 shadow-sm">
|
||||
<h2 className="text-base font-semibold text-main-view-fg mb-4">
|
||||
Current Active Model
|
||||
</h2>
|
||||
<div className="bg-main-view-fg/3 rounded-lg p-4">
|
||||
<div className="flex justify-between items-center mb-2">
|
||||
<span className="font-semibold text-main-view-fg">GPT-4o</span>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2 mt-3">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-main-view-fg/70">Provider</span>
|
||||
<span className="text-main-view-fg">OpenAI</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-main-view-fg/70">Context Length</span>
|
||||
<span className="text-main-view-fg">128K tokens</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-main-view-fg/70">Status</span>
|
||||
<span className="text-main-view-fg">
|
||||
<div className="bg-green-500/20 px-1 font-bold py-0.5 rounded text-green-700 text-xs">
|
||||
Running
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Active GPUs Section */}
|
||||
<div className="mt-6 bg-main-view-fg/2 rounded-lg p-6 shadow-sm">
|
||||
<h2 className="text-base font-semibold text-main-view-fg mb-4">
|
||||
Active GPUs
|
||||
</h2>
|
||||
{hardwareData.gpus.length > 0 ? (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{hardwareData.gpus
|
||||
.filter((gpu) => gpu.activated)
|
||||
.map((gpu, index) => (
|
||||
<div
|
||||
key={gpu.id || index}
|
||||
className="bg-main-view-fg/3 rounded-lg p-4"
|
||||
>
|
||||
<div className="flex justify-between items-center mb-2">
|
||||
<span className="font-semibold text-main-view-fg">
|
||||
{gpu.name}
|
||||
</span>
|
||||
<div className="bg-green-500/20">Active</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2 mt-3">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-main-view-fg/70">VRAM Usage</span>
|
||||
<span className="text-main-view-fg">
|
||||
{formatMegaBytes(gpu.total_vram - gpu.free_vram)} /{' '}
|
||||
{formatMegaBytes(gpu.total_vram)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-main-view-fg/70">
|
||||
Driver Version:
|
||||
</span>
|
||||
<span className="text-main-view-fg">
|
||||
{gpu.additional_information.driver_version}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-main-view-fg/70">
|
||||
Compute Capability:
|
||||
</span>
|
||||
<span className="text-main-view-fg">
|
||||
{gpu.additional_information.compute_cap}
|
||||
</span>
|
||||
</div>
|
||||
<div className="mt-2">
|
||||
<Progress
|
||||
value={
|
||||
((gpu.total_vram - gpu.free_vram) / gpu.total_vram) *
|
||||
100
|
||||
}
|
||||
className={`h-2 w-full ${
|
||||
((gpu.total_vram - gpu.free_vram) / gpu.total_vram) *
|
||||
100 >
|
||||
80
|
||||
? 'bg-red-500/30'
|
||||
: ((gpu.total_vram - gpu.free_vram) /
|
||||
gpu.total_vram) *
|
||||
100 >
|
||||
50
|
||||
? 'bg-yellow-500/30'
|
||||
: 'bg-green-500/30'
|
||||
}`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center text-main-view-fg/50 py-4">
|
||||
No GPUs detected
|
||||
</div>
|
||||
)}
|
||||
{hardwareData.gpus.length > 0 &&
|
||||
!hardwareData.gpus.some((gpu) => gpu.activated) && (
|
||||
<div className="text-center text-main-view-fg/50 py-4">
|
||||
No active GPUs. All GPUs are currently disabled.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user