feat(*): 创作工作台 - 完善画布生成、模型定价与创建流程

This commit is contained in:
ddcat666
2026-10-10 04:40:20 +08:00
parent 508cad2b4e
commit 611949d6cc
39 changed files with 735 additions and 242 deletions

View File

@@ -0,0 +1,38 @@
import { buildImageResolutionOptions } from "./image-resolution-tiers";
import { isImageResolutionTier } from "./image-quality";
import type { ImageCapabilityConfig } from "./model-capabilities";
/** 旧图片价格档把 1K/2K/4K 存在 quality;只在读取价格条件时转换。 */
export function normalizeImagePriceSelector(input: Record<string, string>) {
const selector = { ...input };
for (const key of ["quality", "resolution", "size"] as const) {
if (selector[key]) selector[key] = selector[key].trim().toLowerCase();
}
if (isImageResolutionTier(selector.quality) && (!selector.resolution || selector.resolution === "*")) {
selector.resolution = selector.quality;
delete selector.quality;
}
return selector;
}
export function imagePriceSelector(quality: string, size: string, profile?: ImageCapabilityConfig): Record<string, string> {
quality = quality.trim().toLowerCase();
size = size.trim().toLowerCase();
const selector: Record<string, string> = {};
if (quality && quality !== "auto" && quality !== "any") selector.quality = quality;
if (size && size !== "auto" && size !== "any") selector.size = size;
const pixel = buildImageResolutionOptions([size])[0];
if (pixel) {
selector.resolution = profile?.size.presets?.find((preset) => preset.size.toLowerCase() === size)?.tier || pixel.tier;
} else if (isImageResolutionTier(quality)) {
selector.resolution = quality;
} else if (profile?.size.parameter === "aspect_ratio") {
const tier = ({ low: "1k", medium: "2k", high: "4k" } as Record<string, string>)[quality];
if (tier && profile.quality.supported) selector.resolution = tier;
else if (!profile.quality.supported) {
const tiers = new Set(profile.size.presets?.filter((preset) => preset.ratio === size).map((preset) => preset.tier));
if (tiers.size === 1) selector.resolution = [...tiers][0];
}
}
return selector;
}

View File

@@ -0,0 +1,45 @@
const IMAGE_QUALITY_LABELS: Record<string, string> = {
auto: "自动",
low: "低",
medium: "中",
high: "高",
xhigh: "超高",
max: "最高",
"1k": "1K",
"2k": "2K",
"4k": "4K",
};
export function imageQualityLabel(value: string) {
const normalized = String(value || "").trim().toLowerCase();
return IMAGE_QUALITY_LABELS[normalized] || value;
}
export function imageQualityDescription(value: string) {
switch (String(value || "").trim().toLowerCase()) {
case "auto":
return "由模型决定";
case "low":
return "更快生成";
case "medium":
return "均衡模式";
case "high":
return "优先速度";
case "xhigh":
return "优先质量";
case "max":
return "优先细节";
case "1k":
return "标准清晰度";
case "2k":
return "更高清晰度";
case "4k":
return "最高清晰度";
default:
return "模型支持的质量/分辨率";
}
}
export function isImageResolutionTier(value: string) {
return ["1k", "2k", "4k"].includes(String(value || "").trim().toLowerCase());
}

View File

@@ -1,6 +1,7 @@
import { modelRequestOptions, resolveVideoOperation, type ModelRequirements } from "@/lib/model-selection";
import { videoResolutionComparisonKey } from "@/lib/video-generation-options";
import { buildImageResolutionOptions, imageResolutionOption } from "@/lib/image-resolution-tiers";
import { imagePriceSelector, normalizeImagePriceSelector } from "@/lib/image-pricing";
import { modelCapabilityConfigFor } from "@/lib/model-capabilities";
import type { LogicalModelQuote, ModelQuoteRequest, ModelRequestIntent } from "@/services/api/logical-models";
import { modelOptionName, resolveModelChannel, type AiConfig, type ModelCapability } from "@/stores/use-config-store";
@@ -141,14 +142,7 @@ function priceSelectorForRequest(capability: ModelCapability | undefined, config
const options = { ...modelRequestOptions(config, "image"), ...requirements?.options, ...(requirements?.imageSize ? { size: requirements.imageSize } : {}) };
const imageQuality = String(options.quality ?? "").trim().toLowerCase();
const imageSize = String(options.size ?? "").trim();
if ((imageQuality === "" || imageQuality === "auto" || imageQuality === "any") && imageSize) {
const resolution = imageResolutionOption(buildImageResolutionOptions([imageSize]), imageSize)?.tier;
if (resolution) requested.quality = resolution;
}
for (const key of ["quality", "size"] as const) {
const value = String(options[key] ?? "").trim().toLowerCase();
if (value && value !== "auto" && value !== "any" && !requested[key]) requested[key] = value;
}
Object.assign(requested, imagePriceSelector(imageQuality, imageSize, modelCapabilityConfigFor(config, config.model || config.imageModel)?.image));
}
return requested;
}
@@ -159,7 +153,7 @@ function imagePriceOperation(requirements?: ModelRequirements) {
}
function priceSelectorForTier(tier: ModelPriceTier) {
const selector = { ...(tier.selector || {}) };
const selector = normalizeImagePriceSelector({ ...(tier.selector || {}) });
if (!Object.keys(selector).length) {
const resolution = normalizeTierResolution(tier.resolution);
if (resolution !== "*") selector.vquality = resolution;