chore: add model information in System Monitoring (#5062)

* chore: add model information in System Monitoring

* chore: handle empty models case

* chore: fix type
This commit is contained in:
Louis 2025-05-22 16:07:08 +07:00 committed by GitHub
parent 4d66eaf0a7
commit 570bb8290f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 141 additions and 54 deletions

View File

@ -195,16 +195,12 @@ export default class JanInferenceCortexExtension extends LocalOAIEngine {
this.abortControllers.set(model.id, controller) this.abortControllers.set(model.id, controller)
const loadedModels = await this.apiInstance() const loadedModels = await this.activeModels()
.then((e) => e.get('inferences/server/models'))
.then((e) => e.json())
.then((e) => (e as LoadedModelResponse).data ?? [])
.catch(() => [])
console.log('Loaded models:', loadedModels) console.log('Loaded models:', loadedModels)
// This is to avoid loading the same model multiple times // This is to avoid loading the same model multiple times
if (loadedModels.some((e) => e.id === model.id)) { if (loadedModels.some((e: { id: string }) => e.id === model.id)) {
console.log(`Model ${model.id} already loaded`) console.log(`Model ${model.id} already loaded`)
return return
} }
@ -216,8 +212,8 @@ export default class JanInferenceCortexExtension extends LocalOAIEngine {
...extractModelLoadParams(model.settings), ...extractModelLoadParams(model.settings),
model: model.id, model: model.id,
engine: engine:
model.engine === "nitro" // Legacy model cache model.engine === 'nitro' // Legacy model cache
? "llama-cpp" ? 'llama-cpp'
: model.engine, : model.engine,
cont_batching: this.cont_batching, cont_batching: this.cont_batching,
n_parallel: this.n_parallel, n_parallel: this.n_parallel,
@ -253,6 +249,14 @@ export default class JanInferenceCortexExtension extends LocalOAIEngine {
) )
} }
async activeModels(): Promise<(object & { id: string })[]> {
return await this.apiInstance()
.then((e) => e.get('inferences/server/models'))
.then((e) => e.json())
.then((e) => (e as LoadedModelResponse).data ?? [])
.catch(() => [])
}
/** /**
* Clean cortex processes * Clean cortex processes
* @returns * @returns

View File

@ -1,6 +1,6 @@
use reqwest::blocking::Client; use reqwest::blocking::Client;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{fs, path::PathBuf, io}; use std::{fs, io, path::PathBuf};
use tauri::{AppHandle, Manager, Runtime, State}; use tauri::{AppHandle, Manager, Runtime, State};
use tauri_plugin_updater::UpdaterExt; use tauri_plugin_updater::UpdaterExt;
@ -273,9 +273,15 @@ pub fn get_active_extensions(app: AppHandle) -> Vec<serde_json::Value> {
}) })
}) })
.collect(), .collect(),
Err(_) => vec![], Err(error) => {
log::error!("Failed to parse extensions.json: {}", error);
vec![]
}
},
Err(error) => {
log::error!("Failed to read extensions.json: {}", error);
vec![]
}, },
Err(_) => vec![],
}; };
return contents; return contents;
} }

View File

@ -146,3 +146,29 @@ export function formatMegaBytes(mb: number) {
export function isDev() { export function isDev() {
return window.location.host.startsWith('localhost:') return window.location.host.startsWith('localhost:')
} }
export function formatDuration(startTime: number, endTime?: number): string {
const end = endTime || Date.now();
const durationMs = end - startTime;
if (durationMs < 0) {
return "Invalid duration (start time is in the future)";
}
const seconds = Math.floor(durationMs / 1000);
const minutes = Math.floor(seconds / 60);
const hours = Math.floor(minutes / 60);
const days = Math.floor(hours / 24);
if (days > 0) {
return `${days}d ${hours % 24}h ${minutes % 60}m ${seconds % 60}s`;
} else if (hours > 0) {
return `${hours}h ${minutes % 60}m ${seconds % 60}s`;
} else if (minutes > 0) {
return `${minutes}m ${seconds % 60}s`;
} else if (seconds > 0) {
return `${seconds}s`;
} else {
return `${durationMs}ms`;
}
}

View File

@ -60,7 +60,6 @@ function HTTPSProxy() {
}, },
[ [
noProxy, noProxy,
proxyEnabled,
proxyIgnoreSSL, proxyIgnoreSSL,
proxyPassword, proxyPassword,
proxyUrl, proxyUrl,

View File

@ -1,12 +1,14 @@
import { createFileRoute } from '@tanstack/react-router' import { createFileRoute } from '@tanstack/react-router'
import { useEffect } from 'react' import { useEffect, useState } from 'react'
import { useHardware } from '@/hooks/useHardware' import { useHardware } from '@/hooks/useHardware'
import { getHardwareInfo } from '@/services/hardware' import { getHardwareInfo } from '@/services/hardware'
import { Progress } from '@/components/ui/progress' import { Progress } from '@/components/ui/progress'
import type { HardwareData } from '@/hooks/useHardware' import type { HardwareData } from '@/hooks/useHardware'
import { route } from '@/constants/routes' import { route } from '@/constants/routes'
import { formatMegaBytes } from '@/lib/utils' import { formatDuration, formatMegaBytes } from '@/lib/utils'
import { IconDeviceDesktopAnalytics } from '@tabler/icons-react' import { IconDeviceDesktopAnalytics } from '@tabler/icons-react'
import { getActiveModels } from '@/services/models'
import { ActiveModel } from '@/types/models'
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
export const Route = createFileRoute(route.systemMonitor as any)({ export const Route = createFileRoute(route.systemMonitor as any)({
@ -16,24 +18,27 @@ export const Route = createFileRoute(route.systemMonitor as any)({
function SystemMonitor() { function SystemMonitor() {
const { hardwareData, setHardwareData, updateCPUUsage, updateRAMAvailable } = const { hardwareData, setHardwareData, updateCPUUsage, updateRAMAvailable } =
useHardware() useHardware()
const [activeModels, setActiveModels] = useState<ActiveModel[]>([])
useEffect(() => { useEffect(() => {
// Initial data fetch // Initial data fetch
getHardwareInfo().then((data) => { getHardwareInfo().then((data) => {
setHardwareData(data as unknown as HardwareData) setHardwareData(data as unknown as HardwareData)
}) })
getActiveModels().then(setActiveModels)
// Set up interval for real-time updates // Set up interval for real-time updates
const intervalId = setInterval(() => { const intervalId = setInterval(() => {
getHardwareInfo().then((data) => { getHardwareInfo().then((data) => {
setHardwareData(data as unknown as HardwareData) setHardwareData(data as unknown as HardwareData)
updateCPUUsage(data.cpu.usage) updateCPUUsage(data.cpu?.usage)
updateRAMAvailable(data.ram.available) updateRAMAvailable(data.ram?.available)
}) })
getActiveModels().then(setActiveModels)
}, 5000) }, 5000)
return () => clearInterval(intervalId) return () => clearInterval(intervalId)
}, [setHardwareData, updateCPUUsage, updateRAMAvailable]) }, [setHardwareData, setActiveModels, updateCPUUsage, updateRAMAvailable])
// Calculate RAM usage percentage // Calculate RAM usage percentage
const ramUsagePercentage = const ramUsagePercentage =
@ -125,20 +130,32 @@ function SystemMonitor() {
{/* Current Active Model Section */} {/* Current Active Model Section */}
<div className="mt-6 bg-main-view-fg/2 rounded-lg p-6 shadow-sm"> <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"> <h2 className="text-base font-semibold text-main-view-fg mb-4">
Current Active Model Running Models
</h2> </h2>
<div className="bg-main-view-fg/3 rounded-lg p-4"> {activeModels.length === 0 && (
<div className="text-center text-main-view-fg/50 py-4">
No models are currently running
</div>
)}
{activeModels.length > 0 && (
<div className="flex flex-col gap-4">
{activeModels.map((model) => (
<div className="bg-main-view-fg/3 rounded-lg p-4" key={model.id}>
<div className="flex justify-between items-center mb-2"> <div className="flex justify-between items-center mb-2">
<span className="font-semibold text-main-view-fg">GPT-4o</span> <span className="font-semibold text-main-view-fg">
{model.id}
</span>
</div> </div>
<div className="flex flex-col gap-2 mt-3"> <div className="flex flex-col gap-2 mt-3">
<div className="flex justify-between items-center"> <div className="flex justify-between items-center">
<span className="text-main-view-fg/70">Provider</span> <span className="text-main-view-fg/70">Provider</span>
<span className="text-main-view-fg">OpenAI</span> <span className="text-main-view-fg">llama.cpp</span>
</div> </div>
<div className="flex justify-between items-center"> <div className="flex justify-between items-center">
<span className="text-main-view-fg/70">Context Length</span> <span className="text-main-view-fg/70">Uptime</span>
<span className="text-main-view-fg">128K tokens</span> <span className="text-main-view-fg">
{formatDuration(model.start_time)}
</span>
</div> </div>
<div className="flex justify-between items-center"> <div className="flex justify-between items-center">
<span className="text-main-view-fg/70">Status</span> <span className="text-main-view-fg/70">Status</span>
@ -150,6 +167,9 @@ function SystemMonitor() {
</div> </div>
</div> </div>
</div> </div>
))}
</div>
)}
</div> </div>
{/* Active GPUs Section */} {/* Active GPUs Section */}

View File

@ -1,5 +1,5 @@
import { ExtensionManager } from '@/lib/extension' import { ExtensionManager } from '@/lib/extension'
import { ExtensionTypeEnum, ModelExtension } from '@janhq/core' import { EngineManager, ExtensionTypeEnum, ModelExtension } from '@janhq/core'
import { Model as CoreModel } from '@janhq/core' import { Model as CoreModel } from '@janhq/core'
/** /**
@ -204,6 +204,28 @@ export const importModel = async (
} }
} }
/**
* Gets the active models for a given provider.
* @param provider
* @returns
*/
export const getActiveModels = async (provider?: string) => {
const providerName = provider || 'cortex' // we will go down to llama.cpp extension later on
const extension = EngineManager.instance().get(providerName)
if (!extension) throw new Error('Model extension not found')
try {
return 'activeModels' in extension &&
typeof extension.activeModels === 'function'
? ((await extension.activeModels()) ?? [])
: []
} catch (error) {
console.error('Failed to get active models:', error)
return []
}
}
/** /**
* Configures the proxy options for model downloads. * Configures the proxy options for model downloads.
* @param param0 * @param param0

View File

@ -14,3 +14,13 @@ export enum ModelCapabilities {
TEXT_TO_AUDIO = 'text_to_audio', TEXT_TO_AUDIO = 'text_to_audio',
AUDIO_TO_TEXT = 'audio_to_text', AUDIO_TO_TEXT = 'audio_to_text',
} }
export type ActiveModel = {
engine: string
id: string
model_size: number
object: 'model'
ram: number
start_time: number
vram: number
}