feat: local providers refactor & enhancement (#1968)

* feat: improve local providers health monitoring and model management

- Add automatic health monitoring initialization for enabled providers
- Add LM Studio model management and display functionality
- Fix endpoint status detection by setting default base URLs
- Replace mixed icon libraries with consistent Lucide icons only
- Fix button styling with transparent backgrounds
- Add comprehensive setup guides with web-researched content
- Add proper navigation with back buttons between views
- Fix all TypeScript and linting issues in LocalProvidersTab

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Remove Service Status tab and related code

The Service Status tab and all associated files, components, and provider checkers have been deleted. References to 'service-status' have been removed from tab constants, types, and the control panel. This simplifies the settings UI and codebase by eliminating the service status monitoring feature.

* Update LocalProvidersTab.tsx

* Fix all linter and TypeScript errors in local providers components

- Remove unused imports and fix import formatting
- Fix type-only imports for OllamaModel and LMStudioModel
- Fix Icon component usage in ProviderCard.tsx
- Clean up unused imports across all local provider files
- Ensure all TypeScript and ESLint checks pass

---------

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Stijnus
2025-09-06 19:03:25 +02:00
committed by GitHub
parent 3ea96506ea
commit a44de8addc
37 changed files with 2794 additions and 3706 deletions

View File

@@ -0,0 +1,165 @@
import { useState, useEffect, useCallback } from 'react';
import { localModelHealthMonitor, type ModelHealthStatus } from '~/lib/services/localModelHealthMonitor';
export interface UseLocalModelHealthOptions {
autoStart?: boolean;
checkInterval?: number;
}
export interface UseLocalModelHealthReturn {
healthStatuses: ModelHealthStatus[];
getHealthStatus: (provider: 'Ollama' | 'LMStudio' | 'OpenAILike', baseUrl: string) => ModelHealthStatus | undefined;
startMonitoring: (provider: 'Ollama' | 'LMStudio' | 'OpenAILike', baseUrl: string, checkInterval?: number) => void;
stopMonitoring: (provider: 'Ollama' | 'LMStudio' | 'OpenAILike', baseUrl: string) => void;
performHealthCheck: (provider: 'Ollama' | 'LMStudio' | 'OpenAILike', baseUrl: string) => Promise<void>;
isHealthy: (provider: 'Ollama' | 'LMStudio' | 'OpenAILike', baseUrl: string) => boolean;
getOverallHealth: () => { healthy: number; unhealthy: number; checking: number; unknown: number };
}
/**
* React hook for monitoring local model health
*/
export function useLocalModelHealth(options: UseLocalModelHealthOptions = {}): UseLocalModelHealthReturn {
const { checkInterval } = options;
const [healthStatuses, setHealthStatuses] = useState<ModelHealthStatus[]>([]);
// Update health statuses when they change
useEffect(() => {
const handleStatusChanged = (status: ModelHealthStatus) => {
setHealthStatuses((current) => {
const index = current.findIndex((s) => s.provider === status.provider && s.baseUrl === status.baseUrl);
if (index >= 0) {
const updated = [...current];
updated[index] = status;
return updated;
} else {
return [...current, status];
}
});
};
localModelHealthMonitor.on('statusChanged', handleStatusChanged);
// Initialize with current statuses
setHealthStatuses(localModelHealthMonitor.getAllHealthStatuses());
return () => {
localModelHealthMonitor.off('statusChanged', handleStatusChanged);
};
}, []);
// Get health status for a specific provider
const getHealthStatus = useCallback((provider: 'Ollama' | 'LMStudio' | 'OpenAILike', baseUrl: string) => {
return localModelHealthMonitor.getHealthStatus(provider, baseUrl);
}, []);
// Start monitoring a provider
const startMonitoring = useCallback(
(provider: 'Ollama' | 'LMStudio' | 'OpenAILike', baseUrl: string, interval?: number) => {
console.log(`[Health Monitor] Starting monitoring for ${provider} at ${baseUrl}`);
localModelHealthMonitor.startMonitoring(provider, baseUrl, interval || checkInterval);
},
[checkInterval],
);
// Stop monitoring a provider
const stopMonitoring = useCallback((provider: 'Ollama' | 'LMStudio' | 'OpenAILike', baseUrl: string) => {
console.log(`[Health Monitor] Stopping monitoring for ${provider} at ${baseUrl}`);
localModelHealthMonitor.stopMonitoring(provider, baseUrl);
// Remove from local state
setHealthStatuses((current) => current.filter((s) => !(s.provider === provider && s.baseUrl === baseUrl)));
}, []);
// Perform manual health check
const performHealthCheck = useCallback(async (provider: 'Ollama' | 'LMStudio' | 'OpenAILike', baseUrl: string) => {
await localModelHealthMonitor.performHealthCheck(provider, baseUrl);
}, []);
// Check if a provider is healthy
const isHealthy = useCallback(
(provider: 'Ollama' | 'LMStudio' | 'OpenAILike', baseUrl: string) => {
const status = getHealthStatus(provider, baseUrl);
return status?.status === 'healthy';
},
[getHealthStatus],
);
// Get overall health statistics
const getOverallHealth = useCallback(() => {
const stats = { healthy: 0, unhealthy: 0, checking: 0, unknown: 0 };
healthStatuses.forEach((status) => {
stats[status.status]++;
});
return stats;
}, [healthStatuses]);
return {
healthStatuses,
getHealthStatus,
startMonitoring,
stopMonitoring,
performHealthCheck,
isHealthy,
getOverallHealth,
};
}
/**
* Hook for monitoring a specific provider
*/
export function useProviderHealth(
provider: 'Ollama' | 'LMStudio' | 'OpenAILike',
baseUrl: string,
options: UseLocalModelHealthOptions = {},
) {
const { autoStart = true, checkInterval } = options;
const { getHealthStatus, startMonitoring, stopMonitoring, performHealthCheck, isHealthy } = useLocalModelHealth();
const [status, setStatus] = useState<ModelHealthStatus | undefined>();
// Update status when it changes
useEffect(() => {
const updateStatus = () => {
setStatus(getHealthStatus(provider, baseUrl));
};
const handleStatusChanged = (changedStatus: ModelHealthStatus) => {
if (changedStatus.provider === provider && changedStatus.baseUrl === baseUrl) {
setStatus(changedStatus);
}
};
localModelHealthMonitor.on('statusChanged', handleStatusChanged);
updateStatus();
return () => {
localModelHealthMonitor.off('statusChanged', handleStatusChanged);
};
}, [provider, baseUrl, getHealthStatus]);
// Auto-start monitoring if enabled
useEffect(() => {
if (autoStart && baseUrl) {
startMonitoring(provider, baseUrl, checkInterval);
return () => {
stopMonitoring(provider, baseUrl);
};
}
return undefined;
}, [autoStart, provider, baseUrl, checkInterval, startMonitoring, stopMonitoring]);
return {
status,
isHealthy: isHealthy(provider, baseUrl),
performHealthCheck: () => performHealthCheck(provider, baseUrl),
startMonitoring: (interval?: number) => startMonitoring(provider, baseUrl, interval),
stopMonitoring: () => stopMonitoring(provider, baseUrl),
};
}