mirror of
https://github.com/router-for-me/Cli-Proxy-API-Management-Center.git
synced 2026-06-16 21:03:58 +08:00
refactor(forms): streamline imports and improve state handling for API keys
This commit is contained in:
@@ -2,11 +2,7 @@ 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 { AmpcodeConfig, AmpcodeModelMapping, AmpcodeUpstreamApiKeyMapping } from '@/types';
|
||||
import type { ProviderResource } from '../../types';
|
||||
import styles from './sharedForm.module.scss';
|
||||
|
||||
@@ -34,7 +30,7 @@ function buildState(config?: AmpcodeConfig | null): AmpcodeFormState {
|
||||
: [emptyModelMapping()];
|
||||
return {
|
||||
upstreamUrl: safe.upstreamUrl ?? '',
|
||||
upstreamApiKey: safe.upstreamApiKey ?? '',
|
||||
upstreamApiKey: '',
|
||||
forceModelMappings: safe.forceModelMappings === true,
|
||||
upstreamMappings,
|
||||
modelMappings,
|
||||
@@ -66,9 +62,7 @@ export function AmpcodeForm({
|
||||
const fid = useId();
|
||||
const initialConfig = (resource?.raw as AmpcodeConfig | undefined) ?? {};
|
||||
const [form, setForm] = useState<AmpcodeFormState>(() => buildState(initialConfig));
|
||||
const [initialFormSignature] = useState<string>(() =>
|
||||
JSON.stringify(buildState(initialConfig))
|
||||
);
|
||||
const [initialFormSignature] = useState<string>(() => JSON.stringify(buildState(initialConfig)));
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const isDirty = useMemo(
|
||||
@@ -109,7 +103,8 @@ export function AmpcodeForm({
|
||||
|
||||
const next: AmpcodeConfig = {
|
||||
upstreamUrl: form.upstreamUrl.trim() || undefined,
|
||||
upstreamApiKey: form.upstreamApiKey.trim() || undefined,
|
||||
upstreamApiKey:
|
||||
form.upstreamApiKey.trim() || initialConfig.upstreamApiKey?.trim() || undefined,
|
||||
upstreamApiKeys: upstreamApiKeys.length ? upstreamApiKeys : undefined,
|
||||
modelMappings: modelMappings.length ? modelMappings : undefined,
|
||||
forceModelMappings: form.forceModelMappings,
|
||||
@@ -149,9 +144,7 @@ export function AmpcodeForm({
|
||||
className={styles.input}
|
||||
type="password"
|
||||
value={form.upstreamApiKey}
|
||||
onChange={(e) =>
|
||||
setForm((s) => ({ ...s, upstreamApiKey: e.target.value }))
|
||||
}
|
||||
onChange={(e) => setForm((s) => ({ ...s, upstreamApiKey: e.target.value }))}
|
||||
autoComplete="new-password"
|
||||
data-1p-ignore="true"
|
||||
data-lpignore="true"
|
||||
@@ -165,9 +158,7 @@ export function AmpcodeForm({
|
||||
className={styles.checkboxBox}
|
||||
checked={form.forceModelMappings}
|
||||
disabled={mutating}
|
||||
onChange={(e) =>
|
||||
setForm((s) => ({ ...s, forceModelMappings: e.target.checked }))
|
||||
}
|
||||
onChange={(e) => setForm((s) => ({ ...s, forceModelMappings: e.target.checked }))}
|
||||
/>
|
||||
<span className={styles.checkboxText}>
|
||||
<span>{t('providersPage.ampcode.forceModelMappings')}</span>
|
||||
@@ -197,9 +188,7 @@ export function AmpcodeForm({
|
||||
</button>
|
||||
</div>
|
||||
<div className={styles.field}>
|
||||
<label className={styles.label}>
|
||||
{t('providersPage.ampcode.upstreamApiKey')}
|
||||
</label>
|
||||
<label className={styles.label}>{t('providersPage.ampcode.upstreamApiKey')}</label>
|
||||
<input
|
||||
className={styles.input}
|
||||
value={m.upstreamApiKey}
|
||||
@@ -259,10 +248,7 @@ export function AmpcodeForm({
|
||||
<Collapsible label={t('providersPage.ampcode.modelMappingsSection')}>
|
||||
<div className={styles.entriesList}>
|
||||
{form.modelMappings.map((m, idx) => (
|
||||
<div
|
||||
key={idx}
|
||||
style={{ display: 'grid', gridTemplateColumns: '1fr 1fr auto', gap: 8 }}
|
||||
>
|
||||
<div key={idx} style={{ display: 'grid', gridTemplateColumns: '1fr 1fr auto', gap: 8 }}>
|
||||
<input
|
||||
className={styles.input}
|
||||
placeholder="from"
|
||||
|
||||
@@ -13,11 +13,7 @@ import {
|
||||
import { Collapsible } from '@/components/ui/Collapsible';
|
||||
import { Select } from '@/components/ui/Select';
|
||||
import { hasDisableAllModelsRule } from '@/components/providers/utils';
|
||||
import type {
|
||||
GeminiKeyConfig,
|
||||
OpenAIProviderConfig,
|
||||
ProviderKeyConfig,
|
||||
} from '@/types';
|
||||
import type { GeminiKeyConfig, OpenAIProviderConfig, ProviderKeyConfig } from '@/types';
|
||||
import type { ModelInfo } from '@/utils/models';
|
||||
import { PROVIDER_DESCRIPTORS } from '../../descriptors';
|
||||
import type {
|
||||
@@ -79,13 +75,9 @@ function buildInitialForm(
|
||||
excludedModelsText: '',
|
||||
websockets: brand === 'codex' ? false : undefined,
|
||||
cloak:
|
||||
brand === 'claude'
|
||||
? { mode: '', strictMode: false, sensitiveWordsText: '' }
|
||||
: undefined,
|
||||
testModel:
|
||||
brand === 'openaiCompatibility' || brand === 'claude' ? '' : undefined,
|
||||
apiKeyEntries:
|
||||
brand === 'openaiCompatibility' ? [emptyApiKeyEntry()] : undefined,
|
||||
brand === 'claude' ? { mode: '', strictMode: false, sensitiveWordsText: '' } : undefined,
|
||||
testModel: brand === 'openaiCompatibility' || brand === 'claude' ? '' : undefined,
|
||||
apiKeyEntries: brand === 'openaiCompatibility' ? [emptyApiKeyEntry()] : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -115,7 +107,8 @@ function buildInitialForm(
|
||||
testModel: cfg.testModel ?? '',
|
||||
apiKeyEntries: cfg.apiKeyEntries?.length
|
||||
? cfg.apiKeyEntries.map((entry) => ({
|
||||
apiKey: entry.apiKey,
|
||||
apiKey: '',
|
||||
existingApiKey: entry.apiKey,
|
||||
proxyUrl: entry.proxyUrl ?? '',
|
||||
authIndex: entry.authIndex,
|
||||
}))
|
||||
@@ -150,15 +143,13 @@ function buildInitialForm(
|
||||
? Object.entries(cfg.headers).map(([k, v]) => ({ key: k, value: String(v) }))
|
||||
: [emptyHeader()],
|
||||
excludedModelsText: excludedList.join('\n'),
|
||||
websockets:
|
||||
brand === 'codex' ? (cfg as ProviderKeyConfig).websockets === true : undefined,
|
||||
websockets: brand === 'codex' ? (cfg as ProviderKeyConfig).websockets === true : undefined,
|
||||
cloak:
|
||||
brand === 'claude'
|
||||
? {
|
||||
mode: (cfg as ProviderKeyConfig).cloak?.mode ?? '',
|
||||
strictMode: (cfg as ProviderKeyConfig).cloak?.strictMode === true,
|
||||
sensitiveWordsText:
|
||||
(cfg as ProviderKeyConfig).cloak?.sensitiveWords?.join('\n') ?? '',
|
||||
sensitiveWordsText: (cfg as ProviderKeyConfig).cloak?.sensitiveWords?.join('\n') ?? '',
|
||||
}
|
||||
: undefined,
|
||||
testModel: brand === 'claude' ? '' : undefined,
|
||||
@@ -241,9 +232,7 @@ export function BaseProviderForm({
|
||||
|
||||
const fallbackAuthIndex = useMemo(() => {
|
||||
if (mode !== 'edit' || !resource) return '';
|
||||
return (
|
||||
(resource.raw as { authIndex?: string } | undefined)?.authIndex ?? ''
|
||||
);
|
||||
return (resource.raw as { authIndex?: string } | undefined)?.authIndex ?? '';
|
||||
}, [mode, resource]);
|
||||
|
||||
const connectivityMessages = useMemo<ConnectivityErrorMessages>(
|
||||
@@ -252,8 +241,7 @@ export function BaseProviderForm({
|
||||
endpointInvalid: t('providersPage.connectivity.endpointInvalid'),
|
||||
apiKeyRequired: t('providersPage.connectivity.apiKeyRequired'),
|
||||
modelRequired: t('providersPage.connectivity.modelRequired'),
|
||||
timeout: (seconds: number) =>
|
||||
t('providersPage.connectivity.timeout', { seconds }),
|
||||
timeout: (seconds: number) => t('providersPage.connectivity.timeout', { seconds }),
|
||||
requestFailed: t('providersPage.connectivity.requestFailed'),
|
||||
}),
|
||||
[t]
|
||||
@@ -307,9 +295,7 @@ export function BaseProviderForm({
|
||||
const autoLabel = firstName
|
||||
? t('providersPage.form.testModelAutoWith', { name: firstName })
|
||||
: t('providersPage.form.testModelAutoEmpty');
|
||||
const opts: Array<{ value: string; label: string }> = [
|
||||
{ value: '', label: autoLabel },
|
||||
];
|
||||
const opts: Array<{ value: string; label: string }> = [{ value: '', label: autoLabel }];
|
||||
names.forEach((n) => opts.push({ value: n, label: n }));
|
||||
const tm = (form.testModel ?? '').trim();
|
||||
if (tm && !seen.has(tm)) {
|
||||
@@ -389,11 +375,7 @@ export function BaseProviderForm({
|
||||
if (descriptor.supportsName && !form.name.trim()) {
|
||||
return t('providersPage.form.validation.nameRequired');
|
||||
}
|
||||
if (
|
||||
descriptor.supportsApiKey &&
|
||||
mode === 'create' &&
|
||||
!form.apiKey.trim()
|
||||
) {
|
||||
if (descriptor.supportsApiKey && mode === 'create' && !form.apiKey.trim()) {
|
||||
return t('providersPage.form.validation.apiKeyRequired');
|
||||
}
|
||||
if (descriptor.baseUrlRequired && !form.baseUrl.trim()) {
|
||||
@@ -436,9 +418,7 @@ export function BaseProviderForm({
|
||||
);
|
||||
const apiKeyEntries = useMemo(
|
||||
() =>
|
||||
form.apiKeyEntries && form.apiKeyEntries.length
|
||||
? form.apiKeyEntries
|
||||
: [emptyApiKeyEntry()],
|
||||
form.apiKeyEntries && form.apiKeyEntries.length ? form.apiKeyEntries : [emptyApiKeyEntry()],
|
||||
[form.apiKeyEntries]
|
||||
);
|
||||
|
||||
@@ -519,11 +499,7 @@ export function BaseProviderForm({
|
||||
: t('providersPage.form.showApiKey')
|
||||
}
|
||||
>
|
||||
{showSingleApiKey ? (
|
||||
<IconEyeOff size={16} />
|
||||
) : (
|
||||
<IconEye size={16} />
|
||||
)}
|
||||
{showSingleApiKey ? <IconEyeOff size={16} /> : <IconEye size={16} />}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -647,9 +623,7 @@ export function BaseProviderForm({
|
||||
</div>
|
||||
) : null}
|
||||
{brand === 'claude' && connectivity.claudeStatus.state === 'error' ? (
|
||||
<div className={styles.connectivityError}>
|
||||
{connectivity.claudeStatus.message}
|
||||
</div>
|
||||
<div className={styles.connectivityError}>{connectivity.claudeStatus.message}</div>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
@@ -690,7 +664,9 @@ export function BaseProviderForm({
|
||||
{descriptor.supportsApiKeyEntries && form.apiKeyEntries ? (
|
||||
<Collapsible
|
||||
label={t('providersPage.form.apiKeyEntriesSection')}
|
||||
hint={`${apiKeyEntries.filter((e) => e.apiKey.trim()).length}`}
|
||||
hint={`${
|
||||
apiKeyEntries.filter((e) => e.apiKey.trim() || e.existingApiKey?.trim()).length
|
||||
}`}
|
||||
defaultOpen
|
||||
>
|
||||
<div className={styles.entriesList}>
|
||||
@@ -700,9 +676,7 @@ export function BaseProviderForm({
|
||||
type="button"
|
||||
className={styles.addBtn}
|
||||
disabled={mutating}
|
||||
onClick={() =>
|
||||
updateField('apiKeyEntries', [...apiKeyEntries, emptyApiKeyEntry()])
|
||||
}
|
||||
onClick={() => updateField('apiKeyEntries', [...apiKeyEntries, emptyApiKeyEntry()])}
|
||||
>
|
||||
<IconPlus size={12} />
|
||||
<span>{t('providersPage.form.addApiKeyEntry')}</span>
|
||||
@@ -731,9 +705,7 @@ export function BaseProviderForm({
|
||||
return (
|
||||
<div key={realIdx} className={styles.entryCard}>
|
||||
<div className={styles.entryCardHeader}>
|
||||
<span>
|
||||
{t('providersPage.form.apiKeyEntry', { index: realIdx + 1 })}
|
||||
</span>
|
||||
<span>{t('providersPage.form.apiKeyEntry', { index: realIdx + 1 })}</span>
|
||||
<div className={styles.entryCardHeaderRight}>
|
||||
<ConnectivityStatusIcon state={status.state} />
|
||||
<button
|
||||
@@ -760,9 +732,7 @@ export function BaseProviderForm({
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.field}>
|
||||
<label className={styles.label}>
|
||||
{t('providersPage.form.apiKey')}
|
||||
</label>
|
||||
<label className={styles.label}>{t('providersPage.form.apiKey')}</label>
|
||||
<div className={styles.passwordField}>
|
||||
<input
|
||||
className={styles.passwordInput}
|
||||
@@ -781,7 +751,11 @@ export function BaseProviderForm({
|
||||
data-lpignore="true"
|
||||
data-bwignore="true"
|
||||
disabled={mutating}
|
||||
placeholder={t('providersPage.form.apiKeyCreatePlaceholder')}
|
||||
placeholder={
|
||||
entry.existingApiKey
|
||||
? t('providersPage.form.apiKeyEditPlaceholder')
|
||||
: t('providersPage.form.apiKeyCreatePlaceholder')
|
||||
}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
@@ -808,9 +782,7 @@ export function BaseProviderForm({
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.field}>
|
||||
<label className={styles.label}>
|
||||
{t('providersPage.form.proxyUrl')}
|
||||
</label>
|
||||
<label className={styles.label}>{t('providersPage.form.proxyUrl')}</label>
|
||||
<input
|
||||
className={styles.input}
|
||||
value={entry.proxyUrl}
|
||||
@@ -827,9 +799,7 @@ export function BaseProviderForm({
|
||||
/>
|
||||
</div>
|
||||
{status.state === 'error' ? (
|
||||
<div className={styles.connectivityError}>
|
||||
{status.message}
|
||||
</div>
|
||||
<div className={styles.connectivityError}>{status.message}</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
@@ -853,9 +823,7 @@ export function BaseProviderForm({
|
||||
onChange={(e) =>
|
||||
updateField(
|
||||
'headers',
|
||||
headersList.map((it, i) =>
|
||||
i === idx ? { ...it, key: e.target.value } : it
|
||||
)
|
||||
headersList.map((it, i) => (i === idx ? { ...it, key: e.target.value } : it))
|
||||
)
|
||||
}
|
||||
disabled={mutating}
|
||||
@@ -945,9 +913,7 @@ export function BaseProviderForm({
|
||||
onChange={(e) =>
|
||||
updateField(
|
||||
'models',
|
||||
modelsList.map((it, i) =>
|
||||
i === idx ? { ...it, name: e.target.value } : it
|
||||
)
|
||||
modelsList.map((it, i) => (i === idx ? { ...it, name: e.target.value } : it))
|
||||
)
|
||||
}
|
||||
disabled={mutating}
|
||||
@@ -959,9 +925,7 @@ export function BaseProviderForm({
|
||||
onChange={(e) =>
|
||||
updateField(
|
||||
'models',
|
||||
modelsList.map((it, i) =>
|
||||
i === idx ? { ...it, alias: e.target.value } : it
|
||||
)
|
||||
modelsList.map((it, i) => (i === idx ? { ...it, alias: e.target.value } : it))
|
||||
)
|
||||
}
|
||||
disabled={mutating}
|
||||
@@ -997,9 +961,7 @@ export function BaseProviderForm({
|
||||
{descriptor.supportsExcludedModels ? (
|
||||
<Collapsible label={t('providersPage.form.excludedSection')}>
|
||||
<div className={styles.field}>
|
||||
<span className={styles.labelHint}>
|
||||
{t('providersPage.form.excludedHint')}
|
||||
</span>
|
||||
<span className={styles.labelHint}>{t('providersPage.form.excludedHint')}</span>
|
||||
<textarea
|
||||
className={styles.textarea}
|
||||
rows={4}
|
||||
@@ -1016,9 +978,7 @@ export function BaseProviderForm({
|
||||
<Collapsible label={t('providersPage.form.cloakSection')}>
|
||||
<div className={styles.section}>
|
||||
<div className={styles.field}>
|
||||
<label className={styles.label}>
|
||||
{t('providersPage.form.cloakMode')}
|
||||
</label>
|
||||
<label className={styles.label}>{t('providersPage.form.cloakMode')}</label>
|
||||
<input
|
||||
className={styles.input}
|
||||
value={form.cloak.mode}
|
||||
@@ -1040,16 +1000,12 @@ export function BaseProviderForm({
|
||||
</span>
|
||||
</label>
|
||||
<div className={styles.field}>
|
||||
<label className={styles.label}>
|
||||
{t('providersPage.form.cloakSensitiveWords')}
|
||||
</label>
|
||||
<label className={styles.label}>{t('providersPage.form.cloakSensitiveWords')}</label>
|
||||
<textarea
|
||||
className={styles.textarea}
|
||||
rows={3}
|
||||
value={form.cloak.sensitiveWordsText}
|
||||
onChange={(e) =>
|
||||
updateCloak('sensitiveWordsText', e.target.value)
|
||||
}
|
||||
onChange={(e) => updateCloak('sensitiveWordsText', e.target.value)}
|
||||
disabled={mutating}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -5,11 +5,7 @@ import {
|
||||
buildOpenAIChatCompletionsEndpoint,
|
||||
} from '@/components/providers/utils';
|
||||
import { buildHeaderObject, hasHeader } from '@/utils/headers';
|
||||
import type {
|
||||
ApiKeyEntryInput,
|
||||
ModelEntryInput,
|
||||
ProviderBrand,
|
||||
} from '../../types';
|
||||
import type { ApiKeyEntryInput, ModelEntryInput, ProviderBrand } from '../../types';
|
||||
|
||||
const DEFAULT_TIMEOUT_MS = 30_000;
|
||||
const DEFAULT_ANTHROPIC_VERSION = '2023-06-01';
|
||||
@@ -29,10 +25,7 @@ const errorMessage = (err: unknown): string => {
|
||||
return '';
|
||||
};
|
||||
|
||||
const pickModel = (
|
||||
testModel: string | undefined,
|
||||
models: ModelEntryInput[]
|
||||
): string => {
|
||||
const pickModel = (testModel: string | undefined, models: ModelEntryInput[]): string => {
|
||||
const trimmed = (testModel ?? '').trim();
|
||||
if (trimmed) return trimmed;
|
||||
for (const m of models) {
|
||||
@@ -43,9 +36,7 @@ const pickModel = (
|
||||
};
|
||||
|
||||
const resolveBearerToken = (headers: Record<string, string>): string => {
|
||||
const auth = Object.entries(headers).find(
|
||||
([k]) => k.toLowerCase() === 'authorization'
|
||||
)?.[1];
|
||||
const auth = Object.entries(headers).find(([k]) => k.toLowerCase() === 'authorization')?.[1];
|
||||
if (!auth) return '';
|
||||
const match = String(auth).match(/^Bearer\s+(.+)$/i);
|
||||
return match ? match[1].trim() : '';
|
||||
@@ -99,21 +90,21 @@ export function useConnectivityTest(
|
||||
|
||||
const entriesCount = apiKeyEntries?.length ?? 0;
|
||||
|
||||
const [openaiStatuses, setOpenaiStatuses] = useState<ConnectivityStatus[]>(
|
||||
() => Array.from({ length: entriesCount }, () => IDLE)
|
||||
const [openaiStatuses, setOpenaiStatuses] = useState<ConnectivityStatus[]>(() =>
|
||||
Array.from({ length: entriesCount }, () => IDLE)
|
||||
);
|
||||
const [claudeStatus, setClaudeStatus] = useState<ConnectivityStatus>(IDLE);
|
||||
const [inFlight, setInFlight] = useState(0);
|
||||
|
||||
const entrySignatures = useMemo(
|
||||
() =>
|
||||
(apiKeyEntries ?? []).map(
|
||||
(entry) =>
|
||||
[
|
||||
entry.apiKey ?? '',
|
||||
entry.authIndex ?? '',
|
||||
entry.proxyUrl ?? '',
|
||||
].join('||')
|
||||
(apiKeyEntries ?? []).map((entry) =>
|
||||
[
|
||||
entry.apiKey ?? '',
|
||||
entry.existingApiKey ?? '',
|
||||
entry.authIndex ?? '',
|
||||
entry.proxyUrl ?? '',
|
||||
].join('||')
|
||||
),
|
||||
[apiKeyEntries]
|
||||
);
|
||||
@@ -153,16 +144,13 @@ export function useConnectivityTest(
|
||||
setClaudeStatus(IDLE);
|
||||
}, [signature]);
|
||||
|
||||
const updateOpenaiStatus = useCallback(
|
||||
(idx: number, value: ConnectivityStatus) => {
|
||||
setOpenaiStatuses((prev) => {
|
||||
const next = [...prev];
|
||||
next[idx] = value;
|
||||
return next;
|
||||
});
|
||||
},
|
||||
[]
|
||||
);
|
||||
const updateOpenaiStatus = useCallback((idx: number, value: ConnectivityStatus) => {
|
||||
setOpenaiStatuses((prev) => {
|
||||
const next = [...prev];
|
||||
next[idx] = value;
|
||||
return next;
|
||||
});
|
||||
}, []);
|
||||
|
||||
const runOpenAIKey = useCallback(
|
||||
async (idx: number): Promise<boolean> => {
|
||||
@@ -185,7 +173,7 @@ export function useConnectivityTest(
|
||||
return false;
|
||||
}
|
||||
const entry = apiKeyEntries?.[idx];
|
||||
const entryKey = (entry?.apiKey ?? '').trim();
|
||||
const entryKey = (entry?.apiKey ?? '').trim() || (entry?.existingApiKey ?? '').trim();
|
||||
const resolvedAuthIndex =
|
||||
(entry?.authIndex ?? '').trim() || (authIndex ?? '').trim() || undefined;
|
||||
if (!entryKey && !resolvedAuthIndex) {
|
||||
@@ -356,17 +344,7 @@ export function useConnectivityTest(
|
||||
} finally {
|
||||
setInFlight((n) => n - 1);
|
||||
}
|
||||
}, [
|
||||
apiKey,
|
||||
authIndex,
|
||||
baseUrl,
|
||||
brand,
|
||||
fallbackApiKey,
|
||||
formHeaders,
|
||||
messages,
|
||||
models,
|
||||
testModel,
|
||||
]);
|
||||
}, [apiKey, authIndex, baseUrl, brand, fallbackApiKey, formHeaders, messages, models, testModel]);
|
||||
|
||||
return {
|
||||
openaiStatuses,
|
||||
|
||||
@@ -40,18 +40,8 @@ export interface UseModelDiscoveryResult {
|
||||
reset: () => void;
|
||||
}
|
||||
|
||||
export function useModelDiscovery(
|
||||
args: UseModelDiscoveryArgs
|
||||
): UseModelDiscoveryResult {
|
||||
const {
|
||||
brand,
|
||||
baseUrl,
|
||||
formHeaders,
|
||||
apiKeyEntries,
|
||||
apiKey,
|
||||
fallbackApiKey,
|
||||
authIndex,
|
||||
} = args;
|
||||
export function useModelDiscovery(args: UseModelDiscoveryArgs): UseModelDiscoveryResult {
|
||||
const { brand, baseUrl, formHeaders, apiKeyEntries, apiKey, fallbackApiKey, authIndex } = args;
|
||||
|
||||
const available = isModelDiscoveryBrand(brand);
|
||||
const [loading, setLoading] = useState(false);
|
||||
@@ -92,12 +82,13 @@ export function useModelDiscovery(
|
||||
resolvedAuthIndex
|
||||
);
|
||||
} else if (brand === 'openaiCompatibility') {
|
||||
const firstEntry = (apiKeyEntries ?? []).find((e) =>
|
||||
(e.apiKey ?? '').trim() || (e.authIndex ?? '').trim()
|
||||
const firstEntry = (apiKeyEntries ?? []).find(
|
||||
(e) =>
|
||||
(e.apiKey ?? '').trim() || (e.existingApiKey ?? '').trim() || (e.authIndex ?? '').trim()
|
||||
);
|
||||
const entryKey = (firstEntry?.apiKey ?? '').trim();
|
||||
const entryAuthIndex =
|
||||
(firstEntry?.authIndex ?? '').trim() || resolvedAuthIndex;
|
||||
const entryKey =
|
||||
(firstEntry?.apiKey ?? '').trim() || (firstEntry?.existingApiKey ?? '').trim();
|
||||
const entryAuthIndex = (firstEntry?.authIndex ?? '').trim() || resolvedAuthIndex;
|
||||
try {
|
||||
next = await modelsApi.fetchModelsViaApiCall(
|
||||
baseUrl,
|
||||
@@ -125,16 +116,7 @@ export function useModelDiscovery(
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [
|
||||
available,
|
||||
apiKey,
|
||||
apiKeyEntries,
|
||||
authIndex,
|
||||
baseUrl,
|
||||
brand,
|
||||
fallbackApiKey,
|
||||
formHeaders,
|
||||
]);
|
||||
}, [available, apiKey, apiKeyEntries, authIndex, baseUrl, brand, fallbackApiKey, formHeaders]);
|
||||
|
||||
const reset = useCallback(() => {
|
||||
setModels([]);
|
||||
@@ -144,11 +126,9 @@ export function useModelDiscovery(
|
||||
}, []);
|
||||
|
||||
const inputSignature = useMemo(() => {
|
||||
const headerSig = formHeaders
|
||||
.map((h) => `${h.key}:${h.value}`)
|
||||
.join('|');
|
||||
const headerSig = formHeaders.map((h) => `${h.key}:${h.value}`).join('|');
|
||||
const entriesSig = (apiKeyEntries ?? [])
|
||||
.map((e) => `${e.apiKey ?? ''}::${e.authIndex ?? ''}`)
|
||||
.map((e) => `${e.apiKey ?? ''}::${e.existingApiKey ?? ''}::${e.authIndex ?? ''}`)
|
||||
.join('|');
|
||||
return [
|
||||
baseUrl,
|
||||
|
||||
@@ -90,6 +90,7 @@ export interface ModelEntryInput {
|
||||
|
||||
export interface ApiKeyEntryInput {
|
||||
apiKey: string;
|
||||
existingApiKey?: string;
|
||||
proxyUrl: string;
|
||||
authIndex?: string;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import {
|
||||
ampcodeApi,
|
||||
providersApi,
|
||||
} from '@/services/api';
|
||||
import { ampcodeApi, providersApi } from '@/services/api';
|
||||
import { useAuthStore, useConfigStore } from '@/stores';
|
||||
import {
|
||||
withDisableAllModelsRule,
|
||||
@@ -22,10 +19,7 @@ import {
|
||||
openaiToResource,
|
||||
vertexToResource,
|
||||
} from './adapters';
|
||||
import {
|
||||
PROVIDER_BRAND_ORDER,
|
||||
PROVIDER_PATHS,
|
||||
} from './descriptors';
|
||||
import { PROVIDER_BRAND_ORDER, PROVIDER_PATHS } from './descriptors';
|
||||
import type {
|
||||
ProviderBrand,
|
||||
ProviderEntryFormInput,
|
||||
@@ -49,14 +43,8 @@ export interface UseProviderWorkbenchResult {
|
||||
snapshot: ProviderSnapshot | null;
|
||||
refetch: () => Promise<void>;
|
||||
|
||||
createProvider: (
|
||||
brand: ProviderBrand,
|
||||
input: ProviderEntryFormInput
|
||||
) => Promise<void>;
|
||||
updateProvider: (
|
||||
resource: ProviderResource,
|
||||
input: ProviderEntryFormInput
|
||||
) => Promise<void>;
|
||||
createProvider: (brand: ProviderBrand, input: ProviderEntryFormInput) => Promise<void>;
|
||||
updateProvider: (resource: ProviderResource, input: ProviderEntryFormInput) => Promise<void>;
|
||||
deleteProvider: (resource: ProviderResource) => Promise<void>;
|
||||
toggleDisabled: (resource: ProviderResource, disabled: boolean) => Promise<void>;
|
||||
saveAmpcode: (config: AmpcodeConfig) => Promise<void>;
|
||||
@@ -157,11 +145,15 @@ const buildOpenAIConfig = (
|
||||
.filter((m) => m.name);
|
||||
const apiKeyEntries =
|
||||
input.apiKeyEntries
|
||||
?.map((entry) => ({
|
||||
apiKey: entry.apiKey.trim(),
|
||||
proxyUrl: entry.proxyUrl.trim() || undefined,
|
||||
authIndex: entry.authIndex?.trim() || undefined,
|
||||
}))
|
||||
?.map((entry, index) => {
|
||||
const fallbackApiKey =
|
||||
entry.existingApiKey?.trim() || existing?.apiKeyEntries?.[index]?.apiKey?.trim() || '';
|
||||
return {
|
||||
apiKey: entry.apiKey.trim() || fallbackApiKey,
|
||||
proxyUrl: entry.proxyUrl.trim() || undefined,
|
||||
authIndex: entry.authIndex?.trim() || undefined,
|
||||
};
|
||||
})
|
||||
.filter((entry) => entry.apiKey) ?? [];
|
||||
|
||||
return {
|
||||
@@ -169,9 +161,7 @@ const buildOpenAIConfig = (
|
||||
name: input.name.trim(),
|
||||
baseUrl: input.baseUrl.trim(),
|
||||
prefix: input.prefix.trim() || undefined,
|
||||
apiKeyEntries: apiKeyEntries.length
|
||||
? apiKeyEntries
|
||||
: existing?.apiKeyEntries ?? [],
|
||||
apiKeyEntries: apiKeyEntries.length ? apiKeyEntries : (existing?.apiKeyEntries ?? []),
|
||||
disabled: input.disabled,
|
||||
headers: Object.keys(headers).length ? headers : undefined,
|
||||
models: models.length ? models : undefined,
|
||||
|
||||
Reference in New Issue
Block a user