refactor: unify realtime canvas enhance panel styling
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled

This commit is contained in:
Your Name 2026-04-05 22:26:05 +08:00
parent 3100d10dc0
commit b0245c5366
2 changed files with 232 additions and 227 deletions

View File

@ -20,7 +20,6 @@ import {
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Separator } from "@/components/ui/separator";
import { Switch } from "@/components/ui/switch";
import { Textarea } from "@/components/ui/textarea";
import {
@ -88,10 +87,14 @@ const FALLBACK_CONFIG: EnhanceProClientConfig = {
presets: defaultEnhanceProPresets(),
};
const PANEL_SHELL_CLASS =
"rounded-[28px] border border-[#e7e1d4] bg-white/78 shadow-[0_20px_50px_rgba(15,23,42,0.08)] backdrop-blur-xl";
const SOLID_PANEL_FIELD_CLASS =
"border-border/60 bg-background text-foreground shadow-sm";
"border-[#e8e2d8] bg-[#fffdfa] text-foreground shadow-[0_8px_20px_rgba(15,23,42,0.05)]";
const SOLID_PANEL_CARD_CLASS =
"rounded-2xl border border-border/60 bg-card shadow-sm";
"rounded-[22px] border border-[#e8e2d8] bg-[#fffdfa] shadow-[0_8px_20px_rgba(15,23,42,0.05)]";
const PANEL_SECTION_TITLE_CLASS = "font-semibold text-[13px] text-slate-700";
const PANEL_SECTION_NOTE_CLASS = "text-[11px] leading-5 text-slate-500";
function clampNumber(value: number, min: number, max: number) {
return Math.min(max, Math.max(min, value));
@ -543,12 +546,12 @@ export function EnhanceProWorkspace({
) : null}
</div>
<div className="flex h-full w-[420px] max-w-[42vw] shrink-0 flex-col border-border/60 border-l bg-[#fcfbf7]">
<div className="flex h-full w-[420px] max-w-[42vw] shrink-0 flex-col border-[#e7e1d4] border-l bg-[linear-gradient(180deg,#f8f5ee_0%,#f6f2e9_100%)]">
<div className="min-h-0 flex-1 space-y-4 overflow-y-auto px-4 py-4 pt-6">
{isLoading ? (
<div
className={cn(
SOLID_PANEL_CARD_CLASS,
PANEL_SHELL_CLASS,
"flex items-center gap-2 px-4 py-3 text-muted-foreground text-sm"
)}
>
@ -570,66 +573,66 @@ export function EnhanceProWorkspace({
</div>
) : null}
<div className="space-y-2">
<Label className="text-[11px] uppercase tracking-[0.08em] text-muted-foreground">
增强预设
</Label>
<Select onValueChange={handleSelectPreset} value={selectedPresetId}>
<SelectTrigger className={`${SOLID_PANEL_FIELD_CLASS} h-10`}>
<SelectValue placeholder="选择一个增强预设" />
</SelectTrigger>
<SelectContent>
{presets.map((preset) => (
<SelectItem key={preset.id} value={preset.id}>
{preset.name}
</SelectItem>
))}
</SelectContent>
</Select>
{selectedPreset ? (
<div className="line-clamp-2 text-[11px] leading-5 text-muted-foreground">
{selectedPreset.description ||
`${selectedPreset.model} · ${getSubjectDetectionLabel(selectedPreset.subjectDetection)}`}
</div>
) : null}
</div>
<div className="space-y-2">
<Label
className="text-[11px] uppercase tracking-[0.08em] text-muted-foreground"
htmlFor="enhance-workspace-prompt"
>
增强提示词
</Label>
<Textarea
className={`${SOLID_PANEL_FIELD_CLASS} min-h-[82px] resize-none placeholder:text-muted-foreground/70`}
id="enhance-workspace-prompt"
onChange={(event) =>
setFormState((prev) => ({
...prev,
prompt: event.target.value,
}))
}
placeholder="例如:保持主体不变,提升画质层次、纹理和局部细节,让结果更高级。"
rows={3}
value={formState.prompt}
/>
</div>
<div className="space-y-2">
<div className="text-[11px] uppercase tracking-[0.08em] text-muted-foreground">
基础参数
<div className={cn(PANEL_SHELL_CLASS, "space-y-4 px-4 py-4")}>
<div className="space-y-2">
<Label className={PANEL_SECTION_TITLE_CLASS}>增强预设</Label>
<Select onValueChange={handleSelectPreset} value={selectedPresetId}>
<SelectTrigger
className={`${SOLID_PANEL_FIELD_CLASS} h-11 rounded-[18px]`}
>
<SelectValue placeholder="选择一个增强预设" />
</SelectTrigger>
<SelectContent>
{presets.map((preset) => (
<SelectItem key={preset.id} value={preset.id}>
{preset.name}
</SelectItem>
))}
</SelectContent>
</Select>
{selectedPreset ? (
<div className={cn(PANEL_SECTION_NOTE_CLASS, "line-clamp-2")}>
{selectedPreset.description ||
`${selectedPreset.model} · ${getSubjectDetectionLabel(selectedPreset.subjectDetection)}`}
</div>
) : null}
</div>
<div className="space-y-2">
<Label
className={PANEL_SECTION_TITLE_CLASS}
htmlFor="enhance-workspace-prompt"
>
增强提示词
</Label>
<Textarea
className={`${SOLID_PANEL_FIELD_CLASS} min-h-[92px] resize-none rounded-[22px] placeholder:text-muted-foreground/70`}
id="enhance-workspace-prompt"
onChange={(event) =>
setFormState((prev) => ({
...prev,
prompt: event.target.value,
}))
}
placeholder="例如:保持主体不变,提升画质层次、纹理和局部细节,让结果更高级。"
rows={3}
value={formState.prompt}
/>
</div>
</div>
<div className={cn(PANEL_SHELL_CLASS, "space-y-3 px-4 py-4")}>
<div className={PANEL_SECTION_TITLE_CLASS}>基础参数</div>
<div className="grid grid-cols-3 gap-3">
<div className="space-y-1.5">
<Label
className="text-foreground/80 text-xs"
className="text-slate-600 text-xs"
htmlFor="enhance-workspace-width"
>
宽度
</Label>
<Input
className={`${SOLID_PANEL_FIELD_CLASS} h-10`}
className={`${SOLID_PANEL_FIELD_CLASS} h-11 rounded-[18px]`}
id="enhance-workspace-width"
max={config.maxOutputEdge}
min="1"
@ -649,13 +652,13 @@ export function EnhanceProWorkspace({
</div>
<div className="space-y-1.5">
<Label
className="text-foreground/80 text-xs"
className="text-slate-600 text-xs"
htmlFor="enhance-workspace-height"
>
高度
</Label>
<Input
className={`${SOLID_PANEL_FIELD_CLASS} h-10`}
className={`${SOLID_PANEL_FIELD_CLASS} h-11 rounded-[18px]`}
id="enhance-workspace-height"
max={config.maxOutputEdge}
min="1"
@ -675,7 +678,7 @@ export function EnhanceProWorkspace({
</div>
{config.allowBatchSize ? (
<div className="space-y-1.5">
<Label className="text-foreground/80 text-xs">候选</Label>
<Label className="text-slate-600 text-xs">候选</Label>
<Select
onValueChange={(value) =>
handleNumericField(
@ -690,7 +693,7 @@ export function EnhanceProWorkspace({
value={String(formState.batchSize)}
>
<SelectTrigger
className={`${SOLID_PANEL_FIELD_CLASS} h-10`}
className={`${SOLID_PANEL_FIELD_CLASS} h-11 rounded-[18px]`}
>
<SelectValue />
</SelectTrigger>
@ -709,13 +712,13 @@ export function EnhanceProWorkspace({
) : (
<div className="space-y-1.5">
<Label
className="text-foreground/80 text-xs"
className="text-slate-600 text-xs"
htmlFor="enhance-workspace-seed"
>
Seed
</Label>
<Input
className={`${SOLID_PANEL_FIELD_CLASS} h-10`}
className={`${SOLID_PANEL_FIELD_CLASS} h-11 rounded-[18px]`}
id="enhance-workspace-seed"
onChange={(event) =>
handleNumericField(
@ -729,7 +732,7 @@ export function EnhanceProWorkspace({
</div>
)}
<div className="space-y-1.5">
<Label className="text-foreground/80 text-xs">模式</Label>
<Label className="text-slate-600 text-xs">模式</Label>
<Select
onValueChange={(value) =>
setFormState((prev) => ({
@ -739,7 +742,9 @@ export function EnhanceProWorkspace({
}
value={formState.model}
>
<SelectTrigger className={`${SOLID_PANEL_FIELD_CLASS} h-10`}>
<SelectTrigger
className={`${SOLID_PANEL_FIELD_CLASS} h-11 rounded-[18px]`}
>
<SelectValue />
</SelectTrigger>
<SelectContent>
@ -751,7 +756,7 @@ export function EnhanceProWorkspace({
</Select>
</div>
<div className="space-y-1.5">
<Label className="text-foreground/80 text-xs">格式</Label>
<Label className="text-slate-600 text-xs">格式</Label>
<Select
onValueChange={(value) =>
setFormState((prev) => ({
@ -761,7 +766,9 @@ export function EnhanceProWorkspace({
}
value={formState.output_format}
>
<SelectTrigger className={`${SOLID_PANEL_FIELD_CLASS} h-10`}>
<SelectTrigger
className={`${SOLID_PANEL_FIELD_CLASS} h-11 rounded-[18px]`}
>
<SelectValue />
</SelectTrigger>
<SelectContent>
@ -772,7 +779,7 @@ export function EnhanceProWorkspace({
</Select>
</div>
<div className="space-y-1.5">
<Label className="text-foreground/80 text-xs">区域</Label>
<Label className="text-slate-600 text-xs">区域</Label>
<Select
onValueChange={(value) =>
setFormState((prev) => ({
@ -782,7 +789,9 @@ export function EnhanceProWorkspace({
}
value={formState.subject_detection}
>
<SelectTrigger className={`${SOLID_PANEL_FIELD_CLASS} h-10`}>
<SelectTrigger
className={`${SOLID_PANEL_FIELD_CLASS} h-11 rounded-[18px]`}
>
<SelectValue />
</SelectTrigger>
<SelectContent>
@ -795,13 +804,13 @@ export function EnhanceProWorkspace({
{config.allowBatchSize ? (
<div className="space-y-1.5">
<Label
className="text-foreground/80 text-xs"
className="text-slate-600 text-xs"
htmlFor="enhance-workspace-seed"
>
Seed
</Label>
<Input
className={`${SOLID_PANEL_FIELD_CLASS} h-10`}
className={`${SOLID_PANEL_FIELD_CLASS} h-11 rounded-[18px]`}
id="enhance-workspace-seed"
onChange={(event) =>
handleNumericField(
@ -816,13 +825,13 @@ export function EnhanceProWorkspace({
) : null}
<div className="space-y-1.5">
<Label
className="text-foreground/80 text-xs"
className="text-slate-600 text-xs"
htmlFor="enhance-workspace-scale"
>
放大
</Label>
<Input
className={`${SOLID_PANEL_FIELD_CLASS} h-10`}
className={`${SOLID_PANEL_FIELD_CLASS} h-11 rounded-[18px]`}
id="enhance-workspace-scale"
max="32"
min="1"
@ -844,10 +853,8 @@ export function EnhanceProWorkspace({
</div>
</div>
<div className="space-y-2">
<div className="text-[11px] uppercase tracking-[0.08em] text-muted-foreground">
增强开关
</div>
<div className={cn(PANEL_SHELL_CLASS, "space-y-3 px-4 py-4")}>
<div className={PANEL_SECTION_TITLE_CLASS}>增强开关</div>
<div className="grid grid-cols-3 gap-3">
<div
className={cn(
@ -901,165 +908,163 @@ export function EnhanceProWorkspace({
</div>
{config.enableAdvancedControls ? (
<>
<Separator className="bg-border/60" />
<div className="space-y-2">
<div className="text-[11px] uppercase tracking-[0.08em] text-muted-foreground">
高级调节
<div className={cn(PANEL_SHELL_CLASS, "space-y-3 px-4 py-4")}>
<div className="flex items-center justify-between gap-3">
<div className={PANEL_SECTION_TITLE_CLASS}>高级调节</div>
<div className={PANEL_SECTION_NOTE_CLASS}>细修参数</div>
</div>
<div className="grid grid-cols-3 gap-3">
<div className="space-y-1.5">
<Label className="text-slate-600 text-xs">创造力</Label>
<Input
className={`${SOLID_PANEL_FIELD_CLASS} h-11 rounded-[18px]`}
max="6"
min="1"
onChange={(event) =>
handleNumericField(
"creativity",
clampNumber(
Number.parseInt(event.target.value, 10) || 3,
1,
6
)
)
}
type="number"
value={formState.creativity}
/>
</div>
<div className="grid grid-cols-3 gap-3">
<div className="space-y-1.5">
<Label className="text-foreground/80 text-xs">创造力</Label>
<Input
className={`${SOLID_PANEL_FIELD_CLASS} h-10`}
max="6"
min="1"
onChange={(event) =>
handleNumericField(
"creativity",
clampNumber(
Number.parseInt(event.target.value, 10) || 3,
1,
6
)
<div className="space-y-1.5">
<Label className="text-slate-600 text-xs">纹理</Label>
<Input
className={`${SOLID_PANEL_FIELD_CLASS} h-11 rounded-[18px]`}
max="5"
min="1"
onChange={(event) =>
handleNumericField(
"texture",
clampNumber(
Number.parseInt(event.target.value, 10) || 3,
1,
5
)
}
type="number"
value={formState.creativity}
/>
</div>
<div className="space-y-1.5">
<Label className="text-foreground/80 text-xs">纹理</Label>
<Input
className={`${SOLID_PANEL_FIELD_CLASS} h-10`}
max="5"
min="1"
onChange={(event) =>
handleNumericField(
"texture",
clampNumber(
Number.parseInt(event.target.value, 10) || 3,
1,
5
)
)
}
type="number"
value={formState.texture}
/>
</div>
<div className="space-y-1.5">
<Label className="text-slate-600 text-xs">细节</Label>
<Input
className={`${SOLID_PANEL_FIELD_CLASS} h-11 rounded-[18px]`}
max="1"
min="0"
onChange={(event) =>
handleNumericField(
"detail",
clampNumber(
Number.parseFloat(event.target.value) || 0,
0,
1
)
}
type="number"
value={formState.texture}
/>
</div>
<div className="space-y-1.5">
<Label className="text-foreground/80 text-xs">细节</Label>
<Input
className={`${SOLID_PANEL_FIELD_CLASS} h-10`}
max="1"
min="0"
onChange={(event) =>
handleNumericField(
"detail",
clampNumber(
Number.parseFloat(event.target.value) || 0,
0,
1
)
)
}
step="0.1"
type="number"
value={formState.detail}
/>
</div>
<div className="space-y-1.5">
<Label className="text-slate-600 text-xs">锐化</Label>
<Input
className={`${SOLID_PANEL_FIELD_CLASS} h-11 rounded-[18px]`}
max="1"
min="0"
onChange={(event) =>
handleNumericField(
"sharpen",
clampNumber(
Number.parseFloat(event.target.value) || 0,
0,
1
)
}
step="0.1"
type="number"
value={formState.detail}
/>
</div>
<div className="space-y-1.5">
<Label className="text-foreground/80 text-xs">锐化</Label>
<Input
className={`${SOLID_PANEL_FIELD_CLASS} h-10`}
max="1"
min="0"
onChange={(event) =>
handleNumericField(
"sharpen",
clampNumber(
Number.parseFloat(event.target.value) || 0,
0,
1
)
)
}
step="0.1"
type="number"
value={formState.sharpen}
/>
</div>
<div className="space-y-1.5">
<Label className="text-slate-600 text-xs">降噪</Label>
<Input
className={`${SOLID_PANEL_FIELD_CLASS} h-11 rounded-[18px]`}
max="1"
min="0"
onChange={(event) =>
handleNumericField(
"denoise",
clampNumber(
Number.parseFloat(event.target.value) || 0,
0,
1
)
}
step="0.1"
type="number"
value={formState.sharpen}
/>
</div>
<div className="space-y-1.5">
<Label className="text-foreground/80 text-xs">降噪</Label>
<Input
className={`${SOLID_PANEL_FIELD_CLASS} h-10`}
max="1"
min="0"
onChange={(event) =>
handleNumericField(
"denoise",
clampNumber(
Number.parseFloat(event.target.value) || 0,
0,
1
)
)
}
step="0.1"
type="number"
value={formState.denoise}
/>
</div>
<div className="space-y-1.5">
<Label className="text-slate-600 text-xs">脸部力度</Label>
<Input
className={`${SOLID_PANEL_FIELD_CLASS} h-11 rounded-[18px]`}
max="1"
min="0"
onChange={(event) =>
handleNumericField(
"face_enhancement_strength",
clampNumber(
Number.parseFloat(event.target.value) || 0,
0,
1
)
}
step="0.1"
type="number"
value={formState.denoise}
/>
</div>
<div className="space-y-1.5">
<Label className="text-foreground/80 text-xs">脸部力度</Label>
<Input
className={`${SOLID_PANEL_FIELD_CLASS} h-10`}
max="1"
min="0"
onChange={(event) =>
handleNumericField(
"face_enhancement_strength",
clampNumber(
Number.parseFloat(event.target.value) || 0,
0,
1
)
)
}
step="0.1"
type="number"
value={formState.face_enhancement_strength}
/>
</div>
<div className="space-y-1.5">
<Label className="text-slate-600 text-xs">脸部创意</Label>
<Input
className={`${SOLID_PANEL_FIELD_CLASS} h-11 rounded-[18px]`}
max="1"
min="0"
onChange={(event) =>
handleNumericField(
"face_enhancement_creativity",
clampNumber(
Number.parseFloat(event.target.value) || 0,
0,
1
)
}
step="0.1"
type="number"
value={formState.face_enhancement_strength}
/>
</div>
<div className="space-y-1.5">
<Label className="text-foreground/80 text-xs">脸部创意</Label>
<Input
className={`${SOLID_PANEL_FIELD_CLASS} h-10`}
max="1"
min="0"
onChange={(event) =>
handleNumericField(
"face_enhancement_creativity",
clampNumber(
Number.parseFloat(event.target.value) || 0,
0,
1
)
)
}
step="0.1"
type="number"
value={formState.face_enhancement_creativity}
/>
</div>
)
}
step="0.1"
type="number"
value={formState.face_enhancement_creativity}
/>
</div>
</div>
</>
</div>
) : null}
</div>
<div className="border-border/60 border-t bg-background/90 px-4 py-3">
<div className="border-[#e7e1d4] border-t bg-white/72 px-4 py-3 backdrop-blur-xl">
<div className="mb-3 flex items-center justify-between text-muted-foreground text-xs">
<span>
{generatedImages.length > 0
@ -1075,7 +1080,7 @@ export function EnhanceProWorkspace({
</div>
<div className="flex items-center gap-2">
<Button
className="h-10 flex-1 rounded-2xl bg-primary text-primary-foreground hover:bg-primary/90"
className="h-10 flex-1 rounded-2xl bg-[#2563eb] text-white shadow-sm hover:bg-[#1d4ed8] disabled:bg-slate-200 disabled:text-slate-500"
disabled={isSubmitting || !sourceImageUrl}
onClick={handleSubmit}
type="button"
@ -1088,7 +1093,7 @@ export function EnhanceProWorkspace({
开始增强 Pro
</Button>
<Button
className="h-10 rounded-2xl border-border/60 bg-background px-4 text-foreground hover:bg-muted"
className="h-10 rounded-2xl border-[#bfdbfe] bg-[#eff6ff] px-4 text-[#1d4ed8] shadow-sm hover:bg-[#dbeafe] hover:text-[#1e40af] disabled:border-slate-200 disabled:bg-slate-100 disabled:text-slate-400"
disabled={!selectedGeneratedImage}
onClick={handleApply}
type="button"

View File

@ -2959,7 +2959,7 @@ export function RealtimeCanvasDialog({
<DialogDescription>左侧绘制草图,右侧实时预览。</DialogDescription>
</DialogHeader>
)}
<div className="pointer-events-none absolute top-6 left-6 z-50 flex flex-col gap-3">
<div className="pointer-events-none absolute top-6 left-6 right-24 z-50 flex flex-wrap items-start gap-3">
<button
className="pointer-events-auto flex items-center gap-3 rounded-full border border-border/60 bg-background/95 px-4 py-2 text-foreground shadow-xl backdrop-blur-sm transition hover:bg-background"
onClick={handleGoHome}
@ -2986,7 +2986,7 @@ export function RealtimeCanvasDialog({
</span>
</button>
<div className="pointer-events-auto inline-flex w-fit items-center gap-1 rounded-full border border-border/60 bg-background/95 p-1.5 shadow-xl backdrop-blur-sm">
<div className="pointer-events-auto inline-flex w-fit max-w-full items-center gap-1 rounded-full border border-border/60 bg-background/95 p-1.5 shadow-xl backdrop-blur-sm">
<button
className={cn(
"inline-flex items-center gap-2 rounded-full px-3 py-2 text-sm transition",