refactor(*): 平台架构 - 拆分后端领域模块并同步插件协议与模型能力
This commit is contained in:
@@ -4,13 +4,74 @@ import { ImageSizePicker } from "../src/components/image-size-picker";
|
||||
import { applyImageSizeSelection } from "../src/components/image-settings-panel";
|
||||
import { ImageSizePresetsEditor } from "../src/components/image-size-presets-editor";
|
||||
import { ModelCapabilityEditor } from "../src/components/model-capability-editor";
|
||||
import { defaultImageCapabilityConfig, normalizeModelCapabilityConfig } from "../src/lib/model-capabilities";
|
||||
import { IMAGE_RATIOS, IMAGE_RESOLUTIONS, imagePresetForRatio, imagePresetValue, imageQualityForTier, imageSizeConfigWithPresets, imageSizePresets, imageTierAvailable } from "../src/lib/image-size-presets";
|
||||
import { defaultImageCapabilityConfig, normalizeImageValue, normalizeModelCapabilityConfig } from "../src/lib/model-capabilities";
|
||||
import { IMAGE_RATIOS, IMAGE_RESOLUTIONS, imagePresetForRatio, imagePresetValue, imageQualityForSelection, imageQualityForTier, imageSizeConfigWithPresets, imageSizePresets, imageTierAvailable } from "../src/lib/image-size-presets";
|
||||
import { buildImageResolutionOptions } from "../src/lib/image-resolution-tiers";
|
||||
import { resolveImageRequestSize, validateImageSize } from "../src/services/api/image-validation";
|
||||
import { buildGeminiImageGenerationConfig } from "../src/lib/gemini-image";
|
||||
|
||||
describe("统一图片分辨率与宽高比", () => {
|
||||
test("默认 Gemini 质量枚举不能把未启用的 1K 显示出来", () => {
|
||||
const profile = defaultImageCapabilityConfig("gemini-image", "nano-banana-pro-4k");
|
||||
profile.size = imageSizeConfigWithPresets(profile, IMAGE_RATIOS.map((ratio) => imagePresetForRatio("4k", ratio)));
|
||||
expect(profile.quality.supported).toBe(true);
|
||||
expect(imageTierAvailable(profile, "4k")).toBe(true);
|
||||
expect(imageTierAvailable(profile, "1k")).toBe(false);
|
||||
const picker = renderToStaticMarkup(<ImageSizePicker profile={profile} size="16:9" quality="high" onChange={() => {}} />);
|
||||
expect(picker).toMatch(/aria-pressed="true"[^>]*>4K/);
|
||||
expect(picker).not.toMatch(/<button[^>]*>1K/);
|
||||
});
|
||||
|
||||
test("无质量参数的固定 4K 模型保留管理员档位,重开及自定义比例不回退 1K", () => {
|
||||
const profile = defaultImageCapabilityConfig("gemini-image", "nano-banana-pro-4k");
|
||||
profile.quality = { supported: false, values: [], default: "auto" };
|
||||
profile.size = imageSizeConfigWithPresets(profile, IMAGE_RATIOS.map((ratio) => imagePresetForRatio("4k", ratio)));
|
||||
expect(imageTierAvailable(profile, "4k")).toBe(true);
|
||||
expect(imageTierAvailable(profile, "1k")).toBe(false);
|
||||
expect(imageQualityForTier(profile, "4k")).toBeUndefined();
|
||||
const picker = renderToStaticMarkup(<ImageSizePicker profile={profile} size="16:9" onChange={() => {}} />);
|
||||
expect(picker).toMatch(/aria-pressed="true"[^>]*>4K/);
|
||||
expect(picker).not.toMatch(/<button[^>]*>[12]K/);
|
||||
expect(picker).toContain("3840 × 2160 px");
|
||||
expect(normalizeImageValue(profile, { size: "16:9", quality: "auto" }).quality).toBe("4k");
|
||||
expect(resolveImageRequestSize(profile, undefined, "16:9")).toEqual({ parameter: "aspect_ratio", value: "16:9" });
|
||||
profile.size.allowCustom = true;
|
||||
const custom = renderToStaticMarkup(<ImageSizePicker profile={profile} size="17:11" onChange={() => {}} />);
|
||||
expect(custom).toMatch(/aria-pressed="true"[^>]*>4K/);
|
||||
expect(custom).toContain("17:11 ·");
|
||||
});
|
||||
|
||||
test("无质量参数时管理员启用的 1K 和 4K 都对用户开放,未启用的 2K 仍隐藏", () => {
|
||||
const profile = defaultImageCapabilityConfig("gemini-image", "test");
|
||||
profile.quality = { supported: false, values: [], default: "auto" };
|
||||
profile.size = imageSizeConfigWithPresets(profile, [...IMAGE_RATIOS.flatMap((ratio) => [imagePresetForRatio("1k", ratio), imagePresetForRatio("4k", ratio)])]);
|
||||
expect(imageTierAvailable(profile, "1k")).toBe(true);
|
||||
expect(imageTierAvailable(profile, "4k")).toBe(true);
|
||||
expect(imageTierAvailable(profile, "2k")).toBe(false);
|
||||
expect(imageQualityForSelection(profile, "4k")).toBe("4k");
|
||||
expect(normalizeImageValue(profile, { size: "16:9", quality: "4k" }).quality).toBe("4k");
|
||||
expect(buildGeminiImageGenerationConfig("16:9", "4k").imageConfig).toEqual({ aspectRatio: "16:9", imageSize: "4K" });
|
||||
const picker = renderToStaticMarkup(<ImageSizePicker profile={profile} size="16:9" onChange={() => {}} />);
|
||||
expect(picker).toMatch(/<button[^>]*>1K/);
|
||||
expect(picker).toMatch(/<button[^>]*>4K/);
|
||||
expect(picker).not.toMatch(/<button[^>]*>2K/);
|
||||
expect(picker).not.toContain("当前协议未配置独立分辨率");
|
||||
const selected = renderToStaticMarkup(<ImageSizePicker profile={profile} size="16:9" quality="4k" onChange={() => {}} />);
|
||||
expect(selected).toMatch(/aria-pressed="true"[^>]*>4K/);
|
||||
expect(selected).not.toMatch(/aria-pressed="true"[^>]*>1K/);
|
||||
expect(selected).toContain("3840 × 2160 px");
|
||||
const editor = renderToStaticMarkup(<ImageSizePresetsEditor profile={profile} onChange={() => {}} />);
|
||||
expect(editor).not.toContain("需先配置此档位对应的质量值,用户端才会开放。");
|
||||
});
|
||||
|
||||
test("真实图片质量字段不被分辨率预设推断覆盖", () => {
|
||||
const profile = defaultImageCapabilityConfig("gemini-image", "test");
|
||||
profile.quality = { supported: true, values: ["auto", "low", "medium", "high"], default: "auto" };
|
||||
profile.size = imageSizeConfigWithPresets(profile, [imagePresetForRatio("2k", "16:9"), imagePresetForRatio("4k", "16:9")]);
|
||||
expect(normalizeImageValue(profile, { size: "16:9", quality: "auto" }).quality).toBe("auto");
|
||||
expect(normalizeImageValue(profile, { size: "16:9", quality: "high" }).quality).toBe("high");
|
||||
});
|
||||
|
||||
test("比例协议切换 4K 时同时提交尺寸和真实质量档位", () => {
|
||||
const changes: Array<[string, string]> = [];
|
||||
applyImageSizeSelection((key, value) => changes.push([key, value]), "16:9", "high");
|
||||
|
||||
61
web/test/logical-image-size.test.tsx
Normal file
61
web/test/logical-image-size.test.tsx
Normal file
@@ -0,0 +1,61 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import { renderToStaticMarkup } from "react-dom/server";
|
||||
import { ImageSizePicker } from "../src/components/image-size-picker";
|
||||
import { IMAGE_RATIOS, imagePresetForRatio, imageSizeConfigWithPresets, imageTierAvailable } from "../src/lib/image-size-presets";
|
||||
import { defaultImageCapabilityConfig } from "../src/lib/model-capabilities";
|
||||
import { projectLogicalCapability } from "../src/lib/user-session";
|
||||
import { capabilitySpecFromChannelModel, mergeCapabilitySpecs } from "../src/pages/admin/logical-models/model-routing-capabilities";
|
||||
import type { ChannelModel } from "../src/services/api/wallet";
|
||||
|
||||
function channelModelWithPresets(): ChannelModel {
|
||||
const profile = defaultImageCapabilityConfig("gemini-image", "test");
|
||||
profile.quality = { supported: false, values: [], default: "auto" };
|
||||
profile.size = imageSizeConfigWithPresets(profile, IMAGE_RATIOS.flatMap((ratio) => [imagePresetForRatio("1k", ratio), imagePresetForRatio("4k", ratio)]));
|
||||
return {
|
||||
id: "cm-1",
|
||||
channelId: "ch-1",
|
||||
modelKey: "nano-banana",
|
||||
displayName: "Nano Banana",
|
||||
capability: "image",
|
||||
protocol: "gemini-image",
|
||||
capabilityConfig: { version: 1, image: profile },
|
||||
} as ChannelModel;
|
||||
}
|
||||
|
||||
describe("前台模型同步系统模型尺寸档位", () => {
|
||||
test("供应线路规格保留 aspect_ratio 与 1K/4K 预设", () => {
|
||||
const spec = capabilitySpecFromChannelModel(channelModelWithPresets());
|
||||
expect(spec?.imageSize?.parameter).toBe("aspect_ratio");
|
||||
expect(spec?.imageSize?.presets?.some((preset) => preset.tier === "1k")).toBe(true);
|
||||
expect(spec?.imageSize?.presets?.some((preset) => preset.tier === "4k")).toBe(true);
|
||||
expect(spec?.imageSize?.presets?.some((preset) => preset.tier === "2k")).toBe(false);
|
||||
});
|
||||
|
||||
test("前台投影不再把多档压成只有 1K", () => {
|
||||
const spec = capabilitySpecFromChannelModel(channelModelWithPresets())!;
|
||||
const projected = projectLogicalCapability(spec, { size: "16:9" }).image!;
|
||||
expect(projected.size.parameter).toBe("aspect_ratio");
|
||||
expect(imageTierAvailable(projected, "1k")).toBe(true);
|
||||
expect(imageTierAvailable(projected, "4k")).toBe(true);
|
||||
expect(imageTierAvailable(projected, "2k")).toBe(false);
|
||||
const picker = renderToStaticMarkup(<ImageSizePicker profile={projected} size="16:9" quality="4k" onChange={() => {}} />);
|
||||
expect(picker).toMatch(/<button[^>]*>1K/);
|
||||
expect(picker).toMatch(/<button[^>]*>4K/);
|
||||
expect(picker).not.toMatch(/<button[^>]*>2K/);
|
||||
expect(picker).toMatch(/aria-pressed="true"[^>]*>4K/);
|
||||
});
|
||||
|
||||
test("合并供应线路时并集 1K/4K 预设", () => {
|
||||
const oneK = defaultImageCapabilityConfig("gemini-image", "a");
|
||||
oneK.size = imageSizeConfigWithPresets(oneK, [imagePresetForRatio("1k", "16:9")]);
|
||||
const fourK = defaultImageCapabilityConfig("gemini-image", "b");
|
||||
fourK.quality = { supported: false, values: [], default: "auto" };
|
||||
fourK.size = imageSizeConfigWithPresets(fourK, [imagePresetForRatio("4k", "16:9")]);
|
||||
const merged = mergeCapabilitySpecs("image", [
|
||||
capabilitySpecFromChannelModel({ capability: "image", capabilityConfig: { version: 1, image: oneK } } as ChannelModel)!,
|
||||
capabilitySpecFromChannelModel({ capability: "image", capabilityConfig: { version: 1, image: fourK } } as ChannelModel)!,
|
||||
]);
|
||||
expect(merged.imageSize?.parameter).toBe("aspect_ratio");
|
||||
expect(merged.imageSize?.presets?.map((preset) => preset.tier).sort()).toEqual(["1k", "4k"]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user