import type React from 'react'; import { getProviderMeta } from './ProviderIcons'; import { ModelSourceHint, modelSourceToolForProvider } from './ModelSourceHint'; import { findSessionBridge, getAIProviderSettings, resolveAIProviderSelection, saveAIProviderSelection, savePreferredModel, type AIProviderOption, } from '../utils/aiProvider'; import { useState } from 'react'; import type { Origin } from '@plannotator/core/agents'; interface AIProvider extends AIProviderOption { capabilities: Record; } interface AISettingsTabProps { providers: AIProvider[]; selectedProviderId: string | null; origin?: Origin | null; onProviderChange: (providerId: string | null) => void; } export const AISettingsTab: React.FC = ({ providers, selectedProviderId, origin, onProviderChange, }) => { const settings = getAIProviderSettings(); const originDefault = resolveAIProviderSelection({ providers, origin, settings }).providerId; const effectiveSelection = selectedProviderId ?? originDefault ?? providers[0]?.id ?? null; const [preferredModels, setPreferredModels] = useState>(() => getAIProviderSettings().preferredModels ); const handleSelectProvider = (providerId: string) => { onProviderChange(providerId); // Persist only the provider choice. Writing the resolved model here would // clobber a saved preference the currently-advertised model list doesn't // include (e.g. before deferred Codex discovery has run) with the static // fallback id — the saved model only changes when the user picks one. saveAIProviderSelection({ providerId, model: null, origin }); }; const handleModelChange = (providerId: string, modelId: string) => { savePreferredModel(providerId, modelId); setPreferredModels(prev => ({ ...prev, [providerId]: modelId })); }; if (providers.length === 0) { return ( <>
AI Provider
No AI providers detected. Install the claude or codex CLI and make sure you're authenticated. Setup guide
); } // "Ask this session": the session that opened Plannotator answers Ask AI and // nothing else is offered. Nothing here writes a preference, so a saved // provider still applies to sessions that have no attached session. const sessionBridge = findSessionBridge(providers); if (sessionBridge) { const meta = getProviderMeta(sessionBridge.name, sessionBridge.label); const Icon = meta.icon; return ( <>
AI Provider
Ask AI is answered by the agent session that opened Plannotator.
{meta.label}
Your provider choice applies when Plannotator is opened without an attached session.
); } return ( <>
AI Provider
Choose which AI provider and model to use for inline chat
{providers.map((p) => { const meta = getProviderMeta(p.name, p.label); const Icon = meta.icon; const isSelected = effectiveSelection === p.id; const models = p.models ?? []; const defaultModel = models.find(m => m.default) ?? models[0]; const selectedModel = preferredModels[p.id] ?? defaultModel?.id ?? null; return (
{/* Model selector — shown for selected provider when models available */} {isSelected && models.length > 1 && (
)} {/* Show current model as muted text for unselected providers */} {!isSelected && models.length > 0 && (
{models.find(m => m.id === (preferredModels[p.id] ?? defaultModel?.id))?.label ?? defaultModel?.label}
)}
); })}
Providers are detected from installed CLI tools. No API keys are managed by Plannotator — you must be authenticated with each CLI independently.{' '} Learn more
); };