diff --git a/web-app/src/hooks/useHardware.ts b/web-app/src/hooks/useHardware.ts index f59fcd7f5..da45cd523 100644 --- a/web-app/src/hooks/useHardware.ts +++ b/web-app/src/hooks/useHardware.ts @@ -184,10 +184,10 @@ export const useHardware = create()( set({ hardwareData: { ...data, - gpus: data.gpus.map(gpu => ({ + gpus: data.gpus.map((gpu) => ({ ...gpu, - activated: gpu.activated ?? false - })) + activated: gpu.activated ?? false, + })), }, }), @@ -195,49 +195,50 @@ export const useHardware = create()( set((state) => { // If we have existing GPU data, preserve the order and activation state if (state.hardwareData.gpus.length > 0) { - // Reorder fresh GPU data to match existing order, adding new GPUs at the end const reorderedGpus: GPU[] = [] const processedUuids = new Set() - + // First, add existing GPUs in their current order, preserving activation state - state.hardwareData.gpus.forEach(existingGpu => { - const freshGpu = data.gpus.find(gpu => gpu.uuid === existingGpu.uuid) + state.hardwareData.gpus.forEach((existingGpu) => { + const freshGpu = data.gpus.find( + (gpu) => gpu.uuid === existingGpu.uuid + ) if (freshGpu) { reorderedGpus.push({ ...freshGpu, - activated: existingGpu.activated ?? false + activated: existingGpu.activated ?? false, }) processedUuids.add(freshGpu.uuid) } }) - + // Then, add any new GPUs that weren't in the existing order (default to inactive) - data.gpus.forEach(freshGpu => { + data.gpus.forEach((freshGpu) => { if (!processedUuids.has(freshGpu.uuid)) { reorderedGpus.push({ ...freshGpu, - activated: false + activated: false, }) } }) - + return { hardwareData: { ...data, - gpus: reorderedGpus - } + gpus: reorderedGpus, + }, } } else { // No existing GPU data, initialize all GPUs as inactive return { hardwareData: { ...data, - gpus: data.gpus.map(gpu => ({ + gpus: data.gpus.map((gpu) => ({ ...gpu, - activated: false - })) - } + activated: false, + })), + }, } } }), @@ -265,10 +266,10 @@ export const useHardware = create()( const { pausePolling, resumePolling, setGpuLoading } = get() pausePolling() setGpuLoading(index, true) - + try { await new Promise((resolve) => setTimeout(resolve, 200)) // Simulate async operation - + set((state) => { const newGPUs = [...state.hardwareData.gpus] if (index >= 0 && index < newGPUs.length) { @@ -277,7 +278,7 @@ export const useHardware = create()( activated: !newGPUs[index].activated, } } - + return { hardwareData: { ...state.hardwareData, @@ -285,48 +286,41 @@ export const useHardware = create()( }, } }) - + // Update the device setting after state change const updatedState = get() - + // Import and get backend type const { useModelProvider } = await import('./useModelProvider') - const { updateProvider, getProviderByName } = useModelProvider.getState() - + const { updateProvider, getProviderByName } = + useModelProvider.getState() + const llamacppProvider = getProviderByName('llamacpp') - const backendType = llamacppProvider?.settings.find(s => s.key === 'version_backend')?.controller_props.value as string - - const deviceString = updatedState.getActivatedDeviceString(backendType) - - console.log(`GPU ${index} activation toggled. Backend: "${backendType}", New device string: "${deviceString}"`) - console.log('Activated GPUs:', updatedState.hardwareData.gpus.filter(gpu => gpu.activated).map((gpu, i) => ({ - name: gpu.name, - nvidia: gpu.nvidia_info?.index, - vulkan: gpu.vulkan_info?.index, - activated: gpu.activated - }))) - + const backendType = llamacppProvider?.settings.find( + (s) => s.key === 'version_backend' + )?.controller_props.value as string + + const deviceString = + updatedState.getActivatedDeviceString(backendType) + if (llamacppProvider) { - const updatedSettings = llamacppProvider.settings.map(setting => { + const updatedSettings = llamacppProvider.settings.map((setting) => { if (setting.key === 'device') { return { ...setting, controller_props: { ...setting.controller_props, - value: deviceString - } + value: deviceString, + }, } } return setting }) - + updateProvider('llamacpp', { - settings: updatedSettings + settings: updatedSettings, }) - - console.log(`Updated llamacpp device setting to: "${deviceString}"`) } - } finally { setGpuLoading(index, false) setTimeout(resumePolling, 1000) // Resume polling after 1s @@ -356,14 +350,14 @@ export const useHardware = create()( getActivatedDeviceString: (backendType?: string) => { const { hardwareData } = get() - + // Get activated GPUs and generate appropriate device format based on backend const activatedDevices = hardwareData.gpus - .filter(gpu => gpu.activated) - .map(gpu => { + .filter((gpu) => gpu.activated) + .map((gpu) => { const isCudaBackend = backendType?.includes('cuda') const isVulkanBackend = backendType?.includes('vulkan') - + // Handle different backend scenarios if (isCudaBackend && isVulkanBackend) { // Mixed backend - prefer CUDA for NVIDIA GPUs, Vulkan for others @@ -388,8 +382,8 @@ export const useHardware = create()( } return null }) - .filter(device => device !== null) as string[] - + .filter((device) => device !== null) as string[] + const deviceString = activatedDevices.join(',') return deviceString }, @@ -397,27 +391,30 @@ export const useHardware = create()( updateGPUActivationFromDeviceString: (deviceString: string) => { set((state) => { const newGPUs = [...state.hardwareData.gpus] - + // Parse device string to get active device indices const activeDevices = deviceString .split(',') - .map(device => device.trim()) - .filter(device => device.length > 0) - .map(device => { + .map((device) => device.trim()) + .filter((device) => device.length > 0) + .map((device) => { const match = device.match(/^(cuda|vulkan):(\d+)$/) if (match) { return { type: match[1] as 'cuda' | 'vulkan', - index: parseInt(match[2]) + index: parseInt(match[2]), } } return null }) - .filter(device => device !== null) as Array<{type: 'cuda' | 'vulkan', index: number}> - + .filter((device) => device !== null) as Array<{ + type: 'cuda' | 'vulkan' + index: number + }> + // Update GPU activation states newGPUs.forEach((gpu, gpuIndex) => { - const shouldBeActive = activeDevices.some(device => { + const shouldBeActive = activeDevices.some((device) => { if (device.type === 'cuda' && gpu.nvidia_info) { return gpu.nvidia_info.index === device.index } else if (device.type === 'vulkan' && gpu.vulkan_info) { @@ -425,18 +422,18 @@ export const useHardware = create()( } return false }) - + newGPUs[gpuIndex] = { ...gpu, - activated: shouldBeActive + activated: shouldBeActive, } }) - + return { hardwareData: { ...state.hardwareData, - gpus: newGPUs - } + gpus: newGPUs, + }, } }) }, diff --git a/web-app/src/routes/hub/$modelId.tsx b/web-app/src/routes/hub/$modelId.tsx index 2312f8e40..245909174 100644 --- a/web-app/src/routes/hub/$modelId.tsx +++ b/web-app/src/routes/hub/$modelId.tsx @@ -10,7 +10,7 @@ import { route } from '@/constants/routes' import { useModelSources } from '@/hooks/useModelSources' import { extractModelName, extractDescription } from '@/lib/models' import { RenderMarkdown } from '@/containers/RenderMarkdown' -import { useEffect, useMemo, useCallback } from 'react' +import { useEffect, useMemo, useCallback, useState } from 'react' import { useModelProvider } from '@/hooks/useModelProvider' import { useDownloadStore } from '@/hooks/useDownloadStore' import { pullModel } from '@/services/models' @@ -31,6 +31,10 @@ function HubModelDetail() { const { downloads, localDownloadingModels, addLocalDownloadingModel } = useDownloadStore() + // State for README content + const [readmeContent, setReadmeContent] = useState('') + const [isLoadingReadme, setIsLoadingReadme] = useState(false) + useEffect(() => { fetchSources() }, [fetchSources]) @@ -112,6 +116,24 @@ function HubModelDetail() { }) }, [modelData]) + + // Fetch README content when modelData.readme is available + useEffect(() => { + if (modelData?.readme) { + setIsLoadingReadme(true) + fetch(modelData.readme) + .then((response) => response.text()) + .then((content) => { + setReadmeContent(content) + setIsLoadingReadme(false) + }) + .catch((error) => { + console.error('Failed to fetch README:', error) + setIsLoadingReadme(false) + }) + } + }, [modelData?.readme]) + if (!modelData) { return (
@@ -358,6 +380,49 @@ function HubModelDetail() {
)} + + {/* README Section */} + {modelData.readme && ( +
+
+ +

+ README +

+
+ + {isLoadingReadme ? ( +
+ + Loading README... + +
+ ) : readmeContent ? ( + + ) : ( +
+ + Failed to load README + +
+ )} +
+ )} diff --git a/web-app/src/routes/system-monitor.tsx b/web-app/src/routes/system-monitor.tsx index be1dfff19..885c1f5a9 100644 --- a/web-app/src/routes/system-monitor.tsx +++ b/web-app/src/routes/system-monitor.tsx @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import { createFileRoute } from '@tanstack/react-router' import { useEffect, useState } from 'react' import { useHardware } from '@/hooks/useHardware' @@ -13,22 +14,28 @@ import { useTranslation } from '@/i18n/react-i18next-compat' import { toNumber } from '@/utils/number' import { useModelProvider } from '@/hooks/useModelProvider' -// eslint-disable-next-line @typescript-eslint/no-explicit-any export const Route = createFileRoute(route.systemMonitor as any)({ component: SystemMonitor, }) function SystemMonitor() { const { t } = useTranslation() - const { hardwareData, systemUsage, updateHardwareDataPreservingGpuOrder, updateSystemUsage, updateGPUActivationFromDeviceString } = - useHardware() + const { + hardwareData, + systemUsage, + updateHardwareDataPreservingGpuOrder, + updateSystemUsage, + updateGPUActivationFromDeviceString, + } = useHardware() const [activeModels, setActiveModels] = useState([]) const { providers, getProviderByName } = useModelProvider() const [isInitialized, setIsInitialized] = useState(false) // Determine backend type and filter GPUs accordingly (same logic as hardware.tsx) const llamacpp = providers.find((p) => p.provider === 'llamacpp') - const versionBackend = llamacpp?.settings.find((s) => s.key === "version_backend")?.controller_props.value + const versionBackend = llamacpp?.settings.find( + (s) => s.key === 'version_backend' + )?.controller_props.value useEffect(() => { // Initial data fetch - use updateHardwareDataPreservingGpuOrder like hardware.tsx @@ -52,53 +59,74 @@ function SystemMonitor() { useEffect(() => { if (hardwareData.gpus.length > 0 && !isInitialized) { const llamacppProvider = getProviderByName('llamacpp') - const currentDeviceSetting = llamacppProvider?.settings.find(s => s.key === 'device')?.controller_props.value as string - + const currentDeviceSetting = llamacppProvider?.settings.find( + (s) => s.key === 'device' + )?.controller_props.value as string + if (currentDeviceSetting) { updateGPUActivationFromDeviceString(currentDeviceSetting) } - + setIsInitialized(true) } - }, [hardwareData.gpus.length, isInitialized, getProviderByName, updateGPUActivationFromDeviceString]) + }, [ + hardwareData.gpus.length, + isInitialized, + getProviderByName, + updateGPUActivationFromDeviceString, + ]) // Sync device setting when GPU activations change (only after initialization) - same logic as hardware.tsx const { getActivatedDeviceString } = useHardware() const { updateProvider } = useModelProvider() - const gpuActivationStates = hardwareData.gpus.map(gpu => gpu.activated) - + const gpuActivationStates = hardwareData.gpus.map((gpu) => gpu.activated) + useEffect(() => { if (isInitialized && hardwareData.gpus.length > 0) { const llamacppProvider = getProviderByName('llamacpp') - const backendType = llamacppProvider?.settings.find(s => s.key === 'version_backend')?.controller_props.value as string + const backendType = llamacppProvider?.settings.find( + (s) => s.key === 'version_backend' + )?.controller_props.value as string const deviceString = getActivatedDeviceString(backendType) - + if (llamacppProvider) { - const currentDeviceSetting = llamacppProvider.settings.find(s => s.key === 'device') - + const currentDeviceSetting = llamacppProvider.settings.find( + (s) => s.key === 'device' + ) + // Sync device string when GPU activations change (only after initialization) - if (currentDeviceSetting && currentDeviceSetting.controller_props.value !== deviceString) { - - const updatedSettings = llamacppProvider.settings.map(setting => { + if ( + currentDeviceSetting && + currentDeviceSetting.controller_props.value !== deviceString + ) { + const updatedSettings = llamacppProvider.settings.map((setting) => { if (setting.key === 'device') { return { ...setting, controller_props: { ...setting.controller_props, - value: deviceString - } + value: deviceString, + }, } } return setting }) - + updateProvider('llamacpp', { - settings: updatedSettings + settings: updatedSettings, }) } } } - }, [isInitialized, gpuActivationStates, versionBackend, getActivatedDeviceString, updateProvider, getProviderByName, hardwareData.gpus.length]) + }, [ + isInitialized, + gpuActivationStates, + versionBackend, + getActivatedDeviceString, + updateProvider, + getProviderByName, + hardwareData.gpus.length, + ]) const stopRunningModel = (modelId: string) => { stopModel(modelId) @@ -120,8 +148,10 @@ function SystemMonitor() { ) * 100 // Determine backend type and filter GPUs accordingly - const isCudaBackend = typeof versionBackend === 'string' && versionBackend.includes('cuda') - const isVulkanBackend = typeof versionBackend === 'string' && versionBackend.includes('vulkan') + const isCudaBackend = + typeof versionBackend === 'string' && versionBackend.includes('cuda') + const isVulkanBackend = + typeof versionBackend === 'string' && versionBackend.includes('vulkan') // Check if GPU should be active based on backend compatibility const isGPUCompatible = (gpu: any) => { @@ -144,7 +174,7 @@ function SystemMonitor() { } // Filter to show only active GPUs - const activeGPUs = hardwareData.gpus.filter(gpu => isGPUActive(gpu)) + const activeGPUs = hardwareData.gpus.filter((gpu) => isGPUActive(gpu)) return (
@@ -313,9 +343,10 @@ function SystemMonitor() {
{activeGPUs.map((gpu, index) => { // Find the corresponding system usage data for this GPU - const gpuUsage = systemUsage.gpus.find(usage => usage.uuid === gpu.uuid) - const gpuIndex = hardwareData.gpus.findIndex(hwGpu => hwGpu.uuid === gpu.uuid) - + const gpuUsage = systemUsage.gpus.find( + (usage) => usage.uuid === gpu.uuid + ) + return (
{gpuUsage ? ( <> - {formatMegaBytes(gpuUsage.used_memory)}{' '} - / {formatMegaBytes(gpu.total_memory)} + {formatMegaBytes(gpuUsage.used_memory)} /{' '} + {formatMegaBytes(gpu.total_memory)} ) : ( <> - {formatMegaBytes(0)}{' '} - / {formatMegaBytes(gpu.total_memory)} + {formatMegaBytes(0)} /{' '} + {formatMegaBytes(gpu.total_memory)} )} @@ -362,14 +393,16 @@ function SystemMonitor() { {gpu.nvidia_info?.compute_capability || - gpu.vulkan_info?.api_version || '-'} + gpu.vulkan_info?.api_version || + '-'}
@@ -385,7 +418,6 @@ function SystemMonitor() {
)}
-
) } diff --git a/web-app/src/services/models.ts b/web-app/src/services/models.ts index 264b7f8c3..4b394b824 100644 --- a/web-app/src/services/models.ts +++ b/web-app/src/services/models.ts @@ -20,6 +20,7 @@ export interface CatalogModel { num_quants: number quants: ModelQuant[] created_at?: string + readme?: string } export type ModelCatalog = CatalogModel[]