/** * @license * Copyright 2025 Qwen * SPDX-License-Identifier: Apache-2.0 */ import { useReducer, useCallback, useMemo } from 'react'; import { Box, Text, useInput } from 'ink'; import { wizardReducer, initialWizardState } from './reducers.js'; import { LocationSelector } from './LocationSelector.js'; import { GenerationMethodSelector } from './GenerationMethodSelector.js'; import { DescriptionInput } from './DescriptionInput.js'; import { ToolSelector } from './ToolSelector.js'; import { ColorSelector } from './ColorSelector.js'; import { CreationSummary } from './CreationSummary.js'; import { WizardStepProps } from './types.js'; import { WIZARD_STEPS } from './constants.js'; import { Config } from '@qwen-code/qwen-code-core'; import { Colors } from '../../colors.js'; import { theme } from '../../semantic-colors.js'; interface SubagentCreationWizardProps { onClose: () => void; config: Config | null; } /** * Main orchestrator component for the subagent creation wizard. */ export function SubagentCreationWizard({ onClose, config, }: SubagentCreationWizardProps) { const [state, dispatch] = useReducer(wizardReducer, initialWizardState); const handleNext = useCallback(() => { dispatch({ type: 'GO_TO_NEXT_STEP' }); }, []); const handlePrevious = useCallback(() => { dispatch({ type: 'GO_TO_PREVIOUS_STEP' }); }, []); const handleCancel = useCallback(() => { dispatch({ type: 'RESET_WIZARD' }); onClose(); }, [onClose]); // Centralized ESC key handling for the entire wizard useInput((input, key) => { if (key.escape) { // Step 3 (DescriptionInput) handles its own ESC logic when generating if ( state.currentStep === WIZARD_STEPS.DESCRIPTION_INPUT && state.isGenerating ) { return; // Let DescriptionInput handle it } if (state.currentStep === WIZARD_STEPS.LOCATION_SELECTION) { // On first step, ESC cancels the entire wizard handleCancel(); } else { // On other steps, ESC goes back to previous step handlePrevious(); } } }); const stepProps: WizardStepProps = useMemo( () => ({ state, dispatch, onNext: handleNext, onPrevious: handlePrevious, onCancel: handleCancel, config, }), [state, dispatch, handleNext, handlePrevious, handleCancel, config], ); const renderStepHeader = useCallback(() => { const getStepHeaderText = () => { switch (state.currentStep) { case WIZARD_STEPS.LOCATION_SELECTION: return 'Step 1: Choose Location'; case WIZARD_STEPS.GENERATION_METHOD: return 'Step 2: Choose Generation Method'; case WIZARD_STEPS.DESCRIPTION_INPUT: return 'Step 3: Describe Your Subagent'; case WIZARD_STEPS.TOOL_SELECTION: return 'Step 4: Select Tools'; case WIZARD_STEPS.COLOR_SELECTION: return 'Step 5: Choose Background Color'; case WIZARD_STEPS.FINAL_CONFIRMATION: return 'Step 6: Confirm and Save'; default: return 'Unknown Step'; } }; return ( {getStepHeaderText()} ); }, [state.currentStep]); const renderDebugContent = useCallback(() => { if (process.env['NODE_ENV'] !== 'development') { return null; } return ( Debug Info: Step: {state.currentStep} Can Proceed: {state.canProceed ? 'Yes' : 'No'} Generating: {state.isGenerating ? 'Yes' : 'No'} Location: {state.location} Method: {state.generationMethod} {state.validationErrors.length > 0 && ( Errors: {state.validationErrors.join(', ')} )} ); }, [ state.currentStep, state.canProceed, state.isGenerating, state.location, state.generationMethod, state.validationErrors, ]); const renderStepFooter = useCallback(() => { const getNavigationInstructions = () => { // Special case: During generation in description input step, only show cancel option if ( state.currentStep === WIZARD_STEPS.DESCRIPTION_INPUT && state.isGenerating ) { return 'Esc to cancel'; } if (state.currentStep === WIZARD_STEPS.FINAL_CONFIRMATION) { return 'Press Enter to save, e to save and edit, Esc to go back'; } // Steps that have ↑↓ navigation (RadioButtonSelect components) const stepsWithNavigation = [ WIZARD_STEPS.LOCATION_SELECTION, WIZARD_STEPS.GENERATION_METHOD, WIZARD_STEPS.TOOL_SELECTION, WIZARD_STEPS.COLOR_SELECTION, ] as const; const hasNavigation = (stepsWithNavigation as readonly number[]).includes( state.currentStep, ); const navigationPart = hasNavigation ? '↑↓ to navigate, ' : ''; const escAction = state.currentStep === WIZARD_STEPS.LOCATION_SELECTION ? 'cancel' : 'go back'; return `Press Enter to continue, ${navigationPart}Esc to ${escAction}`; }; return ( {getNavigationInstructions()} ); }, [state.currentStep, state.isGenerating]); const renderStepContent = useCallback(() => { switch (state.currentStep) { case WIZARD_STEPS.LOCATION_SELECTION: return ; case WIZARD_STEPS.GENERATION_METHOD: return ; case WIZARD_STEPS.DESCRIPTION_INPUT: return ; case WIZARD_STEPS.TOOL_SELECTION: return ; case WIZARD_STEPS.COLOR_SELECTION: return ; case WIZARD_STEPS.FINAL_CONFIRMATION: return ; default: return ( Invalid step: {state.currentStep} ); } }, [stepProps, state.currentStep]); return ( {/* Main content wrapped in bounding box */} {renderStepHeader()} {renderStepContent()} {renderDebugContent()} {renderStepFooter()} ); }