import { AiAPIConfig } from '@services/agentInstance/promptConcat/promptConcatSchema'; import { AIProviderConfig } from '@services/externalAPI/interface'; import { cloneDeep } from 'lodash'; import { useCallback, useEffect, useState } from 'react'; interface UseAIConfigManagementProps { agentDefId?: string; agentId?: string; } interface UseAIConfigManagementResult { loading: boolean; config: AiAPIConfig | null; providers: AIProviderConfig[]; setProviders: React.Dispatch>; handleModelChange: (provider: string, model: string) => Promise; handleEmbeddingModelChange: (provider: string, model: string) => Promise; handleSpeechModelChange: (provider: string, model: string) => Promise; handleImageGenerationModelChange: (provider: string, model: string) => Promise; handleTranscriptionsModelChange: (provider: string, model: string) => Promise; handleFreeModelChange: (provider: string, model: string) => Promise; handleConfigChange: (newConfig: AiAPIConfig) => Promise; } export const useAIConfigManagement = ({ agentDefId, agentId }: UseAIConfigManagementProps = {}): UseAIConfigManagementResult => { const [loading, setLoading] = useState(true); const [config, setConfig] = useState(null); const [providers, setProviders] = useState([]); useEffect(() => { const fetchConfig = async () => { try { setLoading(true); let finalConfig: AiAPIConfig | null = null; // Three-tier configuration hierarchy: global < definition < instance // Load global config as base const globalConfig = await window.service.externalAPI.getAIConfig(); if (agentId) { // Get instance config first const agentInstance = await window.service.agentInstance.getAgent(agentId); if (agentInstance?.aiApiConfig && Object.keys(agentInstance.aiApiConfig).length > 0) { finalConfig = agentInstance.aiApiConfig as AiAPIConfig; } else if (agentInstance?.agentDefId) { // Auto-resolve agentDefId from agentId and get definition config const agentDefinition = await window.service.agentDefinition.getAgentDef(agentInstance.agentDefId); if (agentDefinition?.aiApiConfig && Object.keys(agentDefinition.aiApiConfig).length > 0) { finalConfig = agentDefinition.aiApiConfig as AiAPIConfig; } } } else if (agentDefId) { // Get definition config const agentDefinition = await window.service.agentDefinition.getAgentDef(agentDefId); if (agentDefinition?.aiApiConfig && Object.keys(agentDefinition.aiApiConfig).length > 0) { finalConfig = agentDefinition.aiApiConfig as AiAPIConfig; } } // Fallback to global config if no specific config found if (!finalConfig) { finalConfig = globalConfig; } setConfig(finalConfig); const providersData = await window.service.externalAPI.getAIProviders(); setProviders(providersData); setLoading(false); } catch (error) { void window.service.native.log('error', 'Failed to load AI configuration', { function: 'useAIConfigManagement.fetchConfig', error }); setLoading(false); } }; void fetchConfig(); }, [agentDefId, agentId]); const updateConfig = useCallback(async (updatedConfig: AiAPIConfig) => { if (agentId) { // Direct update for instance config await window.service.agentInstance.updateAgent(agentId, { aiApiConfig: updatedConfig }); } else if (agentDefId) { // Direct update for definition config await window.service.agentDefinition.updateAgentDef({ id: agentDefId, aiApiConfig: updatedConfig, }); } else { // Update global config await window.service.externalAPI.updateDefaultAIConfig(updatedConfig); } }, [agentId, agentDefId]); const handleModelChange = useCallback(async (provider: string, model: string) => { if (!config) return; try { const updatedConfig = cloneDeep(config); if (typeof updatedConfig.api === 'undefined') { updatedConfig.api = { provider, model }; } else { updatedConfig.api.provider = provider; updatedConfig.api.model = model; } setConfig(updatedConfig); await updateConfig(updatedConfig); } catch (error) { void window.service.native.log('error', 'Failed to update model configuration', { function: 'useAIConfigManagement.handleModelChange', error }); } }, [config, updateConfig]); const handleEmbeddingModelChange = useCallback(async (provider: string, model: string) => { if (!config) return; try { const updatedConfig = cloneDeep(config); if (typeof updatedConfig.api === 'undefined') { updatedConfig.api = { provider, model, embeddingModel: model }; } else { updatedConfig.api.embeddingModel = model; } setConfig(updatedConfig); await updateConfig(updatedConfig); } catch (error) { void window.service.native.log('error', 'Failed to update embedding model configuration', { function: 'useAIConfigManagement.handleEmbeddingModelChange', error, }); } }, [config, updateConfig]); const handleSpeechModelChange = useCallback(async (provider: string, model: string) => { if (!config) return; try { const updatedConfig = cloneDeep(config); if (typeof updatedConfig.api === 'undefined') { updatedConfig.api = { provider, model, speechModel: model }; } else { updatedConfig.api.speechModel = model; } setConfig(updatedConfig); await updateConfig(updatedConfig); } catch (error) { void window.service.native.log('error', 'Failed to update speech model configuration', { function: 'useAIConfigManagement.handleSpeechModelChange', error, }); } }, [config, updateConfig]); const handleImageGenerationModelChange = useCallback(async (provider: string, model: string) => { if (!config) return; try { const updatedConfig = cloneDeep(config); if (typeof updatedConfig.api === 'undefined') { updatedConfig.api = { provider, model, imageGenerationModel: model }; } else { updatedConfig.api.imageGenerationModel = model; } setConfig(updatedConfig); await updateConfig(updatedConfig); } catch (error) { void window.service.native.log('error', 'Failed to update image generation model configuration', { function: 'useAIConfigManagement.handleImageGenerationModelChange', error, }); } }, [config, updateConfig]); const handleTranscriptionsModelChange = useCallback(async (provider: string, model: string) => { if (!config) return; try { const updatedConfig = cloneDeep(config); if (typeof updatedConfig.api === 'undefined') { updatedConfig.api = { provider, model, transcriptionsModel: model }; } else { updatedConfig.api.transcriptionsModel = model; } setConfig(updatedConfig); await updateConfig(updatedConfig); } catch (error) { void window.service.native.log('error', 'Failed to update transcriptions model configuration', { function: 'useAIConfigManagement.handleTranscriptionsModelChange', error, }); } }, [config, updateConfig]); const handleFreeModelChange = useCallback(async (provider: string, model: string) => { if (!config) return; try { const updatedConfig = cloneDeep(config); if (typeof updatedConfig.api === 'undefined') { updatedConfig.api = { provider, model, freeModel: model }; } else { updatedConfig.api.freeModel = model; } setConfig(updatedConfig); await updateConfig(updatedConfig); } catch (error) { void window.service.native.log('error', 'Failed to update free model configuration', { function: 'useAIConfigManagement.handleFreeModelChange', error, }); } }, [config, updateConfig]); const handleConfigChange = useCallback(async (newConfig: AiAPIConfig) => { try { setConfig(newConfig); await updateConfig(newConfig); } catch (error) { void window.service.native.log('error', 'Failed to update configuration', { function: 'useAIConfigManagement.handleConfigChange', error }); } }, [updateConfig]); return { loading, config, providers, setProviders, handleModelChange, handleEmbeddingModelChange, handleSpeechModelChange, handleImageGenerationModelChange, handleTranscriptionsModelChange, handleFreeModelChange, handleConfigChange, }; };