feat(roundtable): 业务链条左栏卡片拖动手柄 + 加入按钮 + 智能体详情抽屉
- 左栏候选卡片加拖动手柄(与右栏席位一致),仅点手柄才拖动 - 加入改为显式「加入」按钮,点卡片本体改为打开详情抽屉 - 右栏席位操作区加「查看详情」按钮,打开同一抽屉 - 新增 AgentDetailSheet:按需拉取(useAgent)展示名称/模型/描述/ 标签/技能+工具组/人设,对齐智能体详情页内容 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
c05e7df48e
commit
b101ee7fc9
2 files changed
+241
-25
No files matched your search
@@ -0,0 +1,151 @@
|
|||||||
|
/**
|
||||||
|
* 智能体详情抽屉(右侧 Sheet)。
|
||||||
|
*
|
||||||
|
* 业务链条配置页:左栏候选卡片点击卡片本体、右栏席位点「查看详情」都打开这个抽屉,
|
||||||
|
* 展示该智能体的详情(与「智能体」详情页展示的内容差不多):名称 / 模型 / 描述 /
|
||||||
|
* 标签 / 技能 + 工具组 / 人设(SOUL.md)。
|
||||||
|
*
|
||||||
|
* 详情按需拉取:`useAgent(open ? agentId : null)` —— 抽屉打开时才请求 `GET /api/agents/{id}`
|
||||||
|
* (该接口才返回 soul),关闭不请求。字段渲染对齐 `components/workspace/agents/agent-card.tsx`。
|
||||||
|
*/
|
||||||
|
import { Bot, Globe, Loader2, Lock } from "lucide-react";
|
||||||
|
|
||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
import {
|
||||||
|
Sheet,
|
||||||
|
SheetContent,
|
||||||
|
SheetDescription,
|
||||||
|
SheetHeader,
|
||||||
|
SheetTitle,
|
||||||
|
} from "@/components/ui/sheet";
|
||||||
|
import { TagChipList } from "@/components/workspace/tags/tag-chip";
|
||||||
|
import { useAgent } from "@/core/agents";
|
||||||
|
import { desensitize } from "@/lib/desensitize";
|
||||||
|
|
||||||
|
export function AgentDetailSheet({
|
||||||
|
agentId,
|
||||||
|
/** 卡片/席位上的名称快照,详情还没拉回来时先用它占位。 */
|
||||||
|
fallbackName,
|
||||||
|
open,
|
||||||
|
onOpenChange,
|
||||||
|
}: {
|
||||||
|
agentId: string | null;
|
||||||
|
fallbackName?: string;
|
||||||
|
open: boolean;
|
||||||
|
onOpenChange: (open: boolean) => void;
|
||||||
|
}) {
|
||||||
|
// 抽屉打开时才拉详情(含 soul);关闭传 null 让 useAgent 不发请求。
|
||||||
|
const { agent, isLoading } = useAgent(open ? agentId : null);
|
||||||
|
const name = agent?.name || fallbackName || agentId || "";
|
||||||
|
const skillItems = agent?.skills ?? [];
|
||||||
|
const toolGroups = agent?.tool_groups ?? [];
|
||||||
|
const hasSkillsOrTools = skillItems.length > 0 || toolGroups.length > 0;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Sheet open={open} onOpenChange={onOpenChange}>
|
||||||
|
<SheetContent side="right" className="w-full gap-0 p-0 sm:max-w-md">
|
||||||
|
<SheetHeader className="border-b border-border px-5 py-4">
|
||||||
|
<SheetTitle className="flex items-center gap-2.5">
|
||||||
|
<span className="flex size-8 shrink-0 items-center justify-center rounded-md bg-primary/10 text-primary">
|
||||||
|
<Bot className="size-4" />
|
||||||
|
</span>
|
||||||
|
<span className="min-w-0 flex-1 truncate text-base font-bold">{name}</span>
|
||||||
|
{agent?.model && (
|
||||||
|
<Badge variant="outline" className="shrink-0 text-[10px]">
|
||||||
|
{desensitize(agent.model)}
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
</SheetTitle>
|
||||||
|
<SheetDescription className="sr-only">智能体详情</SheetDescription>
|
||||||
|
</SheetHeader>
|
||||||
|
|
||||||
|
<div className="min-h-0 flex-1 space-y-5 overflow-y-auto px-5 py-4 text-sm">
|
||||||
|
{isLoading && !agent ? (
|
||||||
|
<div className="flex items-center gap-2 py-8 text-muted-foreground">
|
||||||
|
<Loader2 className="size-4 animate-spin" />
|
||||||
|
正在加载详情…
|
||||||
|
</div>
|
||||||
|
) : !agent ? (
|
||||||
|
<div className="py-8 text-center text-muted-foreground">
|
||||||
|
未找到该智能体的详情(可能已被删除)。
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{/* 状态徽标 */}
|
||||||
|
<div className="flex flex-wrap items-center gap-1.5">
|
||||||
|
{agent.builtin && (
|
||||||
|
<Badge variant="secondary" className="text-[10px]">内置</Badge>
|
||||||
|
)}
|
||||||
|
{agent.published && (
|
||||||
|
<Badge variant="outline" className="gap-1 text-[10px]">
|
||||||
|
<Globe className="size-3" />
|
||||||
|
已发布
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
{agent.is_private && (
|
||||||
|
<Badge variant="outline" className="gap-1 text-[10px]">
|
||||||
|
<Lock className="size-3" />
|
||||||
|
私密
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 描述 */}
|
||||||
|
<section className="space-y-1.5">
|
||||||
|
<h4 className="text-[11px] font-bold uppercase tracking-wide text-muted-foreground">
|
||||||
|
描述
|
||||||
|
</h4>
|
||||||
|
<p className="whitespace-pre-wrap leading-relaxed text-foreground">
|
||||||
|
{agent.description?.trim() || "(无描述)"}
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* 标签 */}
|
||||||
|
{agent.tags && agent.tags.length > 0 && (
|
||||||
|
<section className="space-y-1.5">
|
||||||
|
<h4 className="text-[11px] font-bold uppercase tracking-wide text-muted-foreground">
|
||||||
|
标签
|
||||||
|
</h4>
|
||||||
|
<TagChipList tags={agent.tags} />
|
||||||
|
</section>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 技能 / 工具组 */}
|
||||||
|
{hasSkillsOrTools && (
|
||||||
|
<section className="space-y-1.5">
|
||||||
|
<h4 className="text-[11px] font-bold uppercase tracking-wide text-muted-foreground">
|
||||||
|
技能与工具
|
||||||
|
</h4>
|
||||||
|
<div className="flex flex-wrap gap-1.5">
|
||||||
|
{toolGroups.map((group) => (
|
||||||
|
<Badge key={`tg:${group}`} variant="outline" className="text-xs">
|
||||||
|
{desensitize(group)}
|
||||||
|
</Badge>
|
||||||
|
))}
|
||||||
|
{skillItems.map((skill) => (
|
||||||
|
<Badge key={`sk:${skill}`} variant="secondary" className="text-xs">
|
||||||
|
{desensitize(skill)}
|
||||||
|
</Badge>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 人设(SOUL.md) */}
|
||||||
|
{agent.soul?.trim() && (
|
||||||
|
<section className="space-y-1.5">
|
||||||
|
<h4 className="text-[11px] font-bold uppercase tracking-wide text-muted-foreground">
|
||||||
|
人设
|
||||||
|
</h4>
|
||||||
|
<pre className="whitespace-pre-wrap break-words rounded-md border border-border bg-muted/30 p-3 text-[12px] leading-relaxed text-foreground">
|
||||||
|
{agent.soul}
|
||||||
|
</pre>
|
||||||
|
</section>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</SheetContent>
|
||||||
|
</Sheet>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -30,6 +30,7 @@ import {
|
|||||||
GitBranch,
|
GitBranch,
|
||||||
Globe,
|
Globe,
|
||||||
GripVertical,
|
GripVertical,
|
||||||
|
Info,
|
||||||
Layers,
|
Layers,
|
||||||
Loader2,
|
Loader2,
|
||||||
Network,
|
Network,
|
||||||
@@ -110,6 +111,7 @@ import {
|
|||||||
} from "@/roundtable-planning/lib/chain-stages";
|
} from "@/roundtable-planning/lib/chain-stages";
|
||||||
import { getAvatarTypeFor } from "@/roundtable-planning/lib/roundtable-constants";
|
import { getAvatarTypeFor } from "@/roundtable-planning/lib/roundtable-constants";
|
||||||
import { DagFlowChart } from "@/roundtable-planning/components/DagFlowChart";
|
import { DagFlowChart } from "@/roundtable-planning/components/DagFlowChart";
|
||||||
|
import { AgentDetailSheet } from "@/roundtable-planning/components/AgentDetailSheet";
|
||||||
|
|
||||||
const POOL_PREFIX = "pool::";
|
const POOL_PREFIX = "pool::";
|
||||||
const SEAT_PREFIX = "seat::";
|
const SEAT_PREFIX = "seat::";
|
||||||
@@ -155,6 +157,11 @@ export default function BusinessChainEditorPage() {
|
|||||||
const [activeDrag, setActiveDrag] = useState<ActiveDrag>(null);
|
const [activeDrag, setActiveDrag] = useState<ActiveDrag>(null);
|
||||||
const [dropdownOpen, setDropdownOpen] = useState(false);
|
const [dropdownOpen, setDropdownOpen] = useState(false);
|
||||||
const [flowOpen, setFlowOpen] = useState(false);
|
const [flowOpen, setFlowOpen] = useState(false);
|
||||||
|
// 详情抽屉:左栏点卡片本体、右栏点「查看详情」都打开它,展示该智能体详情。
|
||||||
|
const [detailAgent, setDetailAgent] = useState<{ id: string; name?: string } | null>(null);
|
||||||
|
const showDetail = useCallback((id: string, name?: string) => {
|
||||||
|
setDetailAgent({ id, name });
|
||||||
|
}, []);
|
||||||
|
|
||||||
const sensors = useSensors(
|
const sensors = useSensors(
|
||||||
useSensor(PointerSensor, { activationConstraint: { distance: 5 } }),
|
useSensor(PointerSensor, { activationConstraint: { distance: 5 } }),
|
||||||
@@ -763,7 +770,7 @@ export default function BusinessChainEditorPage() {
|
|||||||
{pool.length}
|
{pool.length}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-[11px] font-medium text-muted-foreground">拖到右侧 / 点击加入</span>
|
<span className="text-[11px] font-medium text-muted-foreground">拖手柄到右侧 / 点卡片看详情</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="shrink-0 border-b border-border px-4 py-3">
|
<div className="shrink-0 border-b border-border px-4 py-3">
|
||||||
@@ -794,6 +801,7 @@ export default function BusinessChainEditorPage() {
|
|||||||
agent={a}
|
agent={a}
|
||||||
inChain={seatIdSet.has(a.agent_id)}
|
inChain={seatIdSet.has(a.agent_id)}
|
||||||
onAdd={() => addAgentAsNewStage(a.agent_id)}
|
onAdd={() => addAgentAsNewStage(a.agent_id)}
|
||||||
|
onShowDetail={() => showDetail(a.agent_id, a.name)}
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
)}
|
)}
|
||||||
@@ -813,6 +821,7 @@ export default function BusinessChainEditorPage() {
|
|||||||
onMergeSeat={mergeSeatToStage}
|
onMergeSeat={mergeSeatToStage}
|
||||||
onSplitSeat={splitSeat}
|
onSplitSeat={splitSeat}
|
||||||
onStageGoalChange={setGoal}
|
onStageGoalChange={setGoal}
|
||||||
|
onSeatDetail={showDetail}
|
||||||
/>
|
/>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
@@ -873,6 +882,16 @@ export default function BusinessChainEditorPage() {
|
|||||||
</Dialog>
|
</Dialog>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* 智能体详情抽屉(左栏卡片 / 右栏席位共用) */}
|
||||||
|
<AgentDetailSheet
|
||||||
|
agentId={detailAgent?.id ?? null}
|
||||||
|
fallbackName={detailAgent?.name}
|
||||||
|
open={detailAgent !== null}
|
||||||
|
onOpenChange={(o) => {
|
||||||
|
if (!o) setDetailAgent(null);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
{/* 页脚 */}
|
{/* 页脚 */}
|
||||||
<footer className="shrink-0 select-none border-t border-border bg-card px-6 py-3 text-center text-[11px] text-muted-foreground">
|
<footer className="shrink-0 select-none border-t border-border bg-card px-6 py-3 text-center text-[11px] text-muted-foreground">
|
||||||
分层编排:同一 Stage 内的席位并行研讨,Stage 之间串行。全单席位 = 线性链。保存后自动同步至圆桌。
|
分层编排:同一 Stage 内的席位并行研讨,Stage 之间串行。全单席位 = 线性链。保存后自动同步至圆桌。
|
||||||
@@ -887,11 +906,14 @@ function PoolAgentCard({
|
|||||||
agent,
|
agent,
|
||||||
inChain,
|
inChain,
|
||||||
onAdd,
|
onAdd,
|
||||||
|
onShowDetail,
|
||||||
}: {
|
}: {
|
||||||
agent: RecommendCandidate;
|
agent: RecommendCandidate;
|
||||||
inChain: boolean;
|
inChain: boolean;
|
||||||
onAdd: () => void;
|
onAdd: () => void;
|
||||||
|
onShowDetail: () => void;
|
||||||
}) {
|
}) {
|
||||||
|
// 只把拖拽 listeners 挂到左侧手柄上 —— 点手柄才拖动;点卡片本体打开详情抽屉。
|
||||||
const { attributes, listeners, setNodeRef, isDragging } = useDraggable({
|
const { attributes, listeners, setNodeRef, isDragging } = useDraggable({
|
||||||
id: POOL_PREFIX + agent.agent_id,
|
id: POOL_PREFIX + agent.agent_id,
|
||||||
disabled: inChain,
|
disabled: inChain,
|
||||||
@@ -899,55 +921,79 @@ function PoolAgentCard({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={setNodeRef}
|
ref={setNodeRef}
|
||||||
onClick={inChain ? undefined : onAdd}
|
onClick={onShowDetail}
|
||||||
className={cn(
|
className={cn(
|
||||||
"group relative flex items-start gap-3 border p-3.5 transition-all duration-150",
|
"group relative flex items-stretch overflow-hidden border transition-all duration-150",
|
||||||
R_CARD,
|
R_CARD,
|
||||||
inChain
|
inChain
|
||||||
? "cursor-default border-primary/30 bg-primary/[0.03]"
|
? "cursor-pointer border-primary/30 bg-primary/[0.03]"
|
||||||
: "cursor-pointer border-border bg-card hover:border-primary hover:shadow-[0_2px_8px_rgba(0,0,0,0.04)]",
|
: "cursor-pointer border-border bg-card hover:border-primary hover:shadow-[0_2px_8px_rgba(0,0,0,0.04)]",
|
||||||
isDragging && "opacity-30",
|
isDragging && "opacity-30",
|
||||||
)}
|
)}
|
||||||
{...(inChain ? {} : listeners)}
|
|
||||||
{...attributes}
|
{...attributes}
|
||||||
>
|
>
|
||||||
<div
|
{/* 拖拽手柄(与右栏席位一致):点手柄才开始拖动;已加入则禁用拖拽。 */}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
className={cn(
|
className={cn(
|
||||||
"mt-0.5 flex size-6 shrink-0 items-center justify-center transition-colors",
|
"flex w-7 shrink-0 items-center justify-center border-r border-border bg-muted/40 text-muted-foreground/60 transition-colors",
|
||||||
R_CARD,
|
|
||||||
inChain
|
inChain
|
||||||
? "bg-primary/10 text-primary"
|
? "cursor-not-allowed opacity-40"
|
||||||
: "bg-muted text-muted-foreground group-hover:bg-primary/10 group-hover:text-primary",
|
: "cursor-grab touch-none hover:text-foreground active:cursor-grabbing",
|
||||||
)}
|
)}
|
||||||
|
title={inChain ? "已在链条中" : "拖动到右侧加入 / 排序"}
|
||||||
|
{...(inChain ? {} : listeners)}
|
||||||
>
|
>
|
||||||
<Bot className="size-3.5" />
|
<GripVertical className="size-3.5" />
|
||||||
</div>
|
</button>
|
||||||
|
|
||||||
<div className="min-w-0 flex-1 pr-16">
|
<div className="flex min-w-0 flex-1 items-start gap-3 p-3 pr-2">
|
||||||
<div className="truncate text-xs font-bold leading-tight text-foreground transition-colors group-hover:text-primary">
|
<div
|
||||||
{agent.name || agent.agent_id}
|
className={cn(
|
||||||
|
"mt-0.5 flex size-6 shrink-0 items-center justify-center transition-colors",
|
||||||
|
R_CARD,
|
||||||
|
inChain
|
||||||
|
? "bg-primary/10 text-primary"
|
||||||
|
: "bg-muted text-muted-foreground group-hover:bg-primary/10 group-hover:text-primary",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Bot className="size-3.5" />
|
||||||
</div>
|
</div>
|
||||||
{agent.description && (
|
|
||||||
<div className="mt-1 line-clamp-2 text-[11px] font-medium leading-normal text-muted-foreground">
|
<div className="min-w-0 flex-1">
|
||||||
{agent.description}
|
<div className="truncate text-xs font-bold leading-tight text-foreground transition-colors group-hover:text-primary">
|
||||||
|
{agent.name || agent.agent_id}
|
||||||
</div>
|
</div>
|
||||||
)}
|
{agent.description && (
|
||||||
|
<div className="mt-1 line-clamp-2 text-[11px] font-medium leading-normal text-muted-foreground">
|
||||||
|
{agent.description}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="absolute right-3.5 top-3.5 shrink-0 select-none">
|
{/* 加入按钮:只有点它才加入(点卡片其它区域是看详情)。已加入显示「已加入」。 */}
|
||||||
|
<div className="flex shrink-0 items-center pr-3 pl-1 select-none">
|
||||||
{inChain ? (
|
{inChain ? (
|
||||||
<span className={cn("border border-primary/30 bg-primary/10 px-2 py-1 text-[10px] font-bold text-primary", R_CTRL)}>
|
<span className={cn("border border-primary/30 bg-primary/10 px-2 py-1 text-[10px] font-bold text-primary", R_CTRL)}>
|
||||||
已加入
|
已加入
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
<span
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onAdd();
|
||||||
|
}}
|
||||||
className={cn(
|
className={cn(
|
||||||
"border border-transparent bg-muted px-2 py-1 text-[10px] font-semibold text-muted-foreground transition-all group-hover:border-primary/30 group-hover:bg-primary/10 group-hover:text-primary",
|
"inline-flex items-center gap-1 border border-primary/30 bg-primary/5 px-2 py-1 text-[10px] font-semibold text-primary transition-all hover:bg-primary/10",
|
||||||
R_CTRL,
|
R_CTRL,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
点击加入
|
<Plus className="size-3" />
|
||||||
</span>
|
加入
|
||||||
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -968,6 +1014,7 @@ function StagesColumn({
|
|||||||
onMergeSeat,
|
onMergeSeat,
|
||||||
onSplitSeat,
|
onSplitSeat,
|
||||||
onStageGoalChange,
|
onStageGoalChange,
|
||||||
|
onSeatDetail,
|
||||||
}: {
|
}: {
|
||||||
stageGroups: StageGroups;
|
stageGroups: StageGroups;
|
||||||
seatCount: number;
|
seatCount: number;
|
||||||
@@ -980,6 +1027,7 @@ function StagesColumn({
|
|||||||
onMergeSeat: (agentId: string, targetStageIdx: number) => void;
|
onMergeSeat: (agentId: string, targetStageIdx: number) => void;
|
||||||
onSplitSeat: (agentId: string) => void;
|
onSplitSeat: (agentId: string) => void;
|
||||||
onStageGoalChange: (stageIdx: number, goal: string) => void;
|
onStageGoalChange: (stageIdx: number, goal: string) => void;
|
||||||
|
onSeatDetail: (agentId: string, name?: string) => void;
|
||||||
}) {
|
}) {
|
||||||
// 跨 Stage 的全局序号(头像编号):每个 Stage 起始累加。
|
// 跨 Stage 的全局序号(头像编号):每个 Stage 起始累加。
|
||||||
const stageStartIndex = useMemo(() => {
|
const stageStartIndex = useMemo(() => {
|
||||||
@@ -1073,6 +1121,7 @@ function StagesColumn({
|
|||||||
onMergeSeat={onMergeSeat}
|
onMergeSeat={onMergeSeat}
|
||||||
onSplitSeat={onSplitSeat}
|
onSplitSeat={onSplitSeat}
|
||||||
onGoalChange={(goal) => onStageGoalChange(stageIdx, goal)}
|
onGoalChange={(goal) => onStageGoalChange(stageIdx, goal)}
|
||||||
|
onSeatDetail={onSeatDetail}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
<NewStageDropZone hasEmptyStage={hasEmptyStage} onAddStage={onAddStage} />
|
<NewStageDropZone hasEmptyStage={hasEmptyStage} onAddStage={onAddStage} />
|
||||||
@@ -1097,6 +1146,7 @@ function StageSection({
|
|||||||
onMergeSeat,
|
onMergeSeat,
|
||||||
onSplitSeat,
|
onSplitSeat,
|
||||||
onGoalChange,
|
onGoalChange,
|
||||||
|
onSeatDetail,
|
||||||
}: {
|
}: {
|
||||||
stageIdx: number;
|
stageIdx: number;
|
||||||
group: StageGroup;
|
group: StageGroup;
|
||||||
@@ -1110,6 +1160,7 @@ function StageSection({
|
|||||||
onMergeSeat: (agentId: string, targetStageIdx: number) => void;
|
onMergeSeat: (agentId: string, targetStageIdx: number) => void;
|
||||||
onSplitSeat: (agentId: string) => void;
|
onSplitSeat: (agentId: string) => void;
|
||||||
onGoalChange: (goal: string) => void;
|
onGoalChange: (goal: string) => void;
|
||||||
|
onSeatDetail: (agentId: string, name?: string) => void;
|
||||||
}) {
|
}) {
|
||||||
const { setNodeRef, isOver } = useDroppable({ id: STAGE_PREFIX + stageIdx });
|
const { setNodeRef, isOver } = useDroppable({ id: STAGE_PREFIX + stageIdx });
|
||||||
const seats = group.seats;
|
const seats = group.seats;
|
||||||
@@ -1238,6 +1289,7 @@ function StageSection({
|
|||||||
onMergeUp={() => onMergeSeat(seat.agent_id, stageIdx - 1)}
|
onMergeUp={() => onMergeSeat(seat.agent_id, stageIdx - 1)}
|
||||||
onMergeDown={() => onMergeSeat(seat.agent_id, stageIdx + 1)}
|
onMergeDown={() => onMergeSeat(seat.agent_id, stageIdx + 1)}
|
||||||
onSplit={() => onSplitSeat(seat.agent_id)}
|
onSplit={() => onSplitSeat(seat.agent_id)}
|
||||||
|
onDetail={() => onSeatDetail(seat.agent_id, seat.name)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</SortableContext>
|
</SortableContext>
|
||||||
@@ -1259,6 +1311,7 @@ function SeatStrip({
|
|||||||
onMergeUp,
|
onMergeUp,
|
||||||
onMergeDown,
|
onMergeDown,
|
||||||
onSplit,
|
onSplit,
|
||||||
|
onDetail,
|
||||||
}: {
|
}: {
|
||||||
seat: ChainSeat;
|
seat: ChainSeat;
|
||||||
globalIndex: number;
|
globalIndex: number;
|
||||||
@@ -1270,6 +1323,7 @@ function SeatStrip({
|
|||||||
onMergeUp: () => void;
|
onMergeUp: () => void;
|
||||||
onMergeDown: () => void;
|
onMergeDown: () => void;
|
||||||
onSplit: () => void;
|
onSplit: () => void;
|
||||||
|
onDetail: () => void;
|
||||||
}) {
|
}) {
|
||||||
const { attributes, listeners, setNodeRef, transform, transition, isDragging } =
|
const { attributes, listeners, setNodeRef, transform, transition, isDragging } =
|
||||||
useSortable({ id: SEAT_PREFIX + seat.agent_id });
|
useSortable({ id: SEAT_PREFIX + seat.agent_id });
|
||||||
@@ -1319,8 +1373,19 @@ function SeatStrip({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 控制:并入上一阶段 / 独立成阶段 / 并入下一阶段 / 移除 */}
|
{/* 控制:查看详情 / 并入上一阶段 / 独立成阶段 / 并入下一阶段 / 移除 */}
|
||||||
<div className="flex shrink-0 items-center gap-0.5 border-l border-border px-2">
|
<div className="flex shrink-0 items-center gap-0.5 border-l border-border px-2">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={onDetail}
|
||||||
|
title="查看详情"
|
||||||
|
className={cn(
|
||||||
|
"p-1 text-muted-foreground transition hover:bg-muted hover:text-foreground",
|
||||||
|
R_CTRL,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Info className="size-3.5" />
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={onMergeUp}
|
onClick={onMergeUp}
|
||||||
|
|||||||
Reference in new issue
Block a user