From 73ee6eb2f319f91d23bec9c1e670b7c2bfaf9b1e Mon Sep 17 00:00:00 2001 From: Supra4E8C Date: Sat, 10 Jan 2026 14:00:50 +0800 Subject: [PATCH] fix(ai-providers): keep custom header editing stable in modals --- package-lock.json | 14 +++++++------- .../providers/ClaudeSection/ClaudeModal.tsx | 10 +++++----- .../providers/CodexSection/CodexModal.tsx | 10 +++++----- .../providers/GeminiSection/GeminiModal.tsx | 10 +++++----- .../providers/VertexSection/VertexModal.tsx | 10 +++++----- src/components/providers/types.ts | 11 ++++++++--- src/pages/AiProvidersPage.tsx | 8 ++++---- 7 files changed, 39 insertions(+), 34 deletions(-) diff --git a/package-lock.json b/package-lock.json index a6e9fd3..fcad3ab 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3780,9 +3780,9 @@ } }, "node_modules/react-router": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.10.1.tgz", - "integrity": "sha512-gHL89dRa3kwlUYtRQ+m8NmxGI6CgqN+k4XyGjwcFoQwwCWF6xXpOCUlDovkXClS0d0XJN/5q7kc5W3kiFEd0Yw==", + "version": "7.12.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.12.0.tgz", + "integrity": "sha512-kTPDYPFzDVGIIGNLS5VJykK0HfHLY5MF3b+xj0/tTyNYL1gF1qs7u67Z9jEhQk2sQ98SUaHxlG31g1JtF7IfVw==", "license": "MIT", "dependencies": { "cookie": "^1.0.1", @@ -3802,12 +3802,12 @@ } }, "node_modules/react-router-dom": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.10.1.tgz", - "integrity": "sha512-JNBANI6ChGVjA5bwsUIwJk7LHKmqB4JYnYfzFwyp2t12Izva11elds2jx7Yfoup2zssedntwU0oZ5DEmk5Sdaw==", + "version": "7.12.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.12.0.tgz", + "integrity": "sha512-pfO9fiBcpEfX4Tx+iTYKDtPbrSLLCbwJ5EqP+SPYQu1VYCXdy79GSj0wttR0U4cikVdlImZuEZ/9ZNCgoaxwBA==", "license": "MIT", "dependencies": { - "react-router": "7.10.1" + "react-router": "7.12.0" }, "engines": { "node": ">=20.0.0" diff --git a/src/components/providers/ClaudeSection/ClaudeModal.tsx b/src/components/providers/ClaudeSection/ClaudeModal.tsx index bf56c0a..7c5c5a1 100644 --- a/src/components/providers/ClaudeSection/ClaudeModal.tsx +++ b/src/components/providers/ClaudeSection/ClaudeModal.tsx @@ -6,7 +6,7 @@ import { Input } from '@/components/ui/Input'; import { Modal } from '@/components/ui/Modal'; import { ModelInputList, modelsToEntries } from '@/components/ui/ModelInputList'; import type { ProviderKeyConfig } from '@/types'; -import { buildHeaderObject, headersToEntries } from '@/utils/headers'; +import { headersToEntries } from '@/utils/headers'; import { excludedModelsToText } from '../utils'; import type { ProviderFormState, ProviderModalProps } from '../types'; @@ -19,7 +19,7 @@ const buildEmptyForm = (): ProviderFormState => ({ prefix: '', baseUrl: '', proxyUrl: '', - headers: {}, + headers: [], models: [], excludedModels: [], modelEntries: [{ name: '', alias: '' }], @@ -43,7 +43,7 @@ export function ClaudeModal({ // eslint-disable-next-line react-hooks/set-state-in-effect setForm({ ...initialData, - headers: initialData.headers ?? {}, + headers: headersToEntries(initialData.headers), modelEntries: modelsToEntries(initialData.models), excludedText: excludedModelsToText(initialData.excludedModels), }); @@ -95,8 +95,8 @@ export function ClaudeModal({ onChange={(e) => setForm((prev) => ({ ...prev, proxyUrl: e.target.value }))} /> setForm((prev) => ({ ...prev, headers: buildHeaderObject(entries) }))} + entries={form.headers} + onChange={(entries) => 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')} diff --git a/src/components/providers/CodexSection/CodexModal.tsx b/src/components/providers/CodexSection/CodexModal.tsx index 3a6f817..8e84bf2 100644 --- a/src/components/providers/CodexSection/CodexModal.tsx +++ b/src/components/providers/CodexSection/CodexModal.tsx @@ -5,7 +5,7 @@ import { HeaderInputList } from '@/components/ui/HeaderInputList'; import { Input } from '@/components/ui/Input'; import { Modal } from '@/components/ui/Modal'; import type { ProviderKeyConfig } from '@/types'; -import { buildHeaderObject, headersToEntries } from '@/utils/headers'; +import { headersToEntries } from '@/utils/headers'; import { modelsToEntries } from '@/components/ui/ModelInputList'; import { excludedModelsToText } from '../utils'; import type { ProviderFormState, ProviderModalProps } from '../types'; @@ -19,7 +19,7 @@ const buildEmptyForm = (): ProviderFormState => ({ prefix: '', baseUrl: '', proxyUrl: '', - headers: {}, + headers: [], models: [], excludedModels: [], modelEntries: [{ name: '', alias: '' }], @@ -43,7 +43,7 @@ export function CodexModal({ // eslint-disable-next-line react-hooks/set-state-in-effect setForm({ ...initialData, - headers: initialData.headers ?? {}, + headers: headersToEntries(initialData.headers), modelEntries: modelsToEntries(initialData.models), excludedText: excludedModelsToText(initialData.excludedModels), }); @@ -95,8 +95,8 @@ export function CodexModal({ onChange={(e) => setForm((prev) => ({ ...prev, proxyUrl: e.target.value }))} /> setForm((prev) => ({ ...prev, headers: buildHeaderObject(entries) }))} + entries={form.headers} + onChange={(entries) => 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')} diff --git a/src/components/providers/GeminiSection/GeminiModal.tsx b/src/components/providers/GeminiSection/GeminiModal.tsx index d9699ef..0b991b5 100644 --- a/src/components/providers/GeminiSection/GeminiModal.tsx +++ b/src/components/providers/GeminiSection/GeminiModal.tsx @@ -5,7 +5,7 @@ import { HeaderInputList } from '@/components/ui/HeaderInputList'; import { Input } from '@/components/ui/Input'; import { Modal } from '@/components/ui/Modal'; import type { GeminiKeyConfig } from '@/types'; -import { buildHeaderObject, headersToEntries } from '@/utils/headers'; +import { headersToEntries } from '@/utils/headers'; import { excludedModelsToText } from '../utils'; import type { GeminiFormState, ProviderModalProps } from '../types'; @@ -17,7 +17,7 @@ const buildEmptyForm = (): GeminiFormState => ({ apiKey: '', prefix: '', baseUrl: '', - headers: {}, + headers: [], excludedModels: [], excludedText: '', }); @@ -39,7 +39,7 @@ export function GeminiModal({ // eslint-disable-next-line react-hooks/set-state-in-effect setForm({ ...initialData, - headers: initialData.headers ?? {}, + headers: headersToEntries(initialData.headers), excludedText: excludedModelsToText(initialData.excludedModels), }); return; @@ -91,8 +91,8 @@ export function GeminiModal({ onChange={(e) => setForm((prev) => ({ ...prev, baseUrl: e.target.value }))} /> setForm((prev) => ({ ...prev, headers: buildHeaderObject(entries) }))} + entries={form.headers} + onChange={(entries) => 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')} diff --git a/src/components/providers/VertexSection/VertexModal.tsx b/src/components/providers/VertexSection/VertexModal.tsx index bc609a8..169af79 100644 --- a/src/components/providers/VertexSection/VertexModal.tsx +++ b/src/components/providers/VertexSection/VertexModal.tsx @@ -6,7 +6,7 @@ import { Input } from '@/components/ui/Input'; import { Modal } from '@/components/ui/Modal'; import { ModelInputList, modelsToEntries } from '@/components/ui/ModelInputList'; import type { ProviderKeyConfig } from '@/types'; -import { buildHeaderObject, headersToEntries } from '@/utils/headers'; +import { headersToEntries } from '@/utils/headers'; import type { ProviderModalProps, VertexFormState } from '../types'; interface VertexModalProps extends ProviderModalProps { @@ -18,7 +18,7 @@ const buildEmptyForm = (): VertexFormState => ({ prefix: '', baseUrl: '', proxyUrl: '', - headers: {}, + headers: [], models: [], modelEntries: [{ name: '', alias: '' }], }); @@ -40,7 +40,7 @@ export function VertexModal({ // eslint-disable-next-line react-hooks/set-state-in-effect setForm({ ...initialData, - headers: initialData.headers ?? {}, + headers: headersToEntries(initialData.headers), modelEntries: modelsToEntries(initialData.models), }); return; @@ -94,8 +94,8 @@ export function VertexModal({ onChange={(e) => setForm((prev) => ({ ...prev, proxyUrl: e.target.value }))} /> setForm((prev) => ({ ...prev, headers: buildHeaderObject(entries) }))} + entries={form.headers} + onChange={(entries) => 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')} diff --git a/src/components/providers/types.ts b/src/components/providers/types.ts index 825c51b..110a79c 100644 --- a/src/components/providers/types.ts +++ b/src/components/providers/types.ts @@ -32,14 +32,19 @@ export interface AmpcodeFormState { mappingEntries: ModelEntry[]; } -export type GeminiFormState = GeminiKeyConfig & { excludedText: string }; +export type GeminiFormState = Omit & { + headers: HeaderEntry[]; + excludedText: string; +}; -export type ProviderFormState = ProviderKeyConfig & { +export type ProviderFormState = Omit & { + headers: HeaderEntry[]; modelEntries: ModelEntry[]; excludedText: string; }; -export type VertexFormState = Omit & { +export type VertexFormState = Omit & { + headers: HeaderEntry[]; modelEntries: ModelEntry[]; }; diff --git a/src/pages/AiProvidersPage.tsx b/src/pages/AiProvidersPage.tsx index ee3388e..d437b9b 100644 --- a/src/pages/AiProvidersPage.tsx +++ b/src/pages/AiProvidersPage.tsx @@ -23,7 +23,7 @@ import { import { ampcodeApi, providersApi } from '@/services/api'; import { useAuthStore, useConfigStore, useNotificationStore, useThemeStore } from '@/stores'; import type { GeminiKeyConfig, OpenAIProviderConfig, ProviderKeyConfig } from '@/types'; -import { buildHeaderObject, headersToEntries } from '@/utils/headers'; +import { buildHeaderObject } from '@/utils/headers'; import styles from './AiProvidersPage.module.scss'; export function AiProvidersPage() { @@ -151,7 +151,7 @@ export function AiProvidersPage() { apiKey: form.apiKey.trim(), prefix: form.prefix?.trim() || undefined, baseUrl: form.baseUrl?.trim() || undefined, - headers: buildHeaderObject(headersToEntries(form.headers)), + headers: buildHeaderObject(form.headers), excludedModels: parseExcludedModels(form.excludedText), }; const nextList = @@ -307,7 +307,7 @@ export function AiProvidersPage() { prefix: form.prefix?.trim() || undefined, baseUrl, proxyUrl: form.proxyUrl?.trim() || undefined, - headers: buildHeaderObject(headersToEntries(form.headers)), + headers: buildHeaderObject(form.headers), models: entriesToModels(form.modelEntries), excludedModels: parseExcludedModels(form.excludedText), }; @@ -390,7 +390,7 @@ export function AiProvidersPage() { prefix: form.prefix?.trim() || undefined, baseUrl, proxyUrl: form.proxyUrl?.trim() || undefined, - headers: buildHeaderObject(headersToEntries(form.headers)), + headers: buildHeaderObject(form.headers), models: form.modelEntries .map((entry) => { const name = entry.name.trim();