import { createFileRoute } from '@tanstack/react-router' import { route } from '@/constants/routes' import HeaderPage from '@/containers/HeaderPage' import SettingsMenu from '@/containers/SettingsMenu' import { Card, CardItem } from '@/containers/Card' import { Switch } from '@/components/ui/switch' import { Button } from '@/components/ui/button' import { useTranslation } from '@/i18n/react-i18next-compat' import { ServerHostSwitcher } from '@/containers/ServerHostSwitcher' import { PortInput } from '@/containers/PortInput' import { ProxyTimeoutInput } from '@/containers/ProxyTimeoutInput' import { ApiPrefixInput } from '@/containers/ApiPrefixInput' import { TrustedHostsInput } from '@/containers/TrustedHostsInput' import { useLocalApiServer } from '@/hooks/useLocalApiServer' import { useAppState } from '@/hooks/useAppState' import { useModelProvider } from '@/hooks/useModelProvider' import { useServiceHub } from '@/hooks/useServiceHub' import { IconLogs } from '@tabler/icons-react' import { cn } from '@/lib/utils' import { ApiKeyInput } from '@/containers/ApiKeyInput' import { useEffect, useState } from 'react' import { PlatformGuard } from '@/lib/platform/PlatformGuard' import { PlatformFeature } from '@/lib/platform' import { toast } from 'sonner' import { getModelToStart } from '@/utils/getModelToStart' // eslint-disable-next-line @typescript-eslint/no-explicit-any export const Route = createFileRoute(route.settings.local_api_server as any)({ component: LocalAPIServer, }) function LocalAPIServer() { return ( ) } function LocalAPIServerContent() { const { t } = useTranslation() const serviceHub = useServiceHub() const { corsEnabled, setCorsEnabled, verboseLogs, setVerboseLogs, enableOnStartup, setEnableOnStartup, serverHost, serverPort, setServerPort, apiPrefix, apiKey, trustedHosts, proxyTimeout, } = useLocalApiServer() const { serverStatus, setServerStatus } = useAppState() const { selectedModel, selectedProvider, getProviderByName } = useModelProvider() const [showApiKeyError, setShowApiKeyError] = useState(false) const [isApiKeyEmpty, setIsApiKeyEmpty] = useState( !apiKey || apiKey.toString().trim().length === 0 ) useEffect(() => { const checkServerStatus = async () => { serviceHub .app() .getServerStatus() .then((running) => { if (running) { setServerStatus('running') } }) } checkServerStatus() }, [serviceHub, setServerStatus]) const handleApiKeyValidation = (isValid: boolean) => { setIsApiKeyEmpty(!isValid) } const [isModelLoading, setIsModelLoading] = useState(false) const toggleAPIServer = async () => { // Validate API key before starting server if (serverStatus === 'stopped') { console.log('Starting server with port:', serverPort) toast.info('Starting server...', { description: `Attempting to start server on port ${serverPort}`, }) if (!apiKey || apiKey.toString().trim().length === 0) { setShowApiKeyError(true) return } setShowApiKeyError(false) setServerStatus('pending') // Check if there's already a loaded model serviceHub .models() .getActiveModels() .then((loadedModels) => { if (loadedModels && loadedModels.length > 0) { console.log(`Using already loaded model: ${loadedModels[0]}`) // Model already loaded, just start the server return Promise.resolve() } else { // No loaded model, start one first const modelToStart = getModelToStart({ selectedModel, selectedProvider, getProviderByName, }) // Only start server if we have a model to load if (!modelToStart) { console.warn( 'Cannot start Local API Server: No model available to load' ) throw new Error('No model available to load') } setIsModelLoading(true) // Start loading state // Start the model first return serviceHub .models() .startModel(modelToStart.provider, modelToStart.model) .then(() => { console.log(`Model ${modelToStart.model} started successfully`) setIsModelLoading(false) // Model loaded, stop loading state // Add a small delay for the backend to update state return new Promise((resolve) => setTimeout(resolve, 500)) }) } }) .then(() => { // Then start the server return window.core?.api?.startServer({ host: serverHost, port: serverPort, prefix: apiPrefix, apiKey, trustedHosts, isCorsEnabled: corsEnabled, isVerboseEnabled: verboseLogs, proxyTimeout: proxyTimeout, }) }) .then((actualPort: number) => { // Store the actual port that was assigned (important for mobile with port 0) if (actualPort && actualPort !== serverPort) { setServerPort(actualPort) } setServerStatus('running') }) .catch((error: unknown) => { console.error('Error starting server or model:', error) setServerStatus('stopped') setIsModelLoading(false) // Reset loading state on error toast.dismiss() // Extract error message from various error formats const errorMsg = error && typeof error === 'object' && 'message' in error ? String(error.message) : String(error) // Port-related errors (highest priority) if (errorMsg.includes('Address already in use')) { toast.error('Port has been occupied', { description: `Port ${serverPort} is already in use. Please try a different port.`, }) } // Model-related errors else if (errorMsg.includes('Invalid or inaccessible model path')) { toast.error('Invalid or inaccessible model path', { description: errorMsg, }) } else if (errorMsg.includes('model')) { toast.error('Failed to start model', { description: errorMsg, }) } // Generic server errors else { toast.error('Failed to start server', { description: errorMsg, }) } }) } else { setServerStatus('pending') window.core?.api ?.stopServer() .then(() => { setServerStatus('stopped') }) .catch((error: unknown) => { console.error('Error stopping server:', error) setServerStatus('stopped') }) } } const getButtonText = () => { if (isModelLoading) { return t('settings:localApiServer.loadingModel') // TODO: Update this translation } if (serverStatus === 'pending' && !isModelLoading) { return t('settings:localApiServer.startingServer') // TODO: Update this translation } return isServerRunning ? t('settings:localApiServer.stopServer') : t('settings:localApiServer.startServer') } const handleOpenLogs = async () => { try { await serviceHub.window().openLocalApiServerLogsWindow() } catch (error) { console.error('Failed to open logs window:', error) } } const isServerRunning = serverStatus !== 'stopped' return (

{t('common:settings')}

{/* General Settings */}

{t('settings:localApiServer.title')}

{t('settings:localApiServer.description')}

} > { if (!apiKey || apiKey.toString().trim().length === 0) { setShowApiKeyError(true) return } setEnableOnStartup(checked) }} /> } />
{t('settings:localApiServer.openLogs')}
} /> } /> {/* Server Configuration */} } /> } /> } /> } /> } /> } /> {/* Advanced Settings */} } /> } />
) }