import { ChatTabContent } from '@/pages/ChatTabContent'; import { PromptConfigForm } from '@/pages/ChatTabContent/components/PromptPreviewDialog/PromptConfigForm'; import { Box, Button, Container, Step, StepLabel, Stepper, TextField, Typography } from '@mui/material'; import { styled } from '@mui/material/styles'; import type { RJSFSchema } from '@rjsf/utils'; import type { AgentDefinition } from '@services/agentDefinition/interface'; import { HandlerConfig } from '@services/agentInstance/promptConcat/promptConcatSchema'; import useDebouncedCallback from 'beautiful-react-hooks/useDebouncedCallback'; import { nanoid } from 'nanoid'; import React, { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { TemplateSearch } from '../../components/Search/TemplateSearch'; import { useTabStore } from '../../store/tabStore'; import { ICreateNewAgentTab, TabState, TabType } from '../../types/tab'; interface CreateNewAgentContentProps { tab: ICreateNewAgentTab; } const Container_ = styled(Container)` display: flex; flex-direction: column; height: 100%; width: 100%; max-width: none !important; padding: 32px; overflow-y: auto; background-color: ${props => props.theme.palette.background.default}; `; const StepSection = styled(Box)` margin-bottom: 32px; padding: 24px; border-radius: 8px; background-color: ${props => props.theme.palette.background.paper}; border: 1px solid ${props => props.theme.palette.divider}; `; const StepContainer = styled(Box)` min-height: 400px; display: flex; flex-direction: column; `; const ActionBar = styled(Box)` display: flex; justify-content: space-between; align-items: center; padding: 16px 0; margin-top: auto; `; const STEPS = ['setupAgent', 'editPrompt', 'immediateUse'] as const; export const CreateNewAgentContent: React.FC = ({ tab }) => { const { t } = useTranslation('agent'); const { updateTabData, addTab, closeTab } = useTabStore(); const [currentStep, setCurrentStep] = useState(tab.currentStep ?? 0); const [agentName, setAgentName] = useState(''); const [selectedTemplate, setSelectedTemplate] = useState(null); const [temporaryAgentDefinition, setTemporaryAgentDefinition] = useState(null); const [previewAgentId, setPreviewAgentId] = useState(null); const [isLoading, setIsLoading] = useState(false); const [promptSchema, setPromptSchema] = useState(null); // Restore state from backend when component mounts useEffect(() => { const restoreState = async () => { if (tab.agentDefId && window.service?.agentDefinition?.getAgentDef) { try { setIsLoading(true); // Load the temporary agent definition const agentDefinition = await window.service.agentDefinition.getAgentDef(tab.agentDefId); if (agentDefinition) { setTemporaryAgentDefinition(agentDefinition); setAgentName(agentDefinition.name ?? ''); // If there's a template agent def ID, load it as selected template if (tab.templateAgentDefId) { const templateDefinition = await window.service.agentDefinition.getAgentDef(tab.templateAgentDefId); if (templateDefinition) { setSelectedTemplate(templateDefinition); } } } } catch (error) { console.error('Failed to restore CreateNewAgent state:', error); } finally { setIsLoading(false); } } }; void restoreState(); }, [tab.agentDefId, tab.templateAgentDefId]); // Load schema when temporaryAgentDefinition is available useEffect(() => { const loadSchema = async () => { if (temporaryAgentDefinition?.handlerID) { try { const schema = await window.service.agentInstance.getHandlerConfigSchema(temporaryAgentDefinition.handlerID); setPromptSchema(schema as RJSFSchema); } catch (error) { console.error('Failed to load handler config schema:', error); setPromptSchema(null); } } }; void loadSchema(); }, [temporaryAgentDefinition?.handlerID]); // Create preview agent when entering step 3 useEffect(() => { const createPreviewAgent = async () => { if (currentStep === 2 && temporaryAgentDefinition && !previewAgentId) { try { setIsLoading(true); // Flush any pending debounced saves before creating preview agent await saveToBackendDebounced.flush(); // Force save the latest agent definition before creating preview agent await window.service.agentDefinition.updateAgentDef(temporaryAgentDefinition); const previewAgent = await window.service.agentInstance.createAgent( temporaryAgentDefinition.id, { preview: true }, ); setPreviewAgentId(previewAgent.id); } catch (error) { console.error('Failed to create preview agent:', error); void window.service.native.log('error', 'CreateNewAgentContent: Failed to create preview agent', { error }); } finally { setIsLoading(false); } } }; void createPreviewAgent(); }, [currentStep, temporaryAgentDefinition, previewAgentId]); // Auto-save to backend whenever temporaryAgentDefinition changes (debounced) const saveToBackendDebounced = useDebouncedCallback( async () => { if (temporaryAgentDefinition?.id) { try { await window.service.agentDefinition.updateAgentDef(temporaryAgentDefinition); } catch (error) { console.error('Failed to auto-save agent definition:', error); } } }, [temporaryAgentDefinition], 1000, ); useEffect(() => { if (temporaryAgentDefinition) { void saveToBackendDebounced(); } }, [temporaryAgentDefinition, saveToBackendDebounced]); // Only initialize from tab on mount, don't sync back // The component is the source of truth for currentStep during its lifecycle useEffect(() => { if (tab.currentStep !== undefined) { setCurrentStep(tab.currentStep); } }, []); // Only run once on mount // Cleanup when component unmounts or tab closes useEffect(() => { return () => { // Cleanup temporary agent definition and preview agent when tab closes const cleanup = async () => { if (temporaryAgentDefinition?.id && temporaryAgentDefinition.id.startsWith('temp-')) { try { await window.service.agentDefinition.deleteAgentDef(temporaryAgentDefinition.id); } catch (error) { console.error('Failed to cleanup temporary agent definition:', error); } } if (previewAgentId) { try { await window.service.agentInstance.deleteAgent(previewAgentId); } catch (error) { console.error('Failed to cleanup preview agent:', error); } } }; void cleanup(); }; }, [temporaryAgentDefinition?.id, previewAgentId]); const handleNext = async () => { if (currentStep < STEPS.length - 1) { // Force save before advancing to next step (especially step 3) if (temporaryAgentDefinition?.id) { try { await window.service.agentDefinition.updateAgentDef(temporaryAgentDefinition); } catch (error) { console.error('❌ Failed to force save agent definition:', error); } } const nextStep = currentStep + 1; setCurrentStep(nextStep); updateTabData(tab.id, { currentStep: nextStep }); } }; const handleBack = () => { if (currentStep > 0) { const previousStep = currentStep - 1; setCurrentStep(previousStep); updateTabData(tab.id, { currentStep: previousStep }); } }; const handleTemplateSelect = async (template: AgentDefinition) => { try { setIsLoading(true); setSelectedTemplate(template); // Create temporary agent definition based on template const temporaryId = `temp-${nanoid()}`; const newAgentDefinition: AgentDefinition = { ...template, id: temporaryId, name: agentName || `${template.name} (Copy)`, }; const createdDefinition = await window.service.agentDefinition.createAgentDef(newAgentDefinition); setTemporaryAgentDefinition(createdDefinition); // Update agent name if (!agentName) { setAgentName(createdDefinition.name || newAgentDefinition.name || ''); } // Update tab data updateTabData(tab.id, { agentDefId: createdDefinition.id, templateAgentDefId: template.id, }); } catch (error) { console.error('Failed to create temporary agent definition:', error); } finally { setIsLoading(false); } }; const handleAgentDefinitionChange = async (updatedDefinition: AgentDefinition) => { // Immediately update React state setTemporaryAgentDefinition(updatedDefinition); }; const handleSaveAndUse = async () => { try { if (temporaryAgentDefinition) { // Remove 'temp-' prefix to make it permanent const permanentId = temporaryAgentDefinition.id?.replace('temp-', '') || nanoid(); const permanentDefinition = { ...temporaryAgentDefinition, id: permanentId, }; // Save as permanent agent definition await window.service.agentDefinition.createAgentDef(permanentDefinition); // Create chat tab await addTab(TabType.CHAT, { title: permanentDefinition.name || 'New Agent', agentDefId: permanentId, }); // Close this create agent tab closeTab(tab.id); } } catch (error) { console.error('Failed to save and use agent:', error); } }; const canProceed = () => { switch (STEPS[currentStep]) { case 'setupAgent': return selectedTemplate !== null && agentName.trim().length > 0; case 'editPrompt': return temporaryAgentDefinition !== null; case 'immediateUse': return temporaryAgentDefinition !== null; default: return false; } }; const renderStepContent = () => { void window.service.native.log('debug', 'renderStepContent: ', { step: STEPS[currentStep] }); switch (STEPS[currentStep]) { case 'setupAgent': return ( {t('CreateAgent.SetupAgent')} {t('CreateAgent.SetupAgentDescription')} {/* Agent Name Input - placed above template search */} { setAgentName(event.target.value); }} margin='normal' variant='outlined' placeholder={selectedTemplate ? `${selectedTemplate.name} (Copy)` : t('CreateAgent.AgentNamePlaceholder')} helperText={t('CreateAgent.AgentNameHelper')} data-testid='agent-name-input' slotProps={{ htmlInput: { 'data-testid': 'agent-name-input-field', }, }} /> {/* Template Selection */} {t('CreateAgent.SelectTemplate')} {t('CreateAgent.SelectTemplateDescription')} {selectedTemplate && ( {t('CreateAgent.SelectedTemplate')}: {selectedTemplate.name} {selectedTemplate.description && ( {selectedTemplate.description} )} )} ); case 'editPrompt': return ( {t('CreateAgent.EditPrompt')} {t('CreateAgent.EditPromptDescription')} {temporaryAgentDefinition && promptSchema ? ( { void handleAgentDefinitionChange({ ...temporaryAgentDefinition, handlerConfig: updatedConfig as Record, }); }} loading={false} /> ) : ( {t('CreateAgent.LoadingSchema')} )} ); case 'immediateUse': return ( {t('CreateAgent.ImmediateUse')} {t('CreateAgent.ImmediateUseDescription')} {temporaryAgentDefinition && previewAgentId ? ( ) : ( {isLoading ? t('CreateAgent.CreatingPreview') : t('CreateAgent.NoTemplateSelected')} )} ); default: return ( Unknown Step: {STEPS[currentStep]} Current step index: {currentStep}, Step name: {STEPS[currentStep]} ); } }; return ( {t('CreateAgent.Title')} {STEPS.map((step) => ( {t(`CreateAgent.Steps.${step}`)} ))} {renderStepContent()} {currentStep === STEPS.length - 1 ? ( ) : ( )} ); }; export default CreateNewAgentContent;