diff --git a/src/components/providers/AmpcodeSection/AmpcodeModal.tsx b/src/components/providers/AmpcodeSection/AmpcodeModal.tsx deleted file mode 100644 index 876a272..0000000 --- a/src/components/providers/AmpcodeSection/AmpcodeModal.tsx +++ /dev/null @@ -1,281 +0,0 @@ -import { useEffect, useRef, useState } from 'react'; -import { useTranslation } from 'react-i18next'; -import { Button } from '@/components/ui/Button'; -import { Input } from '@/components/ui/Input'; -import { Modal } from '@/components/ui/Modal'; -import { ModelInputList } from '@/components/ui/ModelInputList'; -import { ToggleSwitch } from '@/components/ui/ToggleSwitch'; -import { useConfigStore, useNotificationStore } from '@/stores'; -import { ampcodeApi } from '@/services/api'; -import type { AmpcodeConfig } from '@/types'; -import { maskApiKey } from '@/utils/format'; -import { buildAmpcodeFormState, entriesToAmpcodeMappings } from '../utils'; -import type { AmpcodeFormState } from '../types'; - -interface AmpcodeModalProps { - isOpen: boolean; - disableControls: boolean; - onClose: () => void; - onBusyChange?: (busy: boolean) => void; -} - -export function AmpcodeModal({ isOpen, disableControls, onClose, onBusyChange }: AmpcodeModalProps) { - const { t } = useTranslation(); - const { showNotification, showConfirmation } = useNotificationStore(); - const config = useConfigStore((state) => state.config); - const updateConfigValue = useConfigStore((state) => state.updateConfigValue); - const clearCache = useConfigStore((state) => state.clearCache); - - const [form, setForm] = useState(() => buildAmpcodeFormState(null)); - const [loading, setLoading] = useState(false); - const [loaded, setLoaded] = useState(false); - const [mappingsDirty, setMappingsDirty] = useState(false); - const [error, setError] = useState(''); - const [saving, setSaving] = useState(false); - const initializedRef = useRef(false); - - const getErrorMessage = (err: unknown) => { - if (err instanceof Error) return err.message; - if (typeof err === 'string') return err; - return ''; - }; - - useEffect(() => { - onBusyChange?.(loading || saving); - }, [loading, saving, onBusyChange]); - - useEffect(() => { - if (!isOpen) { - initializedRef.current = false; - setLoading(false); - setSaving(false); - setError(''); - setLoaded(false); - setMappingsDirty(false); - setForm(buildAmpcodeFormState(null)); - onBusyChange?.(false); - return; - } - if (initializedRef.current) return; - initializedRef.current = true; - - setLoading(true); - setLoaded(false); - setMappingsDirty(false); - setError(''); - setForm(buildAmpcodeFormState(config?.ampcode ?? null)); - - void (async () => { - try { - const ampcode = await ampcodeApi.getAmpcode(); - setLoaded(true); - updateConfigValue('ampcode', ampcode); - clearCache('ampcode'); - setForm(buildAmpcodeFormState(ampcode)); - } catch (err: unknown) { - setError(getErrorMessage(err) || t('notification.refresh_failed')); - } finally { - setLoading(false); - } - })(); - }, [clearCache, config?.ampcode, isOpen, onBusyChange, t, updateConfigValue]); - - const clearAmpcodeUpstreamApiKey = async () => { - showConfirmation({ - title: t('ai_providers.ampcode_clear_upstream_api_key_title', { defaultValue: 'Clear Upstream API Key' }), - message: t('ai_providers.ampcode_clear_upstream_api_key_confirm'), - variant: 'danger', - confirmText: t('common.confirm'), - onConfirm: async () => { - setSaving(true); - setError(''); - try { - await ampcodeApi.clearUpstreamApiKey(); - const previous = config?.ampcode ?? {}; - const next: AmpcodeConfig = { ...previous }; - delete next.upstreamApiKey; - updateConfigValue('ampcode', next); - clearCache('ampcode'); - showNotification(t('notification.ampcode_upstream_api_key_cleared'), 'success'); - } catch (err: unknown) { - const message = getErrorMessage(err); - setError(message); - showNotification(`${t('notification.update_failed')}: ${message}`, 'error'); - } finally { - setSaving(false); - } - }, - }); - }; - - const performSaveAmpcode = async () => { - setSaving(true); - setError(''); - try { - const upstreamUrl = form.upstreamUrl.trim(); - const overrideKey = form.upstreamApiKey.trim(); - const modelMappings = entriesToAmpcodeMappings(form.mappingEntries); - - if (upstreamUrl) { - await ampcodeApi.updateUpstreamUrl(upstreamUrl); - } else { - await ampcodeApi.clearUpstreamUrl(); - } - - await ampcodeApi.updateForceModelMappings(form.forceModelMappings); - - if (loaded || mappingsDirty) { - if (modelMappings.length) { - await ampcodeApi.saveModelMappings(modelMappings); - } else { - await ampcodeApi.clearModelMappings(); - } - } - - if (overrideKey) { - await ampcodeApi.updateUpstreamApiKey(overrideKey); - } - - const previous = config?.ampcode ?? {}; - const next: AmpcodeConfig = { - upstreamUrl: upstreamUrl || undefined, - forceModelMappings: form.forceModelMappings, - }; - - if (previous.upstreamApiKey) { - next.upstreamApiKey = previous.upstreamApiKey; - } - - if (Array.isArray(previous.modelMappings)) { - next.modelMappings = previous.modelMappings; - } - - if (overrideKey) { - next.upstreamApiKey = overrideKey; - } - - if (loaded || mappingsDirty) { - if (modelMappings.length) { - next.modelMappings = modelMappings; - } else { - delete next.modelMappings; - } - } - - updateConfigValue('ampcode', next); - clearCache('ampcode'); - showNotification(t('notification.ampcode_updated'), 'success'); - onClose(); - } catch (err: unknown) { - const message = getErrorMessage(err); - setError(message); - showNotification(`${t('notification.update_failed')}: ${message}`, 'error'); - } finally { - setSaving(false); - } - }; - - const saveAmpcode = async () => { - if (!loaded && mappingsDirty) { - showConfirmation({ - title: t('ai_providers.ampcode_mappings_overwrite_title', { defaultValue: 'Overwrite Mappings' }), - message: t('ai_providers.ampcode_mappings_overwrite_confirm'), - variant: 'secondary', // Not dangerous, just a warning - confirmText: t('common.confirm'), - onConfirm: performSaveAmpcode, - }); - return; - } - - await performSaveAmpcode(); - }; - - return ( - - - - - } - > - {error &&
{error}
} - setForm((prev) => ({ ...prev, upstreamUrl: e.target.value }))} - disabled={loading || saving} - hint={t('ai_providers.ampcode_upstream_url_hint')} - /> - setForm((prev) => ({ ...prev, upstreamApiKey: e.target.value }))} - disabled={loading || saving} - hint={t('ai_providers.ampcode_upstream_api_key_hint')} - /> -
-
- {t('ai_providers.ampcode_upstream_api_key_current', { - key: config?.ampcode?.upstreamApiKey - ? maskApiKey(config.ampcode.upstreamApiKey) - : t('common.not_set'), - })} -
- -
- -
- setForm((prev) => ({ ...prev, forceModelMappings: value }))} - disabled={loading || saving} - /> -
{t('ai_providers.ampcode_force_model_mappings_hint')}
-
- -
- - { - setMappingsDirty(true); - setForm((prev) => ({ ...prev, mappingEntries: entries })); - }} - addLabel={t('ai_providers.ampcode_model_mappings_add_btn')} - namePlaceholder={t('ai_providers.ampcode_model_mappings_from_placeholder')} - aliasPlaceholder={t('ai_providers.ampcode_model_mappings_to_placeholder')} - disabled={loading || saving} - /> -
{t('ai_providers.ampcode_model_mappings_hint')}
-
-
- ); -} diff --git a/src/components/providers/ClaudeSection/ClaudeModal.tsx b/src/components/providers/ClaudeSection/ClaudeModal.tsx deleted file mode 100644 index 980a933..0000000 --- a/src/components/providers/ClaudeSection/ClaudeModal.tsx +++ /dev/null @@ -1,129 +0,0 @@ -import { useEffect, useState } from 'react'; -import { useTranslation } from 'react-i18next'; -import { Button } from '@/components/ui/Button'; -import { HeaderInputList } from '@/components/ui/HeaderInputList'; -import { Input } from '@/components/ui/Input'; -import { Modal } from '@/components/ui/Modal'; -import { ModelInputList } from '@/components/ui/ModelInputList'; -import { modelsToEntries } from '@/components/ui/modelInputListUtils'; -import type { ProviderKeyConfig } from '@/types'; -import { headersToEntries } from '@/utils/headers'; -import { excludedModelsToText } from '../utils'; -import type { ProviderFormState, ProviderModalProps } from '../types'; - -interface ClaudeModalProps extends ProviderModalProps { - isSaving: boolean; -} - -const buildEmptyForm = (): ProviderFormState => ({ - apiKey: '', - prefix: '', - baseUrl: '', - proxyUrl: '', - headers: [], - models: [], - excludedModels: [], - modelEntries: [{ name: '', alias: '' }], - excludedText: '', -}); - -export function ClaudeModal({ - isOpen, - editIndex, - initialData, - onClose, - onSave, - isSaving, -}: ClaudeModalProps) { - const { t } = useTranslation(); - const [form, setForm] = useState(buildEmptyForm); - - useEffect(() => { - if (!isOpen) return; - if (initialData) { - // eslint-disable-next-line react-hooks/set-state-in-effect - setForm({ - ...initialData, - headers: headersToEntries(initialData.headers), - modelEntries: modelsToEntries(initialData.models), - excludedText: excludedModelsToText(initialData.excludedModels), - }); - return; - } - setForm(buildEmptyForm()); - }, [initialData, isOpen]); - - return ( - - - - - } - > - setForm((prev) => ({ ...prev, apiKey: e.target.value }))} - /> - setForm((prev) => ({ ...prev, prefix: e.target.value }))} - hint={t('ai_providers.prefix_hint')} - /> - setForm((prev) => ({ ...prev, baseUrl: e.target.value }))} - /> - setForm((prev) => ({ ...prev, proxyUrl: e.target.value }))} - /> - setForm((prev) => ({ ...prev, headers: entries }))} - addLabel={t('common.custom_headers_add')} - keyPlaceholder={t('common.custom_headers_key_placeholder')} - valuePlaceholder={t('common.custom_headers_value_placeholder')} - /> -
- - setForm((prev) => ({ ...prev, modelEntries: entries }))} - addLabel={t('ai_providers.claude_models_add_btn')} - namePlaceholder={t('common.model_name_placeholder')} - aliasPlaceholder={t('common.model_alias_placeholder')} - disabled={isSaving} - /> -
-
- -