fix(*): 模型目录 - 修复前台开关、系统模型价格与访问控制
This commit is contained in:
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user