style: refine detail generation panel ui and scrolling
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-02-17 14:21:57 +08:00
parent 25f962415a
commit 4533547d68

View File

@ -3274,13 +3274,13 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF
data-canvas-chat-input-anchor="true"
>
{isDetailGenerationMode && (
<div className="mx-3 mt-3 rounded-2xl border border-violet-200/80 bg-violet-50/70 p-3 dark:border-violet-500/35 dark:bg-violet-500/10">
<div className="mx-3 mt-3 max-h-[54vh] overflow-y-auto rounded-2xl border border-black/10 bg-white/95 p-3 pr-2">
<div className="flex items-start justify-between gap-3">
<div>
<p className="text-sm font-semibold text-violet-950 dark:text-violet-100">
<p className="text-sm font-semibold text-black">
AI详情生成工作台(PC)
</p>
<p className="mt-0.5 text-[11px] text-violet-700/80 dark:text-violet-200/70">
<p className="mt-0.5 text-[11px] text-black/55">
固定规格 {DETAIL_TARGET_WIDTH} x {DETAIL_TARGET_HEIGHT},先分析后生成,保持整套详情图风格统一。
</p>
</div>
@ -3288,12 +3288,12 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF
className={cn(
"rounded-full px-2 py-0.5 text-[10px] font-medium",
detailProgress.status === "running"
? "bg-violet-100 text-violet-700 dark:bg-violet-500/20 dark:text-violet-100"
? "bg-blue-50 text-blue-700"
: detailProgress.status === "done"
? "bg-emerald-100 text-emerald-700 dark:bg-emerald-500/20 dark:text-emerald-100"
? "bg-emerald-100 text-emerald-700"
: detailProgress.status === "error"
? "bg-red-100 text-red-700 dark:bg-red-500/20 dark:text-red-100"
: "bg-white/80 text-violet-700 dark:bg-zinc-900/60 dark:text-violet-200"
? "bg-red-100 text-red-700"
: "bg-black/5 text-black/65"
)}
>
{detailProgress.status === "running"
@ -3306,22 +3306,22 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF
</span>
</div>
<div className="mt-3 rounded-xl border border-violet-200/70 bg-white/80 p-2 dark:border-violet-500/30 dark:bg-zinc-900/60">
<div className="mb-1 flex items-center justify-between text-[11px] text-violet-700/80 dark:text-violet-200/70">
<div className="mt-3 rounded-xl border border-black/10 bg-black/[0.02] p-2">
<div className="mb-1 flex items-center justify-between text-[11px] text-black/55">
<span>{detailProgress.stage || "待开始:请先补齐输入信息与素材。"}</span>
<span>
{detailProgress.current}/{detailProgress.total || 0}
</span>
</div>
<div className="h-1.5 overflow-hidden rounded-full bg-violet-100 dark:bg-violet-500/20">
<div className="h-1.5 overflow-hidden rounded-full bg-black/10">
<div
className={cn(
"h-full rounded-full transition-all duration-300",
detailProgress.status === "error"
? "bg-red-500"
: detailProgress.status === "done"
: detailProgress.status === "done"
? "bg-emerald-500"
: "bg-violet-500"
: "bg-blue-500"
)}
style={{ width: `${detailProgressPercent}%` }}
/>
@ -3329,162 +3329,162 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF
</div>
<div className="mt-3 space-y-2">
<div className="rounded-xl border border-violet-200/70 bg-white/85 dark:border-violet-500/30 dark:bg-zinc-900/50">
<div className="rounded-xl border border-black/10 bg-white">
<button
type="button"
className="flex w-full items-center justify-between px-3 py-2 text-left"
onClick={() => toggleDetailSection("required")}
>
<div>
<p className="text-xs font-semibold text-violet-900 dark:text-violet-100">必填信息</p>
<p className="text-[11px] text-violet-700/75 dark:text-violet-200/70">人群 / 场景 / 核心需求为必填</p>
<p className="text-xs font-semibold text-black">必填信息</p>
<p className="text-[11px] text-black/55">人群 / 场景 / 核心需求为必填</p>
</div>
<ChevronDown
className={cn(
"h-4 w-4 text-violet-600 transition-transform dark:text-violet-200",
"h-4 w-4 text-black/55 transition-transform",
detailSections.required && "rotate-180"
)}
/>
</button>
{detailSections.required ? (
<div className="grid grid-cols-2 gap-2 border-t border-violet-200/70 p-3 xl:grid-cols-4 dark:border-violet-500/25">
<div className="grid grid-cols-2 gap-2 border-t border-black/10 p-3 xl:grid-cols-4">
<input
value={detailBrief.targetAudience}
onChange={(event) => updateDetailBriefField("targetAudience", event.target.value)}
className="h-8 rounded-lg border border-violet-200/80 bg-white/90 px-2 text-xs outline-none focus:border-violet-400 dark:border-violet-500/30 dark:bg-zinc-900/60"
className="h-8 rounded-lg border border-black/15 bg-white px-2 text-xs outline-none focus:border-blue-400"
placeholder="主购买人群 *"
/>
<input
value={detailBrief.usageScenario}
onChange={(event) => updateDetailBriefField("usageScenario", event.target.value)}
className="h-8 rounded-lg border border-violet-200/80 bg-white/90 px-2 text-xs outline-none focus:border-violet-400 dark:border-violet-500/30 dark:bg-zinc-900/60"
className="h-8 rounded-lg border border-black/15 bg-white px-2 text-xs outline-none focus:border-blue-400"
placeholder="需求场景 *"
/>
<input
value={detailBrief.coreNeed}
onChange={(event) => updateDetailBriefField("coreNeed", event.target.value)}
className="h-8 rounded-lg border border-violet-200/80 bg-white/90 px-2 text-xs outline-none focus:border-violet-400 dark:border-violet-500/30 dark:bg-zinc-900/60"
className="h-8 rounded-lg border border-black/15 bg-white px-2 text-xs outline-none focus:border-blue-400"
placeholder="核心需求 *"
/>
<input
value={detailBrief.productName}
onChange={(event) => updateDetailBriefField("productName", event.target.value)}
className="h-8 rounded-lg border border-violet-200/80 bg-white/90 px-2 text-xs outline-none focus:border-violet-400 dark:border-violet-500/30 dark:bg-zinc-900/60"
className="h-8 rounded-lg border border-black/15 bg-white px-2 text-xs outline-none focus:border-blue-400"
placeholder="产品名称(选填)"
/>
<input
value={detailBrief.mainUser}
onChange={(event) => updateDetailBriefField("mainUser", event.target.value)}
className="h-8 rounded-lg border border-violet-200/80 bg-white/90 px-2 text-xs outline-none focus:border-violet-400 dark:border-violet-500/30 dark:bg-zinc-900/60"
className="h-8 rounded-lg border border-black/15 bg-white px-2 text-xs outline-none focus:border-blue-400"
placeholder="主使用者(选填)"
/>
<input
value={detailBrief.triggerMoment}
onChange={(event) => updateDetailBriefField("triggerMoment", event.target.value)}
className="h-8 rounded-lg border border-violet-200/80 bg-white/90 px-2 text-xs outline-none focus:border-violet-400 dark:border-violet-500/30 dark:bg-zinc-900/60"
className="h-8 rounded-lg border border-black/15 bg-white px-2 text-xs outline-none focus:border-blue-400"
placeholder="触发时刻(选填)"
/>
<input
value={detailBrief.riskConcern}
onChange={(event) => updateDetailBriefField("riskConcern", event.target.value)}
className="h-8 rounded-lg border border-violet-200/80 bg-white/90 px-2 text-xs outline-none focus:border-violet-400 dark:border-violet-500/30 dark:bg-zinc-900/60"
className="h-8 rounded-lg border border-black/15 bg-white px-2 text-xs outline-none focus:border-blue-400"
placeholder="用户顾虑(选填)"
/>
<input
value={detailBrief.expectedResult}
onChange={(event) => updateDetailBriefField("expectedResult", event.target.value)}
className="h-8 rounded-lg border border-violet-200/80 bg-white/90 px-2 text-xs outline-none focus:border-violet-400 dark:border-violet-500/30 dark:bg-zinc-900/60"
className="h-8 rounded-lg border border-black/15 bg-white px-2 text-xs outline-none focus:border-blue-400"
placeholder="期望结果(选填)"
/>
<input
value={detailBrief.coreSellingPoint}
onChange={(event) => updateDetailBriefField("coreSellingPoint", event.target.value)}
className="h-8 rounded-lg border border-violet-200/80 bg-white/90 px-2 text-xs outline-none focus:border-violet-400 xl:col-span-2 dark:border-violet-500/30 dark:bg-zinc-900/60"
className="h-8 rounded-lg border border-black/15 bg-white px-2 text-xs outline-none focus:border-blue-400 xl:col-span-2"
placeholder="核心卖点关键词(选填)"
/>
<input
value={detailBrief.positioningStatement}
onChange={(event) => updateDetailBriefField("positioningStatement", event.target.value)}
className="h-8 rounded-lg border border-violet-200/80 bg-white/90 px-2 text-xs outline-none focus:border-violet-400 xl:col-span-2 dark:border-violet-500/30 dark:bg-zinc-900/60"
className="h-8 rounded-lg border border-black/15 bg-white px-2 text-xs outline-none focus:border-blue-400 xl:col-span-2"
placeholder="一句话定位(选填)"
/>
</div>
) : null}
</div>
<div className="rounded-xl border border-violet-200/70 bg-white/85 dark:border-violet-500/30 dark:bg-zinc-900/50">
<div className="rounded-xl border border-black/10 bg-white">
<button
type="button"
className="flex w-full items-center justify-between px-3 py-2 text-left"
onClick={() => toggleDetailSection("strategy")}
>
<div>
<p className="text-xs font-semibold text-violet-900 dark:text-violet-100">策略信息</p>
<p className="text-[11px] text-violet-700/75 dark:text-violet-200/70">品牌语气、竞品参考、禁用词</p>
<p className="text-xs font-semibold text-black">策略信息</p>
<p className="text-[11px] text-black/55">品牌语气、竞品参考、禁用词</p>
</div>
<ChevronDown
className={cn(
"h-4 w-4 text-violet-600 transition-transform dark:text-violet-200",
"h-4 w-4 text-black/55 transition-transform",
detailSections.strategy && "rotate-180"
)}
/>
</button>
{detailSections.strategy ? (
<div className="grid grid-cols-2 gap-2 border-t border-violet-200/70 p-3 xl:grid-cols-4 dark:border-violet-500/25">
<div className="grid grid-cols-2 gap-2 border-t border-black/10 p-3 xl:grid-cols-4">
<input
value={detailBrief.priceBand}
onChange={(event) => updateDetailBriefField("priceBand", event.target.value)}
className="h-8 rounded-lg border border-violet-200/80 bg-white/90 px-2 text-xs outline-none focus:border-violet-400 dark:border-violet-500/30 dark:bg-zinc-900/60"
className="h-8 rounded-lg border border-black/15 bg-white px-2 text-xs outline-none focus:border-blue-400"
placeholder="价格带(选填)"
/>
<input
value={detailBrief.brandTone}
onChange={(event) => updateDetailBriefField("brandTone", event.target.value)}
className="h-8 rounded-lg border border-violet-200/80 bg-white/90 px-2 text-xs outline-none focus:border-violet-400 dark:border-violet-500/30 dark:bg-zinc-900/60"
className="h-8 rounded-lg border border-black/15 bg-white px-2 text-xs outline-none focus:border-blue-400"
placeholder="品牌语气(选填)"
/>
<input
value={detailBrief.competitorReference}
onChange={(event) => updateDetailBriefField("competitorReference", event.target.value)}
className="h-8 rounded-lg border border-violet-200/80 bg-white/90 px-2 text-xs outline-none focus:border-violet-400 xl:col-span-2 dark:border-violet-500/30 dark:bg-zinc-900/60"
className="h-8 rounded-lg border border-black/15 bg-white px-2 text-xs outline-none focus:border-blue-400 xl:col-span-2"
placeholder="竞品对标(选填)"
/>
<input
value={detailBrief.bannedWords}
onChange={(event) => updateDetailBriefField("bannedWords", event.target.value)}
className="h-8 rounded-lg border border-violet-200/80 bg-white/90 px-2 text-xs outline-none focus:border-violet-400 xl:col-span-2 dark:border-violet-500/30 dark:bg-zinc-900/60"
className="h-8 rounded-lg border border-black/15 bg-white px-2 text-xs outline-none focus:border-blue-400 xl:col-span-2"
placeholder="禁用表达(选填,逗号分隔)"
/>
<textarea
value={detailBrief.userVoice}
onChange={(event) => updateDetailBriefField("userVoice", event.target.value)}
className="min-h-[64px] resize-y rounded-lg border border-violet-200/80 bg-white/90 px-2 py-1.5 text-xs outline-none focus:border-violet-400 xl:col-span-4 dark:border-violet-500/30 dark:bg-zinc-900/60"
className="min-h-[64px] resize-y rounded-lg border border-black/15 bg-white px-2 py-1.5 text-xs outline-none focus:border-blue-400 xl:col-span-4"
placeholder="用户原话样本(选填,建议3-5条)"
/>
</div>
) : null}
</div>
<div className="rounded-xl border border-violet-200/70 bg-white/85 dark:border-violet-500/30 dark:bg-zinc-900/50">
<div className="rounded-xl border border-black/10 bg-white">
<button
type="button"
className="flex w-full items-center justify-between px-3 py-2 text-left"
onClick={() => toggleDetailSection("assets")}
>
<div>
<p className="text-xs font-semibold text-violet-900 dark:text-violet-100">素材上传</p>
<p className="text-[11px] text-violet-700/75 dark:text-violet-200/70">固定 5 张产品图 + 1 张模特图</p>
<p className="text-xs font-semibold text-black">素材上传</p>
<p className="text-[11px] text-black/55">固定 5 张产品图 + 1 张模特图</p>
</div>
<ChevronDown
className={cn(
"h-4 w-4 text-violet-600 transition-transform dark:text-violet-200",
"h-4 w-4 text-black/55 transition-transform",
detailSections.assets && "rotate-180"
)}
/>
</button>
{detailSections.assets ? (
<div className="border-t border-violet-200/70 p-3 dark:border-violet-500/25">
<div className="mb-2 text-[11px] text-violet-700/80 dark:text-violet-200/70">
<div className="border-t border-black/10 p-3">
<div className="mb-2 text-[11px] text-black/55">
Agent 会自动判断是否使用模特图,人物相关分镜将固定使用同一模特。
</div>
<div className="grid grid-cols-2 gap-2 xl:grid-cols-6">
@ -3494,7 +3494,7 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF
return (
<div
key={config.key}
className="group relative h-28 overflow-hidden rounded-xl border border-violet-200/80 bg-white/90 dark:border-violet-500/30 dark:bg-zinc-900/60"
className="group relative h-28 overflow-hidden rounded-xl border border-black/15 bg-white"
>
<input
type="file"
@ -3541,7 +3541,7 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF
) : (
<button
type="button"
className="flex h-full w-full cursor-pointer flex-col items-center justify-center gap-1 text-[11px] text-violet-700 dark:text-violet-200"
className="flex h-full w-full cursor-pointer flex-col items-center justify-center gap-1 text-[11px] text-black/65"
onClick={() => detailSlotInputRefs.current[index]?.click()}
disabled={isSubmitting || isUploading}
>
@ -3557,7 +3557,7 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF
);
})}
</div>
<div className="mt-2 text-[11px] text-violet-700/80 dark:text-violet-200/80">
<div className="mt-2 text-[11px] text-black/55">
当前素材:产品图 {detailProductCount}/5,模特图 {detailModelReady ? "1/1" : "0/1"}
</div>
</div>