From 6d99e26e2acf454c9c2526a4804267fa2debdde2 Mon Sep 17 00:00:00 2001 From: wangbo Date: Sun, 24 May 2026 22:28:07 +0800 Subject: [PATCH] feat(admin): support provider model presets --- .../0040_canonical_provider_model_presets.sql | 774 ++++++++++++++++++ .../pages/admin/PlatformManagementPanel.tsx | 72 +- apps/web/src/pages/admin/platform-form.ts | 116 ++- 3 files changed, 922 insertions(+), 40 deletions(-) create mode 100644 apps/api/migrations/0040_canonical_provider_model_presets.sql diff --git a/apps/api/migrations/0040_canonical_provider_model_presets.sql b/apps/api/migrations/0040_canonical_provider_model_presets.sql new file mode 100644 index 0000000..e3a8010 --- /dev/null +++ b/apps/api/migrations/0040_canonical_provider_model_presets.sql @@ -0,0 +1,774 @@ +-- Represent provider-specific request model names as presets on the canonical +-- base model instead of duplicating the base model catalog row. + +WITH preset AS ( + SELECT jsonb_build_object( + 'providerKey', 'volces-openai', + 'providerCode', 'volces-openai', + 'providerModelName', 'glm-4-7-251222', + 'modelAlias', 'GLM-4.7', + 'displayName', 'GLM-4.7', + 'modelType', jsonb_build_array('text_generate', 'image_analysis', 'tools_call'), + 'source', 'server-main.integration-platform', + 'sourceProviderName', '火山引擎(OpenAI兼容)', + 'baseProviderKey', 'zhipu-openai', + 'baseProviderModelName', 'glm-4.7' + ) AS item +), +target AS ( + SELECT + b.id, + COALESCE(( + SELECT jsonb_agg(existing.item) + FROM jsonb_array_elements( + COALESCE(b.metadata->'providerModelPresets', '[]'::jsonb) + ) AS existing(item) + WHERE existing.item->>'providerKey' <> 'volces-openai' + ), '[]'::jsonb) || jsonb_build_array(preset.item) AS presets + FROM base_model_catalog b + CROSS JOIN preset + WHERE b.provider_key = 'zhipu-openai' + AND b.provider_model_name = 'glm-4.7' +) +UPDATE base_model_catalog b +SET metadata = (b.metadata - 'providerModelPresets') || + jsonb_build_object('providerModelPresets', target.presets), + default_snapshot = CASE + WHEN COALESCE(b.default_snapshot, '{}'::jsonb) = '{}'::jsonb THEN b.default_snapshot + ELSE jsonb_set( + b.default_snapshot, + '{metadata}', + (COALESCE(b.default_snapshot->'metadata', '{}'::jsonb) - 'providerModelPresets') || + jsonb_build_object('providerModelPresets', target.presets), + true + ) + END, + updated_at = now() +FROM target +WHERE b.id = target.id; + +WITH canonical_ref AS ( + SELECT jsonb_build_object( + 'providerKey', 'zhipu-openai', + 'providerCode', 'zhipu-openai', + 'modelAlias', 'GLM-4.7', + 'providerModelName', 'glm-4.7', + 'canonicalModelKey', canonical_model_key + ) AS ref + FROM base_model_catalog + WHERE provider_key = 'zhipu-openai' + AND provider_model_name = 'glm-4.7' + LIMIT 1 +), +deprecated_payload AS ( + SELECT jsonb_build_object( + 'canonicalBaseModelRef', canonical_ref.ref, + 'realModelNameOverride', 'glm-4-7-251222', + 'selectable', false, + 'hiddenReason', 'represented_by_provider_model_preset' + ) AS metadata_patch + FROM canonical_ref +) +UPDATE base_model_catalog b +SET status = 'hidden', + metadata = b.metadata || deprecated_payload.metadata_patch, + default_snapshot = CASE + WHEN COALESCE(b.default_snapshot, '{}'::jsonb) = '{}'::jsonb THEN b.default_snapshot + ELSE jsonb_set( + b.default_snapshot || jsonb_build_object('status', 'hidden'), + '{metadata}', + COALESCE(b.default_snapshot->'metadata', '{}'::jsonb) || + deprecated_payload.metadata_patch, + true + ) + END, + updated_at = now() +FROM deprecated_payload +WHERE b.provider_key = 'volces-openai' + AND b.provider_model_name = 'glm-4-7-251222'; + +WITH canonical AS ( + SELECT id, provider_model_name + FROM base_model_catalog + WHERE provider_key = 'zhipu-openai' + AND provider_model_name = 'glm-4.7' + LIMIT 1 +), +deprecated AS ( + SELECT id + FROM base_model_catalog + WHERE provider_key = 'volces-openai' + AND provider_model_name = 'glm-4-7-251222' + LIMIT 1 +) +UPDATE platform_models model +SET base_model_id = canonical.id, + model_name = canonical.provider_model_name, + provider_model_name = 'glm-4-7-251222', + model_alias = COALESCE(NULLIF(model.model_alias, ''), NULLIF(model.display_name, ''), 'GLM-4.7'), + display_name = COALESCE(NULLIF(model.display_name, ''), NULLIF(model.model_alias, ''), 'GLM-4.7'), + model_type = jsonb_build_array('text_generate', 'image_analysis', 'tools_call'), + enabled = true, + updated_at = now() +FROM integration_platforms platform, canonical, deprecated +WHERE model.platform_id = platform.id + AND platform.provider = 'volces-openai' + AND ( + model.base_model_id = deprecated.id + OR model.model_name = 'glm-4-7-251222' + OR model.provider_model_name = 'glm-4-7-251222' + ) + AND NOT EXISTS ( + SELECT 1 + FROM platform_models peer + WHERE peer.platform_id = model.platform_id + AND peer.id <> model.id + AND peer.model_name = canonical.provider_model_name + ); + +WITH canonical AS ( + SELECT id, provider_model_name + FROM base_model_catalog + WHERE provider_key = 'zhipu-openai' + AND provider_model_name = 'glm-4.7' + LIMIT 1 +), +deprecated AS ( + SELECT id + FROM base_model_catalog + WHERE provider_key = 'volces-openai' + AND provider_model_name = 'glm-4-7-251222' + LIMIT 1 +) +UPDATE platform_models peer +SET base_model_id = canonical.id, + model_name = canonical.provider_model_name, + provider_model_name = 'glm-4-7-251222', + model_alias = COALESCE( + NULLIF(( + SELECT legacy.model_alias + FROM platform_models legacy + WHERE legacy.platform_id = peer.platform_id + AND legacy.id <> peer.id + AND ( + legacy.base_model_id = deprecated.id + OR legacy.model_name = 'glm-4-7-251222' + OR legacy.provider_model_name = 'glm-4-7-251222' + ) + LIMIT 1 + ), ''), + NULLIF(( + SELECT legacy.display_name + FROM platform_models legacy + WHERE legacy.platform_id = peer.platform_id + AND legacy.id <> peer.id + AND ( + legacy.base_model_id = deprecated.id + OR legacy.model_name = 'glm-4-7-251222' + OR legacy.provider_model_name = 'glm-4-7-251222' + ) + LIMIT 1 + ), ''), + NULLIF(peer.model_alias, ''), + NULLIF(peer.display_name, ''), + 'GLM-4.7' + ), + display_name = COALESCE( + NULLIF(( + SELECT legacy.display_name + FROM platform_models legacy + WHERE legacy.platform_id = peer.platform_id + AND legacy.id <> peer.id + AND ( + legacy.base_model_id = deprecated.id + OR legacy.model_name = 'glm-4-7-251222' + OR legacy.provider_model_name = 'glm-4-7-251222' + ) + LIMIT 1 + ), ''), + NULLIF(( + SELECT legacy.model_alias + FROM platform_models legacy + WHERE legacy.platform_id = peer.platform_id + AND legacy.id <> peer.id + AND ( + legacy.base_model_id = deprecated.id + OR legacy.model_name = 'glm-4-7-251222' + OR legacy.provider_model_name = 'glm-4-7-251222' + ) + LIMIT 1 + ), ''), + NULLIF(peer.display_name, ''), + NULLIF(peer.model_alias, ''), + 'GLM-4.7' + ), + model_type = jsonb_build_array('text_generate', 'image_analysis', 'tools_call'), + enabled = true, + updated_at = now() +FROM integration_platforms platform, canonical, deprecated +WHERE peer.platform_id = platform.id + AND platform.provider = 'volces-openai' + AND peer.model_name = canonical.provider_model_name + AND EXISTS ( + SELECT 1 + FROM platform_models legacy + WHERE legacy.platform_id = peer.platform_id + AND legacy.id <> peer.id + AND ( + legacy.base_model_id = deprecated.id + OR legacy.model_name = 'glm-4-7-251222' + OR legacy.provider_model_name = 'glm-4-7-251222' + ) + ); + +WITH canonical AS ( + SELECT id, provider_model_name + FROM base_model_catalog + WHERE provider_key = 'zhipu-openai' + AND provider_model_name = 'glm-4.7' + LIMIT 1 +) +UPDATE platform_models peer +SET base_model_id = canonical.id, + model_name = canonical.provider_model_name, + provider_model_name = 'glm-4-7-251222', + model_alias = COALESCE(NULLIF(peer.model_alias, ''), NULLIF(peer.display_name, ''), 'GLM-4.7'), + display_name = COALESCE(NULLIF(peer.display_name, ''), NULLIF(peer.model_alias, ''), 'GLM-4.7'), + model_type = jsonb_build_array('text_generate', 'image_analysis', 'tools_call'), + enabled = true, + updated_at = now() +FROM integration_platforms platform, canonical +WHERE peer.platform_id = platform.id + AND platform.provider = 'volces-openai' + AND ( + peer.model_name = canonical.provider_model_name + OR peer.provider_model_name = 'glm-4-7-251222' + ); + +WITH canonical AS ( + SELECT id, provider_model_name + FROM base_model_catalog + WHERE provider_key = 'zhipu-openai' + AND provider_model_name = 'glm-4.7' + LIMIT 1 +), +deprecated AS ( + SELECT id + FROM base_model_catalog + WHERE provider_key = 'volces-openai' + AND provider_model_name = 'glm-4-7-251222' + LIMIT 1 +) +DELETE FROM platform_models model +USING integration_platforms platform, canonical, deprecated +WHERE model.platform_id = platform.id + AND platform.provider = 'volces-openai' + AND ( + model.base_model_id = deprecated.id + OR model.model_name = 'glm-4-7-251222' + OR model.provider_model_name = 'glm-4-7-251222' + ) + AND EXISTS ( + SELECT 1 + FROM platform_models peer + WHERE peer.platform_id = model.platform_id + AND peer.id <> model.id + AND peer.model_name = canonical.provider_model_name + ); + +-- DeepSeek V4 belongs to the DeepSeek base-model provider. Aliyun Bailian is +-- represented as a provider preset on those canonical base models. +WITH source_provider(provider_key, provider_code, provider_type, display_name, icon_path) AS ( + VALUES ( + 'deepseek-openai', + 'deepseek-openai', + 'openai', + 'DeepSeek', + 'https://static.51easyai.com/deepseek-color%20%281%29.webp' + ) +) +INSERT INTO model_catalog_providers ( + provider_key, provider_code, provider_type, display_name, icon_path, source, + capability_schema, default_rate_limit_policy, metadata, status +) +SELECT provider_key, + provider_code, + provider_type, + display_name, + icon_path, + 'server-main.integration-platform', + '{}'::jsonb, + '{}'::jsonb, + jsonb_build_object( + 'seed', 'server-main-provider-defaults', + 'syncSource', 'server-main.integration-platform', + 'sourceCode', provider_code, + 'sourceSpecType', provider_type + ), + 'active' +FROM source_provider +ON CONFLICT (provider_key) DO UPDATE +SET provider_code = EXCLUDED.provider_code, + display_name = EXCLUDED.display_name, + provider_type = EXCLUDED.provider_type, + icon_path = EXCLUDED.icon_path, + source = EXCLUDED.source, + metadata = model_catalog_providers.metadata || EXCLUDED.metadata, + status = EXCLUDED.status, + updated_at = now(); + +WITH source_models(provider_model_name, model_alias, display_name) AS ( + VALUES + ('deepseek-v4-pro', 'DeepSeek-V4-Pro', 'DeepSeek-V4-Pro'), + ('deepseek-v4-flash', 'DeepSeek-V4-Flash', 'DeepSeek-V4-Flash') +), +source_rows AS ( + SELECT + source_models.provider_model_name, + source_models.model_alias, + source_models.display_name, + COALESCE( + aliyun.capabilities, + jsonb_build_object('originalTypes', jsonb_build_array('text_generate', 'tools_call')) + ) AS capabilities, + COALESCE(aliyun.base_billing_config, '{}'::jsonb) AS base_billing_config, + COALESCE(aliyun.default_rate_limit_policy, '{}'::jsonb) AS default_rate_limit_policy, + COALESCE(aliyun.pricing_version, 1) AS pricing_version, + COALESCE(aliyun.metadata, '{}'::jsonb) AS metadata + FROM source_models + LEFT JOIN base_model_catalog aliyun + ON aliyun.provider_key = 'aliyun-bailian-openai' + AND aliyun.provider_model_name = source_models.provider_model_name +), +provider AS ( + SELECT id + FROM model_catalog_providers + WHERE provider_key = 'deepseek-openai' + LIMIT 1 +) +INSERT INTO base_model_catalog ( + provider_id, provider_key, canonical_model_key, provider_model_name, model_type, display_name, + capabilities, base_billing_config, default_rate_limit_policy, pricing_version, status, metadata +) +SELECT provider.id, + 'deepseek-openai', + 'deepseek-openai:' || source_rows.provider_model_name, + source_rows.provider_model_name, + jsonb_build_array('text_generate', 'tools_call'), + source_rows.display_name, + source_rows.capabilities, + source_rows.base_billing_config, + source_rows.default_rate_limit_policy, + source_rows.pricing_version, + 'active', + (source_rows.metadata - 'providerModelPresets') || + jsonb_build_object( + 'source', 'server-main.integration-platform', + 'sourceProviderCode', 'deepseek-openai', + 'sourceProviderName', 'DeepSeek', + 'sourceSpecType', 'openai', + 'alias', source_rows.model_alias, + 'iconPath', 'https://static.51easyai.com/deepseek-color%20%281%29.webp', + 'selectable', true + ) +FROM source_rows, provider +ON CONFLICT (canonical_model_key) DO UPDATE +SET provider_id = EXCLUDED.provider_id, + provider_key = EXCLUDED.provider_key, + provider_model_name = EXCLUDED.provider_model_name, + model_type = EXCLUDED.model_type, + display_name = EXCLUDED.display_name, + capabilities = EXCLUDED.capabilities, + base_billing_config = EXCLUDED.base_billing_config, + default_rate_limit_policy = EXCLUDED.default_rate_limit_policy, + metadata = (base_model_catalog.metadata - 'providerModelPresets') || EXCLUDED.metadata, + status = 'active', + updated_at = now(); + +WITH preset_source(provider_model_name, model_alias, display_name) AS ( + VALUES + ('deepseek-v4-pro', 'DeepSeek-V4-Pro', 'DeepSeek-V4-Pro'), + ('deepseek-v4-flash', 'DeepSeek-V4-Flash', 'DeepSeek-V4-Flash') +), +target AS ( + SELECT + b.id, + COALESCE(( + SELECT jsonb_agg(existing.item) + FROM jsonb_array_elements( + COALESCE(b.metadata->'providerModelPresets', '[]'::jsonb) + ) AS existing(item) + WHERE existing.item->>'providerKey' <> 'aliyun-bailian-openai' + ), '[]'::jsonb) || + jsonb_build_array( + jsonb_build_object( + 'providerKey', 'aliyun-bailian-openai', + 'providerCode', 'aliyun-bailian-openai', + 'providerModelName', preset_source.provider_model_name, + 'modelAlias', preset_source.model_alias, + 'displayName', preset_source.display_name, + 'modelType', jsonb_build_array('text_generate', 'tools_call'), + 'source', 'server-main.integration-platform', + 'sourceProviderName', '阿里云百炼(OpenAI兼容)', + 'baseProviderKey', 'deepseek-openai', + 'baseProviderModelName', preset_source.provider_model_name + ) + ) AS presets + FROM base_model_catalog b + JOIN preset_source ON preset_source.provider_model_name = b.provider_model_name + WHERE b.provider_key = 'deepseek-openai' +) +UPDATE base_model_catalog b +SET metadata = (b.metadata - 'providerModelPresets') || + jsonb_build_object('providerModelPresets', target.presets), + default_snapshot = CASE + WHEN COALESCE(b.default_snapshot, '{}'::jsonb) = '{}'::jsonb THEN b.default_snapshot + ELSE jsonb_set( + b.default_snapshot, + '{metadata}', + (COALESCE(b.default_snapshot->'metadata', '{}'::jsonb) - 'providerModelPresets') || + jsonb_build_object('providerModelPresets', target.presets), + true + ) + END, + updated_at = now() +FROM target +WHERE b.id = target.id; + +WITH canonical AS ( + SELECT id, provider_model_name, canonical_model_key + FROM base_model_catalog + WHERE provider_key = 'deepseek-openai' + AND provider_model_name IN ('deepseek-v4-pro', 'deepseek-v4-flash') +) +UPDATE base_model_catalog b +SET status = 'hidden', + metadata = b.metadata || + jsonb_build_object( + 'canonicalBaseModelRef', + jsonb_build_object( + 'providerKey', 'deepseek-openai', + 'providerCode', 'deepseek-openai', + 'providerModelName', canonical.provider_model_name, + 'canonicalModelKey', canonical.canonical_model_key + ), + 'selectable', false, + 'hiddenReason', 'represented_by_provider_model_preset' + ), + updated_at = now() +FROM canonical +WHERE b.provider_key = 'aliyun-bailian-openai' + AND b.provider_model_name = canonical.provider_model_name; + +WITH canonical AS ( + SELECT + id, + provider_model_name, + CASE provider_model_name + WHEN 'deepseek-v4-pro' THEN 'DeepSeek-V4-Pro' + WHEN 'deepseek-v4-flash' THEN 'DeepSeek-V4-Flash' + ELSE provider_model_name + END AS model_alias + FROM base_model_catalog + WHERE provider_key = 'deepseek-openai' + AND provider_model_name IN ('deepseek-v4-pro', 'deepseek-v4-flash') +) +UPDATE platform_models model +SET base_model_id = canonical.id, + model_name = canonical.provider_model_name, + provider_model_name = canonical.provider_model_name, + model_alias = COALESCE(NULLIF(model.model_alias, ''), NULLIF(model.display_name, ''), canonical.model_alias), + display_name = COALESCE(NULLIF(model.display_name, ''), NULLIF(model.model_alias, ''), canonical.model_alias), + model_type = jsonb_build_array('text_generate', 'tools_call'), + enabled = true, + updated_at = now() +FROM integration_platforms platform, canonical +WHERE model.platform_id = platform.id + AND platform.provider = 'aliyun-bailian-openai' + AND ( + model.model_name = canonical.provider_model_name + OR model.provider_model_name = canonical.provider_model_name + OR model.base_model_id IN ( + SELECT deprecated.id + FROM base_model_catalog deprecated + WHERE deprecated.provider_key = 'aliyun-bailian-openai' + AND deprecated.provider_model_name = canonical.provider_model_name + ) + ); + +-- DeepSeek-R1 and DeepSeek-V3 belong to the DeepSeek base-model provider. +-- SiliconFlow exposes them with a deepseek-ai/ request-name prefix. +WITH source_models(provider_model_name, model_alias, display_name, siliconflow_model_name) AS ( + VALUES + ('DeepSeek-R1', 'DeepSeek-R1', 'DeepSeek-R1', 'deepseek-ai/DeepSeek-R1'), + ('DeepSeek-V3', 'DeepSeek-V3', 'DeepSeek-V3', 'deepseek-ai/DeepSeek-V3') +), +source_rows AS ( + SELECT + source_models.provider_model_name, + source_models.model_alias, + source_models.display_name, + source_models.siliconflow_model_name, + COALESCE( + siliconflow.capabilities, + jsonb_build_object('originalTypes', jsonb_build_array('text_generate')) + ) AS capabilities, + COALESCE(siliconflow.base_billing_config, '{}'::jsonb) AS base_billing_config, + COALESCE(siliconflow.default_rate_limit_policy, '{}'::jsonb) AS default_rate_limit_policy, + COALESCE(siliconflow.pricing_version, 1) AS pricing_version, + COALESCE(siliconflow.metadata, '{}'::jsonb) AS metadata + FROM source_models + LEFT JOIN base_model_catalog siliconflow + ON siliconflow.provider_key = 'silicon-flow-openai' + AND siliconflow.provider_model_name = source_models.siliconflow_model_name +), +provider AS ( + SELECT id + FROM model_catalog_providers + WHERE provider_key = 'deepseek-openai' + LIMIT 1 +) +INSERT INTO base_model_catalog ( + provider_id, provider_key, canonical_model_key, provider_model_name, model_type, display_name, + capabilities, base_billing_config, default_rate_limit_policy, pricing_version, status, metadata +) +SELECT provider.id, + 'deepseek-openai', + 'deepseek-openai:' || source_rows.provider_model_name, + source_rows.provider_model_name, + jsonb_build_array('text_generate'), + source_rows.display_name, + source_rows.capabilities, + source_rows.base_billing_config, + source_rows.default_rate_limit_policy, + source_rows.pricing_version, + 'active', + (source_rows.metadata - 'providerModelPresets') || + jsonb_build_object( + 'source', 'server-main.integration-platform', + 'sourceProviderCode', 'deepseek-openai', + 'sourceProviderName', 'DeepSeek', + 'sourceSpecType', 'openai', + 'alias', source_rows.model_alias, + 'iconPath', 'https://static.51easyai.com/deepseek-color%20%281%29.webp', + 'selectable', true + ) +FROM source_rows, provider +ON CONFLICT (canonical_model_key) DO UPDATE +SET provider_id = EXCLUDED.provider_id, + provider_key = EXCLUDED.provider_key, + provider_model_name = EXCLUDED.provider_model_name, + model_type = EXCLUDED.model_type, + display_name = EXCLUDED.display_name, + capabilities = EXCLUDED.capabilities, + base_billing_config = EXCLUDED.base_billing_config, + default_rate_limit_policy = EXCLUDED.default_rate_limit_policy, + metadata = (base_model_catalog.metadata - 'providerModelPresets') || EXCLUDED.metadata, + status = 'active', + updated_at = now(); + +WITH preset_source(provider_model_name, model_alias, display_name, siliconflow_model_name) AS ( + VALUES + ('DeepSeek-R1', 'DeepSeek-R1', 'DeepSeek-R1', 'deepseek-ai/DeepSeek-R1'), + ('DeepSeek-V3', 'DeepSeek-V3', 'DeepSeek-V3', 'deepseek-ai/DeepSeek-V3') +), +target AS ( + SELECT + b.id, + COALESCE(( + SELECT jsonb_agg(existing.item) + FROM jsonb_array_elements( + COALESCE(b.metadata->'providerModelPresets', '[]'::jsonb) + ) AS existing(item) + WHERE existing.item->>'providerKey' <> 'silicon-flow-openai' + ), '[]'::jsonb) || + jsonb_build_array( + jsonb_build_object( + 'providerKey', 'silicon-flow-openai', + 'providerCode', 'silicon-flow-openai', + 'providerModelName', preset_source.siliconflow_model_name, + 'modelAlias', preset_source.model_alias, + 'displayName', preset_source.display_name, + 'modelType', jsonb_build_array('text_generate'), + 'source', 'server-main.integration-platform', + 'sourceProviderName', '硅基流动', + 'baseProviderKey', 'deepseek-openai', + 'baseProviderModelName', preset_source.provider_model_name + ) + ) AS presets + FROM base_model_catalog b + JOIN preset_source ON preset_source.provider_model_name = b.provider_model_name + WHERE b.provider_key = 'deepseek-openai' +) +UPDATE base_model_catalog b +SET metadata = (b.metadata - 'providerModelPresets') || + jsonb_build_object('providerModelPresets', target.presets), + default_snapshot = CASE + WHEN COALESCE(b.default_snapshot, '{}'::jsonb) = '{}'::jsonb THEN b.default_snapshot + ELSE jsonb_set( + b.default_snapshot, + '{metadata}', + (COALESCE(b.default_snapshot->'metadata', '{}'::jsonb) - 'providerModelPresets') || + jsonb_build_object('providerModelPresets', target.presets), + true + ) + END, + updated_at = now() +FROM target +WHERE b.id = target.id; + +WITH canonical AS ( + SELECT + id, + provider_model_name, + canonical_model_key, + CASE provider_model_name + WHEN 'DeepSeek-R1' THEN 'deepseek-ai/DeepSeek-R1' + WHEN 'DeepSeek-V3' THEN 'deepseek-ai/DeepSeek-V3' + END AS siliconflow_model_name + FROM base_model_catalog + WHERE provider_key = 'deepseek-openai' + AND provider_model_name IN ('DeepSeek-R1', 'DeepSeek-V3') +) +UPDATE base_model_catalog b +SET status = 'hidden', + metadata = b.metadata || + jsonb_build_object( + 'canonicalBaseModelRef', + jsonb_build_object( + 'providerKey', 'deepseek-openai', + 'providerCode', 'deepseek-openai', + 'providerModelName', canonical.provider_model_name, + 'canonicalModelKey', canonical.canonical_model_key + ), + 'realModelNameOverride', canonical.siliconflow_model_name, + 'selectable', false, + 'hiddenReason', 'represented_by_provider_model_preset' + ), + default_snapshot = CASE + WHEN COALESCE(b.default_snapshot, '{}'::jsonb) = '{}'::jsonb THEN b.default_snapshot + ELSE jsonb_set( + b.default_snapshot || jsonb_build_object('status', 'hidden'), + '{metadata}', + COALESCE(b.default_snapshot->'metadata', '{}'::jsonb) || + jsonb_build_object( + 'canonicalBaseModelRef', + jsonb_build_object( + 'providerKey', 'deepseek-openai', + 'providerCode', 'deepseek-openai', + 'providerModelName', canonical.provider_model_name, + 'canonicalModelKey', canonical.canonical_model_key + ), + 'realModelNameOverride', canonical.siliconflow_model_name, + 'selectable', false, + 'hiddenReason', 'represented_by_provider_model_preset' + ), + true + ) + END, + updated_at = now() +FROM canonical +WHERE b.provider_key = 'silicon-flow-openai' + AND b.provider_model_name = canonical.siliconflow_model_name; + +WITH canonical AS ( + SELECT + id, + provider_model_name, + provider_model_name AS model_alias, + CASE provider_model_name + WHEN 'DeepSeek-R1' THEN 'deepseek-ai/DeepSeek-R1' + WHEN 'DeepSeek-V3' THEN 'deepseek-ai/DeepSeek-V3' + END AS siliconflow_model_name + FROM base_model_catalog + WHERE provider_key = 'deepseek-openai' + AND provider_model_name IN ('DeepSeek-R1', 'DeepSeek-V3') +) +UPDATE platform_models peer +SET base_model_id = canonical.id, + provider_model_name = canonical.siliconflow_model_name, + model_alias = COALESCE(NULLIF(peer.model_alias, ''), NULLIF(peer.display_name, ''), canonical.model_alias), + model_type = jsonb_build_array('text_generate'), + display_name = COALESCE(NULLIF(peer.display_name, ''), NULLIF(peer.model_alias, ''), canonical.model_alias), + enabled = true, + updated_at = now() +FROM integration_platforms platform, canonical +WHERE peer.platform_id = platform.id + AND platform.provider = 'silicon-flow-openai' + AND peer.model_name = canonical.provider_model_name; + +WITH canonical AS ( + SELECT + id, + provider_model_name, + provider_model_name AS model_alias, + CASE provider_model_name + WHEN 'DeepSeek-R1' THEN 'deepseek-ai/DeepSeek-R1' + WHEN 'DeepSeek-V3' THEN 'deepseek-ai/DeepSeek-V3' + END AS siliconflow_model_name + FROM base_model_catalog + WHERE provider_key = 'deepseek-openai' + AND provider_model_name IN ('DeepSeek-R1', 'DeepSeek-V3') +) +UPDATE platform_models model +SET base_model_id = canonical.id, + model_name = canonical.provider_model_name, + provider_model_name = canonical.siliconflow_model_name, + model_alias = COALESCE(NULLIF(model.model_alias, ''), NULLIF(model.display_name, ''), canonical.model_alias), + model_type = jsonb_build_array('text_generate'), + display_name = COALESCE(NULLIF(model.display_name, ''), NULLIF(model.model_alias, ''), canonical.model_alias), + enabled = true, + updated_at = now() +FROM integration_platforms platform, canonical +WHERE model.platform_id = platform.id + AND platform.provider = 'silicon-flow-openai' + AND ( + model.model_name = canonical.siliconflow_model_name + OR model.provider_model_name = canonical.siliconflow_model_name + OR model.base_model_id IN ( + SELECT deprecated.id + FROM base_model_catalog deprecated + WHERE deprecated.provider_key = 'silicon-flow-openai' + AND deprecated.provider_model_name = canonical.siliconflow_model_name + ) + ) + AND NOT EXISTS ( + SELECT 1 + FROM platform_models peer + WHERE peer.platform_id = model.platform_id + AND peer.id <> model.id + AND peer.model_name = canonical.provider_model_name + ); + +WITH canonical AS ( + SELECT + provider_model_name, + CASE provider_model_name + WHEN 'DeepSeek-R1' THEN 'deepseek-ai/DeepSeek-R1' + WHEN 'DeepSeek-V3' THEN 'deepseek-ai/DeepSeek-V3' + END AS siliconflow_model_name + FROM base_model_catalog + WHERE provider_key = 'deepseek-openai' + AND provider_model_name IN ('DeepSeek-R1', 'DeepSeek-V3') +) +DELETE FROM platform_models model +USING integration_platforms platform, canonical +WHERE model.platform_id = platform.id + AND platform.provider = 'silicon-flow-openai' + AND ( + model.model_name = canonical.siliconflow_model_name + OR model.provider_model_name = canonical.siliconflow_model_name + OR model.base_model_id IN ( + SELECT deprecated.id + FROM base_model_catalog deprecated + WHERE deprecated.provider_key = 'silicon-flow-openai' + AND deprecated.provider_model_name = canonical.siliconflow_model_name + ) + ) + AND EXISTS ( + SELECT 1 + FROM platform_models peer + WHERE peer.platform_id = model.platform_id + AND peer.id <> model.id + AND peer.model_name = canonical.provider_model_name + ); diff --git a/apps/web/src/pages/admin/PlatformManagementPanel.tsx b/apps/web/src/pages/admin/PlatformManagementPanel.tsx index 900e92c..3baec26 100644 --- a/apps/web/src/pages/admin/PlatformManagementPanel.tsx +++ b/apps/web/src/pages/admin/PlatformManagementPanel.tsx @@ -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(() => 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: {
{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 (
{modelLabel} - {props.providerMap.get(model.providerKey)?.displayName ?? model.providerKey} · {model.providerModelName} · {baseModelTypeText(model)} + + {props.providerMap.get(model.providerKey)?.displayName ?? model.providerKey} + {providerPreset ? ` · ${props.providerName} 真实名 ${defaultProviderModelName}` : ` · ${model.providerModelName}`} + {' · '} + {baseModelTypeTextForProvider(model, props.currentProvider)} +
@@ -777,7 +792,7 @@ function ModelSelection(props: { 调用模型名 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(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: {
- {filteredModels.length ? filteredModels.map((model) => ( - - )) :
没有匹配的模型。
} + {filteredModels.length ? filteredModels.map((model) => { + const providerForText = providerFilter === 'all' ? props.currentProvider : providerFilter; + const preset = providerModelPresetFor(model, providerForText); + const providerModelName = providerModelNameForProvider(model, providerForText); + return ( + + ); + }) :
没有匹配的模型。
}