From 1969de4e280e6fb6b662f79cee947925f9241d2c Mon Sep 17 00:00:00 2001 From: LTbinglingfeng Date: Mon, 15 Jun 2026 00:51:57 +0800 Subject: [PATCH] Refactor: Remove Ampcode integration and related configurations - Updated BaseProviderForm to accept all ProviderBrand types. - Removed Ampcode references from ProviderResource and related types. - Deleted ampcode API service and its associated methods. - Cleaned up transformers by removing Ampcode normalization functions. - Updated configuration store to eliminate Ampcode section. - Adjusted DashboardPage to remove Ampcode-related statistics. - Removed Ampcode localization strings from all language files. --- README.md | 1 - README_CN.md | 1 - src/assets/icons/amp.svg | 6 - .../providers/ProvidersWorkbenchPage.tsx | 35 +- src/features/providers/adapters.ts | 52 +-- src/features/providers/brandLogos.ts | 2 - .../components/ProviderCategoryList.tsx | 20 +- .../components/ProviderResourcePanel.tsx | 28 +- .../components/ProviderResourceTable.tsx | 77 +---- src/features/providers/descriptors.ts | 27 -- .../providers/sheets/ProviderSheet.tsx | 30 +- .../providers/sheets/forms/AmpcodeForm.tsx | 315 ------------------ .../sheets/forms/BaseProviderForm.tsx | 4 +- src/features/providers/types.ts | 11 +- .../providers/useProviderWorkbench.ts | 70 +--- src/i18n/locales/en.json | 33 +- src/i18n/locales/ru.json | 33 +- src/i18n/locales/zh-CN.json | 33 +- src/i18n/locales/zh-TW.json | 33 +- src/pages/DashboardPage.tsx | 14 - src/services/api/ampcode.ts | 59 ---- src/services/api/index.ts | 1 - src/services/api/transformers.ts | 88 +---- src/stores/useConfigStore.ts | 6 - src/types/ampcode.ts | 21 -- src/types/config.ts | 3 - src/types/index.ts | 1 - 27 files changed, 75 insertions(+), 929 deletions(-) delete mode 100644 src/assets/icons/amp.svg delete mode 100644 src/features/providers/sheets/forms/AmpcodeForm.tsx delete mode 100644 src/services/api/ampcode.ts delete mode 100644 src/types/ampcode.ts diff --git a/README.md b/README.md index 4b70ee2..873f59d 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,6 @@ Check the CLI Proxy API server documentation/config comments for the full authen - **AI Providers**: - Gemini/Codex/Claude/Vertex key entries (base URL, headers, proxy, model aliases, excluded models, prefix). - OpenAI-compatible providers (multiple API keys, custom headers, model alias import via `/v1/models`, optional browser-side "chat/completions" test). - - Ampcode integration (upstream URL/key, force mappings, model mapping table). - **Auth Files**: upload/download/delete JSON credentials, filter/search/pagination, runtime-only indicators, view supported models per credential (when the server supports it), manage OAuth excluded models (supports `*` wildcards), configure OAuth model alias mappings. - **OAuth**: start OAuth/device flows for Codex, Anthropic/Claude, Antigravity, Gemini CLI, Kimi, and xAI/Grok; poll status; submit callback URLs or xAI/Grok displayed codes; import Vertex JSON credentials and iFlow cookies. - **Quota Management**: manage quota limits and usage for Claude, Antigravity, Codex, Gemini CLI, and other providers. diff --git a/README_CN.md b/README_CN.md index 5d9ec6a..d4827b2 100644 --- a/README_CN.md +++ b/README_CN.md @@ -78,7 +78,6 @@ bun run build - **AI 提供商**: - Gemini/Codex/Claude/Vertex 配置(Base URL、Headers、代理、模型别名、排除模型、Prefix)。 - OpenAI 兼容提供商(多 Key、Header、自助从 `/v1/models` 拉取并导入模型别名、可选浏览器侧 `chat/completions` 测试)。 - - Ampcode 集成(上游地址/密钥、强制映射、模型映射表)。 - **认证文件**:上传/下载/删除 JSON 凭据,筛选/搜索/分页,标记 runtime-only;查看单个凭据可用模型(依赖后端支持);管理 OAuth 排除模型(支持 `*` 通配符);配置 OAuth 模型别名映射。 - **OAuth**:对 Codex、Anthropic/Claude、Antigravity、Gemini CLI、Kimi、xAI/Grok 发起 OAuth/设备码流程并轮询状态;支持提交回调 URL 或 xAI/Grok 页面显示的 code;包含 Vertex JSON 凭据导入与 iFlow Cookie 导入。 - **配额管理**:管理 Claude、Antigravity、Codex、Gemini CLI 等提供商的配额上限与使用情况。 diff --git a/src/assets/icons/amp.svg b/src/assets/icons/amp.svg deleted file mode 100644 index db56603..0000000 --- a/src/assets/icons/amp.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/src/features/providers/ProvidersWorkbenchPage.tsx b/src/features/providers/ProvidersWorkbenchPage.tsx index eef5dd2..bf05f64 100644 --- a/src/features/providers/ProvidersWorkbenchPage.tsx +++ b/src/features/providers/ProvidersWorkbenchPage.tsx @@ -79,7 +79,6 @@ const getResourceRecentSuccess = ( usageByProvider ).success; } - if (resource.brand === 'ampcode') return 0; return getProviderRecentWindowStats( usageByProvider, resource.brand, @@ -285,15 +284,8 @@ export function ProvidersWorkbenchPage() { const openCreate = useCallback(() => { const brand = activeBrand; - if (brand === 'ampcode') { - // ampcode 走单例编辑 - const r = - groups.find((g) => g.id === 'ampcode')?.resources[0] ?? null; - setSheetState({ open: true, brand: 'ampcode', mode: 'edit', resource: r }); - } else { - setSheetState({ open: true, brand, mode: 'create', resource: null }); - } - }, [activeBrand, groups]); + setSheetState({ open: true, brand, mode: 'create', resource: null }); + }, [activeBrand]); const openView = useCallback((resource: ProviderResource) => { setSheetState({ @@ -319,20 +311,13 @@ export function ProvidersWorkbenchPage() { const handleDelete = useCallback( (resource: ProviderResource) => { - const isAmpcode = resource.brand === 'ampcode'; const name = resource.name ?? resource.apiKeyPreview ?? resource.identifier ?? ''; showConfirmation({ - title: isAmpcode - ? t('providersPage.delete.ampcodeTitle') - : t('providersPage.delete.title'), - message: isAmpcode - ? t('providersPage.delete.ampcodeConfirm') - : t('providersPage.delete.confirm', { name }), + title: t('providersPage.delete.title'), + message: t('providersPage.delete.confirm', { name }), variant: 'danger', - confirmText: isAmpcode - ? t('providersPage.actions.clear') - : t('providersPage.actions.delete'), + confirmText: t('providersPage.actions.delete'), onConfirm: async () => { try { await workbench.deleteProvider(resource); @@ -408,8 +393,6 @@ export function ProvidersWorkbenchPage() { ); } - const ampcodeBrandActive = activeBrand === 'ampcode'; - return (
void handleRefresh()} onNew={openCreate} /> diff --git a/src/features/providers/adapters.ts b/src/features/providers/adapters.ts index a2b5b43..a8664f1 100644 --- a/src/features/providers/adapters.ts +++ b/src/features/providers/adapters.ts @@ -1,9 +1,4 @@ -import type { - AmpcodeConfig, - GeminiKeyConfig, - OpenAIProviderConfig, - ProviderKeyConfig, -} from '@/types'; +import type { GeminiKeyConfig, OpenAIProviderConfig, ProviderKeyConfig } from '@/types'; import { hasDisableAllModelsRule, stripDisableAllModelsRule, @@ -27,17 +22,6 @@ const collectModelNames = (models?: Array<{ name?: string }>): string[] => { return Array.from(seen); }; -const collectAmpcodeModelNames = (mappings: AmpcodeConfig['modelMappings']): string[] => { - const seen = new Set(); - (mappings ?? []).forEach((mapping) => { - const from = (mapping?.from ?? '').trim(); - const to = (mapping?.to ?? '').trim(); - if (from) seen.add(from); - if (to) seen.add(to); - }); - return Array.from(seen); -}; - const normalizePriority = (priority?: number): number => typeof priority === 'number' && Number.isFinite(priority) ? priority : 0; @@ -146,37 +130,3 @@ export function openaiToResource( raw: config, }; } - -export function ampcodeToResource(config?: AmpcodeConfig | null): ProviderResource { - const safe: AmpcodeConfig = config ?? {}; - const upstreamApiKey = safe.upstreamApiKey ?? ''; - const upstreamUrl = (safe.upstreamUrl ?? '').trim(); - const hasUpstream = upstreamUrl.length > 0; - const upstreamKeyMappingsCount = safe.upstreamApiKeys?.length ?? 0; - return { - id: 'ampcode:singleton', - brand: 'ampcode', - originalIndex: 0, - name: null, - identifier: 'Amp CLI', - apiKeyPreview: upstreamApiKey ? maskApiKey(upstreamApiKey) : null, - apiKey: upstreamApiKey || null, - authIndex: null, - baseUrl: upstreamUrl || null, - proxyUrl: null, - prefix: null, - modelCount: safe.modelMappings?.length ?? 0, - models: collectAmpcodeModelNames(safe.modelMappings), - priority: 0, - headerCount: 0, - excludedModelCount: 0, - apiKeyEntryCount: upstreamKeyMappingsCount, - disabled: !hasUpstream, - flags: { - forceModelMappings: safe.forceModelMappings === true, - isPlaceholder: !hasUpstream && upstreamKeyMappingsCount === 0, - }, - selector: { brand: 'ampcode' }, - raw: safe, - }; -} diff --git a/src/features/providers/brandLogos.ts b/src/features/providers/brandLogos.ts index 1507a45..7b7ec93 100644 --- a/src/features/providers/brandLogos.ts +++ b/src/features/providers/brandLogos.ts @@ -1,4 +1,3 @@ -import ampcodeLogo from '@/assets/icons/amp.svg'; import claudeLogo from '@/assets/icons/claude.svg'; import codexLogo from '@/assets/icons/codex.svg'; import geminiLogo from '@/assets/icons/gemini.svg'; @@ -17,5 +16,4 @@ export const PROVIDER_LOGOS: Record = { codex: { src: codexLogo }, vertex: { src: vertexLogo }, openaiCompatibility: { src: openaiLogo, invertOnDark: true }, - ampcode: { src: ampcodeLogo }, }; diff --git a/src/features/providers/components/ProviderCategoryList.tsx b/src/features/providers/components/ProviderCategoryList.tsx index cd8fb56..f86bf45 100644 --- a/src/features/providers/components/ProviderCategoryList.tsx +++ b/src/features/providers/components/ProviderCategoryList.tsx @@ -25,7 +25,7 @@ export function ProviderCategoryList({ const realResources = group.resources.filter( (r) => !r.flags.isPlaceholder ); - const total = realResources.length || (group.id === 'ampcode' ? 1 : 0); + const total = realResources.length; const activeCount = realResources.filter((r) => !r.disabled).length; const logo = PROVIDER_LOGOS[group.id]; const itemClass = `${styles.item} ${active ? styles.active : ''}`; @@ -52,25 +52,19 @@ export function ProviderCategoryList({ {t(`providersPage.providerNames.${group.id}`)} - {group.id === 'ampcode' - ? t( - group.resources[0]?.disabled - ? 'providersPage.categories.ampcodeInactive' - : 'providersPage.categories.ampcodeActive' - ) - : t('providersPage.categories.activeCount', { - active: activeCount, - total, - })} + {t('providersPage.categories.activeCount', { + active: activeCount, + total, + })} - {group.id === 'ampcode' ? (group.resources[0]?.disabled ? '—' : '1') : total} + {total} ); diff --git a/src/features/providers/components/ProviderResourcePanel.tsx b/src/features/providers/components/ProviderResourcePanel.tsx index 7935da1..8cfdcaa 100644 --- a/src/features/providers/components/ProviderResourcePanel.tsx +++ b/src/features/providers/components/ProviderResourcePanel.tsx @@ -73,20 +73,18 @@ export function ProviderResourcePanel({
- {group.id !== 'ampcode' ? ( -
- - onFilterChange(event.target.value)} - placeholder={t('providersPage.table.filterPlaceholder')} - /> -
- ) : null} +
+ + onFilterChange(event.target.value)} + placeholder={t('providersPage.table.filterPlaceholder')} + /> +
{toolbarControls ? (
@@ -104,7 +102,7 @@ export function ProviderResourcePanel({ ) : null}
- {realResources.length === 0 && group.id !== 'ampcode' ? ( + {realResources.length === 0 ? (
{t('providersPage.table.empty')}
diff --git a/src/features/providers/components/ProviderResourceTable.tsx b/src/features/providers/components/ProviderResourceTable.tsx index 50d9ec5..e421977 100644 --- a/src/features/providers/components/ProviderResourceTable.tsx +++ b/src/features/providers/components/ProviderResourceTable.tsx @@ -112,11 +112,6 @@ export function ProviderResourceTable({ renderMetric('keys', t('providersPage.table.metrics.keys'), r.apiKeyEntryCount), renderMetric('headers', t('providersPage.table.metrics.headers'), r.headerCount), ); - } else if (r.brand === 'ampcode') { - items.push( - renderMetric('mappings', t('providersPage.table.metrics.mappings'), r.modelCount), - renderMetric('keys', t('providersPage.table.metrics.keys'), r.apiKeyEntryCount), - ); } else { items.push( renderMetric('models', t('providersPage.table.metrics.models'), r.modelCount), @@ -133,14 +128,6 @@ export function ProviderResourceTable({ }; const renderStatus = (r: ProviderResource) => { - if (r.brand === 'ampcode' && r.flags.isPlaceholder) { - return ( - - - {t('providersPage.status.notConfigured')} - - ); - } if (r.disabled) { return ( @@ -169,16 +156,6 @@ export function ProviderResourceTable({
); } - if (r.brand === 'ampcode') { - return ( -
- Amp CLI - - {r.apiKeyPreview ?? t('providersPage.table.noFallbackKey')} - -
- ); - } return (
{r.apiKeyPreview ?? '—'} @@ -197,9 +174,6 @@ export function ProviderResourceTable({ ); } - if (r.brand === 'ampcode' && !r.baseUrl) { - return {t('providersPage.status.notConfigured')}; - } return ( {r.baseUrl ?? t('providersPage.status.notSet')} @@ -225,15 +199,12 @@ export function ProviderResourceTable({ {resources.map((resource) => { - const isAmpcode = resource.brand === 'ampcode'; return ( {renderPrimary(resource)} {renderBaseUrl(resource)} - {resource.brand === 'ampcode' ? ( - - ) : resource.prefix ? ( + {resource.prefix ? ( {resource.prefix} ) : ( {t('providersPage.status.none')} @@ -243,7 +214,7 @@ export function ProviderResourceTable({
{renderStatus(resource)} - {usageByProvider && resource.brand !== 'ampcode' ? ( + {usageByProvider ? ( <> {(() => { const stats = resolveTotalStats(resource, usageByProvider); @@ -268,7 +239,7 @@ export function ProviderResourceTable({
- {!isAmpcode && onToggleDisabled ? ( + {onToggleDisabled ? ( e.stopPropagation()} @@ -312,35 +283,19 @@ export function ProviderResourceTable({ > - {isAmpcode ? ( - - ) : ( - - )} +
diff --git a/src/features/providers/descriptors.ts b/src/features/providers/descriptors.ts index 48972ef..29cfab5 100644 --- a/src/features/providers/descriptors.ts +++ b/src/features/providers/descriptors.ts @@ -17,7 +17,6 @@ export interface ProviderDescriptor { supportsWebsockets: boolean; supportsCloak: boolean; supportsApiKeyEntries: boolean; - supportsAmpcodeMappings: boolean; /** Sheet 默认宽度 */ sheetSize: 'md' | 'lg' | 'xl'; } @@ -40,7 +39,6 @@ export const PROVIDER_DESCRIPTORS: Record = { supportsWebsockets: false, supportsCloak: false, supportsApiKeyEntries: false, - supportsAmpcodeMappings: false, sheetSize: 'md', }, codex: { @@ -60,7 +58,6 @@ export const PROVIDER_DESCRIPTORS: Record = { supportsWebsockets: true, supportsCloak: false, supportsApiKeyEntries: false, - supportsAmpcodeMappings: false, sheetSize: 'md', }, claude: { @@ -80,7 +77,6 @@ export const PROVIDER_DESCRIPTORS: Record = { supportsWebsockets: false, supportsCloak: true, supportsApiKeyEntries: false, - supportsAmpcodeMappings: false, sheetSize: 'md', }, vertex: { @@ -100,7 +96,6 @@ export const PROVIDER_DESCRIPTORS: Record = { supportsWebsockets: false, supportsCloak: false, supportsApiKeyEntries: false, - supportsAmpcodeMappings: false, sheetSize: 'md', }, openaiCompatibility: { @@ -120,27 +115,6 @@ export const PROVIDER_DESCRIPTORS: Record = { supportsWebsockets: false, supportsCloak: false, supportsApiKeyEntries: true, - supportsAmpcodeMappings: false, - sheetSize: 'lg', - }, - ampcode: { - id: 'ampcode', - supportsName: false, - supportsApiKey: false, - supportsDisabled: false, - supportsBaseUrl: true, - baseUrlRequired: false, - supportsProxyUrl: false, - supportsPrefix: false, - supportsModels: false, - supportsHeaders: false, - supportsExcludedModels: false, - supportsPriority: false, - supportsTestModel: false, - supportsWebsockets: false, - supportsCloak: false, - supportsApiKeyEntries: false, - supportsAmpcodeMappings: true, sheetSize: 'lg', }, }; @@ -151,5 +125,4 @@ export const PROVIDER_BRAND_ORDER: ProviderBrand[] = [ 'claude', 'vertex', 'openaiCompatibility', - 'ampcode', ]; diff --git a/src/features/providers/sheets/ProviderSheet.tsx b/src/features/providers/sheets/ProviderSheet.tsx index c63ca6c..afcc926 100644 --- a/src/features/providers/sheets/ProviderSheet.tsx +++ b/src/features/providers/sheets/ProviderSheet.tsx @@ -11,7 +11,6 @@ import type { ProviderResource, } from '../types'; import type { UseProviderWorkbenchResult } from '../useProviderWorkbench'; -import { AmpcodeForm } from './forms/AmpcodeForm'; import { BaseProviderForm } from './forms/BaseProviderForm'; import { ResourceDetailView } from './ResourceDetailView'; import styles from './forms/sharedForm.module.scss'; @@ -70,7 +69,6 @@ export function ProviderSheet({ }, []); const descriptor = PROVIDER_DESCRIPTORS[state.brand]; - const isAmpcode = state.brand === 'ampcode'; const isEditingForm = state.mode === 'create' || state.mode === 'edit'; const formMutating = submitting || mutationDisabled; const submitDisabled = formMutating || (state.mode === 'edit' && !isDirty); @@ -141,20 +139,6 @@ export function ProviderSheet({ [isDirty, mutationDisabled, onUpdated, state.resource, workbench] ); - const handleAmpcodeSubmit = useCallback( - async (config: Parameters[0]) => { - if (mutationDisabled || !isDirty) return; - setSubmitting(true); - try { - await workbench.saveAmpcode(config); - onUpdated(); - } finally { - setSubmitting(false); - } - }, - [isDirty, mutationDisabled, onUpdated, workbench] - ); - const renderBody = () => { if (state.mode === 'detail') { if (!state.resource) { @@ -163,22 +147,10 @@ export function ProviderSheet({ return ; } const formKey = `${state.brand}:${state.resource?.id ?? 'new'}:${state.mode}`; - if (isAmpcode) { - return ( - - ); - } return ( } + brand={state.brand} resource={state.resource} mode={state.mode} mutating={formMutating} diff --git a/src/features/providers/sheets/forms/AmpcodeForm.tsx b/src/features/providers/sheets/forms/AmpcodeForm.tsx deleted file mode 100644 index 49eafc3..0000000 --- a/src/features/providers/sheets/forms/AmpcodeForm.tsx +++ /dev/null @@ -1,315 +0,0 @@ -import { useEffect, useId, useMemo, useState } from 'react'; -import { useTranslation } from 'react-i18next'; -import { Collapsible } from '@/components/ui/Collapsible'; -import { IconPlus, IconX } from '@/components/ui/icons'; -import type { AmpcodeConfig, AmpcodeModelMapping, AmpcodeUpstreamApiKeyMapping } from '@/types'; -import type { ProviderResource } from '../../types'; -import styles from './sharedForm.module.scss'; - -interface AmpcodeFormState { - upstreamUrl: string; - upstreamApiKey: string; - forceModelMappings: boolean; - upstreamMappings: Array<{ upstreamApiKey: string; clientKeysText: string }>; - modelMappings: Array<{ from: string; to: string }>; -} - -const emptyUpstream = () => ({ upstreamApiKey: '', clientKeysText: '' }); -const emptyModelMapping = () => ({ from: '', to: '' }); - -function buildState(config?: AmpcodeConfig | null): AmpcodeFormState { - const safe = config ?? {}; - const upstreamMappings = (safe.upstreamApiKeys ?? []).length - ? (safe.upstreamApiKeys ?? []).map((m) => ({ - upstreamApiKey: m.upstreamApiKey ?? '', - clientKeysText: (m.apiKeys ?? []).join('\n'), - })) - : [emptyUpstream()]; - const modelMappings = (safe.modelMappings ?? []).length - ? (safe.modelMappings ?? []).map((m) => ({ from: m.from ?? '', to: m.to ?? '' })) - : [emptyModelMapping()]; - return { - upstreamUrl: safe.upstreamUrl ?? '', - upstreamApiKey: '', - forceModelMappings: safe.forceModelMappings === true, - upstreamMappings, - modelMappings, - }; -} - -const parseClientKeys = (text: string): string[] => - text - .split(/[\n,]+/) - .map((s) => s.trim()) - .filter(Boolean); - -interface AmpcodeFormProps { - resource: ProviderResource | null; - mutating: boolean; - formId: string; - onSubmit: (config: AmpcodeConfig) => Promise; - onDirtyChange?: (dirty: boolean) => void; -} - -export function AmpcodeForm({ - resource, - mutating, - formId, - onSubmit, - onDirtyChange, -}: AmpcodeFormProps) { - const { t } = useTranslation(); - const fid = useId(); - const initialConfig = (resource?.raw as AmpcodeConfig | undefined) ?? {}; - const [form, setForm] = useState(() => buildState(initialConfig)); - const [initialFormSignature] = useState(() => JSON.stringify(buildState(initialConfig))); - const [error, setError] = useState(null); - - const isDirty = useMemo( - () => JSON.stringify(form) !== initialFormSignature, - [form, initialFormSignature] - ); - - useEffect(() => { - onDirtyChange?.(isDirty); - }, [isDirty, onDirtyChange]); - - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - try { - setError(null); - const upstreamApiKeys: AmpcodeUpstreamApiKeyMapping[] = []; - const seen = new Set(); - form.upstreamMappings.forEach((m) => { - const key = m.upstreamApiKey.trim(); - if (!key || seen.has(key)) return; - const clientKeys = parseClientKeys(m.clientKeysText); - if (!clientKeys.length) return; - seen.add(key); - upstreamApiKeys.push({ upstreamApiKey: key, apiKeys: clientKeys }); - }); - - const modelMappings: AmpcodeModelMapping[] = []; - const seenFrom = new Set(); - form.modelMappings.forEach((m) => { - const from = m.from.trim(); - const to = m.to.trim(); - if (!from || !to) return; - const id = from.toLowerCase(); - if (seenFrom.has(id)) return; - seenFrom.add(id); - modelMappings.push({ from, to }); - }); - - const next: AmpcodeConfig = { - upstreamUrl: form.upstreamUrl.trim() || undefined, - upstreamApiKey: - form.upstreamApiKey.trim() || initialConfig.upstreamApiKey?.trim() || undefined, - upstreamApiKeys: upstreamApiKeys.length ? upstreamApiKeys : undefined, - modelMappings: modelMappings.length ? modelMappings : undefined, - forceModelMappings: form.forceModelMappings, - }; - await onSubmit(next); - } catch (err) { - setError(err instanceof Error ? err.message : String(err)); - } - }; - - return ( -
-
-
- - setForm((s) => ({ ...s, upstreamUrl: e.target.value }))} - placeholder="https://api.ampcode.com" - disabled={mutating} - /> -
-
- - setForm((s) => ({ ...s, upstreamApiKey: e.target.value }))} - autoComplete="new-password" - data-1p-ignore="true" - data-lpignore="true" - data-bwignore="true" - disabled={mutating} - /> -
- -
- - -
- {form.upstreamMappings.map((m, idx) => ( -
-
- {t('providersPage.ampcode.mappingRow', { index: idx + 1 })} - -
-
- - - setForm((s) => ({ - ...s, - upstreamMappings: s.upstreamMappings.map((it, i) => - i === idx ? { ...it, upstreamApiKey: e.target.value } : it - ), - })) - } - disabled={mutating} - /> -
-
- -