/**
* @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' && (
)}
);
};