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:
parent
4d66eaf0a7
commit
570bb8290f
@ -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
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -60,7 +60,6 @@ function HTTPSProxy() {
|
|||||||
},
|
},
|
||||||
[
|
[
|
||||||
noProxy,
|
noProxy,
|
||||||
proxyEnabled,
|
|
||||||
proxyIgnoreSSL,
|
proxyIgnoreSSL,
|
||||||
proxyPassword,
|
proxyPassword,
|
||||||
proxyUrl,
|
proxyUrl,
|
||||||
|
|||||||
@ -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,31 +130,46 @@ 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="flex justify-between items-center mb-2">
|
<div className="text-center text-main-view-fg/50 py-4">
|
||||||
<span className="font-semibold text-main-view-fg">GPT-4o</span>
|
No models are currently running
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-2 mt-3">
|
)}
|
||||||
<div className="flex justify-between items-center">
|
{activeModels.length > 0 && (
|
||||||
<span className="text-main-view-fg/70">Provider</span>
|
<div className="flex flex-col gap-4">
|
||||||
<span className="text-main-view-fg">OpenAI</span>
|
{activeModels.map((model) => (
|
||||||
</div>
|
<div className="bg-main-view-fg/3 rounded-lg p-4" key={model.id}>
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center mb-2">
|
||||||
<span className="text-main-view-fg/70">Context Length</span>
|
<span className="font-semibold text-main-view-fg">
|
||||||
<span className="text-main-view-fg">128K tokens</span>
|
{model.id}
|
||||||
</div>
|
</span>
|
||||||
<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>
|
</div>
|
||||||
</span>
|
<div className="flex flex-col gap-2 mt-3">
|
||||||
</div>
|
<div className="flex justify-between items-center">
|
||||||
|
<span className="text-main-view-fg/70">Provider</span>
|
||||||
|
<span className="text-main-view-fg">llama.cpp</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<span className="text-main-view-fg/70">Uptime</span>
|
||||||
|
<span className="text-main-view-fg">
|
||||||
|
{formatDuration(model.start_time)}
|
||||||
|
</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>
|
</div>
|
||||||
</div>
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Active GPUs Section */}
|
{/* Active GPUs Section */}
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -4,13 +4,23 @@
|
|||||||
* @enum {string}
|
* @enum {string}
|
||||||
*/
|
*/
|
||||||
export enum ModelCapabilities {
|
export enum ModelCapabilities {
|
||||||
COMPLETION = 'completion',
|
COMPLETION = 'completion',
|
||||||
TOOLS = 'tools',
|
TOOLS = 'tools',
|
||||||
EMBEDDINGS = 'embeddings',
|
EMBEDDINGS = 'embeddings',
|
||||||
IMAGE_GENERATION = 'image_generation',
|
IMAGE_GENERATION = 'image_generation',
|
||||||
AUDIO_GENERATION = 'audio_generation',
|
AUDIO_GENERATION = 'audio_generation',
|
||||||
TEXT_TO_IMAGE = 'text_to_image',
|
TEXT_TO_IMAGE = 'text_to_image',
|
||||||
IMAGE_TO_IMAGE = 'image_to_image',
|
IMAGE_TO_IMAGE = 'image_to_image',
|
||||||
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
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user