chore: commit pending gateway changes
This commit is contained in:
@@ -4,91 +4,109 @@ import type {
|
||||
BaseModelCatalogItem,
|
||||
BaseModelUpsertRequest,
|
||||
CatalogProvider,
|
||||
PricingRuleSet,
|
||||
RuntimePolicySet,
|
||||
} from '@easyai-ai-gateway/contracts';
|
||||
import { Badge, Button, Card, CardContent, CardHeader, CardTitle, FormDialog, Input, Label, Select, Textarea } from '../../components/ui';
|
||||
import { Badge, Button, Card, CardContent, CardHeader, CardTitle, ConfirmDialog, FormDialog, FormItem, Label, Input, Select, Textarea } from '../../components/ui';
|
||||
import type { LoadState } from '../../types';
|
||||
import { BaseModelCapabilityEditor } from './BaseModelCapabilityEditor';
|
||||
import {
|
||||
addCapabilityType,
|
||||
capabilitiesFromForm,
|
||||
capabilitiesToForm,
|
||||
createCapabilityEditorState,
|
||||
platformModelTypeDefinitions,
|
||||
removeCapabilityType,
|
||||
syncPrimaryCapability,
|
||||
type CapabilityEditorState,
|
||||
} from './base-model-capabilities';
|
||||
import { ModelCatalogCard } from './ModelCatalogCard';
|
||||
|
||||
type ModelForm = {
|
||||
providerKey: string;
|
||||
canonicalModelKey: string;
|
||||
providerModelName: string;
|
||||
modelType: string;
|
||||
displayName: string;
|
||||
modelAlias: string;
|
||||
status: string;
|
||||
pricingRuleSetId: string;
|
||||
runtimePolicySetId: string;
|
||||
runtimePolicyOverrideJson: string;
|
||||
pricingVersion: string;
|
||||
capabilitiesJson: string;
|
||||
capabilities: CapabilityEditorState;
|
||||
billingJson: string;
|
||||
rateLimitJson: string;
|
||||
metadataJson: string;
|
||||
};
|
||||
|
||||
const statuses = ['active', 'deprecated', 'hidden'];
|
||||
const fallbackTypes = [
|
||||
'text_generate',
|
||||
'image_generate',
|
||||
'image_edit',
|
||||
'image_analysis',
|
||||
'video_generate',
|
||||
'image_to_video',
|
||||
'omni_video',
|
||||
'text_embedding',
|
||||
'text_to_speech',
|
||||
'audio_generate',
|
||||
'digital_human_generate',
|
||||
'text_to_model',
|
||||
'image_to_model',
|
||||
'multiview_to_model',
|
||||
'mesh_edit',
|
||||
];
|
||||
const fallbackTypes = platformModelTypeDefinitions.map((item) => item.key);
|
||||
|
||||
const emptyForm: ModelForm = {
|
||||
providerKey: '',
|
||||
canonicalModelKey: '',
|
||||
providerModelName: '',
|
||||
modelType: 'text_generate',
|
||||
displayName: '',
|
||||
status: 'active',
|
||||
pricingVersion: '1',
|
||||
capabilitiesJson: '{}',
|
||||
billingJson: '{}',
|
||||
rateLimitJson: '{}',
|
||||
metadataJson: '{}',
|
||||
};
|
||||
function createEmptyForm(modelType = 'text_generate'): ModelForm {
|
||||
return {
|
||||
providerKey: '',
|
||||
canonicalModelKey: '',
|
||||
providerModelName: '',
|
||||
modelAlias: '',
|
||||
status: 'active',
|
||||
pricingRuleSetId: '',
|
||||
runtimePolicySetId: '',
|
||||
runtimePolicyOverrideJson: '{}',
|
||||
pricingVersion: '1',
|
||||
capabilities: createCapabilityEditorState(modelType),
|
||||
billingJson: '{}',
|
||||
rateLimitJson: '{}',
|
||||
metadataJson: '{}',
|
||||
};
|
||||
}
|
||||
|
||||
export function BaseModelCatalogPanel(props: {
|
||||
baseModels: BaseModelCatalogItem[];
|
||||
message: string;
|
||||
pricingRuleSets: PricingRuleSet[];
|
||||
providers: CatalogProvider[];
|
||||
runtimePolicySets: RuntimePolicySet[];
|
||||
state: LoadState;
|
||||
onDeleteBaseModel: (baseModelId: string) => Promise<void>;
|
||||
onResetAllBaseModels: () => Promise<void>;
|
||||
onResetBaseModel: (baseModelId: string) => Promise<void>;
|
||||
onSaveBaseModel: (input: BaseModelUpsertRequest, baseModelId?: string) => Promise<void>;
|
||||
}) {
|
||||
const [dialogOpen, setDialogOpen] = useState(false);
|
||||
const [editingId, setEditingId] = useState('');
|
||||
const [form, setForm] = useState<ModelForm>(emptyForm);
|
||||
const [form, setForm] = useState<ModelForm>(() => createEmptyForm());
|
||||
const [localError, setLocalError] = useState('');
|
||||
const [query, setQuery] = useState('');
|
||||
const [providerFilter, setProviderFilter] = useState('all');
|
||||
const [typeFilter, setTypeFilter] = useState('all');
|
||||
const [pendingDeleteModel, setPendingDeleteModel] = useState<BaseModelCatalogItem | null>(null);
|
||||
const [pendingResetModel, setPendingResetModel] = useState<BaseModelCatalogItem | null>(null);
|
||||
const [pendingResetAll, setPendingResetAll] = useState(false);
|
||||
|
||||
const providerNames = useMemo(
|
||||
() => new Map(props.providers.map((item) => [item.providerKey, item.displayName])),
|
||||
[props.providers],
|
||||
);
|
||||
const typeOptions = useMemo(
|
||||
() => Array.from(new Set([...fallbackTypes, ...props.baseModels.map((item) => item.modelType).filter(Boolean)])),
|
||||
() => Array.from(new Set([...fallbackTypes, ...props.baseModels.flatMap(readModelTypes)])),
|
||||
[props.baseModels],
|
||||
);
|
||||
const providerOptions = useMemo(
|
||||
() => Array.from(new Set([...props.providers.map((item) => item.providerKey), ...props.baseModels.map((item) => item.providerKey)])),
|
||||
[props.baseModels, props.providers],
|
||||
);
|
||||
const defaultPricingRuleSetId = useMemo(() => defaultRuleSetId(props.pricingRuleSets), [props.pricingRuleSets]);
|
||||
const defaultRuntimePolicySetId = useMemo(() => defaultRuntimePolicyId(props.runtimePolicySets), [props.runtimePolicySets]);
|
||||
const systemModelCount = useMemo(() => props.baseModels.filter(isSystemBaseModel).length, [props.baseModels]);
|
||||
const customizedSystemModelCount = useMemo(
|
||||
() => props.baseModels.filter((item) => isSystemBaseModel(item) && item.customizedAt).length,
|
||||
[props.baseModels],
|
||||
);
|
||||
const filteredModels = useMemo(() => {
|
||||
const keyword = query.trim().toLowerCase();
|
||||
return props.baseModels.filter((item) => {
|
||||
const matchesProvider = providerFilter === 'all' || item.providerKey === providerFilter;
|
||||
const matchesType = typeFilter === 'all' || readModelTypes(item).includes(typeFilter);
|
||||
const text = `${item.displayName} ${item.providerModelName} ${item.canonicalModelKey}`.toLowerCase();
|
||||
const text = `${baseModelAlias(item)} ${item.providerModelName} ${item.canonicalModelKey}`.toLowerCase();
|
||||
return matchesProvider && matchesType && (!keyword || text.includes(keyword));
|
||||
});
|
||||
}, [props.baseModels, providerFilter, query, typeFilter]);
|
||||
@@ -107,34 +125,63 @@ export function BaseModelCatalogPanel(props: {
|
||||
function openCreateDialog() {
|
||||
const providerKey = providerOptions[0] ?? '';
|
||||
setEditingId('');
|
||||
setForm({ ...emptyForm, providerKey, canonicalModelKey: providerKey ? `${providerKey}:` : '' });
|
||||
setForm({
|
||||
...createEmptyForm(),
|
||||
providerKey,
|
||||
canonicalModelKey: providerKey ? `${providerKey}:` : '',
|
||||
pricingRuleSetId: defaultPricingRuleSetId,
|
||||
runtimePolicySetId: defaultRuntimePolicySetId,
|
||||
});
|
||||
setDialogOpen(true);
|
||||
}
|
||||
|
||||
function editModel(model: BaseModelCatalogItem) {
|
||||
setEditingId(model.id);
|
||||
setForm(modelToForm(model));
|
||||
setForm({
|
||||
...modelToForm(model),
|
||||
pricingRuleSetId: model.pricingRuleSetId || defaultPricingRuleSetId,
|
||||
runtimePolicySetId: model.runtimePolicySetId || defaultRuntimePolicySetId,
|
||||
});
|
||||
setDialogOpen(true);
|
||||
}
|
||||
|
||||
function closeDialog() {
|
||||
setEditingId('');
|
||||
setForm(emptyForm);
|
||||
setForm(createEmptyForm());
|
||||
setLocalError('');
|
||||
setDialogOpen(false);
|
||||
}
|
||||
|
||||
async function deleteModel(model: BaseModelCatalogItem) {
|
||||
const confirmed = window.confirm(`确认删除基准模型 ${model.displayName}?`);
|
||||
if (!confirmed) return;
|
||||
try {
|
||||
await props.onDeleteBaseModel(model.id);
|
||||
setPendingDeleteModel(null);
|
||||
if (editingId === model.id) closeDialog();
|
||||
} catch (err) {
|
||||
setLocalError(err instanceof Error ? err.message : '模型删除失败');
|
||||
}
|
||||
}
|
||||
|
||||
async function resetModel(model: BaseModelCatalogItem) {
|
||||
try {
|
||||
await props.onResetBaseModel(model.id);
|
||||
setPendingResetModel(null);
|
||||
if (editingId === model.id) closeDialog();
|
||||
} catch (err) {
|
||||
setLocalError(err instanceof Error ? err.message : '模型重置失败');
|
||||
}
|
||||
}
|
||||
|
||||
async function resetAllModels() {
|
||||
try {
|
||||
await props.onResetAllBaseModels();
|
||||
setPendingResetAll(false);
|
||||
if (editingId) closeDialog();
|
||||
} catch (err) {
|
||||
setLocalError(err instanceof Error ? err.message : '模型批量重置失败');
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="pageStack">
|
||||
<Card>
|
||||
@@ -145,10 +192,16 @@ export function BaseModelCatalogPanel(props: {
|
||||
<CardContent>
|
||||
<div className="providerToolbar">
|
||||
<p>默认模型来自原 server-main integration-platform,保留模型类型、能力、图标、计费和限流元数据。</p>
|
||||
<Button type="button" onClick={openCreateDialog}>
|
||||
<Plus size={15} />
|
||||
新增模型
|
||||
</Button>
|
||||
<div className="inlineActions">
|
||||
<Button type="button" variant="outline" disabled={!systemModelCount || props.state === 'loading'} onClick={() => setPendingResetAll(true)}>
|
||||
<RotateCcw size={15} />
|
||||
重置所有
|
||||
</Button>
|
||||
<Button type="button" onClick={openCreateDialog}>
|
||||
<Plus size={15} />
|
||||
新增模型
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="modelCatalogFilters">
|
||||
<Input value={query} onChange={(event) => setQuery(event.target.value)} placeholder="搜索模型名称 / canonical key" />
|
||||
@@ -170,9 +223,12 @@ export function BaseModelCatalogPanel(props: {
|
||||
<ModelCard
|
||||
key={model.id}
|
||||
model={model}
|
||||
pricingRuleSetName={pricingRuleSetName(model.pricingRuleSetId, props.pricingRuleSets)}
|
||||
providerName={providerNames.get(model.providerKey)}
|
||||
onDelete={() => void deleteModel(model)}
|
||||
runtimePolicySetName={runtimePolicySetName(model.runtimePolicySetId, props.runtimePolicySets)}
|
||||
onDelete={() => setPendingDeleteModel(model)}
|
||||
onEdit={() => editModel(model)}
|
||||
onReset={() => setPendingResetModel(model)}
|
||||
/>
|
||||
))}
|
||||
{!filteredModels.length && (
|
||||
@@ -206,86 +262,130 @@ export function BaseModelCatalogPanel(props: {
|
||||
onClose={closeDialog}
|
||||
onSubmit={submit}
|
||||
>
|
||||
<Label>
|
||||
Provider
|
||||
<FormItem label="Provider">
|
||||
<Select value={form.providerKey} onChange={(event) => setForm({ ...form, providerKey: event.target.value })}>
|
||||
<option value="">选择厂商</option>
|
||||
{providerOptions.map((item) => <option value={item} key={item}>{providerNames.get(item) ?? item}</option>)}
|
||||
</Select>
|
||||
</Label>
|
||||
<Label>
|
||||
模型类型
|
||||
<Select value={form.modelType} onChange={(event) => setForm({ ...form, modelType: event.target.value })}>
|
||||
{typeOptions.map((item) => <option value={item} key={item}>{item}</option>)}
|
||||
</Select>
|
||||
</Label>
|
||||
<Label>
|
||||
模型名
|
||||
</FormItem>
|
||||
<FormItem label="模型名">
|
||||
<Input value={form.providerModelName} onChange={(event) => setForm({ ...form, providerModelName: event.target.value })} />
|
||||
</Label>
|
||||
<Label>
|
||||
展示名称
|
||||
<Input value={form.displayName} onChange={(event) => setForm({ ...form, displayName: event.target.value })} />
|
||||
</Label>
|
||||
<Label className="spanTwo">
|
||||
Canonical Key
|
||||
</FormItem>
|
||||
<FormItem
|
||||
description="作为调用和展示的模型名;多个不同平台模型设置同一别名后,可按该别名自动负载。"
|
||||
label="模型别名"
|
||||
>
|
||||
<Input value={form.modelAlias} onChange={(event) => setForm({ ...form, modelAlias: event.target.value })} />
|
||||
</FormItem>
|
||||
<FormItem className="spanTwo" label="Canonical Key">
|
||||
<Input value={form.canonicalModelKey} onChange={(event) => setForm({ ...form, canonicalModelKey: event.target.value })} />
|
||||
</Label>
|
||||
<Label>
|
||||
状态
|
||||
</FormItem>
|
||||
<FormItem label="状态">
|
||||
<Select value={form.status} onChange={(event) => setForm({ ...form, status: event.target.value })}>
|
||||
{statuses.map((item) => <option value={item} key={item}>{item}</option>)}
|
||||
</Select>
|
||||
</Label>
|
||||
<Label>
|
||||
定价版本
|
||||
</FormItem>
|
||||
<FormItem label="计价规则">
|
||||
<Select value={form.pricingRuleSetId || defaultPricingRuleSetId} onChange={(event) => setForm({ ...form, pricingRuleSetId: event.target.value })}>
|
||||
{!props.pricingRuleSets.length && <option value="">暂无可用计价规则</option>}
|
||||
{props.pricingRuleSets.map((item) => <option value={item.id} key={item.id}>{item.name}</option>)}
|
||||
</Select>
|
||||
</FormItem>
|
||||
<FormItem label="运行策略">
|
||||
<Select value={form.runtimePolicySetId || defaultRuntimePolicySetId} onChange={(event) => setForm({ ...form, runtimePolicySetId: event.target.value })}>
|
||||
{!props.runtimePolicySets.length && <option value="">暂无可用运行策略</option>}
|
||||
{props.runtimePolicySets.map((item) => <option value={item.id} key={item.id}>{item.name}</option>)}
|
||||
</Select>
|
||||
</FormItem>
|
||||
<FormItem label="定价版本">
|
||||
<Input value={form.pricingVersion} onChange={(event) => setForm({ ...form, pricingVersion: event.target.value })} />
|
||||
</Label>
|
||||
<JsonField label="能力 JSON" value={form.capabilitiesJson} onChange={(value) => setForm({ ...form, capabilitiesJson: value })} />
|
||||
</FormItem>
|
||||
<BaseModelCapabilityEditor
|
||||
modelType={primaryCapabilityType(form.capabilities)}
|
||||
typeOptions={typeOptions}
|
||||
value={form.capabilities}
|
||||
onChange={(capabilities) => setForm({ ...form, capabilities })}
|
||||
/>
|
||||
<JsonField label="基准计费 JSON" value={form.billingJson} onChange={(value) => setForm({ ...form, billingJson: value })} />
|
||||
<JsonField label="限流 JSON" value={form.rateLimitJson} onChange={(value) => setForm({ ...form, rateLimitJson: value })} />
|
||||
<JsonField label="旧版限流 JSON(兼容字段)" value={form.rateLimitJson} onChange={(value) => setForm({ ...form, rateLimitJson: value })} />
|
||||
<JsonField label="运行策略覆盖 JSON" value={form.runtimePolicyOverrideJson} onChange={(value) => setForm({ ...form, runtimePolicyOverrideJson: value })} />
|
||||
<JsonField label="元数据 JSON" value={form.metadataJson} onChange={(value) => setForm({ ...form, metadataJson: value })} />
|
||||
</FormDialog>
|
||||
<ConfirmDialog
|
||||
confirmLabel="删除模型"
|
||||
description="删除后该基准模型不可恢复,已关联的平台模型可能无法继续跟随基准配置。"
|
||||
loading={props.state === 'loading'}
|
||||
open={Boolean(pendingDeleteModel)}
|
||||
title={`确认删除基准模型 ${baseModelAlias(pendingDeleteModel)}?`}
|
||||
onCancel={() => setPendingDeleteModel(null)}
|
||||
onConfirm={() => pendingDeleteModel ? deleteModel(pendingDeleteModel) : undefined}
|
||||
/>
|
||||
<ConfirmDialog
|
||||
confirmLabel="重置为默认"
|
||||
description="系统内置模型会恢复到默认模型库快照,当前手动修改的能力、计价、运行策略和元数据会被默认值覆盖。"
|
||||
loading={props.state === 'loading'}
|
||||
open={Boolean(pendingResetModel)}
|
||||
title={`确认重置 ${baseModelAlias(pendingResetModel)}?`}
|
||||
onCancel={() => setPendingResetModel(null)}
|
||||
onConfirm={() => pendingResetModel ? resetModel(pendingResetModel) : undefined}
|
||||
/>
|
||||
<ConfirmDialog
|
||||
confirmLabel="重置所有"
|
||||
description={`将恢复 ${systemModelCount} 个系统内置基准模型的默认快照,其中 ${customizedSystemModelCount} 个显示为已修改。用户添加的模型不会被影响。`}
|
||||
loading={props.state === 'loading'}
|
||||
open={pendingResetAll}
|
||||
title="确认重置所有系统内置模型?"
|
||||
onCancel={() => setPendingResetAll(false)}
|
||||
onConfirm={resetAllModels}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ModelCard(props: {
|
||||
model: BaseModelCatalogItem;
|
||||
pricingRuleSetName: string;
|
||||
providerName?: string;
|
||||
runtimePolicySetName: string;
|
||||
onDelete: () => void;
|
||||
onEdit: () => void;
|
||||
onReset: () => void;
|
||||
}) {
|
||||
const types = readModelTypes(props.model);
|
||||
const chips = [...types.slice(0, 5), ...(types.length > 5 ? [`+${types.length - 5}`] : [])];
|
||||
const isSystem = isSystemBaseModel(props.model);
|
||||
return (
|
||||
<article className="baseModelCard">
|
||||
<ModelIcon model={props.model} />
|
||||
<div className="baseModelCardBody">
|
||||
<div>
|
||||
<strong>{props.model.displayName}</strong>
|
||||
<span>{props.model.providerModelName}</span>
|
||||
</div>
|
||||
<div className="providerCatalogMeta">
|
||||
<Badge variant={props.model.status === 'active' ? 'success' : 'secondary'}>{props.model.status}</Badge>
|
||||
<span>{props.providerName ?? props.model.providerKey}</span>
|
||||
<span>{props.model.canonicalModelKey}</span>
|
||||
</div>
|
||||
<div className="modelAbilityChips">
|
||||
{types.slice(0, 5).map((item) => <span key={item}>{item}</span>)}
|
||||
{types.length > 5 && <span>+{types.length - 5}</span>}
|
||||
</div>
|
||||
</div>
|
||||
<div className="providerCatalogActions">
|
||||
<Button type="button" variant="outline" size="sm" onClick={props.onEdit}>
|
||||
<Pencil size={14} />
|
||||
修改
|
||||
</Button>
|
||||
<Button type="button" variant="destructive" size="sm" onClick={props.onDelete}>
|
||||
<Trash2 size={14} />
|
||||
删除
|
||||
</Button>
|
||||
</div>
|
||||
</article>
|
||||
<ModelCatalogCard
|
||||
actions={(
|
||||
<>
|
||||
{isSystem && (
|
||||
<Button type="button" variant="outline" size="sm" onClick={props.onReset}>
|
||||
<RotateCcw size={14} />
|
||||
重置
|
||||
</Button>
|
||||
)}
|
||||
<Button type="button" variant="outline" size="sm" onClick={props.onEdit}>
|
||||
<Pencil size={14} />
|
||||
修改
|
||||
</Button>
|
||||
<Button type="button" variant="destructive" size="sm" onClick={props.onDelete}>
|
||||
<Trash2 size={14} />
|
||||
删除
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
badges={[
|
||||
<Badge key="status" variant={props.model.status === 'active' ? 'success' : 'secondary'}>{props.model.status}</Badge>,
|
||||
<Badge key="catalog-type" variant={isSystem ? 'secondary' : 'outline'}>{baseModelCatalogTypeLabel(props.model)}</Badge>,
|
||||
...(isSystem && props.model.customizedAt ? [<Badge key="customized" variant="warning">已修改</Badge>] : []),
|
||||
]}
|
||||
chips={chips}
|
||||
iconPath={readModelIconPath(props.model)}
|
||||
iconText={props.model.providerKey}
|
||||
meta={[props.providerName ?? props.model.providerKey, props.pricingRuleSetName, props.runtimePolicySetName, props.model.canonicalModelKey]}
|
||||
subtitle={props.model.providerModelName}
|
||||
title={baseModelAlias(props.model)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -298,54 +398,115 @@ function JsonField(props: { label: string; value: string; onChange: (value: stri
|
||||
);
|
||||
}
|
||||
|
||||
function ModelIcon(props: { model: BaseModelCatalogItem }) {
|
||||
const iconPath = readString(props.model.metadata?.iconPath) || readString(readRecord(props.model.metadata?.rawModel)?.icon_path);
|
||||
if (iconPath) {
|
||||
return (
|
||||
<div className="providerCatalogLogo">
|
||||
<img src={iconPath} alt="" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return <div className="providerCatalogLogo">{props.model.providerKey.slice(0, 2).toUpperCase()}</div>;
|
||||
}
|
||||
|
||||
function modelToForm(model: BaseModelCatalogItem): ModelForm {
|
||||
const capabilities = readObject(model.capabilities);
|
||||
const metadataTypes = Array.isArray(model.metadata?.originalTypes) ? { originalTypes: model.metadata.originalTypes } : {};
|
||||
const modelTypes = readModelTypes(model);
|
||||
const capabilityState = capabilitiesToForm({ ...capabilities, ...metadataTypes }, modelTypes[0] ?? 'text_generate');
|
||||
return {
|
||||
providerKey: model.providerKey,
|
||||
canonicalModelKey: model.canonicalModelKey,
|
||||
providerModelName: model.providerModelName,
|
||||
modelType: model.modelType,
|
||||
displayName: model.displayName,
|
||||
modelAlias: baseModelAlias(model),
|
||||
status: model.status,
|
||||
pricingRuleSetId: model.pricingRuleSetId ?? '',
|
||||
runtimePolicySetId: model.runtimePolicySetId ?? '',
|
||||
pricingVersion: String(model.pricingVersion || 1),
|
||||
capabilitiesJson: stringifyJson(model.capabilities),
|
||||
capabilities: capabilityState,
|
||||
billingJson: stringifyJson(model.baseBillingConfig),
|
||||
rateLimitJson: stringifyJson(model.defaultRateLimitPolicy),
|
||||
runtimePolicyOverrideJson: stringifyJson(model.runtimePolicyOverride),
|
||||
metadataJson: stringifyJson(model.metadata),
|
||||
};
|
||||
}
|
||||
|
||||
function readModelIconPath(model: BaseModelCatalogItem) {
|
||||
return readString(model.metadata?.iconPath) || readString(readRecord(model.metadata?.rawModel)?.icon_path);
|
||||
}
|
||||
|
||||
function baseModelAlias(model?: BaseModelCatalogItem | null) {
|
||||
return model?.modelAlias || model?.displayName || model?.providerModelName || '';
|
||||
}
|
||||
|
||||
function primaryCapabilityType(capabilities: CapabilityEditorState) {
|
||||
return capabilities.types[0] ?? 'text_generate';
|
||||
}
|
||||
|
||||
function isSystemBaseModel(model: BaseModelCatalogItem) {
|
||||
if (model.catalogType) return model.catalogType === 'system';
|
||||
return model.metadata?.source === 'server-main.integration-platform' || Boolean(model.metadata?.rawModel || model.metadata?.sourceProviderCode);
|
||||
}
|
||||
|
||||
function baseModelCatalogTypeLabel(model: BaseModelCatalogItem) {
|
||||
return isSystemBaseModel(model) ? '系统内置' : '用户添加';
|
||||
}
|
||||
|
||||
function formToPayload(form: ModelForm): BaseModelUpsertRequest {
|
||||
const modelTypes = uniqueStrings(form.capabilities.types).filter(Boolean);
|
||||
const primaryType = modelTypes[0] ?? 'text_generate';
|
||||
const capabilities = syncCapabilityTypes(form.capabilities, modelTypes);
|
||||
const modelAlias = form.modelAlias.trim() || form.providerModelName.trim();
|
||||
const metadata = parseJsonObject(form.metadataJson, '元数据 JSON');
|
||||
return {
|
||||
providerKey: form.providerKey.trim(),
|
||||
canonicalModelKey: form.canonicalModelKey.trim() || undefined,
|
||||
providerModelName: form.providerModelName.trim(),
|
||||
modelType: form.modelType.trim(),
|
||||
displayName: form.displayName.trim() || form.providerModelName.trim(),
|
||||
capabilities: parseJsonObject(form.capabilitiesJson, '能力 JSON'),
|
||||
modelType: modelTypes.length ? modelTypes : [primaryType],
|
||||
modelAlias,
|
||||
capabilities: capabilitiesFromForm(syncPrimaryCapability(capabilities, primaryType)),
|
||||
baseBillingConfig: parseJsonObject(form.billingJson, '基准计费 JSON'),
|
||||
defaultRateLimitPolicy: parseJsonObject(form.rateLimitJson, '限流 JSON'),
|
||||
metadata: parseJsonObject(form.metadataJson, '元数据 JSON'),
|
||||
pricingRuleSetId: form.pricingRuleSetId.trim() || undefined,
|
||||
runtimePolicySetId: form.runtimePolicySetId.trim() || undefined,
|
||||
runtimePolicyOverride: parseJsonObject(form.runtimePolicyOverrideJson, '运行策略覆盖 JSON'),
|
||||
metadata: { ...metadata, alias: modelAlias, originalTypes: modelTypes },
|
||||
pricingVersion: Number.parseInt(form.pricingVersion, 10) || 1,
|
||||
status: form.status,
|
||||
};
|
||||
}
|
||||
|
||||
function uniqueStrings(values: string[]) {
|
||||
return Array.from(new Set(values.map((item) => item.trim()).filter(Boolean)));
|
||||
}
|
||||
|
||||
function syncCapabilityTypes(state: CapabilityEditorState, modelTypes: string[]): CapabilityEditorState {
|
||||
const nextTypes = uniqueStrings(modelTypes).filter(Boolean);
|
||||
if (!nextTypes.length) return state;
|
||||
let next = state;
|
||||
nextTypes.forEach((type) => {
|
||||
next = addCapabilityType(next, type);
|
||||
});
|
||||
next.types
|
||||
.filter((type) => !nextTypes.includes(type))
|
||||
.forEach((type) => {
|
||||
next = removeCapabilityType(next, type);
|
||||
});
|
||||
return { ...next, types: nextTypes };
|
||||
}
|
||||
|
||||
function readModelTypes(model: BaseModelCatalogItem) {
|
||||
if (Array.isArray(model.modelType)) return model.modelType.map(String);
|
||||
const values = model.metadata?.originalTypes ?? model.capabilities?.originalTypes;
|
||||
if (Array.isArray(values)) return values.map(String);
|
||||
return [model.modelType].filter(Boolean);
|
||||
return [];
|
||||
}
|
||||
|
||||
function defaultRuleSetId(ruleSets: PricingRuleSet[]) {
|
||||
return ruleSets.find((item) => item.ruleSetKey === 'default-multimodal-v1')?.id ?? ruleSets[0]?.id ?? '';
|
||||
}
|
||||
|
||||
function defaultRuntimePolicyId(policySets: RuntimePolicySet[]) {
|
||||
return policySets.find((item) => item.policyKey === 'default-runtime-v1')?.id ?? policySets[0]?.id ?? '';
|
||||
}
|
||||
|
||||
function pricingRuleSetName(ruleSetId: string | undefined, ruleSets: PricingRuleSet[]) {
|
||||
if (!ruleSetId) return defaultRuleSetId(ruleSets) ? '默认计价规则' : '未绑定计价规则';
|
||||
return ruleSets.find((item) => item.id === ruleSetId)?.name ?? '未知计价规则';
|
||||
}
|
||||
|
||||
function runtimePolicySetName(policySetId: string | undefined, policySets: RuntimePolicySet[]) {
|
||||
if (!policySetId) return defaultRuntimePolicyId(policySets) ? '默认运行策略' : '未绑定运行策略';
|
||||
return policySets.find((item) => item.id === policySetId)?.name ?? '未知运行策略';
|
||||
}
|
||||
|
||||
function parseJsonObject(value: string, label: string) {
|
||||
@@ -361,10 +522,14 @@ function parseJsonObject(value: string, label: string) {
|
||||
}
|
||||
}
|
||||
|
||||
function stringifyJson(value?: Record<string, unknown>) {
|
||||
function stringifyJson(value?: object) {
|
||||
return JSON.stringify(value ?? {}, null, 2);
|
||||
}
|
||||
|
||||
function readObject(value: unknown) {
|
||||
return value && !Array.isArray(value) && typeof value === 'object' ? value as Record<string, unknown> : {};
|
||||
}
|
||||
|
||||
function readString(value: unknown) {
|
||||
return typeof value === 'string' ? value : '';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user