fix(ai-providers): keep custom header editing stable in modals

This commit is contained in:
Supra4E8C
2026-01-10 14:00:50 +08:00
parent 161d5d1e7f
commit 73ee6eb2f3
7 changed files with 39 additions and 34 deletions

14
package-lock.json generated
View File

@@ -3780,9 +3780,9 @@
} }
}, },
"node_modules/react-router": { "node_modules/react-router": {
"version": "7.10.1", "version": "7.12.0",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.10.1.tgz", "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.12.0.tgz",
"integrity": "sha512-gHL89dRa3kwlUYtRQ+m8NmxGI6CgqN+k4XyGjwcFoQwwCWF6xXpOCUlDovkXClS0d0XJN/5q7kc5W3kiFEd0Yw==", "integrity": "sha512-kTPDYPFzDVGIIGNLS5VJykK0HfHLY5MF3b+xj0/tTyNYL1gF1qs7u67Z9jEhQk2sQ98SUaHxlG31g1JtF7IfVw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"cookie": "^1.0.1", "cookie": "^1.0.1",
@@ -3802,12 +3802,12 @@
} }
}, },
"node_modules/react-router-dom": { "node_modules/react-router-dom": {
"version": "7.10.1", "version": "7.12.0",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.10.1.tgz", "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.12.0.tgz",
"integrity": "sha512-JNBANI6ChGVjA5bwsUIwJk7LHKmqB4JYnYfzFwyp2t12Izva11elds2jx7Yfoup2zssedntwU0oZ5DEmk5Sdaw==", "integrity": "sha512-pfO9fiBcpEfX4Tx+iTYKDtPbrSLLCbwJ5EqP+SPYQu1VYCXdy79GSj0wttR0U4cikVdlImZuEZ/9ZNCgoaxwBA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"react-router": "7.10.1" "react-router": "7.12.0"
}, },
"engines": { "engines": {
"node": ">=20.0.0" "node": ">=20.0.0"

View File

@@ -6,7 +6,7 @@ import { Input } from '@/components/ui/Input';
import { Modal } from '@/components/ui/Modal'; import { Modal } from '@/components/ui/Modal';
import { ModelInputList, modelsToEntries } from '@/components/ui/ModelInputList'; import { ModelInputList, modelsToEntries } from '@/components/ui/ModelInputList';
import type { ProviderKeyConfig } from '@/types'; import type { ProviderKeyConfig } from '@/types';
import { buildHeaderObject, headersToEntries } from '@/utils/headers'; import { headersToEntries } from '@/utils/headers';
import { excludedModelsToText } from '../utils'; import { excludedModelsToText } from '../utils';
import type { ProviderFormState, ProviderModalProps } from '../types'; import type { ProviderFormState, ProviderModalProps } from '../types';
@@ -19,7 +19,7 @@ const buildEmptyForm = (): ProviderFormState => ({
prefix: '', prefix: '',
baseUrl: '', baseUrl: '',
proxyUrl: '', proxyUrl: '',
headers: {}, headers: [],
models: [], models: [],
excludedModels: [], excludedModels: [],
modelEntries: [{ name: '', alias: '' }], modelEntries: [{ name: '', alias: '' }],
@@ -43,7 +43,7 @@ export function ClaudeModal({
// eslint-disable-next-line react-hooks/set-state-in-effect // eslint-disable-next-line react-hooks/set-state-in-effect
setForm({ setForm({
...initialData, ...initialData,
headers: initialData.headers ?? {}, headers: headersToEntries(initialData.headers),
modelEntries: modelsToEntries(initialData.models), modelEntries: modelsToEntries(initialData.models),
excludedText: excludedModelsToText(initialData.excludedModels), excludedText: excludedModelsToText(initialData.excludedModels),
}); });
@@ -95,8 +95,8 @@ export function ClaudeModal({
onChange={(e) => setForm((prev) => ({ ...prev, proxyUrl: e.target.value }))} onChange={(e) => setForm((prev) => ({ ...prev, proxyUrl: e.target.value }))}
/> />
<HeaderInputList <HeaderInputList
entries={headersToEntries(form.headers)} entries={form.headers}
onChange={(entries) => setForm((prev) => ({ ...prev, headers: buildHeaderObject(entries) }))} onChange={(entries) => setForm((prev) => ({ ...prev, headers: entries }))}
addLabel={t('common.custom_headers_add')} addLabel={t('common.custom_headers_add')}
keyPlaceholder={t('common.custom_headers_key_placeholder')} keyPlaceholder={t('common.custom_headers_key_placeholder')}
valuePlaceholder={t('common.custom_headers_value_placeholder')} valuePlaceholder={t('common.custom_headers_value_placeholder')}

View File

@@ -5,7 +5,7 @@ import { HeaderInputList } from '@/components/ui/HeaderInputList';
import { Input } from '@/components/ui/Input'; import { Input } from '@/components/ui/Input';
import { Modal } from '@/components/ui/Modal'; import { Modal } from '@/components/ui/Modal';
import type { ProviderKeyConfig } from '@/types'; import type { ProviderKeyConfig } from '@/types';
import { buildHeaderObject, headersToEntries } from '@/utils/headers'; import { headersToEntries } from '@/utils/headers';
import { modelsToEntries } from '@/components/ui/ModelInputList'; import { modelsToEntries } from '@/components/ui/ModelInputList';
import { excludedModelsToText } from '../utils'; import { excludedModelsToText } from '../utils';
import type { ProviderFormState, ProviderModalProps } from '../types'; import type { ProviderFormState, ProviderModalProps } from '../types';
@@ -19,7 +19,7 @@ const buildEmptyForm = (): ProviderFormState => ({
prefix: '', prefix: '',
baseUrl: '', baseUrl: '',
proxyUrl: '', proxyUrl: '',
headers: {}, headers: [],
models: [], models: [],
excludedModels: [], excludedModels: [],
modelEntries: [{ name: '', alias: '' }], modelEntries: [{ name: '', alias: '' }],
@@ -43,7 +43,7 @@ export function CodexModal({
// eslint-disable-next-line react-hooks/set-state-in-effect // eslint-disable-next-line react-hooks/set-state-in-effect
setForm({ setForm({
...initialData, ...initialData,
headers: initialData.headers ?? {}, headers: headersToEntries(initialData.headers),
modelEntries: modelsToEntries(initialData.models), modelEntries: modelsToEntries(initialData.models),
excludedText: excludedModelsToText(initialData.excludedModels), excludedText: excludedModelsToText(initialData.excludedModels),
}); });
@@ -95,8 +95,8 @@ export function CodexModal({
onChange={(e) => setForm((prev) => ({ ...prev, proxyUrl: e.target.value }))} onChange={(e) => setForm((prev) => ({ ...prev, proxyUrl: e.target.value }))}
/> />
<HeaderInputList <HeaderInputList
entries={headersToEntries(form.headers)} entries={form.headers}
onChange={(entries) => setForm((prev) => ({ ...prev, headers: buildHeaderObject(entries) }))} onChange={(entries) => setForm((prev) => ({ ...prev, headers: entries }))}
addLabel={t('common.custom_headers_add')} addLabel={t('common.custom_headers_add')}
keyPlaceholder={t('common.custom_headers_key_placeholder')} keyPlaceholder={t('common.custom_headers_key_placeholder')}
valuePlaceholder={t('common.custom_headers_value_placeholder')} valuePlaceholder={t('common.custom_headers_value_placeholder')}

View File

@@ -5,7 +5,7 @@ import { HeaderInputList } from '@/components/ui/HeaderInputList';
import { Input } from '@/components/ui/Input'; import { Input } from '@/components/ui/Input';
import { Modal } from '@/components/ui/Modal'; import { Modal } from '@/components/ui/Modal';
import type { GeminiKeyConfig } from '@/types'; import type { GeminiKeyConfig } from '@/types';
import { buildHeaderObject, headersToEntries } from '@/utils/headers'; import { headersToEntries } from '@/utils/headers';
import { excludedModelsToText } from '../utils'; import { excludedModelsToText } from '../utils';
import type { GeminiFormState, ProviderModalProps } from '../types'; import type { GeminiFormState, ProviderModalProps } from '../types';
@@ -17,7 +17,7 @@ const buildEmptyForm = (): GeminiFormState => ({
apiKey: '', apiKey: '',
prefix: '', prefix: '',
baseUrl: '', baseUrl: '',
headers: {}, headers: [],
excludedModels: [], excludedModels: [],
excludedText: '', excludedText: '',
}); });
@@ -39,7 +39,7 @@ export function GeminiModal({
// eslint-disable-next-line react-hooks/set-state-in-effect // eslint-disable-next-line react-hooks/set-state-in-effect
setForm({ setForm({
...initialData, ...initialData,
headers: initialData.headers ?? {}, headers: headersToEntries(initialData.headers),
excludedText: excludedModelsToText(initialData.excludedModels), excludedText: excludedModelsToText(initialData.excludedModels),
}); });
return; return;
@@ -91,8 +91,8 @@ export function GeminiModal({
onChange={(e) => setForm((prev) => ({ ...prev, baseUrl: e.target.value }))} onChange={(e) => setForm((prev) => ({ ...prev, baseUrl: e.target.value }))}
/> />
<HeaderInputList <HeaderInputList
entries={headersToEntries(form.headers)} entries={form.headers}
onChange={(entries) => setForm((prev) => ({ ...prev, headers: buildHeaderObject(entries) }))} onChange={(entries) => setForm((prev) => ({ ...prev, headers: entries }))}
addLabel={t('common.custom_headers_add')} addLabel={t('common.custom_headers_add')}
keyPlaceholder={t('common.custom_headers_key_placeholder')} keyPlaceholder={t('common.custom_headers_key_placeholder')}
valuePlaceholder={t('common.custom_headers_value_placeholder')} valuePlaceholder={t('common.custom_headers_value_placeholder')}

View File

@@ -6,7 +6,7 @@ import { Input } from '@/components/ui/Input';
import { Modal } from '@/components/ui/Modal'; import { Modal } from '@/components/ui/Modal';
import { ModelInputList, modelsToEntries } from '@/components/ui/ModelInputList'; import { ModelInputList, modelsToEntries } from '@/components/ui/ModelInputList';
import type { ProviderKeyConfig } from '@/types'; import type { ProviderKeyConfig } from '@/types';
import { buildHeaderObject, headersToEntries } from '@/utils/headers'; import { headersToEntries } from '@/utils/headers';
import type { ProviderModalProps, VertexFormState } from '../types'; import type { ProviderModalProps, VertexFormState } from '../types';
interface VertexModalProps extends ProviderModalProps<ProviderKeyConfig, VertexFormState> { interface VertexModalProps extends ProviderModalProps<ProviderKeyConfig, VertexFormState> {
@@ -18,7 +18,7 @@ const buildEmptyForm = (): VertexFormState => ({
prefix: '', prefix: '',
baseUrl: '', baseUrl: '',
proxyUrl: '', proxyUrl: '',
headers: {}, headers: [],
models: [], models: [],
modelEntries: [{ name: '', alias: '' }], modelEntries: [{ name: '', alias: '' }],
}); });
@@ -40,7 +40,7 @@ export function VertexModal({
// eslint-disable-next-line react-hooks/set-state-in-effect // eslint-disable-next-line react-hooks/set-state-in-effect
setForm({ setForm({
...initialData, ...initialData,
headers: initialData.headers ?? {}, headers: headersToEntries(initialData.headers),
modelEntries: modelsToEntries(initialData.models), modelEntries: modelsToEntries(initialData.models),
}); });
return; return;
@@ -94,8 +94,8 @@ export function VertexModal({
onChange={(e) => setForm((prev) => ({ ...prev, proxyUrl: e.target.value }))} onChange={(e) => setForm((prev) => ({ ...prev, proxyUrl: e.target.value }))}
/> />
<HeaderInputList <HeaderInputList
entries={headersToEntries(form.headers)} entries={form.headers}
onChange={(entries) => setForm((prev) => ({ ...prev, headers: buildHeaderObject(entries) }))} onChange={(entries) => setForm((prev) => ({ ...prev, headers: entries }))}
addLabel={t('common.custom_headers_add')} addLabel={t('common.custom_headers_add')}
keyPlaceholder={t('common.custom_headers_key_placeholder')} keyPlaceholder={t('common.custom_headers_key_placeholder')}
valuePlaceholder={t('common.custom_headers_value_placeholder')} valuePlaceholder={t('common.custom_headers_value_placeholder')}

View File

@@ -32,14 +32,19 @@ export interface AmpcodeFormState {
mappingEntries: ModelEntry[]; mappingEntries: ModelEntry[];
} }
export type GeminiFormState = GeminiKeyConfig & { excludedText: string }; export type GeminiFormState = Omit<GeminiKeyConfig, 'headers'> & {
headers: HeaderEntry[];
excludedText: string;
};
export type ProviderFormState = ProviderKeyConfig & { export type ProviderFormState = Omit<ProviderKeyConfig, 'headers'> & {
headers: HeaderEntry[];
modelEntries: ModelEntry[]; modelEntries: ModelEntry[];
excludedText: string; excludedText: string;
}; };
export type VertexFormState = Omit<ProviderKeyConfig, 'excludedModels'> & { export type VertexFormState = Omit<ProviderKeyConfig, 'headers' | 'excludedModels'> & {
headers: HeaderEntry[];
modelEntries: ModelEntry[]; modelEntries: ModelEntry[];
}; };

View File

@@ -23,7 +23,7 @@ import {
import { ampcodeApi, providersApi } from '@/services/api'; import { ampcodeApi, providersApi } from '@/services/api';
import { useAuthStore, useConfigStore, useNotificationStore, useThemeStore } from '@/stores'; import { useAuthStore, useConfigStore, useNotificationStore, useThemeStore } from '@/stores';
import type { GeminiKeyConfig, OpenAIProviderConfig, ProviderKeyConfig } from '@/types'; import type { GeminiKeyConfig, OpenAIProviderConfig, ProviderKeyConfig } from '@/types';
import { buildHeaderObject, headersToEntries } from '@/utils/headers'; import { buildHeaderObject } from '@/utils/headers';
import styles from './AiProvidersPage.module.scss'; import styles from './AiProvidersPage.module.scss';
export function AiProvidersPage() { export function AiProvidersPage() {
@@ -151,7 +151,7 @@ export function AiProvidersPage() {
apiKey: form.apiKey.trim(), apiKey: form.apiKey.trim(),
prefix: form.prefix?.trim() || undefined, prefix: form.prefix?.trim() || undefined,
baseUrl: form.baseUrl?.trim() || undefined, baseUrl: form.baseUrl?.trim() || undefined,
headers: buildHeaderObject(headersToEntries(form.headers)), headers: buildHeaderObject(form.headers),
excludedModels: parseExcludedModels(form.excludedText), excludedModels: parseExcludedModels(form.excludedText),
}; };
const nextList = const nextList =
@@ -307,7 +307,7 @@ export function AiProvidersPage() {
prefix: form.prefix?.trim() || undefined, prefix: form.prefix?.trim() || undefined,
baseUrl, baseUrl,
proxyUrl: form.proxyUrl?.trim() || undefined, proxyUrl: form.proxyUrl?.trim() || undefined,
headers: buildHeaderObject(headersToEntries(form.headers)), headers: buildHeaderObject(form.headers),
models: entriesToModels(form.modelEntries), models: entriesToModels(form.modelEntries),
excludedModels: parseExcludedModels(form.excludedText), excludedModels: parseExcludedModels(form.excludedText),
}; };
@@ -390,7 +390,7 @@ export function AiProvidersPage() {
prefix: form.prefix?.trim() || undefined, prefix: form.prefix?.trim() || undefined,
baseUrl, baseUrl,
proxyUrl: form.proxyUrl?.trim() || undefined, proxyUrl: form.proxyUrl?.trim() || undefined,
headers: buildHeaderObject(headersToEntries(form.headers)), headers: buildHeaderObject(form.headers),
models: form.modelEntries models: form.modelEntries
.map((entry) => { .map((entry) => {
const name = entry.name.trim(); const name = entry.name.trim();