feat(ai-providers): add ampcode multi-upstream routing and vertex excluded models

This commit is contained in:
Supra4E8C
2026-03-07 15:36:47 +08:00
parent a84869ed47
commit 38d7e20320
14 changed files with 358 additions and 25 deletions
@@ -71,6 +71,10 @@ export function AmpcodeSection({
<span className={styles.fieldLabel}>{t('ai_providers.ampcode_model_mappings_count')}:</span>
<span className={styles.fieldValue}>{config?.modelMappings?.length || 0}</span>
</div>
<div className={styles.fieldRow}>
<span className={styles.fieldLabel}>{t('ai_providers.ampcode_upstream_api_keys_count')}:</span>
<span className={styles.fieldValue}>{config?.upstreamApiKeys?.length || 0}</span>
</div>
{config?.modelMappings?.length ? (
<div className={styles.modelTagList}>
{config.modelMappings.slice(0, 5).map((mapping) => (
@@ -87,6 +87,7 @@ export function VertexSection({
renderContent={(item, index) => {
const stats = getStatsBySource(item.apiKey, keyStats, item.prefix);
const headerEntries = Object.entries(item.headers || {});
const excludedModels = item.excludedModels ?? [];
const statusData = statusBarCache.get(item.apiKey) || calculateStatusBarData([]);
return (
@@ -140,6 +141,20 @@ export function VertexSection({
))}
</div>
) : null}
{excludedModels.length ? (
<div className={styles.excludedModelsSection}>
<div className={styles.excludedModelsLabel}>
{t('ai_providers.excluded_models_count', { count: excludedModels.length })}
</div>
<div className={styles.modelTagList}>
{excludedModels.map((model) => (
<span key={model} className={`${styles.modelTag} ${styles.excludedModelTag}`}>
<span className={styles.modelName}>{model}</span>
</span>
))}
</div>
</div>
) : null}
<div className={styles.cardStats}>
<span className={`${styles.statPill} ${styles.statSuccess}`}>
{t('stats.success')}: {stats.success}
+8 -1
View File
@@ -18,11 +18,17 @@ export interface OpenAIFormState {
apiKeyEntries: ApiKeyEntry[];
}
export interface AmpcodeUpstreamApiKeyEntry {
upstreamApiKey: string;
clientApiKeysText: string;
}
export interface AmpcodeFormState {
upstreamUrl: string;
upstreamApiKey: string;
forceModelMappings: boolean;
mappingEntries: ModelEntry[];
upstreamApiKeyEntries: AmpcodeUpstreamApiKeyEntry[];
}
export type GeminiFormState = Omit<GeminiKeyConfig, 'headers' | 'models'> & {
@@ -37,9 +43,10 @@ export type ProviderFormState = Omit<ProviderKeyConfig, 'headers'> & {
excludedText: string;
};
export type VertexFormState = Omit<ProviderKeyConfig, 'headers' | 'excludedModels'> & {
export type VertexFormState = Omit<ProviderKeyConfig, 'headers'> & {
headers: HeaderEntry[];
modelEntries: ModelEntry[];
excludedText: string;
};
export interface ProviderSectionProps<TConfig> {
+36 -2
View File
@@ -1,6 +1,6 @@
import type { AmpcodeConfig, AmpcodeModelMapping, ApiKeyEntry } from '@/types';
import type { AmpcodeConfig, AmpcodeModelMapping, AmpcodeUpstreamApiKeyMapping, ApiKeyEntry } from '@/types';
import { buildCandidateUsageSourceIds, type KeyStatBucket, type KeyStats } from '@/utils/usage';
import type { AmpcodeFormState, ModelEntry } from './types';
import type { AmpcodeFormState, AmpcodeUpstreamApiKeyEntry, ModelEntry } from './types';
export const DISABLE_ALL_MODELS_RULE = '*';
@@ -168,9 +168,43 @@ export const entriesToAmpcodeMappings = (entries: ModelEntry[]): AmpcodeModelMap
return mappings;
};
export const ampcodeUpstreamApiKeysToEntries = (
mappings?: AmpcodeUpstreamApiKeyMapping[]
): AmpcodeUpstreamApiKeyEntry[] => {
if (!Array.isArray(mappings) || mappings.length === 0) {
return [{ upstreamApiKey: '', clientApiKeysText: '' }];
}
return mappings.map((mapping) => ({
upstreamApiKey: mapping.upstreamApiKey ?? '',
clientApiKeysText: Array.isArray(mapping.apiKeys) ? mapping.apiKeys.join('\n') : '',
}));
};
export const entriesToAmpcodeUpstreamApiKeys = (
entries: AmpcodeUpstreamApiKeyEntry[]
): AmpcodeUpstreamApiKeyMapping[] => {
const seen = new Set<string>();
const mappings: AmpcodeUpstreamApiKeyMapping[] = [];
entries.forEach((entry) => {
const upstreamApiKey = String(entry?.upstreamApiKey ?? '').trim();
if (!upstreamApiKey || seen.has(upstreamApiKey)) return;
const apiKeys = Array.from(new Set(parseTextList(String(entry?.clientApiKeysText ?? ''))));
if (!apiKeys.length) return;
seen.add(upstreamApiKey);
mappings.push({ upstreamApiKey, apiKeys });
});
return mappings;
};
export const buildAmpcodeFormState = (ampcode?: AmpcodeConfig | null): AmpcodeFormState => ({
upstreamUrl: ampcode?.upstreamUrl ?? '',
upstreamApiKey: '',
forceModelMappings: ampcode?.forceModelMappings ?? false,
mappingEntries: ampcodeMappingsToEntries(ampcode?.modelMappings),
upstreamApiKeyEntries: ampcodeUpstreamApiKeysToEntries(ampcode?.upstreamApiKeys),
});