fix(style-pro): block incompatible image models
This commit is contained in:
parent
a3e3938f7b
commit
04ea2da58f
51
lib/style-pro/model-availability.ts
Normal file
51
lib/style-pro/model-availability.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import type { AiModel, AiProvider } from "@/lib/db/schema";
|
||||
import {
|
||||
getAiModelFrontendAvailability,
|
||||
type ModelFrontendAvailability,
|
||||
} from "@/lib/ai/model-frontend-availability";
|
||||
import { modelSupportsReferenceImageInput } from "@/lib/ai/reference-image-capability";
|
||||
|
||||
type StyleProAvailabilityEntry = {
|
||||
model: Pick<
|
||||
AiModel,
|
||||
| "displayName"
|
||||
| "modelId"
|
||||
| "supportsVision"
|
||||
| "capabilities"
|
||||
| "defaultParams"
|
||||
| "isEnabled"
|
||||
| "healthStatus"
|
||||
| "healthAutoDisabledAt"
|
||||
| "healthObservationStartedAt"
|
||||
>;
|
||||
provider: Pick<AiProvider, "type">;
|
||||
};
|
||||
|
||||
export function getStyleProModelAvailability(
|
||||
entry: StyleProAvailabilityEntry
|
||||
): ModelFrontendAvailability {
|
||||
if (!modelSupportsReferenceImageInput(entry.model, entry.provider)) {
|
||||
return {
|
||||
availabilityStatus: "maintenance",
|
||||
availabilityLabel: "仅文生图",
|
||||
selectable: false,
|
||||
disabledReason: "风格化需要参考图,该模型仅支持文生图",
|
||||
};
|
||||
}
|
||||
|
||||
const availability = getAiModelFrontendAvailability(entry.model);
|
||||
if (availability.availabilityStatus === "testing") {
|
||||
return {
|
||||
availabilityStatus: "testing",
|
||||
availabilityLabel: "暂不可用",
|
||||
selectable: false,
|
||||
disabledReason: "模型近期生成异常,恢复健康后会自动开放",
|
||||
};
|
||||
}
|
||||
|
||||
return availability;
|
||||
}
|
||||
|
||||
export function isStyleProModelRunnable(entry: StyleProAvailabilityEntry) {
|
||||
return getStyleProModelAvailability(entry).selectable;
|
||||
}
|
||||
@ -6,7 +6,6 @@ import {
|
||||
getImageModelCreditDiscountLabel,
|
||||
} from "@/lib/ai/image-model-pricing";
|
||||
import {
|
||||
getAiModelFrontendAvailability,
|
||||
type ModelFrontendAvailabilityStatus,
|
||||
} from "@/lib/ai/model-frontend-availability";
|
||||
import { isStillImageGenerationModelDescriptor } from "@/lib/ai/model-classification";
|
||||
@ -14,6 +13,10 @@ import {
|
||||
getEnabledAiModelsWithProvider,
|
||||
getFrontendDisplayAiModelsWithProvider,
|
||||
} from "@/lib/db/queries";
|
||||
import {
|
||||
getStyleProModelAvailability,
|
||||
isStyleProModelRunnable,
|
||||
} from "./model-availability";
|
||||
|
||||
export type StyleProModelEntry = Awaited<
|
||||
ReturnType<typeof getEnabledAiModelsWithProvider>
|
||||
@ -56,7 +59,9 @@ export async function getStyleProImageModelEntries(options?: {
|
||||
false,
|
||||
options?.includeHidden === true
|
||||
);
|
||||
return models.filter(isStyleProImageModel);
|
||||
return models
|
||||
.filter(isStyleProImageModel)
|
||||
.filter(isStyleProModelRunnable);
|
||||
}
|
||||
|
||||
export async function getStyleProImageModelDisplayEntries(options?: {
|
||||
@ -105,7 +110,7 @@ export function toPublicStyleProModelOption(
|
||||
): PublicStyleProModelOption {
|
||||
const imageGenConfig = (entry.model.imageGenConfig ||
|
||||
null) as DiscountableImageGenConfigLike | null;
|
||||
const availability = getAiModelFrontendAvailability(entry.model);
|
||||
const availability = getStyleProModelAvailability(entry);
|
||||
return {
|
||||
id: entry.model.id,
|
||||
modelId: entry.model.modelId,
|
||||
|
||||
@ -4,6 +4,35 @@ import {
|
||||
isStillImageGenerationModelDescriptor,
|
||||
isVideoModelDescriptor,
|
||||
} from "@/lib/ai/model-classification";
|
||||
import { getStyleProModelAvailability } from "@/lib/style-pro/model-availability";
|
||||
import type { AiProvider } from "@/lib/db/schema";
|
||||
|
||||
function styleProEntry(overrides?: {
|
||||
providerType?: AiProvider["type"];
|
||||
modelId?: string;
|
||||
supportsVision?: boolean;
|
||||
healthStatus?: "healthy" | "degraded" | "auto_disabled";
|
||||
healthAutoDisabledAt?: Date | null;
|
||||
healthObservationStartedAt?: Date | null;
|
||||
}) {
|
||||
return {
|
||||
model: {
|
||||
displayName: "Test Image Model",
|
||||
modelId: overrides?.modelId || "test-image-model",
|
||||
supportsVision: overrides?.supportsVision ?? true,
|
||||
capabilities: null,
|
||||
defaultParams: null,
|
||||
isEnabled: true,
|
||||
healthStatus: overrides?.healthStatus || "healthy",
|
||||
healthAutoDisabledAt: overrides?.healthAutoDisabledAt || null,
|
||||
healthObservationStartedAt:
|
||||
overrides?.healthObservationStartedAt || null,
|
||||
},
|
||||
provider: {
|
||||
type: overrides?.providerType || "openai-image",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
test.describe("model classification", () => {
|
||||
test("keeps multi-reference BananaImage models classified as image models", () => {
|
||||
@ -51,4 +80,37 @@ test.describe("model classification", () => {
|
||||
})
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
test("prevents reference-incompatible models from being selected in Style Pro", () => {
|
||||
const availability = getStyleProModelAvailability(
|
||||
styleProEntry({ supportsVision: false })
|
||||
);
|
||||
|
||||
expect(availability.selectable).toBe(false);
|
||||
expect(availability.availabilityLabel).toBe("仅文生图");
|
||||
});
|
||||
|
||||
test("temporarily disables degraded models in Style Pro", () => {
|
||||
const availability = getStyleProModelAvailability(
|
||||
styleProEntry({
|
||||
healthStatus: "degraded",
|
||||
healthObservationStartedAt: new Date(),
|
||||
})
|
||||
);
|
||||
|
||||
expect(availability.selectable).toBe(false);
|
||||
expect(availability.availabilityLabel).toBe("暂不可用");
|
||||
});
|
||||
|
||||
test("keeps known reference-capable BFL models selectable", () => {
|
||||
const availability = getStyleProModelAvailability(
|
||||
styleProEntry({
|
||||
providerType: "bfl-image",
|
||||
modelId: "flux-2-klein-4b",
|
||||
supportsVision: false,
|
||||
})
|
||||
);
|
||||
|
||||
expect(availability.selectable).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@ -77,6 +77,19 @@ test.describe("3D workspace and Style Pro regressions", () => {
|
||||
expect(workspace).toContain("revalidateOnReconnect: true");
|
||||
});
|
||||
|
||||
test("enforces Style Pro model availability on both display and execution", () => {
|
||||
const options = readSource("lib/style-pro/model-options.ts");
|
||||
const availability = readSource(
|
||||
"lib/style-pro/model-availability.ts"
|
||||
);
|
||||
|
||||
expect(options).toContain(".filter(isStyleProModelRunnable)");
|
||||
expect(options).toContain("getStyleProModelAvailability(entry)");
|
||||
expect(availability).toContain("modelSupportsReferenceImageInput");
|
||||
expect(availability).toContain('availabilityLabel: "仅文生图"');
|
||||
expect(availability).toContain('availabilityLabel: "暂不可用"');
|
||||
});
|
||||
|
||||
test("opens the canvas conversation panel by default", () => {
|
||||
const page = readSource(
|
||||
"app/canvas/[id]/CanvasEditorLegacyPageClient.tsx"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user