and newlines into
. */
function renderSetupStep(text: string) {
// Split on backtick-wrapped segments
const parts = text.split(/(`[^`]+`)/g);
return parts.map((part, i) => {
if (part.startsWith('`') && part.endsWith('`')) {
return (
{part.slice(1, -1)}
);
}
// Handle newlines within text
const lines = part.split('\n');
return (
{lines.map((line, j) => (
{j > 0 &&
}
{line}
))}
);
});
}
interface ProviderConfigurationModalProps {
provider: ProviderDetails;
onClose: () => void;
onConfigured?: (provider: ProviderDetails) => void;
}
export default function ProviderConfigurationModal({
provider,
onClose,
onConfigured,
}: ProviderConfigurationModalProps) {
const intl = useIntl();
const [validationErrors, setValidationErrors] = useState>({});
const { getCurrentModelAndProvider } = useModelAndProvider();
const [configValues, setConfigValues] = useState>({});
const [showDeleteConfirmation, setShowDeleteConfirmation] = useState(false);
const [isActiveProvider, setIsActiveProvider] = useState(false);
const [error, setError] = useState(null);
const [isOAuthLoading, setIsOAuthLoading] = useState(false);
let primaryParameters = provider.metadata.config_keys.filter((param) => param.primary);
if (primaryParameters.length === 0) {
primaryParameters = provider.metadata.config_keys;
}
const configKeys = provider.metadata.config_keys.filter((key) => !key.oauth_flow);
const hasOAuth = provider.metadata.config_keys.some((key) => key.oauth_flow);
const hasConfig = configKeys.length > 0;
const hasDeviceCodeFlow = provider.metadata.config_keys.some((key) => key.device_code_flow);
const isHuggingFaceProvider = provider.name === 'huggingface';
const isConfigured = provider.is_configured;
const headerText = showDeleteConfirmation
? intl.formatMessage(i18n.deleteConfigHeader, { providerName: provider.metadata.display_name })
: intl.formatMessage(i18n.configureHeader, { providerName: provider.metadata.display_name });
const isExternalSetup =
provider.metadata.config_keys.length === 0 &&
provider.metadata.setup_steps &&
provider.metadata.setup_steps.length > 0;
const descriptionText = showDeleteConfirmation
? isActiveProvider
? intl.formatMessage(i18n.cannotDeleteActive)
: intl.formatMessage(i18n.deleteConfirmation)
: hasOAuth
? intl.formatMessage(i18n.oauthSignInDescription, {
providerName: provider.metadata.display_name,
})
: isExternalSetup
? provider.metadata.description
: intl.formatMessage(i18n.addApiKeyDescription);
const handleOAuthLogin = async () => {
setIsOAuthLoading(true);
setError(null);
try {
if (hasConfig) {
const fields: { key: string; value: string }[] = [];
for (const key of configKeys) {
const entry = configValues[key.name];
const value =
entry?.value ?? (typeof entry?.serverValue === 'string' ? entry.serverValue : null);
if (value) {
fields.push({ key: key.name, value });
}
}
if (fields.length > 0) {
await acpSaveProviderConfig(provider.name, fields);
}
}
await acpAuthenticateProvider(provider.name);
if (onConfigured) {
onConfigured(provider);
} else {
onClose();
}
} catch (err) {
setError(intl.formatMessage(i18n.oauthLoginFailed, { error: errorMessage(err) }));
} finally {
setIsOAuthLoading(false);
}
};
const handleSubmitForm = async (e: React.FormEvent) => {
e.preventDefault();
setValidationErrors({});
const parameters = provider.metadata.config_keys || [];
const errors: Record = {};
parameters.forEach((parameter) => {
if (
parameter.required &&
!configValues[parameter.name]?.value &&
!configValues[parameter.name]?.serverValue
) {
errors[parameter.name] = intl.formatMessage(i18n.parameterRequired, {
paramName: parameter.name,
});
}
});
if (Object.keys(errors).length > 0) {
setValidationErrors(errors);
return;
}
const toSubmit = Object.fromEntries(
Object.entries(configValues)
.filter(
([_k, entry]) =>
!!entry.value || (entry.serverValue != null && typeof entry.serverValue === 'string')
)
.map(([k, entry]) => [
k,
entry.value ?? (typeof entry.serverValue === 'string' ? entry.serverValue : ''),
])
);
try {
await providerConfigSubmitHandler(provider, toSubmit);
if (onConfigured) {
onConfigured(provider);
} else {
onClose();
}
} catch (error) {
setError(errorMessage(error));
}
};
const handleCancel = () => {
onClose();
};
const handleDelete = async () => {
try {
const providerModel = await getCurrentModelAndProvider();
if (provider.name === providerModel.provider) {
setIsActiveProvider(true);
setShowDeleteConfirmation(true);
return;
}
} catch (error) {
console.error('Failed to check current provider:', error);
}
setIsActiveProvider(false);
setShowDeleteConfirmation(true);
};
const handleConfirmDelete = async () => {
if (isActiveProvider) {
return;
}
const isCustomProvider = provider.provider_type === 'Custom';
if (isCustomProvider) {
await acpDeleteCustomProvider(provider.name);
} else {
// Deletes all config/secret fields and cleans up provider-specific cache
// (e.g. OAuth tokens) server-side.
await acpDeleteProviderConfig(provider.name);
}
onClose();
};
const getModalIcon = () => {
if (showDeleteConfirmation) {
return (
);
}
return ;
};
return (
<>
>
);
}