feat(admin): support provider model presets
This commit is contained in:
@@ -7,11 +7,15 @@ import {
|
||||
authTypes,
|
||||
applyProviderDefaults,
|
||||
baseModelTypes,
|
||||
baseModelTypeText,
|
||||
baseModelTypeTextForProvider,
|
||||
createEmptyPlatformForm,
|
||||
modelMatchesProvider,
|
||||
modelsForProvider,
|
||||
platformModelPayloads,
|
||||
platformPayload,
|
||||
providerModelNameForProvider,
|
||||
providerModelPresetFor,
|
||||
providerModelPresets,
|
||||
providerLabel,
|
||||
selectedModelsForForm,
|
||||
stableModelAlias,
|
||||
@@ -48,7 +52,11 @@ export function PlatformManagementPanel(props: {
|
||||
const platformMap = useMemo(() => new Map(props.platforms.map((item) => [item.id, item])), [props.platforms]);
|
||||
const [form, setForm] = useState<PlatformWizardForm>(() => createEmptyPlatformForm(defaultProvider, providerDefaults(providerMap.get(defaultProvider))));
|
||||
const providerOptions = useMemo(
|
||||
() => Array.from(new Set([...props.providers.map((item) => item.providerKey), ...props.baseModels.map((item) => item.providerKey)])).filter(Boolean),
|
||||
() => Array.from(new Set([
|
||||
...props.providers.map((item) => item.providerKey),
|
||||
...props.baseModels.map((item) => item.providerKey),
|
||||
...props.baseModels.flatMap((item) => providerModelPresets(item).map((preset) => preset.providerKey)),
|
||||
])).filter(Boolean),
|
||||
[props.baseModels, props.providers],
|
||||
);
|
||||
const availableModels = useMemo(() => props.baseModels.filter((item) => item.status !== 'hidden'), [props.baseModels]);
|
||||
@@ -763,13 +771,20 @@ function ModelSelection(props: {
|
||||
<div className="platformModelChoices">
|
||||
{selectedModels.map((model) => {
|
||||
const modelLabel = stableModelAlias(model) || model.providerModelName;
|
||||
const providerModelName = props.form.modelNameMappings[model.id] ?? model.providerModelName;
|
||||
const defaultProviderModelName = providerModelNameForProvider(model, props.currentProvider);
|
||||
const providerModelName = props.form.modelNameMappings[model.id] ?? defaultProviderModelName;
|
||||
const providerPreset = providerModelPresetFor(model, props.currentProvider);
|
||||
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>
|
||||
<small>
|
||||
{props.providerMap.get(model.providerKey)?.displayName ?? model.providerKey}
|
||||
{providerPreset ? ` · ${props.providerName} 真实名 ${defaultProviderModelName}` : ` · ${model.providerModelName}`}
|
||||
{' · '}
|
||||
{baseModelTypeTextForProvider(model, props.currentProvider)}
|
||||
</small>
|
||||
</span>
|
||||
</div>
|
||||
<div className="platformModelChoiceFields">
|
||||
@@ -777,7 +792,7 @@ function ModelSelection(props: {
|
||||
调用模型名
|
||||
<Input
|
||||
aria-label={`${modelLabel} 调用模型名`}
|
||||
placeholder={model.providerModelName}
|
||||
placeholder={defaultProviderModelName}
|
||||
value={providerModelName}
|
||||
onChange={(event) => updateModelNameMapping(model.id, event.target.value)}
|
||||
/>
|
||||
@@ -826,19 +841,30 @@ function ModelPickerDialog(props: {
|
||||
const [query, setQuery] = useState('');
|
||||
const [providerFilter, setProviderFilter] = useState(props.currentProvider || 'all');
|
||||
const [draftIds, setDraftIds] = useState<string[]>(props.selectedModelIds);
|
||||
const providerOptions = useMemo(() => Array.from(new Set(props.models.map((model) => model.providerKey))).filter(Boolean), [props.models]);
|
||||
const providerOptions = useMemo(() => Array.from(new Set([
|
||||
...props.models.map((model) => model.providerKey),
|
||||
...props.models.flatMap((model) => providerModelPresets(model).map((preset) => preset.providerKey)),
|
||||
])).filter(Boolean), [props.models]);
|
||||
const draftIdSet = new Set(draftIds);
|
||||
const keyword = query.trim().toLowerCase();
|
||||
const filteredModels = props.models.filter((model) => {
|
||||
if (providerFilter !== 'all' && model.providerKey !== providerFilter) return false;
|
||||
if (providerFilter !== 'all' && !modelMatchesProvider(model, providerFilter)) return false;
|
||||
const providerForText = providerFilter === 'all' ? props.currentProvider : providerFilter;
|
||||
if (!keyword) return true;
|
||||
return [
|
||||
stableModelAlias(model),
|
||||
model.providerModelName,
|
||||
providerModelNameForProvider(model, providerForText),
|
||||
model.canonicalModelKey,
|
||||
baseModelTypeText(model),
|
||||
baseModelTypeTextForProvider(model, providerForText),
|
||||
model.providerKey,
|
||||
props.providerMap.get(model.providerKey)?.displayName,
|
||||
...providerModelPresets(model).flatMap((preset) => [
|
||||
preset.providerKey,
|
||||
preset.providerModelName,
|
||||
preset.displayName,
|
||||
preset.sourceProviderName,
|
||||
]),
|
||||
JSON.stringify(model.capabilities ?? {}),
|
||||
].filter(Boolean).join(' ').toLowerCase().includes(keyword);
|
||||
});
|
||||
@@ -919,16 +945,26 @@ function ModelPickerDialog(props: {
|
||||
</Button>
|
||||
</div>
|
||||
<div className="modelPickerList">
|
||||
{filteredModels.length ? filteredModels.map((model) => (
|
||||
<label className="modelPickerItem" key={model.id}>
|
||||
<input type="checkbox" checked={draftIdSet.has(model.id)} onChange={() => toggleModel(model.id)} />
|
||||
<span>
|
||||
<strong>{stableModelAlias(model) || model.providerModelName}</strong>
|
||||
<small>{props.providerMap.get(model.providerKey)?.displayName ?? model.providerKey} · {model.providerModelName} · {baseModelTypeText(model)}</small>
|
||||
</span>
|
||||
<Badge variant="outline">{baseModelTypeText(model)}</Badge>
|
||||
</label>
|
||||
)) : <div className="platformModelEmpty">没有匹配的模型。</div>}
|
||||
{filteredModels.length ? filteredModels.map((model) => {
|
||||
const providerForText = providerFilter === 'all' ? props.currentProvider : providerFilter;
|
||||
const preset = providerModelPresetFor(model, providerForText);
|
||||
const providerModelName = providerModelNameForProvider(model, providerForText);
|
||||
return (
|
||||
<label className="modelPickerItem" key={model.id}>
|
||||
<input type="checkbox" checked={draftIdSet.has(model.id)} onChange={() => toggleModel(model.id)} />
|
||||
<span>
|
||||
<strong>{stableModelAlias(model) || model.providerModelName}</strong>
|
||||
<small>
|
||||
{props.providerMap.get(model.providerKey)?.displayName ?? model.providerKey}
|
||||
{preset ? ` · ${providerForText} 真实名 ${providerModelName}` : ` · ${model.providerModelName}`}
|
||||
{' · '}
|
||||
{baseModelTypeTextForProvider(model, providerForText)}
|
||||
</small>
|
||||
</span>
|
||||
<Badge variant="outline">{baseModelTypeTextForProvider(model, providerForText)}</Badge>
|
||||
</label>
|
||||
);
|
||||
}) : <div className="platformModelEmpty">没有匹配的模型。</div>}
|
||||
</div>
|
||||
<footer className="modelPickerActions">
|
||||
<Button type="button" variant="outline" onClick={props.onClose}>取消</Button>
|
||||
|
||||
@@ -54,6 +54,18 @@ export interface ProviderConnectionDefaults {
|
||||
defaultBaseUrl?: string;
|
||||
}
|
||||
|
||||
export interface ProviderModelPreset {
|
||||
providerKey: string;
|
||||
providerCode?: string;
|
||||
providerModelName?: string;
|
||||
modelAlias?: string;
|
||||
displayName?: string;
|
||||
modelType?: string[];
|
||||
iconPath?: string;
|
||||
sourceProviderName?: string;
|
||||
baseProviderKey?: string;
|
||||
}
|
||||
|
||||
export function createEmptyPlatformForm(provider = '', defaults?: ProviderConnectionDefaults): PlatformWizardForm {
|
||||
return {
|
||||
provider,
|
||||
@@ -110,7 +122,7 @@ export function applyProviderDefaults(form: PlatformWizardForm, provider: string
|
||||
}
|
||||
|
||||
export function modelsForProvider(models: BaseModelCatalogItem[], provider: string) {
|
||||
return models.filter((item) => item.providerKey === provider && item.status !== 'hidden');
|
||||
return models.filter((item) => item.status !== 'hidden' && modelMatchesProvider(item, provider));
|
||||
}
|
||||
|
||||
export function selectedModelsForForm(models: BaseModelCatalogItem[], form: PlatformWizardForm) {
|
||||
@@ -152,27 +164,37 @@ export function platformPayload(form: PlatformWizardForm, options: { preserveEmp
|
||||
}
|
||||
|
||||
export function platformModelPayloads(models: BaseModelCatalogItem[], form: PlatformWizardForm): PlatformModelBindingInput[] {
|
||||
return selectedModelsForForm(models, form).map((model) => ({
|
||||
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,
|
||||
pricingMode: 'inherit_discount',
|
||||
discountFactor: optionalPositiveNumber(form.modelDiscountFactors[model.id]) ?? optionalPositiveNumber(form.modelDiscountFactor),
|
||||
retryPolicy: form.modelOverrideRetry
|
||||
? { enabled: form.modelRetryEnabled, maxAttempts: form.modelRetryEnabled ? positiveInt(form.modelRetryMaxAttempts, 2) : 1 }
|
||||
: undefined,
|
||||
rateLimitPolicy: form.modelOverrideRateLimit ? rateLimitPolicyPayload({
|
||||
rpmLimit: form.modelRpmLimit,
|
||||
rpsLimit: form.modelRpsLimit,
|
||||
tpmLimit: form.modelTpmLimit,
|
||||
concurrencyLimit: form.modelConcurrencyLimit,
|
||||
}) : undefined,
|
||||
runtimePolicyOverride: platformModelRuntimeOverride(form),
|
||||
}));
|
||||
return selectedModelsForForm(models, form).map((model) => {
|
||||
const preset = providerModelPresetFor(model, form.provider);
|
||||
const providerModelName =
|
||||
optionalString(form.modelNameMappings[model.id]) ??
|
||||
providerModelNameForProvider(model, form.provider);
|
||||
return {
|
||||
baseModelId: model.id,
|
||||
canonicalModelKey: model.canonicalModelKey,
|
||||
modelName: model.providerModelName,
|
||||
providerModelName,
|
||||
modelAlias: preset?.modelAlias || stableModelAlias(model),
|
||||
modelType: baseModelTypesForProvider(model, form.provider),
|
||||
displayName:
|
||||
preset?.displayName ||
|
||||
stableModelAlias(model) ||
|
||||
providerModelName ||
|
||||
model.providerModelName,
|
||||
pricingMode: 'inherit_discount',
|
||||
discountFactor: optionalPositiveNumber(form.modelDiscountFactors[model.id]) ?? optionalPositiveNumber(form.modelDiscountFactor),
|
||||
retryPolicy: form.modelOverrideRetry
|
||||
? { enabled: form.modelRetryEnabled, maxAttempts: form.modelRetryEnabled ? positiveInt(form.modelRetryMaxAttempts, 2) : 1 }
|
||||
: undefined,
|
||||
rateLimitPolicy: form.modelOverrideRateLimit ? rateLimitPolicyPayload({
|
||||
rpmLimit: form.modelRpmLimit,
|
||||
rpsLimit: form.modelRpsLimit,
|
||||
tpmLimit: form.modelTpmLimit,
|
||||
concurrencyLimit: form.modelConcurrencyLimit,
|
||||
}) : undefined,
|
||||
runtimePolicyOverride: platformModelRuntimeOverride(form),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export function stableModelAlias(model: BaseModelCatalogItem) {
|
||||
@@ -195,10 +217,56 @@ export function baseModelTypes(model: BaseModelCatalogItem) {
|
||||
return [];
|
||||
}
|
||||
|
||||
export function providerModelPresets(model: BaseModelCatalogItem): ProviderModelPreset[] {
|
||||
const rawPresets = model.metadata?.providerModelPresets;
|
||||
if (!Array.isArray(rawPresets)) return [];
|
||||
return rawPresets
|
||||
.map(readRecord)
|
||||
.filter((item): item is Record<string, unknown> => Boolean(item))
|
||||
.map((item) => ({
|
||||
providerKey: readString(item.providerKey || item.providerCode),
|
||||
providerCode: readString(item.providerCode || item.providerKey),
|
||||
providerModelName: readString(item.providerModelName || item.realModelNameOverride),
|
||||
modelAlias: readString(item.modelAlias || item.alias),
|
||||
displayName: readString(item.displayName || item.modelAlias || item.alias),
|
||||
modelType: readStringArray(item.modelType || item.modelTypes),
|
||||
iconPath: readString(item.iconPath || item.icon_path),
|
||||
sourceProviderName: readString(item.sourceProviderName || item.providerName),
|
||||
baseProviderKey: readString(item.baseProviderKey || item.baseProviderCode),
|
||||
}))
|
||||
.filter((item) => item.providerKey);
|
||||
}
|
||||
|
||||
export function providerModelPresetFor(model: BaseModelCatalogItem, provider: string) {
|
||||
const providerKey = provider.trim();
|
||||
if (!providerKey) return undefined;
|
||||
return providerModelPresets(model).find(
|
||||
(preset) => preset.providerKey === providerKey || preset.providerCode === providerKey,
|
||||
);
|
||||
}
|
||||
|
||||
export function modelMatchesProvider(model: BaseModelCatalogItem, provider: string) {
|
||||
if (!provider) return true;
|
||||
return model.providerKey === provider || Boolean(providerModelPresetFor(model, provider));
|
||||
}
|
||||
|
||||
export function providerModelNameForProvider(model: BaseModelCatalogItem, provider: string) {
|
||||
return providerModelPresetFor(model, provider)?.providerModelName || model.providerModelName;
|
||||
}
|
||||
|
||||
export function baseModelTypesForProvider(model: BaseModelCatalogItem, provider: string) {
|
||||
const presetTypes = providerModelPresetFor(model, provider)?.modelType;
|
||||
return presetTypes?.length ? presetTypes : baseModelTypes(model);
|
||||
}
|
||||
|
||||
export function baseModelTypeText(model: BaseModelCatalogItem) {
|
||||
return baseModelTypes(model).join(', ');
|
||||
}
|
||||
|
||||
export function baseModelTypeTextForProvider(model: BaseModelCatalogItem, provider: string) {
|
||||
return baseModelTypesForProvider(model, provider).join(', ');
|
||||
}
|
||||
|
||||
export function primaryBaseModelType(model: BaseModelCatalogItem) {
|
||||
return baseModelTypes(model)[0] ?? 'text_generate';
|
||||
}
|
||||
@@ -223,6 +291,10 @@ function readString(value: unknown) {
|
||||
return typeof value === 'string' ? value.trim() : '';
|
||||
}
|
||||
|
||||
function readStringArray(value: unknown) {
|
||||
return Array.isArray(value) ? value.map(String).map((item) => item.trim()).filter(Boolean) : [];
|
||||
}
|
||||
|
||||
function platformModelRuntimeOverride(form: PlatformWizardForm) {
|
||||
const override: Record<string, unknown> = {};
|
||||
if (form.modelOverrideRetry) {
|
||||
|
||||
Reference in New Issue
Block a user