feat: improve model catalog aggregation

This commit is contained in:
2026-05-11 17:44:57 +08:00
parent ec87816c95
commit 0431cb8157
41 changed files with 4745 additions and 550 deletions
@@ -58,6 +58,7 @@ export function PlatformManagementPanel(props: {
const text = [
model.displayName,
model.modelName,
model.providerModelName,
model.modelAlias,
...model.modelType,
model.provider,
@@ -482,9 +483,9 @@ function PlatformModelTable(props: {
meta={[
platform ? platformDisplayName(platform) : model.platformName ?? '-',
provider?.displayName ?? platform?.provider ?? model.provider ?? '-',
model.modelAlias || model.baseModelId || '-',
model.modelAlias || model.modelName || '-',
]}
subtitle={model.modelName}
subtitle={model.providerModelName ? `调用模型名:${model.providerModelName}` : model.modelName}
title={model.displayName || model.modelName}
/>
);
@@ -608,10 +609,13 @@ function ModelSelection(props: {
const next = new Set(selectedIds);
next.delete(modelId);
const modelDiscountFactors = { ...props.form.modelDiscountFactors };
const modelNameMappings = { ...props.form.modelNameMappings };
delete modelDiscountFactors[modelId];
delete modelNameMappings[modelId];
props.onChange({
...props.form,
modelDiscountFactors,
modelNameMappings,
selectionMode: 'partial',
selectedModelIds: Array.from(next),
});
@@ -633,6 +637,16 @@ function ModelSelection(props: {
});
}
function updateModelNameMapping(modelId: string, value: string) {
props.onChange({
...props.form,
modelNameMappings: {
...props.form.modelNameMappings,
[modelId]: value,
},
});
}
return (
<div className="platformModelSelector spanTwo">
<div className="platformModelSelectorHeader">
@@ -649,26 +663,44 @@ function ModelSelection(props: {
<div className="platformModelEmpty"></div>
) : (
<div className="platformModelChoices">
{selectedModels.map((model) => (
<div className="platformModelChoice" key={model.id}>
<div className="platformModelChoiceMain">
<span>
<strong>{stableModelAlias(model) || model.providerModelName}</strong>
<small>{props.providerMap.get(model.providerKey)?.displayName ?? model.providerKey} · {model.providerModelName} · {baseModelTypeText(model)}</small>
</span>
{selectedModels.map((model) => {
const modelLabel = stableModelAlias(model) || model.providerModelName;
const providerModelName = props.form.modelNameMappings[model.id] ?? model.providerModelName;
return (
<div className="platformModelChoice" key={model.id}>
<div className="platformModelChoiceMain">
<span>
<strong>{modelLabel}</strong>
<small>{props.providerMap.get(model.providerKey)?.displayName ?? model.providerKey} · {model.providerModelName} · {baseModelTypeText(model)}</small>
</span>
</div>
<div className="platformModelChoiceFields">
<Label>
<Input
aria-label={`${modelLabel} 调用模型名`}
placeholder={model.providerModelName}
value={providerModelName}
onChange={(event) => updateModelNameMapping(model.id, event.target.value)}
/>
</Label>
<Label>
<Input
aria-label={`${modelLabel} 折扣率`}
inputMode="decimal"
placeholder="继承"
value={props.form.modelDiscountFactors[model.id] ?? ''}
onChange={(event) => updateModelDiscount(model.id, event.target.value)}
/>
</Label>
</div>
<Button type="button" variant="ghost" size="icon" aria-label="移除模型" onClick={() => removeModel(model.id)}>
<Trash2 size={14} />
</Button>
</div>
<Input
aria-label={`${stableModelAlias(model) || model.providerModelName} 折扣率`}
inputMode="decimal"
placeholder="折扣率"
value={props.form.modelDiscountFactors[model.id] ?? ''}
onChange={(event) => updateModelDiscount(model.id, event.target.value)}
/>
<Button type="button" variant="ghost" size="icon" aria-label="移除模型" onClick={() => removeModel(model.id)}>
<Trash2 size={14} />
</Button>
</div>
))}
);
})}
</div>
)}
<ModelPickerDialog
@@ -864,6 +896,7 @@ function platformToForm(
supportUrlInput: readBoolean(config, 'supportUrlInput', true),
selectedModelIds: platformModelBaseIds(platform, baseModels, currentModels),
modelDiscountFactors: platformModelDiscountFactors(platform, baseModels, currentModels),
modelNameMappings: platformModelNameMappings(platform, baseModels, currentModels),
selectionMode: 'partial',
};
}
@@ -885,10 +918,19 @@ function platformModelDiscountFactors(platform: IntegrationPlatform, baseModels:
}, {});
}
function platformModelNameMappings(platform: IntegrationPlatform, baseModels: BaseModelCatalogItem[], platformModels: PlatformModel[]) {
return platformModels.reduce<Record<string, string>>((acc, model) => {
const baseModel = findBaseModelForPlatformModel(platform, baseModels, model);
if (baseModel?.id) acc[baseModel.id] = model.providerModelName || model.modelName;
return acc;
}, {});
}
function findBaseModelForPlatformModel(platform: IntegrationPlatform | undefined, baseModels: BaseModelCatalogItem[], model: PlatformModel) {
return baseModels.find((item) => item.id === model.baseModelId) ??
baseModels.find((item) => item.canonicalModelKey === model.modelAlias) ??
baseModels.find((item) => stableModelAlias(item) === model.modelAlias) ??
baseModels.find((item) => item.providerModelName === model.modelName && model.modelType.some((type) => baseModelTypes(item).includes(type))) ??
baseModels.find((item) => item.providerKey === platform?.provider && item.providerModelName === model.modelName && model.modelType.some((type) => baseModelTypes(item).includes(type)));
}
+6 -2
View File
@@ -34,6 +34,7 @@ export interface PlatformWizardForm {
supportUrlInput: boolean;
modelDiscountFactor: string;
modelDiscountFactors: Record<string, string>;
modelNameMappings: Record<string, string>;
modelOverrideRetry: boolean;
modelRetryEnabled: boolean;
modelRetryMaxAttempts: string;
@@ -78,6 +79,7 @@ export function createEmptyPlatformForm(provider = '', defaults?: ProviderConnec
supportUrlInput: true,
modelDiscountFactor: '',
modelDiscountFactors: {},
modelNameMappings: {},
modelOverrideRetry: false,
modelRetryEnabled: true,
modelRetryMaxAttempts: '2',
@@ -99,6 +101,7 @@ export function applyProviderDefaults(form: PlatformWizardForm, provider: string
authType: defaults?.defaultAuthType ?? 'APIKey',
selectedModelIds: [],
modelDiscountFactors: {},
modelNameMappings: {},
};
}
@@ -148,6 +151,7 @@ export function platformModelPayloads(models: BaseModelCatalogItem[], form: Plat
baseModelId: model.id,
canonicalModelKey: model.canonicalModelKey,
modelName: model.providerModelName,
providerModelName: optionalString(form.modelNameMappings[model.id]) ?? model.providerModelName,
modelAlias: stableModelAlias(model),
modelType: baseModelTypes(model),
displayName: stableModelAlias(model) || model.providerModelName,
@@ -329,8 +333,8 @@ function limitRule(metric: string, value: string, windowSeconds = 60) {
};
}
function optionalString(value: string) {
const trimmed = value.trim();
function optionalString(value: string | null | undefined) {
const trimmed = value?.trim() ?? '';
return trimmed || undefined;
}