diff --git a/frontend-web/src/roundtable-planning/components/BusinessChainListDialog.tsx b/frontend-web/src/roundtable-planning/components/BusinessChainListDialog.tsx new file mode 100644 index 00000000..d17e6e09 --- /dev/null +++ b/frontend-web/src/roundtable-planning/components/BusinessChainListDialog.tsx @@ -0,0 +1,177 @@ +/** + * 业务链条列表弹窗(圆桌页顶栏「业务链条」按钮入口)。 + * + * 点击「业务链条」按钮后打开本弹窗:展示当前用户已配置的业务链条卡片列表。 + * - 每张卡片右侧「设置」按钮 → 进入配置页并**切换到该链条**(带 `chainId`)。 + * - footer「配置」按钮 → 进入配置页(空白新建,等价于旧按钮的直接跳转)。 + * + * 纯导航 / 浏览用途,不参与研讨席位选择(席位选择走 `BusinessChainPicker`)。 + * 卡片席位名直接用落库快照(`seat.name`),无需实时校验,点「设置」进编辑页后 + * 会看到实时数据。 + */ +import { Fragment, useEffect, useState } from "react"; +import { + ArrowRightIcon, + LayersIcon, + Loader2Icon, + SettingsIcon, +} from "lucide-react"; + +import { Button } from "@/components/ui/button"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { cn } from "@/lib/utils"; + +import { listChains } from "../api/chains"; +import { chainTagClasses, type BusinessChain } from "../lib/business-chain"; +import { getAvatarTypeFor } from "../lib/roundtable-constants"; + +export interface BusinessChainListDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; + /** 进入配置页。带 chainId = 打开并切到该链条;不带 = 空白新建。 */ + onConfigure: (chainId?: string) => void; +} + +export function BusinessChainListDialog({ + open, + onOpenChange, + onConfigure, +}: BusinessChainListDialogProps) { + const [chains, setChains] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + // 每次打开都重新拉一次,保证配置页改动后回来看到的是最新列表。 + useEffect(() => { + if (!open) return; + let cancelled = false; + setLoading(true); + setError(null); + void (async () => { + try { + const list = await listChains(); + if (!cancelled) setChains(list); + } catch (err) { + if (!cancelled) setError(err instanceof Error ? err.message : String(err)); + } finally { + if (!cancelled) setLoading(false); + } + })(); + return () => { + cancelled = true; + }; + }, [open]); + + return ( + + + + + + 业务链条 + + + 可复用的有序智能体编排。点击卡片「设置」可进入配置页编辑该链条。 + + + +
+ {loading ? ( +
+ + 正在加载业务链条… +
+ ) : error ? ( +
+ 加载业务链条失败:{error} +
+ ) : chains.length === 0 ? ( +
+ +
还没有业务链条
+

+ 业务链条是可复用的有序智能体编排。点击下方「配置」去创建第一条链条。 +

+
+ ) : ( +
+ {chains.map((chain) => ( + onConfigure(chain.id)} + /> + ))} +
+ )} +
+ + + + +
+
+ ); +} + +// ── 单张链条行卡片 ───────────────────────────────────────────────────────── + +function ChainRow({ + chain, + onSettings, +}: { + chain: BusinessChain; + onSettings: () => void; +}) { + return ( +
+ {/* 标题行 + 设置按钮 */} +
+
{chain.title || "未命名链条"}
+ + {chain.seats.length} 席 + + +
+ + {chain.description && ( +
{chain.description}
+ )} + + {/* 席位名 tag(按顺序,→ 箭头连接发言次序) */} +
+ {chain.seats.map((seat, i) => ( + + {i > 0 && } + + {seat.name || seat.agent_id} + + + ))} +
+
+ ); +} diff --git a/frontend-web/src/roundtable-planning/pages/BusinessChainEditorPage.tsx b/frontend-web/src/roundtable-planning/pages/BusinessChainEditorPage.tsx index 94354ac9..2ed3f44f 100644 --- a/frontend-web/src/roundtable-planning/pages/BusinessChainEditorPage.tsx +++ b/frontend-web/src/roundtable-planning/pages/BusinessChainEditorPage.tsx @@ -21,8 +21,8 @@ * * 详见 `docs/multi-agent-business-chain-dev.md` §7 阶段 1。 */ -import { useCallback, useEffect, useMemo, useState } from "react"; -import { useNavigate } from "react-router-dom"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { useNavigate, useSearchParams } from "react-router-dom"; import { toast } from "sonner"; import { nanoid } from "nanoid"; import { @@ -101,6 +101,7 @@ type ActiveDrag = export default function BusinessChainEditorPage() { const navigate = useNavigate(); + const [searchParams] = useSearchParams(); // ─── 数据 ──────────────────────────────────────────────────────────── const [pool, setPool] = useState([]); @@ -184,6 +185,19 @@ export default function BusinessChainEditorPage() { setSeats([]); }, []); + // ─── URL 参数 chainId:从「业务链条」弹窗点「设置」进来时自动切到该链条 ────── + // 只在链条列表加载完后应用一次(用 ref 闸门避免覆盖用户后续的手动切换)。 + const appliedChainParamRef = useRef(false); + useEffect(() => { + if (appliedChainParamRef.current) return; + const chainId = searchParams.get("chainId"); + if (!chainId) return; + if (chains.length === 0) return; // 等 refreshChains 拉回列表 + const chain = chains.find((c) => c.id === chainId); + if (chain) loadChain(chain); + appliedChainParamRef.current = true; // 命中或链条已不存在,都不再重试 + }, [chains, searchParams, loadChain]); + // ─── 席位增删改 ─────────────────────────────────────────────────────── const addAgentToSeats = useCallback( (agentId: string, atIndex?: number) => { diff --git a/frontend-web/src/roundtable-planning/pages/RoundtablePlanningPage.tsx b/frontend-web/src/roundtable-planning/pages/RoundtablePlanningPage.tsx index ac457087..2910c69e 100644 --- a/frontend-web/src/roundtable-planning/pages/RoundtablePlanningPage.tsx +++ b/frontend-web/src/roundtable-planning/pages/RoundtablePlanningPage.tsx @@ -55,6 +55,7 @@ import { useModels } from "@/core/models/hooks"; import { getAvatar, Man1Avatar, RobotAvatar } from "@/roundtable-planning/components/Avatars"; import { RecommendAgentsDialog } from "@/roundtable-planning/components/RecommendAgentsDialog"; +import { BusinessChainListDialog } from "@/roundtable-planning/components/BusinessChainListDialog"; import { Step1Panel } from "@/roundtable-planning/components/Step1Panel"; import { Step2Panel } from "@/roundtable-planning/components/Step2Panel"; import { Step3Panel } from "@/roundtable-planning/components/Step3Panel"; @@ -85,6 +86,8 @@ export default function RoundtablePlanningPage() { const [currentStep, setCurrentStep] = useState(1); const [showToast, setShowToast] = useState(null); const [isRecommendDialogOpen, setIsRecommendDialogOpen] = useState(false); + // 「业务链条」按钮 → 打开链条列表弹窗(弹窗里再决定进配置页 / 切到某条链条)。 + const [isChainListOpen, setIsChainListOpen] = useState(false); const [selectedModel, setSelectedModel] = useState(""); // Step 2 编排模式:recommend = 总控自由派活;chain = 总控按业务链条顺序派活。 // 由推荐弹窗确认时的 SeatSelectionResult.mode 决定(见 handleConfirmAgents)。 @@ -374,6 +377,19 @@ export default function RoundtablePlanningPage() { onConfirm={handleConfirmAgents} /> + { + setIsChainListOpen(false); + navigate( + chainId + ? `/page/workspace/roundtable/chains?chainId=${encodeURIComponent(chainId)}` + : "/page/workspace/roundtable/chains", + ); + }} + /> + {showToast && (
@@ -394,8 +410,8 @@ export default function RoundtablePlanningPage() {