feat: add Ampcode (Amp CLI Integration) support with configuration UI and i18n

- Add ampcodeApi service for upstream URL, API key, and model mappings management
  - Implement Ampcode configuration modal in AiProvidersPage
  - Add complete i18n translations for Ampcode features (en and zh-CN)
  - Enhance UsagePage with mobile-responsive chart improvements and legend display
  - Optimize chart rendering for smaller screens
  - Improve page layout styles (SystemPage, AiProvidersPage alignment)
This commit is contained in:
Supra4E8C
2025-12-14 00:31:05 +08:00
parent c4034c6467
commit e0584af365
14 changed files with 744 additions and 40 deletions

View File

@@ -3,11 +3,25 @@ import type {
GeminiKeyConfig,
ModelAlias,
OpenAIProviderConfig,
ProviderKeyConfig
ProviderKeyConfig,
AmpcodeConfig,
AmpcodeModelMapping
} from '@/types';
import type { Config } from '@/types/config';
import { buildHeaderObject } from '@/utils/headers';
const normalizeBoolean = (value: any): boolean | undefined => {
if (value === undefined || value === null) return undefined;
if (typeof value === 'boolean') return value;
if (typeof value === 'number') return value !== 0;
if (typeof value === 'string') {
const trimmed = value.trim().toLowerCase();
if (['true', '1', 'yes', 'y', 'on'].includes(trimmed)) return true;
if (['false', '0', 'no', 'n', 'off'].includes(trimmed)) return false;
}
return Boolean(value);
};
const normalizeModelAliases = (models: any): ModelAlias[] => {
if (!Array.isArray(models)) return [];
return models
@@ -162,6 +176,61 @@ const normalizeOauthExcluded = (payload: any): Record<string, string[]> | undefi
return map;
};
const normalizeAmpcodeModelMappings = (input: any): AmpcodeModelMapping[] => {
if (!Array.isArray(input)) return [];
const seen = new Set<string>();
const mappings: AmpcodeModelMapping[] = [];
input.forEach((entry) => {
if (!entry || typeof entry !== 'object') return;
const from = String(entry.from ?? entry['from'] ?? '').trim();
const to = String(entry.to ?? entry['to'] ?? '').trim();
if (!from || !to) return;
const key = from.toLowerCase();
if (seen.has(key)) return;
seen.add(key);
mappings.push({ from, to });
});
return mappings;
};
const normalizeAmpcodeConfig = (payload: any): AmpcodeConfig | undefined => {
const source = payload?.ampcode ?? payload;
if (!source || typeof source !== 'object') return undefined;
const config: AmpcodeConfig = {};
const upstreamUrl = source['upstream-url'] ?? source.upstreamUrl ?? source['upstream_url'];
if (upstreamUrl) config.upstreamUrl = String(upstreamUrl);
const upstreamApiKey = source['upstream-api-key'] ?? source.upstreamApiKey ?? source['upstream_api_key'];
if (upstreamApiKey) config.upstreamApiKey = String(upstreamApiKey);
const restrictManagementToLocalhost = normalizeBoolean(
source['restrict-management-to-localhost'] ??
source.restrictManagementToLocalhost ??
source['restrict_management_to_localhost']
);
if (restrictManagementToLocalhost !== undefined) {
config.restrictManagementToLocalhost = restrictManagementToLocalhost;
}
const forceModelMappings = normalizeBoolean(
source['force-model-mappings'] ?? source.forceModelMappings ?? source['force_model_mappings']
);
if (forceModelMappings !== undefined) {
config.forceModelMappings = forceModelMappings;
}
const modelMappings = normalizeAmpcodeModelMappings(
source['model-mappings'] ?? source.modelMappings ?? source['model_mappings']
);
if (modelMappings.length) {
config.modelMappings = modelMappings;
}
return config;
};
/**
* 规范化 /config 返回值
*/
@@ -217,6 +286,11 @@ export const normalizeConfigResponse = (raw: any): Config => {
.filter(Boolean) as OpenAIProviderConfig[];
}
const ampcode = normalizeAmpcodeConfig(raw.ampcode);
if (ampcode) {
config.ampcode = ampcode;
}
const oauthExcluded = normalizeOauthExcluded(raw['oauth-excluded-models'] ?? raw.oauthExcludedModels);
if (oauthExcluded) {
config.oauthExcludedModels = oauthExcluded;
@@ -232,5 +306,7 @@ export {
normalizeOpenAIProvider,
normalizeProviderKeyConfig,
normalizeHeaders,
normalizeExcludedModels
normalizeExcludedModels,
normalizeAmpcodeConfig,
normalizeAmpcodeModelMappings
};