feat: 圆桌会商 Step1 左栏样式优化(业务链关系图/分析记录卡片)
- 业务链:红色主题卡片头+单选圆点+席位胶囊+描述;选中可展开关系图, 席位按 stages 分组用彩色圆形小图标排列,悬停浮层显示名称/描述 - 分析记录:圆角改 5px,新增已完成/未完成标签与「第 N 步」进度, 标题/状态/时间/步骤四角布局,加选中高亮与 hover 阴影 - 修复滚动容器 overflow 裁剪导致卡片左边框被切 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
ef1a12ab74
commit
fc7576fea3
1 file changed
+260
-52
@@ -1595,6 +1595,7 @@ export default function RoundtablePlanningPage() {
|
||||
selectedChainId={autoChainResult?.chain?.id ?? null}
|
||||
onSelectChain={handleSelectChainFromSidebar}
|
||||
onOpenDraft={drafts.handleLoadDraft}
|
||||
activeDraftId={drafts.currentDraftId}
|
||||
/>
|
||||
|
||||
{/* MIDDLE VIEWPORT */}
|
||||
@@ -2100,6 +2101,8 @@ interface LeftSidebarProps {
|
||||
onSelectChain: (chain: BusinessChain) => void;
|
||||
/** 需求 0616 #2:Step1 落地左栏「分析记录」点击 → 打开历史会商分析(加载草稿)。 */
|
||||
onOpenDraft: (draftId: string) => void;
|
||||
/** 当前已加载草稿 id(分析记录选中高亮用),来自 drafts.currentDraftId。 */
|
||||
activeDraftId: string | null;
|
||||
}
|
||||
|
||||
function Step2SandboxSidebarSync({
|
||||
@@ -2144,6 +2147,7 @@ function LeftSidebar({
|
||||
selectedChainId,
|
||||
onSelectChain,
|
||||
onOpenDraft,
|
||||
activeDraftId,
|
||||
}: LeftSidebarProps) {
|
||||
// 整片脱敏:左侧面板(任务概览/参与角色/人工参与等)的所有文本——标题、动态内容、
|
||||
// 嵌套子组件文本一并脱敏。消息列表与沙箱是另外的容器,不挂此 Hook,保持原文。
|
||||
@@ -2279,12 +2283,41 @@ function LeftSidebar({
|
||||
selectedChainId={selectedChainId}
|
||||
onSelectChain={onSelectChain}
|
||||
onOpenDraft={onOpenDraft}
|
||||
activeDraftId={activeDraftId}
|
||||
/>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
/** 「展开关系图」示例芯片的轮换配色(仅作视觉区分,无业务语义)。 */
|
||||
const SEAT_CHIP_PALETTE = [
|
||||
"bg-rose-50 text-rose-700 border-rose-200",
|
||||
"bg-amber-50 text-amber-700 border-amber-200",
|
||||
"bg-teal-50 text-teal-700 border-teal-200",
|
||||
"bg-purple-50 text-purple-700 border-purple-200",
|
||||
"bg-sky-50 text-sky-700 border-sky-200",
|
||||
"bg-emerald-50 text-emerald-700 border-emerald-200",
|
||||
];
|
||||
|
||||
/**
|
||||
* 把业务链席位按 `stages`(分层并行编排)分组:组内并行、组间串行;无 `stages`
|
||||
* (线性链)时每个席位单独成一层。仅供左栏「展开关系图」做编排示意展示。
|
||||
*/
|
||||
function getChainStageGroups(chain: BusinessChain): BusinessChain["seats"][] {
|
||||
const byId = new Map(chain.seats.map((s) => [s.agent_id, s]));
|
||||
if (chain.stages && chain.stages.length > 0) {
|
||||
return chain.stages
|
||||
.map((stage) =>
|
||||
stage
|
||||
.map((id) => byId.get(id))
|
||||
.filter((s): s is BusinessChain["seats"][number] => Boolean(s)),
|
||||
)
|
||||
.filter((stage) => stage.length > 0);
|
||||
}
|
||||
return chain.seats.map((s) => [s]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 需求 0616 #2:多智能体会商 Step1(落地)左栏入口栏。
|
||||
* - 业务链:仅公共链条(``listChains("public")``)前 3 个;点击以该链席位启动会商。
|
||||
@@ -2295,12 +2328,15 @@ function RoundtableEntryRail({
|
||||
selectedChainId,
|
||||
onSelectChain,
|
||||
onOpenDraft,
|
||||
activeDraftId,
|
||||
}: {
|
||||
selectedChainId: string | null;
|
||||
onSelectChain: (chain: BusinessChain) => void;
|
||||
onOpenDraft: (draftId: string) => void;
|
||||
activeDraftId: string | null;
|
||||
}) {
|
||||
const [chains, setChains] = useState<BusinessChain[]>([]);
|
||||
const [expandedChainId, setExpandedChainId] = useState<string | null>(null);
|
||||
const { drafts: records, refresh } = useRoundtableDrafts();
|
||||
|
||||
useEffect(() => {
|
||||
@@ -2333,51 +2369,170 @@ function RoundtableEntryRail({
|
||||
return (
|
||||
<>
|
||||
{/* 业务链(公共,前 3 个) */}
|
||||
<div className="bg-white border border-slate-200/50 rounded-2xl p-4 shadow-[0_2px_8px_rgba(0,0,0,0.01)]">
|
||||
<div className="flex items-center gap-2 border-b border-slate-100 pb-2.5 mb-3">
|
||||
<Layers className="w-4 h-4 text-primary" strokeWidth={2.5} />
|
||||
<h2 className="text-xs font-bold text-slate-900 uppercase tracking-widest">业务链</h2>
|
||||
<div className="bg-white border border-[#ECE7E4] rounded-2xl p-4 shadow-[0_4px_20px_rgba(0,0,0,0.02)] transition-shadow duration-300 hover:shadow-[0_4px_24px_rgba(45,37,34,0.04)]">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<span className="flex items-center justify-center w-5 h-5 rounded-md bg-primary text-primary-foreground">
|
||||
<Layers className="w-3 h-3" strokeWidth={2.5} />
|
||||
</span>
|
||||
<h2 className="text-sm font-semibold tracking-wide text-slate-900">业务链</h2>
|
||||
</div>
|
||||
{chains.length === 0 ? (
|
||||
<p className="text-xs text-slate-400 py-1.5">暂无公共业务链</p>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
<div className="flex flex-col gap-2.5">
|
||||
{chains.map((chain) => {
|
||||
const selected = selectedChainId === chain.id;
|
||||
const graphOpen = expandedChainId === chain.id;
|
||||
const stageGroups = getChainStageGroups(chain);
|
||||
let seatColorIndex = 0;
|
||||
return (
|
||||
<button
|
||||
<div
|
||||
key={chain.id}
|
||||
type="button"
|
||||
onClick={() => onSelectChain(chain)}
|
||||
title={selected ? "再次点击取消勾选" : `勾选业务链「${chain.title}」`}
|
||||
aria-pressed={selected}
|
||||
className={cn(
|
||||
"w-full rounded-xl border p-2.5 text-left transition-colors",
|
||||
"rounded-xl border transition-all duration-200",
|
||||
selected
|
||||
? "border-primary/40 bg-primary/5"
|
||||
: "border-slate-100 bg-slate-50/60 hover:border-primary/30 hover:bg-white",
|
||||
? "border-primary/25 bg-primary/5 ring-1 ring-primary/20 shadow-sm"
|
||||
: "border-[#ECE7E4] bg-neutral-50/50 hover:border-neutral-300 hover:bg-neutral-50",
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<span
|
||||
className={cn(
|
||||
"truncate text-xs font-extrabold",
|
||||
selected ? "text-primary" : "text-slate-800",
|
||||
)}
|
||||
>
|
||||
{chain.title}
|
||||
</span>
|
||||
{selected ? (
|
||||
<Check className="h-3.5 w-3.5 shrink-0 text-primary" />
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onSelectChain(chain)}
|
||||
title={selected ? "再次点击取消勾选" : `勾选业务链「${chain.title}」`}
|
||||
aria-pressed={selected}
|
||||
className="w-full rounded-xl p-3 text-left"
|
||||
>
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<div className="flex min-w-0 items-center gap-2">
|
||||
{/* 单选圆点 */}
|
||||
<span
|
||||
className={cn(
|
||||
"flex h-3.5 w-3.5 shrink-0 items-center justify-center rounded-full border transition-all",
|
||||
selected
|
||||
? "border-primary bg-primary"
|
||||
: "border-neutral-300 bg-white",
|
||||
)}
|
||||
>
|
||||
{selected && <span className="h-1.5 w-1.5 rounded-full bg-white" />}
|
||||
</span>
|
||||
<span
|
||||
className={cn(
|
||||
"truncate text-xs font-semibold",
|
||||
selected ? "text-primary" : "text-neutral-800",
|
||||
)}
|
||||
>
|
||||
{chain.title}
|
||||
</span>
|
||||
</div>
|
||||
<span
|
||||
className={cn(
|
||||
"shrink-0 rounded-full px-1.5 py-0.5 text-[10px] font-semibold",
|
||||
selected
|
||||
? "bg-primary/10 text-primary"
|
||||
: "bg-neutral-100 text-neutral-500",
|
||||
)}
|
||||
>
|
||||
{chain.seats.length} 席
|
||||
</span>
|
||||
</div>
|
||||
{chain.description ? (
|
||||
<p className="mt-1.5 line-clamp-2 text-[10.5px] leading-relaxed text-neutral-500">
|
||||
{chain.description}
|
||||
</p>
|
||||
) : (
|
||||
<ChevronRight className="h-3.5 w-3.5 shrink-0 text-slate-400" />
|
||||
<p className="mt-1.5 text-[10px] font-medium text-neutral-400">
|
||||
{selected ? "已选 · 再次点击取消" : "点击勾选该业务链"}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="mt-1 text-[10px] font-semibold text-slate-400">
|
||||
{chain.seats.length} 个席位
|
||||
{selected ? " · 已选" : ""}
|
||||
</div>
|
||||
</button>
|
||||
</button>
|
||||
|
||||
{/* 关系图示例:选中后可展开,把该链席位智能体用小图标排成编排示意 */}
|
||||
{selected && (
|
||||
<div className="mx-3 mb-3 border-t border-dashed border-[#E8D0CC]/60 pt-2.5">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
setExpandedChainId(graphOpen ? null : chain.id)
|
||||
}
|
||||
className="flex w-full items-center justify-center gap-1 rounded-full border border-primary/15 bg-primary/5 px-3 py-1 text-[10.5px] font-semibold text-primary transition-colors hover:bg-primary/10"
|
||||
>
|
||||
<Layers className="h-3 w-3" />
|
||||
<span>{graphOpen ? "收起关系图" : "展开关系图"}</span>
|
||||
{graphOpen ? (
|
||||
<ChevronUp className="h-3 w-3" />
|
||||
) : (
|
||||
<ChevronDown className="h-3 w-3" />
|
||||
)}
|
||||
</button>
|
||||
|
||||
{graphOpen && (
|
||||
<div className="mt-2 rounded-xl border border-dashed border-[#E8D0CC]/60 bg-[#FAF6F5] p-2.5">
|
||||
{stageGroups.length === 0 ? (
|
||||
<p className="py-2 text-center text-[10px] text-neutral-400">
|
||||
该业务链暂无智能体席位
|
||||
</p>
|
||||
) : (
|
||||
<div className="flex flex-col items-center gap-1.5">
|
||||
{stageGroups.map((stage, si) => (
|
||||
<div
|
||||
key={si}
|
||||
className="flex flex-col items-center gap-1"
|
||||
>
|
||||
{si > 0 && (
|
||||
<ChevronDown className="h-3 w-3 text-neutral-300" />
|
||||
)}
|
||||
{stage.length > 1 && (
|
||||
<span className="rounded-full bg-primary/5 px-1.5 text-[8.5px] font-semibold text-primary">
|
||||
并行 {stage.length}
|
||||
</span>
|
||||
)}
|
||||
<div className="flex flex-wrap items-center justify-center gap-1.5">
|
||||
{stage.map((seat, sj) => {
|
||||
const palette =
|
||||
SEAT_CHIP_PALETTE[
|
||||
seatColorIndex++ %
|
||||
SEAT_CHIP_PALETTE.length
|
||||
];
|
||||
return (
|
||||
<div
|
||||
key={`${si}-${sj}-${seat.agent_id}`}
|
||||
className="group/seat relative"
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
"flex h-6 w-6 items-center justify-center rounded-full border",
|
||||
palette,
|
||||
)}
|
||||
>
|
||||
<Cpu className="h-3 w-3" />
|
||||
</span>
|
||||
{/* 悬停浮层显示名称 / 描述 */}
|
||||
<div className="pointer-events-none absolute bottom-full left-1/2 z-50 mb-1.5 hidden -translate-x-1/2 group-hover/seat:block">
|
||||
<div className="max-w-[180px] rounded-lg bg-neutral-900 px-2 py-1 text-white shadow-lg">
|
||||
<div className="whitespace-nowrap text-[10px] font-semibold">
|
||||
{seat.name}
|
||||
</div>
|
||||
{seat.description && (
|
||||
<div className="mt-0.5 whitespace-normal text-[9px] leading-snug text-neutral-300">
|
||||
{seat.description}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="absolute left-1/2 top-full h-0 w-0 -translate-x-1/2 border-l-4 border-r-4 border-t-4 border-l-transparent border-r-transparent border-t-neutral-900" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
@@ -2385,30 +2540,83 @@ function RoundtableEntryRail({
|
||||
</div>
|
||||
|
||||
{/* 分析记录(圆桌历史草稿) */}
|
||||
<div className="bg-white border border-slate-200/50 rounded-2xl p-4 shadow-[0_2px_8px_rgba(0,0,0,0.01)] flex-1 flex flex-col min-h-[200px]">
|
||||
<div className="flex items-center gap-2 border-b border-slate-100 pb-2.5 mb-3">
|
||||
<History className="w-4 h-4 text-primary" strokeWidth={2.5} />
|
||||
<h2 className="text-xs font-bold text-slate-900 uppercase tracking-widest">分析记录</h2>
|
||||
<div className="bg-white border border-[#ECE7E4] rounded-2xl p-4 shadow-[0_4px_20px_rgba(0,0,0,0.02)] flex-1 flex flex-col min-h-[200px] overflow-hidden">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<span className="flex items-center justify-center w-5 h-5 rounded-md bg-primary text-primary-foreground">
|
||||
<History className="w-3 h-3" strokeWidth={2.5} />
|
||||
</span>
|
||||
<h2 className="text-sm font-semibold tracking-wide text-slate-900">分析记录</h2>
|
||||
</div>
|
||||
{records.length === 0 ? (
|
||||
<p className="text-xs text-slate-400 py-1.5">暂无分析记录</p>
|
||||
<p className="flex-1 text-xs text-neutral-400 text-center py-8">暂无分析记录</p>
|
||||
) : (
|
||||
<div className="space-y-2 overflow-y-auto flex-1 pr-1 custom-scrollbar">
|
||||
{records.map((rec) => (
|
||||
<button
|
||||
key={rec.id}
|
||||
type="button"
|
||||
onClick={() => onOpenDraft(rec.id)}
|
||||
title={rec.title}
|
||||
className="w-full rounded-xl border border-slate-100 bg-slate-50/60 p-2.5 text-left transition-colors hover:border-primary/20 hover:bg-white"
|
||||
>
|
||||
<div className="truncate text-xs font-bold text-slate-700">{rec.title}</div>
|
||||
<div className="mt-1 text-[10px] font-semibold text-slate-400">
|
||||
{formatTime(rec.updatedAt)}
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<>
|
||||
<div className="space-y-2 overflow-y-auto flex-1 px-1 py-0.5 custom-scrollbar">
|
||||
{records.map((rec) => {
|
||||
// 已完成:进行到第 3 步(执行过方案总结报告)
|
||||
const completed = rec.furthestStep === 3;
|
||||
// 选中:当前已加载的草稿(正在查看的那条)
|
||||
const active = activeDraftId === rec.id;
|
||||
return (
|
||||
<button
|
||||
key={rec.id}
|
||||
type="button"
|
||||
onClick={() => onOpenDraft(rec.id)}
|
||||
title={rec.title}
|
||||
aria-current={active}
|
||||
className={cn(
|
||||
"group/rec w-full rounded-[5px] border px-3 py-2.5 text-left transition-all duration-200",
|
||||
active
|
||||
? "border-primary/40 bg-primary/5 ring-1 ring-primary/20 shadow-[0_2px_10px_rgba(45,37,34,0.05)]"
|
||||
: "border-[#ECE7E4] bg-white hover:border-primary/30 hover:bg-neutral-50 hover:shadow-[0_2px_10px_rgba(45,37,34,0.05)]",
|
||||
)}
|
||||
>
|
||||
{/* 上:标题(左) + 状态标签(右上、放大) */}
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<span
|
||||
className={cn(
|
||||
"truncate text-[13px] font-semibold",
|
||||
active
|
||||
? "text-primary"
|
||||
: "text-neutral-800 group-hover/rec:text-primary",
|
||||
)}
|
||||
>
|
||||
{rec.title}
|
||||
</span>
|
||||
<span
|
||||
className={cn(
|
||||
"inline-flex shrink-0 items-center gap-1 rounded-[5px] px-2 py-1 text-[11px] font-semibold",
|
||||
completed
|
||||
? "bg-emerald-50 text-emerald-600"
|
||||
: "bg-amber-50 text-amber-600",
|
||||
)}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
"h-1.5 w-1.5 rounded-full",
|
||||
completed ? "bg-emerald-500" : "bg-amber-500",
|
||||
)}
|
||||
/>
|
||||
{completed ? "已完成" : "未完成"}
|
||||
</span>
|
||||
</div>
|
||||
{/* 下:时间(左下) + 步骤(右下) */}
|
||||
<div className="mt-2 flex items-center justify-between">
|
||||
<span className="font-mono text-[10px] text-neutral-400">
|
||||
{formatTime(rec.updatedAt)}
|
||||
</span>
|
||||
<span className="rounded-[5px] bg-neutral-100 px-1.5 py-0.5 text-[10px] font-medium text-neutral-500">
|
||||
第 {rec.furthestStep} 步
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div className="mt-2 pt-2 border-t border-neutral-100 text-center text-[11px] text-neutral-400">
|
||||
共 {records.length} 条分析记录
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
|
||||
Reference in new issue
Block a user