import { Card, Container, Stack, Tabs, Group, Button } from '@mantine/core'; import { useState, useEffect, useCallback, useRef } from 'react'; import { tryExecute, logError, safeExecute } from '@/utils/logger'; import { useLaunchConfig } from '@/hooks/useLaunchConfig'; import { useLaunchLogic } from '@/hooks/useLaunchLogic'; import { useWarnings } from '@/hooks/useWarnings'; import { GeneralTab } from '@/components/screens/Launch/GeneralTab/index'; import { AdvancedTab } from '@/components/screens/Launch/AdvancedTab'; import { NetworkTab } from '@/components/screens/Launch/NetworkTab'; import { ImageGenerationTab } from '@/components/screens/Launch/ImageGenerationTab'; import { WarningDisplay } from '@/components/WarningDisplay'; import { ConfigFileManager } from '@/components/screens/Launch/ConfigFileManager'; import { DEFAULT_MODEL_URL } from '@/constants'; import type { ConfigFile } from '@/types'; interface LaunchScreenProps { onLaunch: () => void; } export const LaunchScreen = ({ onLaunch }: LaunchScreenProps) => { const [configFiles, setConfigFiles] = useState([]); const [selectedFile, setSelectedFile] = useState(null); const [, setInstallDir] = useState(''); const [activeTab, setActiveTab] = useState('general'); const [configLoaded, setConfigLoaded] = useState(false); const defaultsSetRef = useRef(false); const { gpuLayers, autoGpuLayers, contextSize, model, additionalArguments, port, host, multiuser, multiplayer, remotetunnel, nocertify, websearch, noshift, flashattention, noavx2, failsafe, lowvram, quantmatmul, usemmap, backend, gpuDeviceSelection, gpuPlatform, tensorSplit, sdmodel, sdt5xxl, sdclipl, sdclipg, sdphotomaker, sdvae, sdlora, sdconvdirect, moecpu, moeexperts, parseAndApplyConfigFile, loadConfigFromFile, handleModelChange, handleBackendChange, } = useLaunchConfig(); const { isLaunching, handleLaunch } = useLaunchLogic({ model, sdmodel, onLaunch, }); const { warnings: combinedWarnings } = useWarnings({ model, sdmodel, backend, noavx2, failsafe, configLoaded, }); const setHappyDefaults = useCallback(async () => { const backends = await safeExecute( () => window.electronAPI.kobold.getAvailableBackends(), 'Failed to set defaults:' ); if (!backend && backends && backends.length > 0) { handleBackendChange(backends[0].value); } }, [backend, handleBackendChange]); const setInitialDefaults = useCallback( async (currentModel: string, currentSdModel: string) => { await tryExecute(async () => { if ( !defaultsSetRef.current && !currentModel.trim() && !currentSdModel.trim() ) { handleModelChange(DEFAULT_MODEL_URL); defaultsSetRef.current = true; } }, 'Failed to set initial defaults:'); }, [handleModelChange] ); useEffect(() => { if (configLoaded && !defaultsSetRef.current) { void setHappyDefaults(); if (!model.trim() && !sdmodel.trim()) { void setInitialDefaults(model, sdmodel); } } // eslint-disable-next-line react-hooks/exhaustive-deps }, [configLoaded, setHappyDefaults]); const loadConfigFiles = useCallback(async () => { const [files, currentDir, savedConfig] = await Promise.all([ window.electronAPI.kobold.getConfigFiles(), window.electronAPI.kobold.getCurrentInstallDir(), window.electronAPI.kobold.getSelectedConfig(), ]); setConfigFiles(files); setInstallDir(currentDir); if (savedConfig && files.some((f) => f.name === savedConfig)) { setSelectedFile(savedConfig); } else if (files.length > 0 && !selectedFile) { setSelectedFile(files[0].name); } const loadedConfigFileName = await loadConfigFromFile(files, savedConfig); if (loadedConfigFileName && !selectedFile) { setSelectedFile(loadedConfigFileName); } setConfigLoaded(true); }, [selectedFile, loadConfigFromFile]); const handleFileSelection = async (fileName: string) => { setSelectedFile(fileName); await window.electronAPI.kobold.setSelectedConfig(fileName); const selectedConfig = configFiles.find((f) => f.name === fileName); if (selectedConfig) { await parseAndApplyConfigFile(selectedConfig.path); } }; const buildConfigData = () => ({ autoGpuLayers, gpulayers: gpuLayers, contextsize: contextSize, model, additionalArguments, port, host, multiuser: multiuser ? 1 : 0, multiplayer, remotetunnel, nocertify, websearch, noshift, flashattention, noavx2, failsafe, usemmap, moecpu, moeexperts, usecuda: backend === 'cuda' || backend === 'rocm', usevulkan: backend === 'vulkan', useclblast: backend === 'clblast', gpuDeviceSelection, tensorSplit, sdmodel, sdt5xxl, sdclipl, sdclipg, sdphotomaker, sdvae, }); const handleCreateNewConfig = async (configName: string) => { await safeExecute(async () => { const fullConfigName = `${configName}.json`; const saveSuccess = await window.electronAPI.kobold.saveConfigFile( fullConfigName, buildConfigData() ); if (saveSuccess) { await loadConfigFiles(); setSelectedFile(fullConfigName); await window.electronAPI.kobold.setSelectedConfig(fullConfigName); } else { logError( 'Failed to create new configuration', new Error('Save operation failed') ); } }, 'Failed to create new configuration:'); }; const handleSaveConfig = async () => { if (!selectedFile) { logError( 'No configuration file selected for saving', new Error('Selected file is null') ); return false; } const success = await safeExecute(async () => { const saveSuccess = await window.electronAPI.kobold.saveConfigFile( selectedFile, buildConfigData() ); if (!saveSuccess) { logError( 'Failed to save configuration', new Error('Save operation failed') ); return false; } return true; }, 'Failed to save configuration:'); return success ?? false; }; useEffect(() => { void loadConfigFiles(); const handleInstallDirChange = () => { void loadConfigFiles(); }; const cleanup = window.electronAPI.kobold.onInstallDirChanged( handleInstallDirChange ); return cleanup; }, [loadConfigFiles]); const handleLaunchClick = () => { handleLaunch({ autoGpuLayers, gpuLayers, contextSize, port: port ?? 5001, host, multiuser, multiplayer, remotetunnel, nocertify, websearch, noshift, flashattention, noavx2, failsafe, backend, lowvram, gpuDeviceSelection, gpuPlatform, tensorSplit, quantmatmul, usemmap, additionalArguments, sdt5xxl, sdclipl, sdclipg, sdphotomaker, sdvae, sdlora, sdconvdirect, moecpu, moeexperts, }); }; return ( General Image Generation Network Advanced ); };