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 { ApiPrefixInput } from '@/containers/ApiPrefixInput' import { TrustedHostsInput } from '@/containers/TrustedHostsInput' import { useLocalApiServer } from '@/hooks/useLocalApiServer' import { WebviewWindow } from '@tauri-apps/api/webviewWindow' import { useAppState } from '@/hooks/useAppState' import { useModelProvider } from '@/hooks/useModelProvider' import { startModel } from '@/services/models' import { localStorageKey } from '@/constants/localStorage' import { windowKey } from '@/constants/windows' import { IconLogs } from '@tabler/icons-react' import { cn } from '@/lib/utils' import { ApiKeyInput } from '@/containers/ApiKeyInput' import { useEffect, useState } from 'react' import { invoke } from '@tauri-apps/api/core' // eslint-disable-next-line @typescript-eslint/no-explicit-any export const Route = createFileRoute(route.settings.local_api_server as any)({ component: LocalAPIServer, }) function LocalAPIServer() { const { t } = useTranslation() const { corsEnabled, setCorsEnabled, verboseLogs, setVerboseLogs, enableOnStartup, setEnableOnStartup, serverHost, serverPort, apiPrefix, apiKey, trustedHosts, } = 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 () => { invoke('get_server_status').then((running) => { if (running) { setServerStatus('running') } }) } checkServerStatus() }, [setServerStatus]) const handleApiKeyValidation = (isValid: boolean) => { setIsApiKeyEmpty(!isValid) } const getLastUsedModel = (): { provider: string; model: string } | null => { try { const stored = localStorage.getItem(localStorageKey.lastUsedModel) return stored ? JSON.parse(stored) : null } catch (error) { console.debug('Failed to get last used model from localStorage:', error) return null } } // Helper function to determine which model to start const getModelToStart = () => { // Use last used model if available const lastUsedModel = getLastUsedModel() if (lastUsedModel) { const provider = getProviderByName(lastUsedModel.provider) if ( provider && provider.models.some((m) => m.id === lastUsedModel.model) ) { return { model: lastUsedModel.model, provider } } } // Use selected model if available if (selectedModel && selectedProvider) { const provider = getProviderByName(selectedProvider) if (provider) { return { model: selectedModel.id, provider } } } // Use first model from llamacpp provider const llamacppProvider = getProviderByName('llamacpp') if ( llamacppProvider && llamacppProvider.models && llamacppProvider.models.length > 0 ) { return { model: llamacppProvider.models[0].id, provider: llamacppProvider, } } return null } const toggleAPIServer = async () => { // Validate API key before starting server if (serverStatus === 'stopped') { if (!apiKey || apiKey.toString().trim().length === 0) { setShowApiKeyError(true) return } setShowApiKeyError(false) const modelToStart = getModelToStart() // Only start server if we have a model to load if (!modelToStart) { console.warn( 'Cannot start Local API Server: No model available to load' ) return } setServerStatus('pending') // Start the model first startModel(modelToStart.provider, modelToStart.model) .then(() => { console.log(`Model ${modelToStart.model} started successfully`) // Then start the server return window.core?.api?.startServer({ host: serverHost, port: serverPort, prefix: apiPrefix, apiKey, trustedHosts, isCorsEnabled: corsEnabled, isVerboseEnabled: verboseLogs, }) }) .then(() => { setServerStatus('running') }) .catch((error: unknown) => { console.error('Error starting server:', error) setServerStatus('stopped') }) } else { setServerStatus('pending') window.core?.api ?.stopServer() .then(() => { setServerStatus('stopped') }) .catch((error: unknown) => { console.error('Error stopping server:', error) setServerStatus('stopped') }) } } const handleOpenLogs = async () => { try { // Check if logs window already exists const existingWindow = await WebviewWindow.getByLabel( windowKey.logsWindowLocalApiServer ) if (existingWindow) { // If window exists, focus it await existingWindow.setFocus() console.log('Focused existing logs window') } else { // Create a new logs window using Tauri v2 WebviewWindow API const logsWindow = new WebviewWindow( windowKey.logsWindowLocalApiServer, { url: route.localApiServerlogs, title: 'Local API server Logs - Jan', width: 800, height: 600, resizable: true, center: true, } ) // Listen for window creation logsWindow.once('tauri://created', () => { console.log('Logs window created') }) // Listen for window errors logsWindow.once('tauri://error', (e) => { console.error('Error creating logs window:', e) }) } } catch (error) { console.error('Failed to open logs window:', error) } } const isServerRunning = serverStatus === 'running' return (
{t('settings:localApiServer.description')}