/** * @license * Copyright 2025 Vybestack LLC * SPDX-License-Identifier: Apache-2.0 */ import type React from 'react'; import { useState, useCallback } from 'react'; import { Box, Text } from 'ink'; import { Colors } from '../../colors.js'; import { RadioButtonSelect } from '../shared/RadioButtonSelect.js'; import { TextInput } from './TextInput.js'; import { useKeypress } from '../../hooks/useKeypress.js'; import { PARAMETER_DEFAULTS } from './constants.js'; import { PARAM_VALIDATORS } from './validation.js'; import { getStepPosition } from './utils.js'; import type { WizardState, AdvancedParams } from './types.js'; import { firstNonEmptyString } from '../../../utils/coalesce.js'; const getParameterDefaults = (provider: string | null): AdvancedParams => { if (provider === null || provider === '') { return PARAMETER_DEFAULTS.anthropic; } if (Object.prototype.hasOwnProperty.call(PARAMETER_DEFAULTS, provider)) { return PARAMETER_DEFAULTS[provider]; } return PARAMETER_DEFAULTS.anthropic; }; const FIELD_LABELS = { temperature: 'Temperature (0.0-2.0)', maxTokens: 'Max Tokens (positive integer)', contextLimit: 'Context Limit (positive integer)', } as const; const FIELD_HELP = { temperature: 'Controls randomness. Lower = more focused, Higher = more creative', maxTokens: 'Maximum tokens to generate in responses', contextLimit: 'Maximum context window size', } as const; const FIELD_PROGRESS = { temperature: '1', maxTokens: '2', contextLimit: '3', } as const; type ParamField = 'temperature' | 'maxTokens' | 'contextLimit'; const advanceField = ( currentField: ParamField, customParams: AdvancedParams, setCurrentField: (f: ParamField) => void, onUpdateParams: (params: AdvancedParams | undefined) => void, onContinue: () => void, ) => { if (currentField === 'temperature') { setCurrentField('maxTokens'); } else if (currentField === 'maxTokens') { setCurrentField('contextLimit'); } else { onUpdateParams(customParams); onContinue(); } }; const buildParamOptions = (providerDefaults: AdvancedParams) => [ { label: `Use recommended defaults (temp: ${providerDefaults.temperature}, max tokens: ${providerDefaults.maxTokens})`, value: 'defaults', key: 'defaults', }, { label: 'Configure custom parameters', value: 'custom', key: 'custom', }, { label: 'Skip (use system defaults)', value: 'skip', key: 'skip', }, ]; const CustomFieldInput: React.FC<{ currentField: ParamField; fieldInput: string; validationError: string | null; handleFieldChange: (value: string) => void; handleFieldSubmit: () => void; }> = ({ currentField, fieldInput, validationError, handleFieldChange, handleFieldSubmit, }) => ( <> {FIELD_LABELS[currentField]}: {FIELD_HELP[currentField]} {validationError && ( ✗ {validationError} )} {!validationError && fieldInput && ( ✓ Valid )} Press Enter to set value or leave empty to skip Progress: {FIELD_PROGRESS[currentField]}/3 Enter Continue Esc Back to menu ); const ParamSelectView: React.FC<{ paramOptions: Array<{ label: string; value: string; key: string }>; handleParamSelect: (value: string) => void; }> = ({ paramOptions, handleParamSelect }) => ( <> Esc Cancel ); const useEscapeHandler = ( focusedComponent: 'select' | 'custom', setFocusedComponent: (v: 'select' | 'custom') => void, setFieldInput: (v: string) => void, setValidationError: (v: string | null) => void, onCancel: () => void, ) => { useKeypress( (key) => { if (key.name === 'escape') { if (focusedComponent === 'custom') { setFocusedComponent('select'); setFieldInput(''); setValidationError(null); } else { onCancel(); } } }, { isActive: true }, ); }; const useFieldSubmitHandler = ( fieldInput: string, currentField: ParamField, customParams: AdvancedParams, onUpdateParams: (params: AdvancedParams | undefined) => void, onContinue: () => void, setFieldInput: (v: string) => void, setCurrentField: (f: ParamField) => void, setCustomParams: (p: AdvancedParams) => void, setValidationError: (v: string | null) => void, ) => useCallback(() => { const numValue = currentField === 'temperature' ? Number.parseFloat(fieldInput) : Number.parseInt(fieldInput, 10); if (!fieldInput.trim()) { setFieldInput(''); setValidationError(null); advanceField( currentField, customParams, setCurrentField, onUpdateParams, onContinue, ); return; } if (Number.isNaN(numValue)) { setValidationError('Must be a valid number'); return; } const validation = PARAM_VALIDATORS[currentField](numValue); if (!validation.valid) { setValidationError( firstNonEmptyString(validation.error, 'Invalid value'), ); return; } const updated = { ...customParams, [currentField]: numValue }; setCustomParams(updated); setFieldInput(''); setValidationError(null); advanceField( currentField, updated, setCurrentField, onUpdateParams, onContinue, ); }, [ fieldInput, currentField, customParams, onUpdateParams, onContinue, setFieldInput, setCurrentField, setCustomParams, setValidationError, ]); const AdvancedParamsHeader: React.FC<{ current: number; total: number }> = ({ current, total, }) => ( <> Create New Profile - Step {current} of {total} Advanced Parameters: Configure temperature, max tokens, and context limits (optional) ); interface AdvancedParamsStepProps { state: WizardState; onUpdateParams: (params: AdvancedParams | undefined) => void; onContinue: () => void; onBack: () => void; onCancel: () => void; } export const AdvancedParamsStep: React.FC = ({ state, onUpdateParams, onContinue, onBack: _onBack, onCancel, }) => { const [focusedComponent, setFocusedComponent] = useState<'select' | 'custom'>( 'select', ); const [customParams, setCustomParams] = useState({ temperature: undefined, maxTokens: undefined, contextLimit: undefined, }); const [currentField, setCurrentField] = useState('temperature'); const [fieldInput, setFieldInput] = useState(''); const [validationError, setValidationError] = useState(null); useEscapeHandler( focusedComponent, setFocusedComponent, setFieldInput, setValidationError, onCancel, ); const handleParamSelect = useCallback( (value: string) => { if (value === 'defaults') { const defaults = getParameterDefaults(state.config.provider); onUpdateParams(defaults); onContinue(); } else if (value === 'skip') { onUpdateParams(undefined); onContinue(); } else if (value === 'custom') { setFocusedComponent('custom'); setCurrentField('temperature'); } }, [state.config.provider, onUpdateParams, onContinue], ); const handleFieldChange = useCallback((value: string) => { setFieldInput(value); setValidationError(null); }, []); const handleFieldSubmit = useFieldSubmitHandler( fieldInput, currentField, customParams, onUpdateParams, onContinue, setFieldInput, setCurrentField, setCustomParams, setValidationError, ); const providerDefaults = getParameterDefaults(state.config.provider); const paramOptions = buildParamOptions(providerDefaults); const { current, total } = getStepPosition(state); return ( {focusedComponent === 'select' && ( )} {focusedComponent === 'custom' && ( )} ); };