'use client'; import { useState, useCallback } from 'react'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Switch } from '@/components/ui/switch'; import { Eye, EyeOff, CheckCircle2, Zap, MessageSquare, Image as ImageIcon, Video, Volume2, Search, type LucideIcon, } from 'lucide-react'; import { useI18n } from '@/lib/hooks/use-i18n'; import { cn } from '@/lib/utils'; import { useSettingsStore } from '@/lib/store/settings'; import { TOKEN_PLAN_PRESETS, PRESET_CATEGORY_ORDER, MODALITY_ORDER, type TokenPlanPreset, type PresetCategory, type TokenPlanModality, } from '@/lib/config/token-plan-presets'; import { applyTokenPlan, removeTokenPlan } from '@/lib/config/apply-token-plan'; const CATEGORY_LABEL_KEYS: Record = { token_plan: 'settings.presetCategory.tokenPlan', aggregator: 'settings.presetCategory.aggregator', third_party: 'settings.presetCategory.thirdParty', official: 'settings.presetCategory.official', }; const MODALITY_LABEL_KEYS: Record = { llm: 'settings.providers', image: 'settings.imageSettings', video: 'settings.videoSettings', tts: 'settings.ttsSettings', webSearch: 'settings.webSearchSettings', }; const MODALITY_ICONS: Record = { llm: MessageSquare, image: ImageIcon, video: Video, tts: Volume2, webSearch: Search, }; /** The models a preset declares for one modality (display-only, no probing). */ function modalityModels(preset: TokenPlanPreset, m: TokenPlanModality): string[] { const target = preset.modalities[m]; if (!target) return []; if (target.defaultModels?.length) return target.defaultModels; if (target.defaultModelId) return [target.defaultModelId]; return [target.providerId]; } export function TokenPlanSettings() { const { t } = useI18n(); const setProviderConfig = useSettingsStore((s) => s.setProviderConfig); const setImageProviderConfig = useSettingsStore((s) => s.setImageProviderConfig); const setVideoProviderConfig = useSettingsStore((s) => s.setVideoProviderConfig); const setTTSProviderConfig = useSettingsStore((s) => s.setTTSProviderConfig); const setWebSearchProviderConfig = useSettingsStore((s) => s.setWebSearchProviderConfig); const setImageProvider = useSettingsStore((s) => s.setImageProvider); const setImageModelId = useSettingsStore((s) => s.setImageModelId); const setVideoProvider = useSettingsStore((s) => s.setVideoProvider); const setVideoModelId = useSettingsStore((s) => s.setVideoModelId); // Read provider configs so the page can reflect already-persisted state // (other settings panels read the store directly; this page must too). const providersConfig = useSettingsStore((s) => s.providersConfig); const [selected, setSelected] = useState(null); const [apiKey, setApiKey] = useState(''); const [showKey, setShowKey] = useState(false); const [activeTab, setActiveTab] = useState('llm'); const grouped = PRESET_CATEGORY_ORDER.map((cat) => ({ category: cat, presets: TOKEN_PLAN_PRESETS.filter((p) => p.category === cat), })).filter((g) => g.presets.length > 0); const presetSavedKey = (preset: TokenPlanPreset): string => { const llmId = preset.modalities.llm?.providerId; return llmId ? (providersConfig[llmId as keyof typeof providersConfig]?.apiKey ?? '') : ''; }; // Whether a preset's LLM provider already has a saved key (= enabled). const isPresetEnabled = (preset: TokenPlanPreset): boolean => !!presetSavedKey(preset); // The modalities a preset declares, in display order — drives the tab bar. const presetModalities = (preset: TokenPlanPreset): TokenPlanModality[] => MODALITY_ORDER.filter((m) => preset.modalities[m]); const disablePreset = (preset: TokenPlanPreset) => { removeTokenPlan(preset, { setProviderConfig, setImageProviderConfig, setVideoProviderConfig, setTTSProviderConfig, setWebSearchProviderConfig, }); }; const selectPreset = (preset: TokenPlanPreset) => { setSelected(preset); setActiveTab(presetModalities(preset)[0] ?? 'llm'); // Reflect persisted state: prefill the saved key so the page isn't blank // on return (mirrors how other settings panels read the store). setApiKey(presetSavedKey(preset)); }; // Apply is synchronous: seed every declared modality's config + select the // first image/video model, then we're done. No probing — the models a plan // offers are listed as-is, and the user picks/toggles on the generation bar. const handleApply = useCallback(() => { const trimmedKey = apiKey.trim(); if (!selected || !trimmedKey) return; applyTokenPlan(selected, trimmedKey, { setProviderConfig, setImageProviderConfig, setVideoProviderConfig, setTTSProviderConfig, setWebSearchProviderConfig, setImageProvider, setImageModelId, setVideoProvider, setVideoModelId, }); }, [ selected, apiKey, setProviderConfig, setImageProviderConfig, setVideoProviderConfig, setTTSProviderConfig, setWebSearchProviderConfig, setImageProvider, setImageModelId, setVideoProvider, setVideoModelId, ]); return (

{t('settings.tokenPlan.title')}

{t('settings.tokenPlan.desc')}

{grouped.map((group) => (
{t(CATEGORY_LABEL_KEYS[group.category])}
{group.presets.map((preset) => ( ))}
))}
{!selected ? (
{t('settings.tokenPlan.selectPlan')}
) : (
{selected.name}
setApiKey(e.target.value)} className="h-8 pr-8" />
{(() => { const enabled = isPresetEnabled(selected); const savedKey = presetSavedKey(selected); const trimmedKey = apiKey.trim(); const updatingKey = enabled && trimmedKey.length > 0 && trimmedKey !== savedKey; return (
{updatingKey && ( )}
{t('settings.tokenPlan.enable')} { if (checked) { handleApply(); } else { disablePreset(selected); } }} aria-label={t('settings.tokenPlan.enable')} />
); })()}
{/* Capabilities: a display-only tab list of the models the plan offers per modality. No status, no probing — selection and enable/disable happen on the generation bar. */}
{t('settings.tokenPlan.capabilities')}
{/* Tab bar (segmented control) */}
{presetModalities(selected).map((m) => { const Icon = MODALITY_ICONS[m]; const isActive = activeTab === m; return ( ); })}
{/* Tab content: static model list for the active modality */} {(() => { const models = modalityModels(selected, activeTab); return (
{t(MODALITY_LABEL_KEYS[activeTab])} {t('settings.tokenPlan.modelsCount', { n: models.length })}
    {models.map((id) => (
  • {id}
  • ))}
); })()}

{t('settings.tokenPlan.offeredNote')}

)}
); }