import { Box, Text, useInput } from 'ink' import * as React from 'react' import { useState } from 'react' import figures from 'figures' import { getTheme } from '../utils/theme' import { GlobalConfig, saveGlobalConfig, getGlobalConfig, } from '../utils/config.js' import chalk from 'chalk' import { useExitOnCtrlCD } from '../hooks/useExitOnCtrlCD' import { getModelManager } from '../utils/model' type Props = { onClose: () => void } type Setting = | { id: string label: string value: boolean onChange(value: boolean): void type: 'boolean' disabled?: boolean } | { id: string label: string value: string options: string[] onChange(value: string): void type: 'enum' disabled?: boolean } | { id: string label: string value: string onChange(value: string): void type: 'string' disabled?: boolean } | { id: string label: string value: number onChange(value: number): void type: 'number' disabled?: boolean } export function Config({ onClose }: Props): React.ReactNode { const [globalConfig, setGlobalConfig] = useState(getGlobalConfig()) const initialConfig = React.useRef(getGlobalConfig()) const [selectedIndex, setSelectedIndex] = useState(0) const exitState = useExitOnCtrlCD(() => process.exit(0)) const [editingString, setEditingString] = useState(false) const [currentInput, setCurrentInput] = useState('') const [inputError, setInputError] = useState(null) const modelManager = getModelManager() const activeProfiles = modelManager.getAvailableModels() const settings: Setting[] = [ // Global settings { id: 'theme', label: 'Theme', value: globalConfig.theme ?? 'dark', options: ['dark', 'light'], onChange(theme: string) { const config = { ...getGlobalConfig(), theme: theme as any } saveGlobalConfig(config) setGlobalConfig(config) }, type: 'enum', }, { id: 'verbose', label: 'Verbose mode', value: globalConfig.verbose ?? false, onChange(verbose: boolean) { const config = { ...getGlobalConfig(), verbose } saveGlobalConfig(config) setGlobalConfig(config) }, type: 'boolean', }, { id: 'stream', label: 'Stream responses', value: globalConfig.stream ?? true, onChange(stream: boolean) { const config = { ...getGlobalConfig(), stream } saveGlobalConfig(config) setGlobalConfig(config) }, type: 'boolean', }, ] const theme = getTheme() useInput((input, key) => { if (editingString) { if (key.return) { const currentSetting = settings[selectedIndex] if (currentSetting?.type === 'string') { try { currentSetting.onChange(currentInput) setEditingString(false) setCurrentInput('') setInputError(null) } catch (error) { setInputError( error instanceof Error ? error.message : 'Invalid input', ) } } else if (currentSetting?.type === 'number') { const numValue = parseFloat(currentInput) if (isNaN(numValue)) { setInputError('Please enter a valid number') } else { try { ;(currentSetting as any).onChange(numValue) setEditingString(false) setCurrentInput('') setInputError(null) } catch (error) { setInputError( error instanceof Error ? error.message : 'Invalid input', ) } } } } else if (key.escape) { setEditingString(false) setCurrentInput('') setInputError(null) } else if (key.delete || key.backspace) { setCurrentInput(prev => prev.slice(0, -1)) } else if (input) { setCurrentInput(prev => prev + input) } return } if (key.upArrow && !exitState.pending) { setSelectedIndex(prev => Math.max(0, prev - 1)) } else if (key.downArrow && !exitState.pending) { setSelectedIndex(prev => Math.min(settings.length - 1, prev + 1)) } else if (key.return && !exitState.pending) { const currentSetting = settings[selectedIndex] if (currentSetting?.disabled) return if (currentSetting?.type === 'boolean') { currentSetting.onChange(!currentSetting.value) } else if (currentSetting?.type === 'enum') { const currentIndex = currentSetting.options.indexOf( currentSetting.value, ) const nextIndex = (currentIndex + 1) % currentSetting.options.length currentSetting.onChange(currentSetting.options[nextIndex]) } else if ( currentSetting?.type === 'string' || currentSetting?.type === 'number' ) { setCurrentInput(String(currentSetting.value)) setEditingString(true) setInputError(null) } } else if (key.escape && !exitState.pending) { // Check if config has changed const currentConfigString = JSON.stringify(getGlobalConfig()) const initialConfigString = JSON.stringify(initialConfig.current) if (currentConfigString !== initialConfigString) { // Config has changed, save it saveGlobalConfig(getGlobalConfig()) } onClose() } }) return ( Configuration{' '} {exitState.pending ? `(press ${exitState.keyName} again to exit)` : ''} {/* Model Configuration Summary */} Model Configuration: {activeProfiles.length === 0 ? ( No models configured. Use /model to add models. ) : ( {activeProfiles.map(profile => ( • {profile.name} ({profile.provider}) ))} Use /model to manage model configurations )} {/* Settings List */} {settings.map((setting, index) => ( {index === selectedIndex ? figures.pointer : ' '}{' '} {setting.label} {setting.type === 'boolean' ? setting.value ? 'enabled' : 'disabled' : setting.type === 'enum' ? setting.value : String(setting.value)} {index === selectedIndex && editingString && ( Enter new value: {currentInput} {inputError && {inputError}} )} ))} {editingString ? ( 'Enter to save · Esc to cancel' ) : ( <> ↑/↓ to navigate · Enter to change · Esc to close {' '} · Use /model for model config )} ) }