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)
|
||||
|
||||
const loadedModels = await this.apiInstance()
|
||||
.then((e) => e.get('inferences/server/models'))
|
||||
.then((e) => e.json())
|
||||
.then((e) => (e as LoadedModelResponse).data ?? [])
|
||||
.catch(() => [])
|
||||
const loadedModels = await this.activeModels()
|
||||
|
||||
console.log('Loaded models:', loadedModels)
|
||||
|
||||
// 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`)
|
||||
return
|
||||
}
|
||||
@ -216,8 +212,8 @@ export default class JanInferenceCortexExtension extends LocalOAIEngine {
|
||||
...extractModelLoadParams(model.settings),
|
||||
model: model.id,
|
||||
engine:
|
||||
model.engine === "nitro" // Legacy model cache
|
||||
? "llama-cpp"
|
||||
model.engine === 'nitro' // Legacy model cache
|
||||
? 'llama-cpp'
|
||||
: model.engine,
|
||||
cont_batching: this.cont_batching,
|
||||
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
|
||||
* @returns
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
use reqwest::blocking::Client;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{fs, path::PathBuf, io};
|
||||
use std::{fs, io, path::PathBuf};
|
||||
use tauri::{AppHandle, Manager, Runtime, State};
|
||||
use tauri_plugin_updater::UpdaterExt;
|
||||
|
||||
@ -273,9 +273,15 @@ pub fn get_active_extensions(app: AppHandle) -> Vec<serde_json::Value> {
|
||||
})
|
||||
})
|
||||
.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;
|
||||
}
|
||||
@ -315,13 +321,13 @@ pub fn change_app_data_folder(
|
||||
// Get current data folder path
|
||||
let current_data_folder = get_jan_data_folder_path(app_handle.clone());
|
||||
let new_data_folder_path = PathBuf::from(&new_data_folder);
|
||||
|
||||
|
||||
// Create the new data folder if it doesn't exist
|
||||
if !new_data_folder_path.exists() {
|
||||
fs::create_dir_all(&new_data_folder_path)
|
||||
.map_err(|e| format!("Failed to create new data folder: {}", e))?;
|
||||
}
|
||||
|
||||
|
||||
// Copy all files from the old folder to the new one
|
||||
if current_data_folder.exists() {
|
||||
log::info!(
|
||||
@ -329,17 +335,17 @@ pub fn change_app_data_folder(
|
||||
current_data_folder,
|
||||
new_data_folder_path
|
||||
);
|
||||
|
||||
|
||||
copy_dir_recursive(¤t_data_folder, &new_data_folder_path)
|
||||
.map_err(|e| format!("Failed to copy data to new folder: {}", e))?;
|
||||
} else {
|
||||
log::info!("Current data folder does not exist, nothing to copy");
|
||||
}
|
||||
|
||||
|
||||
// Update the configuration to point to the new folder
|
||||
let mut configuration = get_app_configurations(app_handle.clone());
|
||||
configuration.data_folder = new_data_folder;
|
||||
|
||||
|
||||
// Save the updated configuration
|
||||
update_app_configuration(app_handle, configuration)
|
||||
}
|
||||
|
||||
@ -146,3 +146,29 @@ export function formatMegaBytes(mb: number) {
|
||||
export function isDev() {
|
||||
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,
|
||||
proxyEnabled,
|
||||
proxyIgnoreSSL,
|
||||
proxyPassword,
|
||||
proxyUrl,
|
||||
|
||||
@ -1,12 +1,14 @@
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
import { useEffect } from 'react'
|
||||
import { useEffect, useState } 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 { formatDuration, formatMegaBytes } from '@/lib/utils'
|
||||
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
|
||||
export const Route = createFileRoute(route.systemMonitor as any)({
|
||||
@ -16,24 +18,27 @@ export const Route = createFileRoute(route.systemMonitor as any)({
|
||||
function SystemMonitor() {
|
||||
const { hardwareData, setHardwareData, updateCPUUsage, updateRAMAvailable } =
|
||||
useHardware()
|
||||
const [activeModels, setActiveModels] = useState<ActiveModel[]>([])
|
||||
|
||||
useEffect(() => {
|
||||
// Initial data fetch
|
||||
getHardwareInfo().then((data) => {
|
||||
setHardwareData(data as unknown as HardwareData)
|
||||
})
|
||||
getActiveModels().then(setActiveModels)
|
||||
|
||||
// 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)
|
||||
updateCPUUsage(data.cpu?.usage)
|
||||
updateRAMAvailable(data.ram?.available)
|
||||
})
|
||||
getActiveModels().then(setActiveModels)
|
||||
}, 5000)
|
||||
|
||||
return () => clearInterval(intervalId)
|
||||
}, [setHardwareData, updateCPUUsage, updateRAMAvailable])
|
||||
}, [setHardwareData, setActiveModels, updateCPUUsage, updateRAMAvailable])
|
||||
|
||||
// Calculate RAM usage percentage
|
||||
const ramUsagePercentage =
|
||||
@ -125,31 +130,46 @@ function SystemMonitor() {
|
||||
{/* 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
|
||||
Running Models
|
||||
</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>
|
||||
{activeModels.length === 0 && (
|
||||
<div className="text-center text-main-view-fg/50 py-4">
|
||||
No models are currently running
|
||||
</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
|
||||
)}
|
||||
{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">
|
||||
<span className="font-semibold text-main-view-fg">
|
||||
{model.id}
|
||||
</span>
|
||||
</div>
|
||||
</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">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>
|
||||
|
||||
{/* Active GPUs Section */}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
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'
|
||||
|
||||
/**
|
||||
@ -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.
|
||||
* @param param0
|
||||
|
||||
@ -4,13 +4,23 @@
|
||||
* @enum {string}
|
||||
*/
|
||||
export enum ModelCapabilities {
|
||||
COMPLETION = 'completion',
|
||||
TOOLS = 'tools',
|
||||
EMBEDDINGS = 'embeddings',
|
||||
IMAGE_GENERATION = 'image_generation',
|
||||
AUDIO_GENERATION = 'audio_generation',
|
||||
TEXT_TO_IMAGE = 'text_to_image',
|
||||
IMAGE_TO_IMAGE = 'image_to_image',
|
||||
TEXT_TO_AUDIO = 'text_to_audio',
|
||||
AUDIO_TO_TEXT = 'audio_to_text',
|
||||
}
|
||||
COMPLETION = 'completion',
|
||||
TOOLS = 'tools',
|
||||
EMBEDDINGS = 'embeddings',
|
||||
IMAGE_GENERATION = 'image_generation',
|
||||
AUDIO_GENERATION = 'audio_generation',
|
||||
TEXT_TO_IMAGE = 'text_to_image',
|
||||
IMAGE_TO_IMAGE = 'image_to_image',
|
||||
TEXT_TO_AUDIO = 'text_to_audio',
|
||||
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