refactor(*): 平台架构 - 拆分后端领域模块并同步插件协议与模型能力

This commit is contained in:
ddCat
2026-09-11 14:02:49 +08:00
parent 916043fb5a
commit 6e5cbc8b05
644 changed files with 62731 additions and 2320 deletions

View File

@@ -61,17 +61,29 @@ export function imageResolutionUsesQuality(profile: ImageCapabilityConfig) {
}
export function imageTierAvailable(profile: ImageCapabilityConfig, tier: ImageResolutionTier) {
// 比例协议必须能把档位传给上游;允许自定义比例不等于支持任意分辨率。
// 质量参数能映射档位时按上游质量裁剪;管理员尺寸预设存在时再取交集,避免默认 low/medium/high 把未启用的 1K 显示出来。
if (profile.size.parameter !== "aspect_ratio") return profile.size.parameter !== "none";
return imageResolutionUsesQuality(profile) ? Boolean(imageQualityForTier(profile, tier)) : tier === "1k";
const configuredTiers = new Set(profile.size.presets?.map((preset) => preset.tier));
if (imageResolutionUsesQuality(profile)) {
if (!imageQualityForTier(profile, tier)) return false;
return configuredTiers.size ? configuredTiers.has(tier) : true;
}
if (configuredTiers.size) return configuredTiers.has(tier);
// 无质量映射且无预设时,后端 filterImageSizePresets 会返回全部预设或空;
// 前端不应硬编码 1k 兜底,避免产生后端不认可的幻影选项。
return false;
}
export function imageQualityForSelection(profile: ImageCapabilityConfig, tier: ImageResolutionTier) {
return imageQualityForTier(profile, tier) || (!imageResolutionUsesQuality(profile) && imageTierAvailable(profile, tier) ? tier : undefined);
}
export function imageSizePresets(profile: ImageCapabilityConfig): ImageResolutionOption[] {
if (profile.size.parameter === "none") return [];
if (profile.size.presets) return profile.size.presets;
const pixels = buildImageResolutionOptions(profile.size.values);
const tiers = profile.size.parameter === "aspect_ratio" ? IMAGE_RESOLUTIONS.filter((tier) => imageQualityForTier(profile, tier)) : ["1k" as const];
const ratios = (tiers.length ? tiers : ["1k" as const]).flatMap((tier) =>
const tiers = profile.size.parameter === "aspect_ratio" ? IMAGE_RESOLUTIONS.filter((tier) => imageQualityForTier(profile, tier)) : [];
const ratios = tiers.flatMap((tier) =>
profile.size.values.flatMap((ratio) => {
try {
const preset = imagePresetForRatio(tier, ratio);

View File

@@ -1,5 +1,5 @@
import type { ModelProtocol, ModelProtocolWorkflow } from "@/lib/model-protocols";
import type { ImageResolutionOption } from "@/lib/image-resolution-tiers";
import type { ImageResolutionOption, ImageResolutionTier } from "@/lib/image-resolution-tiers";
export type ModelCapabilityConfig = {
version: number;
@@ -897,18 +897,29 @@ function matchWorkflowValue(value: string, options: string[]) {
export function normalizeImageValue(profile: ImageCapabilityConfig, value: { size?: string; quality?: string; count?: string; transparentBackground?: string }) {
const size = normalizeImageSizeSetting(profile, value.size);
const requestedQuality = String(value.quality || "").trim().toLowerCase();
// 比例协议的固定分辨率预设没有独立 quality 字段时,UI 仍需把当前比例对应的
// 预设档位带入请求。仅在 quality 未声明支持时启用,避免与 auto/low/medium/high
// 这组真实图片质量语义混用。
const presetTier = !profile.quality.supported ? imagePresetTierForSelection(profile, size) : undefined;
const quality = profile.quality.supported
? requestedQuality === "auto" || requestedQuality === "any"
? "auto"
: value.quality && profile.quality.values.includes(value.quality)
? value.quality
: profile.quality.default || "auto"
: profile.quality.default || "auto";
: requestedQuality === "1k" || requestedQuality === "2k" || requestedQuality === "4k"
? requestedQuality
: presetTier || profile.quality.default || "auto";
const count = String(Math.max(1, Math.min(profile.maxOutputs, Math.floor(Math.abs(Number(value.count)) || 1))));
const transparentBackground = profile.transparentBackground.supported && value.transparentBackground === "true" ? "true" : "false";
return { size, quality, count, transparentBackground };
}
function imagePresetTierForSelection(profile: ImageCapabilityConfig, size: string): ImageResolutionTier | undefined {
if (profile.size.parameter !== "aspect_ratio" || !size || size === "auto") return undefined;
return profile.size.presets?.find((preset) => preset.ratio === size)?.tier;
}
export function normalizeImageSizeSetting(profile: ImageCapabilityConfig, value?: string) {
if (profile.size.parameter === "none") return "auto";
const candidate = value?.trim() || profile.size.default;

View File

@@ -9,6 +9,7 @@ import { ASSET_STORE_KEY, flushAssetStorePersistence, useAssetStore } from "@/st
import { CONFIG_STORE_KEY, PUBLIC_MODEL_CATALOG_ID, defaultConfig, normalizeConfigSnapshot, useConfigStore, type ModelCapability, type ModelChannel } from "@/stores/use-config-store";
import { CREATION_PREFERENCES_STORE_KEY, useCreationPreferencesStore } from "@/stores/use-creation-preferences-store";
import { defaultModelCapabilityConfig, STANDARD_IMAGE_SIZE_VALUES, type ModelCapabilityConfig } from "@/lib/model-capabilities";
import { imageSizeConfigWithPresets } from "@/lib/image-size-presets";
import { useUserStore } from "@/stores/use-user-store";
import { PLUGIN_STORE_KEY, usePluginStore } from "@/stores/use-plugin-store";
import { initializeRemoteUserDataSession, installRemoteUserDataAutoSync, resetRemoteUserDataSync, withRemoteUserDataSyncExclusive } from "@/services/user-data-sync";
@@ -198,7 +199,7 @@ export function systemChannelModelChannels(channels: PublicChannelCatalog[]): Mo
});
}
function projectLogicalCapability(spec: CapabilitySpec, defaults: Record<string, unknown>): ModelCapabilityConfig {
export function projectLogicalCapability(spec: CapabilitySpec, defaults: Record<string, unknown>): ModelCapabilityConfig {
const projected = defaultModelCapabilityConfig();
if (spec.capability === "image" && projected.image) {
projected.image.references.maxImages = spec.inputs?.image?.max ?? 0;
@@ -208,11 +209,13 @@ function projectLogicalCapability(spec: CapabilitySpec, defaults: Record<string,
projected.image.transparentBackground = { supported: false, default: false };
const sizeOption = spec.options?.size || spec.options?.aspectRatio;
const sizeValues = stringValues(sizeOption);
const sizeAllowsCustom = sizeValues.includes("*");
const sizeAllowsCustom = sizeValues.includes("*") || Boolean(spec.imageSize?.allowCustom);
const concreteSizeValues = sizeValues.filter((value) => value !== "*");
const sizePresets = concreteSizeValues.length ? concreteSizeValues : sizeAllowsCustom ? [...STANDARD_IMAGE_SIZE_VALUES] : [];
if (sizePresets.length || sizeAllowsCustom) {
projected.image!.size = { parameter: "size", values: sizePresets, default: concreteDefault(defaults.size, sizePresets, "1:1"), allowCustom: sizeAllowsCustom };
if (sizePresets.length || sizeAllowsCustom || spec.imageSize?.presets?.length) {
const parameter = spec.imageSize?.parameter === "aspect_ratio" || spec.imageSize?.parameter === "size" ? spec.imageSize.parameter : "size";
projected.image.size = { parameter, values: sizePresets, default: concreteDefault(defaults.size, sizePresets, "1:1"), allowCustom: sizeAllowsCustom };
if (spec.imageSize?.presets?.length) projected.image.size = imageSizeConfigWithPresets(projected.image, spec.imageSize.presets);
}
applyStringOption(spec.options?.quality, defaults.quality, (values, initial) => {
projected.image!.quality = { supported: true, values, default: initial };