import React, { useState, useCallback } from 'react' import { Box, Text, useInput } from 'ink' import { useI18n } from '../i18n-context' import type { MiphamConfig, ProviderConfig } from '../shared/index.ts' interface PickerProps { config: MiphamConfig currentProvider: string currentModel: string onSelect: (providerId: string, modelId: string) => void onNeedsApiKey?: (providerId: string, modelId: string, providerName: string) => void onClose: () => void } type Panel = 'provider' | 'model' export function ModelPicker({ config, currentProvider, currentModel, onSelect, onNeedsApiKey, onClose, }: PickerProps) { const { t } = useI18n() // Get active providers only const providers = config.providers.filter((p) => p.status !== 'upcoming') const activeModels = (p: ProviderConfig) => p.models.filter((m) => m.status === 'active') // State const [activePanel, setActivePanel] = useState('provider') const [providerIdx, setProviderIdx] = useState(() => { const idx = providers.findIndex((p) => p.id === currentProvider) return idx >= 0 ? idx : 0 }) const [modelIdx, setModelIdx] = useState(0) const selectedProvider = providers[providerIdx] const models = selectedProvider ? activeModels(selectedProvider) : [] // Reset model index when provider changes const goToProvider = useCallback( (idx: number) => { const wrapped = ((idx % providers.length) + providers.length) % providers.length setProviderIdx(wrapped) setModelIdx(0) setActivePanel('model') // auto-switch to model panel }, [providers.length], ) const selectModel = useCallback( (idx: number) => { if (!selectedProvider) return const wrapped = ((idx % models.length) + models.length) % models.length setModelIdx(wrapped) }, [selectedProvider, models.length], ) const confirmSelection = useCallback(() => { if (!selectedProvider) return const model = models[modelIdx] if (model) { // Check API key before confirming switch const apiKey = selectedProvider.apiKey if (!apiKey || apiKey.trim() === '' || /^\$\{[A-Z_]+\}$/.test(apiKey.trim())) { if (selectedProvider.id !== 'ollama' && onNeedsApiKey) { onNeedsApiKey(selectedProvider.id, model.id, selectedProvider.name) return } } onSelect(selectedProvider.id, model.id) } }, [selectedProvider, models, modelIdx, onSelect, onNeedsApiKey]) useInput((input, key) => { // Global keys if (key.escape) { onClose() return } if (key.return) { if (activePanel === 'provider') { goToProvider(providerIdx) // switches to model panel } else { confirmSelection() } return } // Tab or right arrow → switch to model panel if (key.tab || (activePanel === 'provider' && input === 'l')) { setActivePanel('model') return } // Left arrow → switch to provider panel if (key.leftArrow || (activePanel === 'model' && input === 'h')) { setActivePanel('provider') return } // Up/Down navigation if (key.upArrow) { if (activePanel === 'provider') { setProviderIdx((providerIdx - 1 + providers.length) % providers.length) } else { selectModel(modelIdx - 1) } return } if (key.downArrow) { if (activePanel === 'provider') { setProviderIdx((providerIdx + 1) % providers.length) } else { selectModel(modelIdx + 1) } return } }) // Provider labels const providerColor = (p: ProviderConfig) => (p.id === currentProvider ? 'green' : 'white') return ( {/* Title */} {t('ui.picker.title')} {t('ui.picker.nav_help')} {/* Two-column layout */} {/* ── Provider Panel (一级) ── */} {t('ui.picker.provider_label')} {activePanel === 'provider' ? '◀' : ''} {providers.map((p, i) => { const isCurrent = p.id === currentProvider const isSelected = i === providerIdx const isUpcoming = p.status === 'upcoming' return ( {isSelected ? '▶ ' : ' '} {isCurrent ? '✓' : ' '} {p.name.padEnd(16)} {isUpcoming ? t('ui.picker.coming_soon') : `${activeModels(p).length}m`} ) })} {/* ── Model Panel (二级) ── */} {t('ui.picker.models_label')} {activePanel === 'model' ? '◀' : ''} {selectedProvider ? ` — ${selectedProvider.name}` : ''} {models.length === 0 && {t('ui.picker.no_active_models')}} {models.map((m, i) => { const isCurrentModel = selectedProvider?.id === currentProvider && m.id === currentModel const isSelected = i === modelIdx return ( {isSelected ? '▶ ' : ' '} {isCurrentModel ? '✓' : ' '} {m.name} {' '} {m.id} {m.vision ? ' · 🖼 vision' : ''} ) })} {/* Footer hint */} {activePanel === 'provider' ? t('ui.picker.select_provider_hint') : t('ui.picker.select_model_hint')} ) }