fix(canvas): 局部重绘、图片规格与任务参数修复
This commit is contained in:
@@ -49,16 +49,16 @@ export const CanvasDrawingExcalidrawEditor = forwardRef<CanvasDrawingEditorHandl
|
||||
|
||||
return (
|
||||
<Excalidraw
|
||||
initialData={initialData}
|
||||
langCode="zh-CN"
|
||||
theme={colorScheme}
|
||||
autoFocus
|
||||
excalidrawAPI={(api) => {
|
||||
apiRef.current = api;
|
||||
api.setActiveTool({ type: "freedraw" });
|
||||
onReady();
|
||||
}}
|
||||
UIOptions={{ canvasActions: { loadScene: false, saveToActiveFile: false } }}
|
||||
initialData={initialData}
|
||||
langCode="zh-CN"
|
||||
theme={colorScheme}
|
||||
autoFocus
|
||||
excalidrawAPI={(api) => {
|
||||
apiRef.current = api;
|
||||
api.setActiveTool({ type: "freedraw" });
|
||||
onReady();
|
||||
}}
|
||||
UIOptions={{ canvasActions: { loadScene: false, saveToActiveFile: false } }}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -41,10 +41,10 @@ export function CanvasNodeMaskEditDialog({ dataUrl, open, config, onClose, onCon
|
||||
setBrushSize(defaultBrushSize);
|
||||
setMode("paint");
|
||||
setError("");
|
||||
setAdvancedOpen(false);
|
||||
setAdvancedOpen(true);
|
||||
setGenerationConfig(config);
|
||||
void readImageMeta(dataUrl).then(setImage);
|
||||
}, [config, dataUrl, open]);
|
||||
}, [dataUrl, open]);
|
||||
|
||||
useEffect(() => {
|
||||
clearCanvas(maskCanvasRef.current);
|
||||
@@ -115,7 +115,7 @@ export function CanvasNodeMaskEditDialog({ dataUrl, open, config, onClose, onCon
|
||||
<div className="grid gap-5 lg:grid-cols-[minmax(360px,1fr)_340px]">
|
||||
<div className="flex min-h-[360px] items-center justify-center rounded-lg bg-surface-active p-0">
|
||||
<div className="relative inline-block max-w-full overflow-hidden rounded-lg bg-transparent select-none">
|
||||
<img src={dataUrl} alt="" className="block max-h-[68vh] max-w-full bg-transparent" draggable={false} />
|
||||
<img src={dataUrl} alt="" className="relative z-0 block max-h-[68vh] max-w-full bg-transparent" draggable={false} />
|
||||
{image ? (
|
||||
<>
|
||||
<canvas ref={maskCanvasRef} width={image.width} height={image.height} className="hidden" />
|
||||
@@ -123,7 +123,7 @@ export function CanvasNodeMaskEditDialog({ dataUrl, open, config, onClose, onCon
|
||||
ref={previewCanvasRef}
|
||||
width={image.width}
|
||||
height={image.height}
|
||||
className="absolute inset-0 h-full w-full cursor-crosshair touch-none"
|
||||
className="absolute inset-0 z-10 h-full w-full cursor-crosshair touch-none"
|
||||
onPointerDown={startDraw}
|
||||
onPointerMove={moveDraw}
|
||||
onPointerUp={stopDraw}
|
||||
@@ -173,17 +173,6 @@ export function CanvasNodeMaskEditDialog({ dataUrl, open, config, onClose, onCon
|
||||
{error ? <div className="text-xs font-medium text-destructive">{error}</div> : null}
|
||||
</div>
|
||||
|
||||
<ImageSettingsPanel
|
||||
config={generationConfig}
|
||||
showTitle={false}
|
||||
showQuality={false}
|
||||
showTransparent={false}
|
||||
showSize={false}
|
||||
className="space-y-2"
|
||||
theme={theme}
|
||||
onConfigChange={(key, value) => setGenerationConfig((current) => ({ ...current, [key]: value }))}
|
||||
/>
|
||||
|
||||
<div className="rounded-xl border border-border/60">
|
||||
<button
|
||||
type="button"
|
||||
@@ -211,6 +200,7 @@ export function CanvasNodeMaskEditDialog({ dataUrl, open, config, onClose, onCon
|
||||
config={generationConfig}
|
||||
showTitle={false}
|
||||
showCount={false}
|
||||
bypassPriceGuard
|
||||
className="space-y-3"
|
||||
theme={theme}
|
||||
onConfigChange={(key, value) => setGenerationConfig((current) => ({ ...current, [key]: value }))}
|
||||
|
||||
@@ -53,9 +53,11 @@ type ImageSettingsPanelProps = {
|
||||
className?: string;
|
||||
maxCount?: number;
|
||||
quickCount?: number;
|
||||
/** 局部编辑等场景需要先允许选择参数,由后端负责最终计费校验。 */
|
||||
bypassPriceGuard?: boolean;
|
||||
};
|
||||
|
||||
export function ImageSettingsPanel({ config, onConfigChange, theme, showTitle = true, showQuality = true, showTransparent = true, showSize = true, showCount = true, className = "w-[304px] space-y-3 rounded-2xl px-1 py-0.5", maxCount = 15, quickCount = 3 }: ImageSettingsPanelProps) {
|
||||
export function ImageSettingsPanel({ config, onConfigChange, theme, showTitle = true, showQuality = true, showTransparent = true, showSize = true, showCount = true, className = "w-[304px] space-y-3 rounded-2xl px-1 py-0.5", maxCount = 15, quickCount = 3, bypassPriceGuard = false }: ImageSettingsPanelProps) {
|
||||
const profile = mergedImageCapabilityConfig(config, config.model || config.imageModel);
|
||||
const normalized = normalizeImageValue(profile, config);
|
||||
const quality = normalized.quality;
|
||||
@@ -82,7 +84,7 @@ export function ImageSettingsPanel({ config, onConfigChange, theme, showTitle =
|
||||
<SettingTitle color={theme.node.muted}>{isGrokResolutionQuality(profile) ? "分辨率" : "质量"}</SettingTitle>
|
||||
<div className={`grid gap-1.5 ${activeQualityOptions.length <= 2 ? "grid-cols-2" : "grid-cols-4"}`}>
|
||||
{activeQualityOptions.map((item) => (
|
||||
<OptionPill key={item.value} selected={quality === item.value} disabled={!hasPriceTierForImageSelection(priceTiers, item.value, activeSize)} theme={theme} onClick={() => onConfigChange("quality", item.value)}>
|
||||
<OptionPill key={item.value} selected={quality === item.value} disabled={!bypassPriceGuard && !hasPriceTierForImageSelection(priceTiers, item.value, activeSize)} theme={theme} onClick={() => onConfigChange("quality", item.value)}>
|
||||
{item.label}
|
||||
</OptionPill>
|
||||
))}
|
||||
@@ -103,7 +105,7 @@ export function ImageSettingsPanel({ config, onConfigChange, theme, showTitle =
|
||||
/>
|
||||
</span>
|
||||
</div> : null}
|
||||
{showSize ? <ImageSizePicker profile={profile} size={activeSize} quality={quality} onChange={(size, nextQuality) => { onConfigChange("size", size); if (nextQuality) onConfigChange("quality", nextQuality); }} /> : null}
|
||||
{showSize ? <ImageSizePicker profile={profile} size={activeSize} quality={quality} onChange={(size) => onConfigChange("size", size)} /> : null}
|
||||
{showCount && effectiveMaxCount > 1 ? (
|
||||
<div className="space-y-2">
|
||||
<SettingTitle color={theme.node.muted}>生成张数</SettingTitle>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useId, useState } from "react";
|
||||
import { Button, Input } 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, imageSizeConfigWithPresets, imageSizePresets, imageTierAvailable } from "@/lib/image-size-presets";
|
||||
@@ -11,6 +12,10 @@ export function ImageSizePresetsEditor({ profile, disabled, onChange }: { profil
|
||||
const id = useId();
|
||||
const [drafts, setDrafts] = useState<Partial<Record<ImageResolutionTier, string>>>({});
|
||||
const [errors, setErrors] = useState<Partial<Record<ImageResolutionTier, string>>>({});
|
||||
const toggleTier = (tier: ImageResolutionTier, ratios: string[], enabled: boolean) => {
|
||||
if (disabled) return;
|
||||
update(tier, enabled ? ratios : []);
|
||||
};
|
||||
const update = (tier: ImageResolutionTier, ratios: string[]) => {
|
||||
if (disabled) return false;
|
||||
try {
|
||||
@@ -50,13 +55,10 @@ export function ImageSizePresetsEditor({ profile, disabled, onChange }: { profil
|
||||
return (
|
||||
<section key={tier} className="image-size-preset-group" aria-labelledby={`${id}-${tier}-title`}>
|
||||
<header className="image-size-preset-heading">
|
||||
<div>
|
||||
<div className="image-size-preset-heading-label">
|
||||
<strong id={`${id}-${tier}-title`}>{tier.toUpperCase()}</strong>
|
||||
<span>{items.length ? `${items.length} 个比例` : "未启用"}</span>
|
||||
</div>
|
||||
<div className="image-size-preset-bulk-actions">
|
||||
<Button type="link" size="small" disabled={disabled || !items.length} onClick={() => update(tier, [])}>全不选</Button>
|
||||
<Button type="link" size="small" disabled={disabled || items.length === ratios.length} onClick={() => update(tier, ratios)}>全选</Button>
|
||||
<Switch size="sm" checked={items.length === ratios.length} disabled={disabled} aria-label={`启用 ${tier.toUpperCase()} 规格`} onChange={(enabled) => toggleTier(tier, ratios, enabled)} />
|
||||
</div>
|
||||
</header>
|
||||
<div className="image-size-preset-body">
|
||||
|
||||
Reference in New Issue
Block a user