import { useState, useCallback } from 'react'; import { logError } from '@/utils/logger'; import type { SdConvDirectMode } from '@/types'; interface UseLaunchLogicProps { model: string; sdmodel: string; onLaunch: () => void; } interface LaunchArgs { autoGpuLayers: boolean; gpuLayers: number; contextSize: number; port: number; host: string; multiuser: boolean; multiplayer: boolean; remotetunnel: boolean; nocertify: boolean; websearch: boolean; noshift: boolean; flashattention: boolean; noavx2: boolean; failsafe: boolean; backend: string; lowvram: boolean; gpuDeviceSelection: string; gpuPlatform: number; tensorSplit: string; quantmatmul: boolean; usemmap: boolean; additionalArguments: string; sdt5xxl: string; sdclipl: string; sdclipg: string; sdphotomaker: string; sdvae: string; sdlora: string; sdconvdirect: SdConvDirectMode; moecpu: number; moeexperts: number; } const buildModelArgs = ( isImageMode: boolean, model: string, sdmodel: string, launchArgs: LaunchArgs ): string[] => { const args: string[] = []; if (isImageMode) { args.push('--sdmodel', sdmodel); const imageModels = [ ['--sdt5xxl', launchArgs.sdt5xxl], ['--sdclipl', launchArgs.sdclipl], ['--sdclipg', launchArgs.sdclipg], ['--sdphotomaker', launchArgs.sdphotomaker], ['--sdvae', launchArgs.sdvae], ['--sdlora', launchArgs.sdlora], ]; imageModels.forEach(([flag, value]) => { if (value.trim()) { args.push(flag, value); } }); if (launchArgs.flashattention) { args.push('--sdflashattention'); } if (launchArgs.sdconvdirect !== 'off') { args.push('--sdconvdirect', launchArgs.sdconvdirect); } } else { args.push('--model', model); } return args; }; const buildConfigArgs = ( isImageMode: boolean, launchArgs: LaunchArgs ): string[] => { const args: string[] = []; if (launchArgs.autoGpuLayers) { args.push('--gpulayers', '-1'); } else if (launchArgs.gpuLayers > 0) { args.push('--gpulayers', launchArgs.gpuLayers.toString()); } if (launchArgs.contextSize) { args.push('--contextsize', launchArgs.contextSize.toString()); } if (launchArgs.port) { args.push('--port', launchArgs.port.toString()); } if (launchArgs.host !== 'localhost' && launchArgs.host) { args.push('--host', launchArgs.host); } const flagMappings: [boolean, string, string?][] = [ [launchArgs.multiuser, '--multiuser', '1'], [launchArgs.multiplayer, '--multiplayer'], [launchArgs.remotetunnel, '--remotetunnel'], [launchArgs.nocertify, '--nocertify'], [launchArgs.websearch, '--websearch'], [launchArgs.noshift, '--noshift'], [!isImageMode && launchArgs.flashattention, '--flashattention'], [launchArgs.noavx2, '--noavx2'], [launchArgs.failsafe, '--failsafe'], [launchArgs.usemmap, '--usemmap'], ]; flagMappings.forEach(([condition, flag, value]) => { if (condition) { args.push(flag); if (value) args.push(value); } }); if (launchArgs.moeexperts !== -1) { args.push('--moeexperts', launchArgs.moeexperts.toString()); } if (launchArgs.moecpu > 0) { args.push('--moecpu', launchArgs.moecpu.toString()); } return args; }; const buildCudaArgs = (launchArgs: LaunchArgs): string[] => { const cudaArgs = ['--usecuda']; cudaArgs.push(launchArgs.lowvram ? 'lowvram' : 'normal'); if (launchArgs.gpuDeviceSelection === 'all') { cudaArgs.push('0'); } else { cudaArgs.push(launchArgs.gpuDeviceSelection || '0'); } cudaArgs.push(launchArgs.quantmatmul ? 'mmq' : 'nommq'); return cudaArgs; }; const buildVulkanArgs = (): string[] => ['--usevulkan']; const buildClblastArgs = (launchArgs: LaunchArgs): string[] => { const clblastArgs = ['--useclblast']; if ( typeof launchArgs.gpuDeviceSelection === 'string' && launchArgs.gpuDeviceSelection.includes(':') ) { const parsed = parseCLBlastDevice(launchArgs.gpuDeviceSelection); if (parsed) { clblastArgs.push( parsed.platformIndex.toString(), parsed.deviceIndex.toString() ); } else { clblastArgs.push(launchArgs.gpuPlatform.toString(), '0'); } } else { clblastArgs.push( launchArgs.gpuPlatform.toString(), launchArgs.gpuDeviceSelection || '0' ); } return clblastArgs; }; const addTensorSplitArgs = (args: string[], launchArgs: LaunchArgs): void => { if (launchArgs.tensorSplit && launchArgs.tensorSplit.trim()) { const tensorValues = launchArgs.tensorSplit .split(',') .map((value) => value.trim()) .filter((value) => value !== '' && !isNaN(Number(value))); if (tensorValues.length > 0) { args.push('--tensorsplit', ...tensorValues); } } }; const buildBackendArgs = (launchArgs: LaunchArgs): string[] => { const args: string[] = []; if (!launchArgs.backend || launchArgs.backend === 'cpu') { return args; } const isTensorSplitSupported = launchArgs.backend === 'cuda' || launchArgs.backend === 'rocm' || launchArgs.backend === 'vulkan'; if (launchArgs.backend === 'cuda' || launchArgs.backend === 'rocm') { args.push(...buildCudaArgs(launchArgs)); if (launchArgs.gpuDeviceSelection === 'all' && isTensorSplitSupported) { addTensorSplitArgs(args, launchArgs); } } else if (launchArgs.backend === 'vulkan') { args.push(...buildVulkanArgs()); if (launchArgs.gpuDeviceSelection === 'all' && isTensorSplitSupported) { addTensorSplitArgs(args, launchArgs); } } else if (launchArgs.backend === 'clblast') { args.push(...buildClblastArgs(launchArgs)); } return args; }; function parseCLBlastDevice(deviceString: string): { deviceIndex: number; platformIndex: number; } | null { const match = deviceString.match(/\[(\d+),(\d+)\]$/); if (match) { return { deviceIndex: parseInt(match[1], 10), platformIndex: parseInt(match[2], 10), }; } return null; } export const useLaunchLogic = ({ model, sdmodel, onLaunch, }: UseLaunchLogicProps) => { const [isLaunching, setIsLaunching] = useState(false); const handleLaunch = useCallback( async (launchArgs: LaunchArgs) => { const isImageMode = sdmodel.trim() !== ''; const isTextMode = model.trim() !== ''; if (isLaunching || (!isImageMode && !isTextMode)) { return; } setIsLaunching(true); onLaunch(); try { const args: string[] = [ ...buildModelArgs(isImageMode, model, sdmodel, launchArgs), ...buildConfigArgs(isImageMode, launchArgs), ...buildBackendArgs(launchArgs), ]; if (launchArgs.additionalArguments.trim()) { const additionalArgs = launchArgs.additionalArguments .trim() .split(/\s+/); args.push(...additionalArgs); } const result = await window.electronAPI.kobold.launchKoboldCpp(args); if (result.success) { onLaunch(); } else { const errorMessage = result.error || 'Unknown launch error'; window.electronAPI.logs.logError( 'Launch failed:', new Error(errorMessage) ); } } catch (err) { logError('Error launching:', err as Error); } finally { setIsLaunching(false); } }, [model, sdmodel, isLaunching, onLaunch] ); return { isLaunching, handleLaunch, }; };