fix(*): 模型目录 - 修复前台开关、系统模型价格与访问控制

This commit is contained in:
ddCat
2026-08-22 20:57:31 +08:00
parent 5d9986f6c7
commit 1bb5d1423a
19 changed files with 989 additions and 84 deletions

View File

@@ -1,11 +1,11 @@
import { getFeatureAvailability, type AuthSessionPayload } from "@/services/api/auth";
import { listLogicalModels, type CapabilitySpec, type OptionConstraint, type PublicLogicalModel } from "@/services/api/logical-models";
import { getModelCatalog, listLogicalModels, type CapabilitySpec, type ModelCatalogResponse, type OptionConstraint, type PublicChannelCatalog, type PublicLogicalModel } from "@/services/api/logical-models";
import { localForageStorage } from "@/lib/localforage-storage";
import { appQueryClient } from "@/lib/query-client";
import { scopedLocalStorage, setActiveUserScope } from "@/lib/user-scope";
import { CANVAS_STORE_KEY, flushCanvasStorePersistence, useCanvasStore } from "@/stores/canvas/use-canvas-store";
import { ASSET_STORE_KEY, flushAssetStorePersistence, useAssetStore } from "@/stores/use-asset-store";
import { CONFIG_STORE_KEY, PUBLIC_MODEL_CATALOG_ID, defaultConfig, normalizeConfigSnapshot, useConfigStore, type ModelChannel } from "@/stores/use-config-store";
import { CONFIG_STORE_KEY, PUBLIC_MODEL_CATALOG_ID, defaultConfig, normalizeConfigSnapshot, useConfigStore, type ModelCapability, type ModelChannel } from "@/stores/use-config-store";
import { defaultModelCapabilityConfig, STANDARD_IMAGE_SIZE_VALUES, type ModelCapabilityConfig } from "@/lib/model-capabilities";
import { useUserStore } from "@/stores/use-user-store";
import { installRemoteUserDataAutoSync, resetRemoteUserDataSync, syncRemoteUserData, withRemoteUserDataSyncPaused } from "@/services/user-data-sync";
@@ -41,9 +41,17 @@ export async function applyUserSession(payload: AuthSessionPayload) {
if (!persistedAssets) useAssetStore.setState({ assets: [] });
if (!persistedConfig) {
// 只有首次配置缺失时才生成能力推荐;已有配置中的空数组代表用户明确清空。
// 使用统一模型目录接口
const catalog = await getModelCatalog();
let channels: ModelChannel[] = [];
if (catalog.source === "frontend" && catalog.models) {
channels = managedModelChannels(catalog.models);
} else if (catalog.source === "system" && catalog.channels) {
channels = systemChannelModelChannels(catalog.channels);
}
const initialSystemConfig = {
...defaultConfig,
channels: managedModelChannels(payload.logicalModels || []),
channels,
imageModels: undefined,
videoModels: undefined,
textModels: undefined,
@@ -51,7 +59,13 @@ export async function applyUserSession(payload: AuthSessionPayload) {
};
useConfigStore.getState().replaceConfig(normalizeConfigSnapshot({ config: initialSystemConfig }).config);
} else {
useConfigStore.getState().mergeSystemChannels(managedModelChannels(payload.logicalModels || []));
// 已有配置时也需要合并最新的系统渠道
const catalog = await getModelCatalog();
if (catalog.source === "frontend" && catalog.models) {
useConfigStore.getState().mergeSystemChannels(managedModelChannels(catalog.models));
} else if (catalog.source === "system" && catalog.channels) {
useConfigStore.getState().mergeSystemChannels(systemChannelModelChannels(catalog.channels));
}
}
installRemoteUserDataAutoSync();
if (payload.user?.id) {
@@ -64,9 +78,16 @@ export async function applyUserSession(payload: AuthSessionPayload) {
}
export async function refreshSystemChannels() {
// 创作端只刷新公开前台模型;供应渠道目录仅管理员页面可见。
const logicalPayload = await listLogicalModels();
useConfigStore.getState().mergeSystemChannels(managedModelChannels(logicalPayload.models || []));
// 使用统一模型目录接口,根据 frontendModelsEnabled 自动返回前台模型或系统渠道模型
const catalog = await getModelCatalog();
if (catalog.source === "frontend" && catalog.models) {
// 前台模型模式
useConfigStore.getState().mergeSystemChannels(managedModelChannels(catalog.models));
} else if (catalog.source === "system" && catalog.channels) {
// 系统渠道模式
useConfigStore.getState().mergeSystemChannels(systemChannelModelChannels(catalog.channels));
}
}
function managedModelChannels(models: PublicLogicalModel[]) {
@@ -107,6 +128,62 @@ function managedModelChannels(models: PublicLogicalModel[]) {
return [managed];
}
// 系统渠道模型转换为前端配置格式
function systemChannelModelChannels(channels: PublicChannelCatalog[]): ModelChannel[] {
return channels.map((channel) => {
const availableModels = channel.models.filter((m) => m.available);
return {
id: channel.id,
name: channel.displayName,
baseUrl: "/api",
apiKey: "system",
apiFormat: "openai",
scope: "system" as const,
enabled: true,
models: availableModels.map((m) => m.modelKey),
modelAliases: {},
modelCosts: availableModels.map((model) => {
// 标量价格仅用于旧配置兼容;创作端按完整 SKU 档位展示和匹配价格。
const firstTier = model.priceTiers?.[0];
const unitPrice = firstTier?.unitPriceMicrocredits || 0;
const inputPrice = firstTier?.inputTokenPriceMicrocredits || 0;
const outputPrice = firstTier?.outputTokenPriceMicrocredits || 0;
const cachedPrice = firstTier?.cachedTokenPriceMicrocredits || 0;
const billingMode = firstTier?.billingMode || "fixed_request";
const logicalPriceTiers = (model.priceTiers || []).map((tier) => ({
selector: tier.selector || {},
resolution: tier.resolution || "*",
videoSeconds: tier.videoSeconds || 0,
billingMode: tier.billingMode as "fixed_request" | "per_second" | "token",
unitPriceMicrocredits: tier.unitPriceMicrocredits || 0,
inputTokenPriceMicrocredits: tier.inputTokenPriceMicrocredits || 0,
outputTokenPriceMicrocredits: tier.outputTokenPriceMicrocredits || 0,
cachedTokenPriceMicrocredits: tier.cachedTokenPriceMicrocredits || 0,
}));
return {
model: model.modelKey,
displayName: model.displayName,
description: "",
icon: "",
capability: model.capability as ModelCapability,
pricePolicy: "channel" as const,
billingMode: billingMode as any,
unitPriceMicrocredits: unitPrice,
inputTokenPriceMicrocredits: inputPrice,
outputTokenPriceMicrocredits: outputPrice,
cachedTokenPriceMicrocredits: cachedPrice,
capabilityConfig: (model.capabilityConfig as ModelCapabilityConfig | undefined) || defaultModelCapabilityConfig(),
channelModelId: model.id,
channelId: channel.id,
modelKey: model.modelKey,
logicalPriceTiers,
};
}),
};
});
}
function projectLogicalCapability(spec: CapabilitySpec, defaults: Record<string, unknown>): ModelCapabilityConfig {
const projected = defaultModelCapabilityConfig();
if (spec.capability === "image" && projected.image) {