refactor: simplify ai wash preview layout
This commit is contained in:
parent
40190a6034
commit
afc41c5a42
@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
ChevronDown,
|
||||
Clock3,
|
||||
History as HistoryIcon,
|
||||
ImageIcon,
|
||||
@ -304,6 +305,22 @@ function getControlLevelByIndex(
|
||||
return control.levels[clampedIndex] || control.levels[0] || null;
|
||||
}
|
||||
|
||||
function getAiWashStatusLabel(status?: AiWashResultStatus) {
|
||||
switch (status) {
|
||||
case "submitting":
|
||||
return "提交中";
|
||||
case "running":
|
||||
return "生成中";
|
||||
case "succeeded":
|
||||
return "已完成";
|
||||
case "failed":
|
||||
return "失败";
|
||||
case "idle":
|
||||
default:
|
||||
return "待生成";
|
||||
}
|
||||
}
|
||||
|
||||
function composePresetPrompt(params: {
|
||||
preset: AiWashPreset;
|
||||
selectedLevelIndexByControl: Record<string, number>;
|
||||
@ -392,6 +409,7 @@ export function AiWashWorkspace({
|
||||
useState<Record<string, number>>({});
|
||||
const [historySourceOverride, setHistorySourceOverride] = useState("");
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const [isHistoryExpanded, setIsHistoryExpanded] = useState(false);
|
||||
const [lastErrorMessage, setLastErrorMessage] = useState("");
|
||||
const [comparePositionById, setComparePositionById] = useState<
|
||||
Record<string, number>
|
||||
@ -449,12 +467,18 @@ export function AiWashWorkspace({
|
||||
() => createHistoryScopeKey(resolvedHistoryScopeInput),
|
||||
[resolvedHistoryScopeInput]
|
||||
);
|
||||
const selectedPreviewResult = useMemo(
|
||||
() =>
|
||||
results.find((item) => item.presetId === selectedResultId) ||
|
||||
results.find((item) => item.isDefault) ||
|
||||
results[0] ||
|
||||
null,
|
||||
[results, selectedResultId]
|
||||
);
|
||||
const selectedResult = useMemo(
|
||||
() =>
|
||||
results.find(
|
||||
(item) => item.presetId === selectedResultId && item.status === "succeeded"
|
||||
) || null,
|
||||
[results, selectedResultId]
|
||||
selectedPreviewResult?.status === "succeeded" ? selectedPreviewResult : null,
|
||||
[selectedPreviewResult]
|
||||
);
|
||||
const visibleHistoryBatches = useMemo(() => {
|
||||
const scoped = persistedHistoryBatches
|
||||
@ -905,6 +929,11 @@ export function AiWashWorkspace({
|
||||
[persistHistoryBatches]
|
||||
);
|
||||
|
||||
const previewComparePosition = selectedPreviewResult
|
||||
? comparePositionById[selectedPreviewResult.presetId] ?? 50
|
||||
: 50;
|
||||
const previewHasImage = Boolean(selectedPreviewResult?.imageUrl);
|
||||
|
||||
return (
|
||||
<div className="relative flex h-full w-full bg-[#ffffff] text-[#111111]">
|
||||
<div className="pointer-events-none absolute inset-0 bg-[#ffffff]" />
|
||||
@ -1036,30 +1065,71 @@ export function AiWashWorkspace({
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
{presetExecutionStates.map((item) => (
|
||||
<div
|
||||
className="rounded-[16px] border border-[#ececec] bg-white px-3 py-2.5"
|
||||
key={item.preset.id}
|
||||
>
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<div className="min-w-0 truncate font-medium text-sm text-[#111111]">
|
||||
{item.preset.title}
|
||||
{presetExecutionStates.map((item) => {
|
||||
const targetResult = results.find(
|
||||
(result) => result.presetId === item.preset.id
|
||||
);
|
||||
const isActive =
|
||||
selectedResultId === item.preset.id ||
|
||||
(!selectedResultId &&
|
||||
selectedPreviewResult?.presetId === item.preset.id);
|
||||
return (
|
||||
<button
|
||||
aria-pressed={isActive}
|
||||
className={cn(
|
||||
"w-full rounded-[16px] border px-3 py-2.5 text-left transition",
|
||||
isActive
|
||||
? "border-[#111111] bg-[#111111] text-white shadow-[0_12px_24px_rgba(0,0,0,0.12)]"
|
||||
: "border-[#ececec] bg-white text-[#111111] hover:border-[#111111]"
|
||||
)}
|
||||
key={item.preset.id}
|
||||
onClick={() => setSelectedResultId(item.preset.id)}
|
||||
type="button"
|
||||
>
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<div className="min-w-0 truncate font-medium text-sm">
|
||||
{item.preset.title}
|
||||
</div>
|
||||
<span
|
||||
className={cn(
|
||||
"shrink-0 rounded-full border px-2 py-1 text-[11px]",
|
||||
isActive
|
||||
? "border-white/20 bg-white/10 text-white"
|
||||
: "border-[#ebebeb] bg-[#fafafa] text-[#666666]"
|
||||
)}
|
||||
>
|
||||
{getAiWashStatusLabel(targetResult?.status)}
|
||||
</span>
|
||||
</div>
|
||||
<span className="shrink-0 rounded-full border border-[#ebebeb] bg-[#fafafa] px-2 py-1 text-[11px] text-[#666666]">
|
||||
{item.unitCost > 0 ? `${item.unitCost} 积分/张` : "未计费"}
|
||||
</span>
|
||||
</div>
|
||||
<div className="mt-2 flex min-w-0 items-center gap-2 text-[11px] text-[#666666]">
|
||||
<span className="min-w-0 truncate rounded-full border border-[#ebebeb] bg-[#fafafa] px-2 py-1">
|
||||
{item.resolvedModel?.displayName ||
|
||||
item.resolvedModel?.modelId ||
|
||||
(item.usesUserSelectableModel
|
||||
? "前台自选模型"
|
||||
: "待配置模型")}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<div className="mt-2 flex min-w-0 items-center gap-2 text-[11px]">
|
||||
<span
|
||||
className={cn(
|
||||
"min-w-0 truncate rounded-full border px-2 py-1",
|
||||
isActive
|
||||
? "border-white/15 bg-white/10 text-white/80"
|
||||
: "border-[#ebebeb] bg-[#fafafa] text-[#666666]"
|
||||
)}
|
||||
>
|
||||
{item.resolvedModel?.displayName ||
|
||||
item.resolvedModel?.modelId ||
|
||||
(item.usesUserSelectableModel
|
||||
? "前台自选模型"
|
||||
: "待配置模型")}
|
||||
</span>
|
||||
<span
|
||||
className={cn(
|
||||
"shrink-0 rounded-full border px-2 py-1",
|
||||
isActive
|
||||
? "border-white/15 bg-white/10 text-white/80"
|
||||
: "border-[#ebebeb] bg-[#fafafa] text-[#666666]"
|
||||
)}
|
||||
>
|
||||
{item.unitCost > 0 ? `${item.unitCost} 积分/张` : "未计费"}
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1178,177 +1248,179 @@ export function AiWashWorkspace({
|
||||
</aside>
|
||||
|
||||
<main className="flex min-w-0 flex-1 flex-col overflow-hidden px-6 pb-6 pt-6">
|
||||
<div className="grid min-h-0 flex-1 gap-5 xl:grid-cols-3">
|
||||
{results.length > 0 ? (
|
||||
results.map((result) => {
|
||||
const hasImage = Boolean(result.imageUrl);
|
||||
const comparePosition = comparePositionById[result.presetId] ?? 50;
|
||||
const isSelected = selectedResultId === result.presetId && hasImage;
|
||||
|
||||
return (
|
||||
<button
|
||||
className={cn(
|
||||
PANEL_CLASS,
|
||||
"flex min-h-0 flex-col overflow-hidden p-4 text-left transition",
|
||||
isSelected &&
|
||||
"border-[#111111] shadow-[0_0_0_1px_rgba(0,0,0,0.08),0_18px_36px_rgba(0,0,0,0.08)]"
|
||||
)}
|
||||
key={result.presetId}
|
||||
onClick={() => hasImage && setSelectedResultId(result.presetId)}
|
||||
type="button"
|
||||
>
|
||||
<div className="mb-3 flex items-start justify-between gap-3">
|
||||
<div className="min-w-0">
|
||||
<div className="text-[22px] font-semibold text-[#111111]">
|
||||
{result.title}
|
||||
</div>
|
||||
<div className="mt-1 text-sm leading-6 text-[#666666]">
|
||||
{result.description}
|
||||
</div>
|
||||
</div>
|
||||
<div className="shrink-0 rounded-full border border-[#ebebeb] bg-[#fafafa] px-3 py-1 text-xs text-[#666666]">
|
||||
{result.status === "idle"
|
||||
? "待生成"
|
||||
: result.status === "submitting"
|
||||
? "提交中"
|
||||
: result.status === "running"
|
||||
? "生成中"
|
||||
: result.status === "failed"
|
||||
? "失败"
|
||||
: "已完成"}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="relative min-h-[360px] flex-1 overflow-hidden rounded-[22px] border border-[#ececec] bg-[#f7f7f7]">
|
||||
{hasImage ? (
|
||||
<>
|
||||
<img
|
||||
alt={result.title}
|
||||
className="absolute inset-0 h-full w-full object-contain"
|
||||
src={getDisplayImageUrl(result.imageUrl || "", 1400)}
|
||||
/>
|
||||
{compareSourceUrl ? (
|
||||
<>
|
||||
<div
|
||||
className="pointer-events-none absolute inset-0 overflow-hidden"
|
||||
style={{
|
||||
clipPath: `inset(0 ${100 - comparePosition}% 0 0)`,
|
||||
}}
|
||||
>
|
||||
<img
|
||||
alt={`${result.title} 原图对比`}
|
||||
className="h-full w-full object-contain"
|
||||
src={getDisplayImageUrl(compareSourceUrl, 1400)}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="pointer-events-none absolute inset-y-0 w-px bg-white shadow-[0_0_0_1px_rgba(0,0,0,0.2),0_0_16px_rgba(0,0,0,0.18)]"
|
||||
style={{ left: `${comparePosition}%` }}
|
||||
/>
|
||||
<div
|
||||
className="absolute inset-0 cursor-col-resize touch-none"
|
||||
onPointerCancel={(event) =>
|
||||
finishCompareDrag(result.presetId, event)
|
||||
}
|
||||
onPointerDown={(event) =>
|
||||
handleComparePointerDown(result.presetId, event)
|
||||
}
|
||||
onPointerMove={(event) =>
|
||||
handleComparePointerMove(result.presetId, event)
|
||||
}
|
||||
onPointerUp={(event) =>
|
||||
finishCompareDrag(result.presetId, event)
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="pointer-events-none absolute inset-y-0 w-12 -translate-x-1/2"
|
||||
style={{ left: `${comparePosition}%` }}
|
||||
>
|
||||
<div className="absolute inset-y-0 left-1/2 w-px -translate-x-1/2 bg-white" />
|
||||
<div className="absolute left-1/2 top-1/2 flex h-11 w-11 -translate-x-1/2 -translate-y-1/2 items-center justify-center rounded-full border border-white bg-black/70 text-white shadow-[0_12px_24px_rgba(0,0,0,0.2)]">
|
||||
<RefreshCcw className="h-4 w-4" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="pointer-events-none absolute left-3 top-3 rounded-full bg-black/65 px-2.5 py-1 text-[11px] text-white">
|
||||
原图
|
||||
</div>
|
||||
<div className="pointer-events-none absolute right-3 top-3 rounded-full bg-black/65 px-2.5 py-1 text-[11px] text-white">
|
||||
洗后
|
||||
</div>
|
||||
</>
|
||||
) : null}
|
||||
</>
|
||||
) : (
|
||||
<div className="flex h-full min-h-[360px] items-center justify-center bg-[linear-gradient(180deg,#ffffff,#f7f7f7)]">
|
||||
<div className="px-6 text-center">
|
||||
{result.status === "running" || result.status === "submitting" ? (
|
||||
<>
|
||||
<Loader2 className="mx-auto mb-3 h-8 w-8 animate-spin text-[#666666]" />
|
||||
<div className="text-sm text-[#666666]">
|
||||
{result.status === "submitting"
|
||||
? "正在提交模型任务"
|
||||
: "模型正在生成结果"}
|
||||
</div>
|
||||
</>
|
||||
) : result.status === "failed" ? (
|
||||
<div className="text-sm leading-6 text-[#9b2c2c]">
|
||||
{result.error || "该预设生成失败"}
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<ImageIcon className="mx-auto mb-3 h-8 w-8 text-[#b2b2b2]" />
|
||||
<div className="text-sm text-[#888888]">
|
||||
生成后会在这里完整展示可拖动对比图
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mt-4 flex items-center justify-between gap-3 text-xs text-[#666666]">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
{result.modelId ? (
|
||||
<span className="rounded-full border border-[#ebebeb] bg-[#fafafa] px-2.5 py-1">
|
||||
{result.modelId}
|
||||
</span>
|
||||
) : null}
|
||||
{isSelected ? (
|
||||
<span className="rounded-full border border-[#111111] bg-white px-2.5 py-1 text-[#111111]">
|
||||
当前发送目标
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
{result.createdAt ? (
|
||||
<span>
|
||||
{new Date(result.createdAt).toLocaleTimeString("zh-CN", {
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
})}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<div className={cn(PANEL_CLASS, "col-span-full p-10 text-center")}>
|
||||
<Sparkles className="mx-auto mb-4 h-8 w-8 text-[#8a8a8a]" />
|
||||
<div className="text-lg font-medium text-[#111111]">AI洗图准备就绪</div>
|
||||
<div className="mt-2 text-sm text-[#666666]">
|
||||
上传底图后,系统会按管理员配置好的多个模式同时生成洗图结果。
|
||||
<section
|
||||
className={cn(
|
||||
PANEL_CLASS,
|
||||
"flex min-h-0 flex-1 flex-col overflow-hidden p-4"
|
||||
)}
|
||||
>
|
||||
<div className="mb-4 flex flex-wrap items-start justify-between gap-3">
|
||||
<div className="min-w-0">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<div className="text-[24px] font-semibold leading-tight text-[#111111]">
|
||||
{selectedPreviewResult?.title || "AI洗图结果预览"}
|
||||
</div>
|
||||
<span className="rounded-full border border-[#ebebeb] bg-[#fafafa] px-3 py-1 text-xs text-[#666666]">
|
||||
{getAiWashStatusLabel(selectedPreviewResult?.status)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="mt-1 max-w-3xl text-sm leading-6 text-[#666666]">
|
||||
{selectedPreviewResult?.description ||
|
||||
"左侧选择渲染目标后,所有洗图结果都会共用这里的大尺寸预览区。"}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center gap-2 text-xs text-[#666666]">
|
||||
{selectedPreviewResult?.modelId ? (
|
||||
<span className="rounded-full border border-[#ebebeb] bg-[#fafafa] px-3 py-1.5">
|
||||
{selectedPreviewResult.modelId}
|
||||
</span>
|
||||
) : null}
|
||||
{selectedPreviewResult?.createdAt ? (
|
||||
<span className="rounded-full border border-[#ebebeb] bg-[#fafafa] px-3 py-1.5">
|
||||
{new Date(selectedPreviewResult.createdAt).toLocaleTimeString(
|
||||
"zh-CN",
|
||||
{
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
}
|
||||
)}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="relative min-h-[560px] flex-1 overflow-hidden rounded-[28px] border border-[#e9e9e9] bg-[radial-gradient(circle_at_50%_0%,#ffffff_0%,#f8f8f8_52%,#f2f2f2_100%)]">
|
||||
{selectedPreviewResult ? (
|
||||
previewHasImage ? (
|
||||
<>
|
||||
<img
|
||||
alt={selectedPreviewResult.title}
|
||||
className="absolute inset-0 h-full w-full object-contain"
|
||||
src={getDisplayImageUrl(
|
||||
selectedPreviewResult.imageUrl || "",
|
||||
1800
|
||||
)}
|
||||
/>
|
||||
{compareSourceUrl ? (
|
||||
<>
|
||||
<div
|
||||
className="pointer-events-none absolute inset-0 overflow-hidden"
|
||||
style={{
|
||||
clipPath: `inset(0 ${100 - previewComparePosition}% 0 0)`,
|
||||
}}
|
||||
>
|
||||
<img
|
||||
alt={`${selectedPreviewResult.title} 原图对比`}
|
||||
className="h-full w-full object-contain"
|
||||
src={getDisplayImageUrl(compareSourceUrl, 1800)}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="pointer-events-none absolute inset-y-0 w-px bg-white shadow-[0_0_0_1px_rgba(0,0,0,0.22),0_0_20px_rgba(0,0,0,0.18)]"
|
||||
style={{ left: `${previewComparePosition}%` }}
|
||||
/>
|
||||
<div
|
||||
className="absolute inset-0 cursor-col-resize touch-none"
|
||||
onPointerCancel={(event) =>
|
||||
finishCompareDrag(selectedPreviewResult.presetId, event)
|
||||
}
|
||||
onPointerDown={(event) =>
|
||||
handleComparePointerDown(
|
||||
selectedPreviewResult.presetId,
|
||||
event
|
||||
)
|
||||
}
|
||||
onPointerMove={(event) =>
|
||||
handleComparePointerMove(
|
||||
selectedPreviewResult.presetId,
|
||||
event
|
||||
)
|
||||
}
|
||||
onPointerUp={(event) =>
|
||||
finishCompareDrag(selectedPreviewResult.presetId, event)
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="pointer-events-none absolute inset-y-0 w-12 -translate-x-1/2"
|
||||
style={{ left: `${previewComparePosition}%` }}
|
||||
>
|
||||
<div className="absolute inset-y-0 left-1/2 w-px -translate-x-1/2 bg-white" />
|
||||
<div className="absolute left-1/2 top-1/2 flex h-12 w-12 -translate-x-1/2 -translate-y-1/2 items-center justify-center rounded-full border border-white bg-black/72 text-white shadow-[0_14px_28px_rgba(0,0,0,0.24)]">
|
||||
<RefreshCcw className="h-4 w-4" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="pointer-events-none absolute left-4 top-4 rounded-full bg-black/65 px-3 py-1.5 text-[11px] text-white">
|
||||
原图
|
||||
</div>
|
||||
<div className="pointer-events-none absolute right-4 top-4 rounded-full bg-black/65 px-3 py-1.5 text-[11px] text-white">
|
||||
洗后
|
||||
</div>
|
||||
</>
|
||||
) : null}
|
||||
</>
|
||||
) : (
|
||||
<div className="absolute inset-0 flex items-center justify-center px-6 text-center">
|
||||
<div className="max-w-sm">
|
||||
{selectedPreviewResult.status === "running" ||
|
||||
selectedPreviewResult.status === "submitting" ? (
|
||||
<>
|
||||
<Loader2 className="mx-auto mb-4 h-9 w-9 animate-spin text-[#666666]" />
|
||||
<div className="text-base font-medium text-[#111111]">
|
||||
{selectedPreviewResult.status === "submitting"
|
||||
? "正在提交模型任务"
|
||||
: "模型正在生成结果"}
|
||||
</div>
|
||||
<div className="mt-2 text-sm leading-6 text-[#777777]">
|
||||
生成完成后,这里会自动切换为大图对比预览。
|
||||
</div>
|
||||
</>
|
||||
) : selectedPreviewResult.status === "failed" ? (
|
||||
<>
|
||||
<div className="text-base font-medium text-[#9b2c2c]">
|
||||
该目标生成失败
|
||||
</div>
|
||||
<div className="mt-2 text-sm leading-6 text-[#9b2c2c]">
|
||||
{selectedPreviewResult.error || "请调整配置后重新生成。"}
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<ImageIcon className="mx-auto mb-4 h-10 w-10 text-[#b2b2b2]" />
|
||||
<div className="text-base font-medium text-[#111111]">
|
||||
固定单图预览区
|
||||
</div>
|
||||
<div className="mt-2 text-sm leading-6 text-[#777777]">
|
||||
先上传底图并点击开始 AI洗图,结果会在这里完整展示。
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
) : (
|
||||
<div className="absolute inset-0 flex items-center justify-center px-6 text-center">
|
||||
<div>
|
||||
<Sparkles className="mx-auto mb-4 h-10 w-10 text-[#9a9a9a]" />
|
||||
<div className="text-base font-medium text-[#111111]">
|
||||
AI洗图准备就绪
|
||||
</div>
|
||||
<div className="mt-2 text-sm text-[#777777]">
|
||||
管理员配置预设后,这里会展示共享结果预览。
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div className="mt-5 flex flex-wrap items-center justify-between gap-3">
|
||||
<div className="rounded-full border border-[#e5e5e5] bg-white px-4 py-2 text-xs text-[#666666] shadow-[0_0_0_1px_rgba(0,0,0,0.02),0_10px_20px_rgba(0,0,0,0.03)]">
|
||||
{selectedResult
|
||||
? `已选中 ${selectedResult.title},可直接发送回画布`
|
||||
: "请选择或生成一张洗图结果"}
|
||||
: selectedPreviewResult
|
||||
? `当前预览 ${selectedPreviewResult.title},生成完成后可发送回画布`
|
||||
: "请选择或生成一张洗图结果"}
|
||||
</div>
|
||||
<Button
|
||||
className={cn(
|
||||
@ -1365,8 +1437,12 @@ export function AiWashWorkspace({
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className={cn(PANEL_CLASS, "mt-5 p-4")}>
|
||||
<div className="mb-3 flex items-center justify-between gap-3">
|
||||
<div className={cn(PANEL_CLASS, "mt-4 overflow-hidden p-3")}>
|
||||
<button
|
||||
className="flex w-full items-center justify-between gap-3 rounded-[22px] px-1 py-1 text-left transition hover:bg-[#fafafa]"
|
||||
onClick={() => setIsHistoryExpanded((current) => !current)}
|
||||
type="button"
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex h-11 w-11 items-center justify-center rounded-[16px] border border-[#ececec] bg-white shadow-[0_10px_22px_rgba(0,0,0,0.05)]">
|
||||
<HistoryIcon className="h-5 w-5 text-[#111111]" />
|
||||
@ -1380,13 +1456,25 @@ export function AiWashWorkspace({
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="rounded-full border border-[#ebebeb] bg-[#fafafa] px-3 py-1 text-xs text-[#666666]">
|
||||
最近 {visibleHistoryBatches.length} 条
|
||||
<div className="flex shrink-0 items-center gap-2">
|
||||
<div className="rounded-full border border-[#ebebeb] bg-[#fafafa] px-3 py-1 text-xs text-[#666666]">
|
||||
最近 {visibleHistoryBatches.length} 条
|
||||
</div>
|
||||
<div className="inline-flex items-center gap-1 rounded-full border border-[#ebebeb] bg-white px-3 py-1 text-xs text-[#666666]">
|
||||
{isHistoryExpanded ? "收起" : "展开"}
|
||||
<ChevronDown
|
||||
className={cn(
|
||||
"h-3.5 w-3.5 transition-transform",
|
||||
isHistoryExpanded && "rotate-180"
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
{visibleHistoryBatches.length > 0 ? (
|
||||
<div className="flex gap-3 overflow-x-auto pb-1">
|
||||
{isHistoryExpanded ? (
|
||||
visibleHistoryBatches.length > 0 ? (
|
||||
<div className="mt-3 flex gap-3 overflow-x-auto border-t border-[#eeeeee] pt-3 pb-1">
|
||||
{visibleHistoryBatches.map((entry) => {
|
||||
const cover = entry.items[0];
|
||||
return (
|
||||
@ -1457,12 +1545,13 @@ export function AiWashWorkspace({
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<div className="rounded-[22px] border border-dashed border-[#dcdcdc] bg-[#fafafa] px-5 py-8 text-center text-sm text-[#8a8a8a]">
|
||||
还没有历史洗图记录,生成一次后会自动出现在这里。
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className="mt-3 rounded-[22px] border border-dashed border-[#dcdcdc] bg-[#fafafa] px-5 py-8 text-center text-sm text-[#8a8a8a]">
|
||||
还没有历史洗图记录,生成一次后会自动出现在这里。
|
||||
</div>
|
||||
)
|
||||
) : null}
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user