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

This commit is contained in:
ddcat666 committed 2026-10-10 04:40:20 +08:00
1 parent a20df86cbd
commit 982021be93
39 files changed
+735 -242

No files matched your search

@@ -1,10 +1,11 @@
import { useId, useState } from "react";
import { Button, Input } from "antd";
import { Button, Input, InputNumber } from "antd";
import { Check, Plus } from "lucide-react";
import { Switch } from "@/components/ui/base/switch";
import "./image-size-picker.css";
import type { ImageCapabilityConfig } from "@/lib/model-capabilities";
import { IMAGE_RATIOS, IMAGE_RESOLUTIONS, imagePresetForRatio, imageResolutionUsesQuality, imageSizeConfigWithPresets, imageSizePresets, imageTierAvailable } from "@/lib/image-size-presets";
import { validateImageSize } from "@/services/api/image-validation";
import type { ImageResolutionTier } from "@/lib/image-resolution-tiers";
export function ImageSizePresetsEditor({ profile, disabled, onChange }: { profile: ImageCapabilityConfig; disabled?: boolean; onChange: (size: ImageCapabilityConfig["size"]) => void }) {
@@ -36,6 +37,25 @@ export function ImageSizePresetsEditor({ profile, disabled, onChange }: { profil
return false;
}
};
const editDimensions = (tier: ImageResolutionTier, ratio: string, field: "width" | "height", rawValue: number | null) => {
if (disabled) return;
const value = rawValue == null ? NaN : Math.round(rawValue);
const selected = presets.find((preset) => preset.tier === tier && preset.ratio === ratio);
if (!selected || !Number.isInteger(value) || value <= 0) {
setErrors((current) => ({ ...current, [tier]: "请输入正整数像素" }));
return;
}
const width = field === "width" ? value : selected.width;
const height = field === "height" ? value : selected.height;
try {
validateImageSize(width, height);
const next = presets.map((preset) => preset === selected ? { ...preset, width, height, size: `${width}x${height}` } : preset);
onChange(imageSizeConfigWithPresets(profile, next));
setErrors((current) => ({ ...current, [tier]: "" }));
} catch (reason) {
setErrors((current) => ({ ...current, [tier]: reason instanceof Error ? reason.message : "像素尺寸无效" }));
}
};
const addRatio = (tier: ImageResolutionTier) => {
const ratios = (drafts[tier] || "")
.split(/[,,\n]/)
@@ -68,24 +88,38 @@ export function ImageSizePresetsEditor({ profile, disabled, onChange }: { profil
const selected = items.find((item) => item.ratio === ratio);
const dimensions = selected || imagePresetForRatio(tier, ratio);
return (
<button
key={ratio}
type="button"
disabled={disabled}
aria-pressed={Boolean(selected)}
aria-label={`${tier.toUpperCase()} ${ratio}`}
className="image-size-preset-option"
title={`${ratio} · ${dimensions.width} × ${dimensions.height} px${selected ? " · 点击移除" : " · 点击添加"}`}
onClick={() => update(tier, selected ? items.filter((item) => item.ratio !== ratio).map((item) => item.ratio) : [...items.map((item) => item.ratio), ratio])}
>
<span className="image-size-preset-option-label">
<span>{ratio}</span>
{selected ? <Check size={12} aria-hidden="true" /> : <Plus size={12} aria-hidden="true" />}
</span>
<small>
{dimensions.width} × {dimensions.height}
</small>
</button>
<div key={ratio} className="image-size-preset-option-wrap">
<button
type="button"
disabled={disabled}
aria-pressed={Boolean(selected)}
aria-label={`${tier.toUpperCase()} ${ratio}`}
className="image-size-preset-option"
title={`${ratio} · ${dimensions.width} × ${dimensions.height} px${selected ? " · 点击移除" : " · 点击添加"}`}
onClick={() => update(tier, selected ? items.filter((item) => item.ratio !== ratio).map((item) => item.ratio) : [...items.map((item) => item.ratio), ratio])}
>
<span className="image-size-preset-option-label">
<span>{ratio}</span>
{selected ? <Check size={12} aria-hidden="true" /> : <Plus size={12} aria-hidden="true" />}
</span>
<small>
{dimensions.width} × {dimensions.height}
</small>
</button>
{selected ? (
<div className="image-size-preset-dimensions" aria-label={`${tier.toUpperCase()} ${ratio} 精确像素`}>
<label>
<span>宽</span>
<InputNumber min={1} precision={0} value={selected.width} disabled={disabled} aria-label={`${tier.toUpperCase()} ${ratio} 宽度像素`} onChange={(value) => editDimensions(tier, ratio, "width", value)} />
</label>
<span aria-hidden="true">×</span>
<label>
<span>高</span>
<InputNumber min={1} precision={0} value={selected.height} disabled={disabled} aria-label={`${tier.toUpperCase()} ${ratio} 高度像素`} onChange={(value) => editDimensions(tier, ratio, "height", value)} />
</label>
</div>
) : null}
</div>
);
})}
</div>