From 6521218308a3fc3933b0d8eced20b9781f362027 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=91=A3=E5=AE=8F=E5=87=AF?= Date: Wed, 3 Jun 2026 09:54:11 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E6=A0=B7=E5=BC=8F=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pages/BusinessChainEditorPage.tsx | 544 ++++++++---- yewuliantiao/.env.example | 9 + yewuliantiao/README.md | 20 + yewuliantiao/index.html | 13 + yewuliantiao/metadata.json | 6 + yewuliantiao/package.json | 35 + yewuliantiao/src/App.tsx | 833 ++++++++++++++++++ yewuliantiao/src/index.css | 1 + yewuliantiao/src/initialAgents.ts | 79 ++ yewuliantiao/src/main.tsx | 10 + yewuliantiao/tsconfig.json | 26 + yewuliantiao/vite.config.ts | 22 + 12 files changed, 1435 insertions(+), 163 deletions(-) create mode 100644 yewuliantiao/.env.example create mode 100644 yewuliantiao/README.md create mode 100644 yewuliantiao/index.html create mode 100644 yewuliantiao/metadata.json create mode 100644 yewuliantiao/package.json create mode 100644 yewuliantiao/src/App.tsx create mode 100644 yewuliantiao/src/index.css create mode 100644 yewuliantiao/src/initialAgents.ts create mode 100644 yewuliantiao/src/main.tsx create mode 100644 yewuliantiao/tsconfig.json create mode 100644 yewuliantiao/vite.config.ts diff --git a/frontend-web/src/roundtable-planning/pages/BusinessChainEditorPage.tsx b/frontend-web/src/roundtable-planning/pages/BusinessChainEditorPage.tsx index 6144b8fb..94354ac9 100644 --- a/frontend-web/src/roundtable-planning/pages/BusinessChainEditorPage.tsx +++ b/frontend-web/src/roundtable-planning/pages/BusinessChainEditorPage.tsx @@ -5,15 +5,16 @@ * - 左栏:候选智能体列表(`listAgents()` + `filterRecommendCandidates` 复用,剔除 * intent/recommender/coordinator),支持**名称检索 + 标签筛选**(`TagSearchBar`, * 服务端过滤),竖排卡片,可拖到右栏、也可点击快捷加入。 - * - 右栏:当前链条的**有序**长条卡片(序号 + 名称 + 描述),可拖拽排序、删除单项; - * 左栏拖入时按落点插入到对应位置。 + * - 右栏:当前链条的**有序**长条卡片(序号 + 名称 + 描述),可拖拽排序、上/下移、 + * 删除单项;左栏拖入时按落点插入到对应位置。 * * 拖拽用 `@dnd-kit`:右栏是 `SortableContext`(席位互相排序),左栏卡片是 * `useDraggable`(pool→右栏插入)。落点计算在 `onDragEnd`。 * - * 视觉:直接遵循《浅红色 UI 规范》/《深蓝色 UI 规范》—— 用 app 语义 token(光红暗蓝 - * 已内置)+ 规范小圆角(输入/按钮/tag 2px、卡片/分栏 4px)+ 克制阴影。**不**套圆桌页 - * 的 `roundtable-planning` scope。 + * 视觉:对齐设计稿 `yewuliantiao`(浅红 / 绛红风格)。统一走 app 语义 token + * (`primary` 在浅色主题即设计稿的绛红 #c25555,深色主题自动切蓝),配规范小圆角 + * (输入/按钮/tag 2px、卡片/分栏 4px)+ 克制阴影。**不**套圆桌页的 + * `roundtable-planning` scope。 * * 持久化走 `api/chains.ts`(后端 MySQL `/api/roundtable-chains`)。席位数约束 * 2 ≤ N ≤ 8、agent 去重,保存时校验(后端 `_validate_seats` 也兜底)。 @@ -26,14 +27,16 @@ import { toast } from "sonner"; import { nanoid } from "nanoid"; import { ArrowLeft, + Bot, + ChevronDown, + ChevronUp, GripVertical, - Layers, ListOrdered, Loader2, Plus, Save, Trash2, - Users, + UserPlus, X, } from "lucide-react"; import { @@ -57,7 +60,6 @@ import { import { CSS } from "@dnd-kit/utilities"; import { cn } from "@/lib/utils"; -import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { TagSearchBar } from "@/components/workspace/tags/tag-search-bar"; import { listAgents } from "@/core/agents"; @@ -116,7 +118,7 @@ export default function BusinessChainEditorPage() { const [saving, setSaving] = useState(false); const [activeDrag, setActiveDrag] = useState(null); - const selectValue = currentChainId ?? ""; + const [dropdownOpen, setDropdownOpen] = useState(false); const sensors = useSensors( useSensor(PointerSensor, { activationConstraint: { distance: 5 } }), @@ -214,6 +216,14 @@ export default function BusinessChainEditorPage() { setSeats((prev) => prev.filter((s) => s.agent_id !== agentId)); }, []); + const moveSeat = useCallback((index: number, dir: -1 | 1) => { + setSeats((prev) => { + const target = index + dir; + if (target < 0 || target >= prev.length) return prev; + return arrayMove(prev, index, target); + }); + }, []); + // ─── DnD ───────────────────────────────────────────────────────────── const onDragStart = useCallback( (e: DragStartEvent) => { @@ -344,108 +354,196 @@ export default function BusinessChainEditorPage() { // ─── Render ────────────────────────────────────────────────────────── const seatItemIds = seats.map((s) => SEAT_PREFIX + s.agent_id); + const triggerLabel = currentChainId ? title || "未命名链条" : "+ 新建链条…"; return (
{/* 顶栏 */} -
- -
+ + + {/* 品牌标识 */}
- - - +
+ 🪶 +
-
业务链条配置
-
编排有序的智能体研讨链路
+
业务链条配置
+
+ 编排有序的智能体研讨链路 +
- {/* 链条选择 + 新建 */} -
- - + {triggerLabel} + + + + {dropdownOpen && ( + <> +
setDropdownOpen(false)} /> +
+
+ 切换已有链条 +
+ {chains.length === 0 ? ( +
暂无已保存链条
+ ) : ( + chains.map((c) => ( + + )) + )} +
+ +
+
+ + )}
+ {/* 快捷新建 */} + + + {/* 右侧操作 */}
- - +
{/* 标题 + 描述 */} -
-
- 标题 - setTitle(e.target.value)} - placeholder="如:应急方案三段链" - className={cn("h-8 bg-background", R_CTRL)} - maxLength={120} - /> +
+
+
+ + 标题 + + setTitle(e.target.value)} + placeholder="如:应急方案三段链" + maxLength={120} + className="h-auto rounded-none border-0 bg-transparent px-4 py-2.5 text-sm font-medium shadow-none focus-visible:ring-0 dark:border-0 dark:bg-transparent" + /> +
+ +
+ + 描述 + + setDescription(e.target.value)} + placeholder="链条用途说明(可选)" + maxLength={500} + className="h-auto rounded-none border-0 bg-transparent px-4 py-2.5 text-sm font-medium shadow-none focus-visible:ring-0 dark:border-0 dark:bg-transparent" + /> +
-
- 描述 - setDescription(e.target.value)} - placeholder="链条用途说明(可选)" - className={cn("h-8 bg-background", R_CTRL)} - maxLength={500} - /> -
-
+ {/* 双栏拖拽编辑器 */} -
+
{/* 左栏:候选智能体 */} -
-
-
- - 候选智能体 - +
+
+
+ + + +

候选智能体

+ {pool.length} - - 拖到右侧 / 点击加入 -
+ 拖到右侧 / 点击加入 +
+ +
-
+ +
{poolLoading ? (
正在加载候选智能体…
) : pool.length === 0 ? ( -
+
{search || filterTagIds.length ? "没有匹配的智能体,换个关键词或标签试试。" : `候选池为空,请先在「智能体」里创建至少 ${CHAIN_MIN_SEATS} 个智能体。`} @@ -506,18 +619,29 @@ export default function BusinessChainEditorPage() { seatItemIds={seatItemIds} seatCountInvalid={seatCountInvalid} onRemove={removeSeat} + onMove={moveSeat} /> -
+
{activeDrag ? ( -
+
{activeDrag.name}
) : null} + + {/* 页脚 */} +
+ 编排有序的业务链条,让智能体决策更有深度。保存后,配置将自动同步至圆桌。 +
); } @@ -540,43 +664,57 @@ function PoolAgentCard({ return (
-
-
{agent.name || agent.agent_id}
+
+ +
+ +
+
+ {agent.name || agent.agent_id} +
{agent.description && ( -
+
{agent.description}
)}
- {inChain ? ( - - 已加入 - - ) : ( - - )} + +
+ {inChain ? ( + + 已加入 + + ) : ( + + 点击加入 + + )} +
); } @@ -588,45 +726,72 @@ function SeatsColumn({ seatItemIds, seatCountInvalid, onRemove, + onMove, }: { seats: ChainSeat[]; seatItemIds: string[]; seatCountInvalid: boolean; onRemove: (agentId: string) => void; + onMove: (index: number, dir: -1 | 1) => void; }) { const { setNodeRef, isOver } = useDroppable({ id: SEATS_DROPPABLE_ID }); return ( -
-
- - 链条席位 - - 按顺序发言 - - - {seats.length} / {CHAIN_MAX_SEATS} - {seats.length < CHAIN_MIN_SEATS && ` · 至少 ${CHAIN_MIN_SEATS}`} - +
+
+
+ + + +

链条席位

+ + 按顺序发言 + +
+ +
+ 配置席位 + + {seats.length} / {CHAIN_MAX_SEATS} + {seats.length < CHAIN_MIN_SEATS && ` · 至少 ${CHAIN_MIN_SEATS}`} + +
+
{seats.length === 0 ? ( -
- - 从左侧把智能体拖到这里,组成有序业务链条 +
+
+ +
+

暂无席位

+

+ 该业务链条当前为空。请从左侧「候选智能体」点击或拖拽,将其按顺序编排加入此处。 +

) : ( @@ -635,7 +800,9 @@ function SeatsColumn({ key={seat.agent_id} seat={seat} index={index} + total={seats.length} onRemove={() => onRemove(seat.agent_id)} + onMove={(dir) => onMove(index, dir)} /> ))} @@ -650,59 +817,110 @@ function SeatsColumn({ function SeatStrip({ seat, index, + total, onRemove, + onMove, }: { seat: ChainSeat; index: number; + total: number; onRemove: () => void; + onMove: (dir: -1 | 1) => void; }) { const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id: SEAT_PREFIX + seat.agent_id }); const avatarType = getAvatarTypeFor(seat.agent_id, index); + const isFirst = index === 0; + const isLast = index === total - 1; return (
+ {/* 拖拽手柄 */} - - {index + 1} - -
-
{seat.name || seat.agent_id}
+ + {/* 序号 */} +
+ + {index + 1} + +
+ + {/* 详情 */} +
+
{seat.name || seat.agent_id}
{seat.description && ( -
+
{seat.description}
)}
- + + {/* 控制:上移 / 下移 / 移除 */} +
+ + + +
); } diff --git a/yewuliantiao/.env.example b/yewuliantiao/.env.example new file mode 100644 index 00000000..7a550fe7 --- /dev/null +++ b/yewuliantiao/.env.example @@ -0,0 +1,9 @@ +# GEMINI_API_KEY: Required for Gemini AI API calls. +# AI Studio automatically injects this at runtime from user secrets. +# Users configure this via the Secrets panel in the AI Studio UI. +GEMINI_API_KEY="MY_GEMINI_API_KEY" + +# APP_URL: The URL where this applet is hosted. +# AI Studio automatically injects this at runtime with the Cloud Run service URL. +# Used for self-referential links, OAuth callbacks, and API endpoints. +APP_URL="MY_APP_URL" diff --git a/yewuliantiao/README.md b/yewuliantiao/README.md new file mode 100644 index 00000000..283c70e2 --- /dev/null +++ b/yewuliantiao/README.md @@ -0,0 +1,20 @@ +
+GHBanner +
+ +# Run and deploy your AI Studio app + +This contains everything you need to run your app locally. + +View your app in AI Studio: https://ai.studio/apps/afcf08ae-640d-4fb2-9eab-4d3094ffaa28 + +## Run Locally + +**Prerequisites:** Node.js + + +1. Install dependencies: + `npm install` +2. Set the `GEMINI_API_KEY` in [.env.local](.env.local) to your Gemini API key +3. Run the app: + `npm run dev` diff --git a/yewuliantiao/index.html b/yewuliantiao/index.html new file mode 100644 index 00000000..254a3742 --- /dev/null +++ b/yewuliantiao/index.html @@ -0,0 +1,13 @@ + + + + + + 业务链条配置 + + +
+ + + + diff --git a/yewuliantiao/metadata.json b/yewuliantiao/metadata.json new file mode 100644 index 00000000..8f987a4a --- /dev/null +++ b/yewuliantiao/metadata.json @@ -0,0 +1,6 @@ +{ + "name": "业务链条配置", + "description": "编排有序的智能体研讨链路配置系统。", + "requestFramePermissions": [], + "majorCapabilities": ["MAJOR_CAPABILITY_SERVER_SIDE_GEMINI_API"] +} diff --git a/yewuliantiao/package.json b/yewuliantiao/package.json new file mode 100644 index 00000000..44745ed3 --- /dev/null +++ b/yewuliantiao/package.json @@ -0,0 +1,35 @@ +{ + "name": "react-example", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite --port=3000 --host=0.0.0.0", + "build": "vite build", + "preview": "vite preview", + "clean": "rm -rf dist server.js", + "lint": "tsc --noEmit" + }, + "dependencies": { + "@google/genai": "^2.4.0", + "@tailwindcss/vite": "^4.1.14", + "@vitejs/plugin-react": "^5.0.4", + "lucide-react": "^0.546.0", + "react": "^19.0.1", + "react-dom": "^19.0.1", + "vite": "^6.2.3", + "express": "^4.21.2", + "dotenv": "^17.2.3", + "motion": "^12.23.24" + }, + "devDependencies": { + "@types/node": "^22.14.0", + "autoprefixer": "^10.4.21", + "esbuild": "^0.25.0", + "tailwindcss": "^4.1.14", + "tsx": "^4.21.0", + "typescript": "~5.8.2", + "vite": "^6.2.3", + "@types/express": "^4.17.21" + } +} diff --git a/yewuliantiao/src/App.tsx b/yewuliantiao/src/App.tsx new file mode 100644 index 00000000..040d17a7 --- /dev/null +++ b/yewuliantiao/src/App.tsx @@ -0,0 +1,833 @@ +import React, { useState, useEffect, useMemo } from "react"; +import { + ArrowLeft, + Trash2, + Save, + Plus, + ChevronDown, + Search, + GripVertical, + X, + ListOrdered, + Languages, + Sparkles, + ChevronUp, + UserPlus, + Bot +} from "lucide-react"; +import { INITIAL_AGENTS, Agent } from "./initialAgents"; + +interface ChainConfig { + id: string; + title: string; + description: string; + seats: string[]; // Holds reference to Agent IDs in order +} + +const STORAGE_CHANNELS_KEY = "business_decision_chains"; +const STORAGE_CURRENT_KEY = "business_decision_current_id"; +const STORAGE_CUSTOM_AGENTS_KEY = "business_decision_custom_agents"; + +// Preset colors for the seat index badges +const SEAT_COLORS = [ + { bg: "bg-blue-50 border border-blue-200 text-blue-600", label: "蓝色" }, + { bg: "bg-purple-50 border border-purple-200 text-purple-600", label: "紫色" }, + { bg: "bg-emerald-50 border border-emerald-200 text-emerald-600", label: "绿色" }, + { bg: "bg-orange-50 border border-orange-200 text-orange-600", label: "橙色" }, + { bg: "bg-indigo-50 border border-indigo-200 text-indigo-600", label: "靛蓝" }, + { bg: "bg-pink-50 border border-pink-200 text-pink-600", label: "粉色" }, + { bg: "bg-amber-50 border border-amber-200 text-amber-600", label: "琥珀" }, + { bg: "bg-sky-50 border border-sky-200 text-sky-600", label: "天空蓝" }, +]; + +export default function App() { + // 1. Storage & Config states + const [chains, setChains] = useState([]); + const [currentChainId, setCurrentChainId] = useState(""); + const [customAgents, setCustomAgents] = useState([]); + + // 2. Search & UI states + const [searchTerm, setSearchTerm] = useState(""); + const [showDropdown, setShowDropdown] = useState(false); + const [draggedIndex, setDraggedIndex] = useState(null); + const [dragOverIndex, setDragOverIndex] = useState(null); + + // Custom Agent Form Modal + const [showAgentModal, setShowAgentModal] = useState(false); + const [newAgentName, setNewAgentName] = useState(""); + const [newAgentDesc, setNewAgentDesc] = useState(""); + + // Toast feedback + const [toast, setToast] = useState<{ message: string; type: "success" | "error" | "info" } | null>(null); + + // Initialize data on load + useEffect(() => { + // Load custom agents + const savedCustom = localStorage.getItem(STORAGE_CUSTOM_AGENTS_KEY); + let loadedCustom: Agent[] = []; + if (savedCustom) { + try { + loadedCustom = JSON.parse(savedCustom); + setCustomAgents(loadedCustom); + } catch (e) { + console.error("Failed to parse custom agents", e); + } + } + + // Load chains + const savedChains = localStorage.getItem(STORAGE_CHANNELS_KEY); + const savedCurrentId = localStorage.getItem(STORAGE_CURRENT_KEY); + + let loadedChains: ChainConfig[] = []; + if (savedChains) { + try { + loadedChains = JSON.parse(savedChains); + } catch (e) { + console.error("Failed to parse chains from localStorage", e); + } + } + + // Default configuration if empty + if (loadedChains.length === 0) { + loadedChains = [ + { + id: "chain-1", + title: "应急方案三段链", + description: "链条用途说明(可选)", + seats: ["agent-1", "agent-2", "agent-3"], + } + ]; + } + + setChains(loadedChains); + + // Set current active id + if (savedCurrentId && loadedChains.some(c => c.id === savedCurrentId)) { + setCurrentChainId(savedCurrentId); + } else { + setCurrentChainId(loadedChains[0].id); + } + }, []); + + // Save chains list to storage when it updates + const saveAllToLocalStorage = (newChainsList: ChainConfig[]) => { + localStorage.setItem(STORAGE_CHANNELS_KEY, JSON.stringify(newChainsList)); + }; + + // Toast helper + const triggerToast = (message: string, type: "success" | "error" | "info" = "success") => { + setToast({ message, type }); + setTimeout(() => { + setToast(null); + }, 2500); + }; + + // Complete list of agents (built-in + custom) + const allAgents = useMemo(() => { + return [...INITIAL_AGENTS, ...customAgents]; + }, [customAgents]); + + const activeChain = useMemo(() => { + return chains.find(c => c.id === currentChainId) || { + id: "fallback", + title: "", + description: "", + seats: [] + } as ChainConfig; + }, [chains, currentChainId]); + + // Handle text field updates + const handleTitleChange = (val: string) => { + setChains(prev => { + const updated = prev.map(c => c.id === currentChainId ? { ...c, title: val } : c); + saveAllToLocalStorage(updated); + return updated; + }); + }; + + const handleDescChange = (val: string) => { + setChains(prev => { + const updated = prev.map(c => c.id === currentChainId ? { ...c, description: val } : c); + saveAllToLocalStorage(updated); + return updated; + }); + }; + + // Add / Toggle seat inclusion + const handleToggleAgent = (agentId: string) => { + const isAlreadyIn = activeChain.seats.includes(agentId); + + if (isAlreadyIn) { + // Remove it + const newSeats = activeChain.seats.filter(id => id !== agentId); + updateActiveSeats(newSeats); + triggerToast("已将智能体移出席位"); + } else { + // Add it + if (activeChain.seats.length >= 8) { + triggerToast("席位已满,每个链条最多配置 8 个席位!", "error"); + return; + } + const newSeats = [...activeChain.seats, agentId]; + updateActiveSeats(newSeats); + triggerToast("成功添加到链条席位"); + } + }; + + const updateActiveSeats = (newSeats: string[]) => { + setChains(prev => { + const updated = prev.map(c => c.id === currentChainId ? { ...c, seats: newSeats } : c); + saveAllToLocalStorage(updated); + return updated; + }); + }; + + // Multi-chain operations + const createNewChain = () => { + const nextId = `chain-${Date.now()}`; + const newChainItem: ChainConfig = { + id: nextId, + title: `空白新链条 ${chains.length + 1}`, + description: "", + seats: [] + }; + const updated = [...chains, newChainItem]; + setChains(updated); + setCurrentChainId(nextId); + localStorage.setItem(STORAGE_CURRENT_KEY, nextId); + saveAllToLocalStorage(updated); + triggerToast("已创建新的空白链条", "success"); + setShowDropdown(false); + }; + + const handleSelectChain = (id: string) => { + setCurrentChainId(id); + localStorage.setItem(STORAGE_CURRENT_KEY, id); + setShowDropdown(false); + triggerToast(`切换到: ${chains.find(c => c.id === id)?.title || "新链条"}`); + }; + + const handleSaveChain = () => { + saveAllToLocalStorage(chains); + triggerToast("保存配置成功", "success"); + }; + + const handleDeleteChain = () => { + if (chains.length <= 1) { + // Create a blank placeholder instead of locking + const nextId = `chain-${Date.now()}`; + const placeholder: ChainConfig = { + id: nextId, + title: "新配置链条", + description: "", + seats: [] + }; + setChains([placeholder]); + setCurrentChainId(nextId); + localStorage.setItem(STORAGE_CURRENT_KEY, nextId); + saveAllToLocalStorage([placeholder]); + triggerToast("重置当前链条"); + return; + } + const filtered = chains.filter(c => c.id !== currentChainId); + setChains(filtered); + const nextId = filtered[0].id; + setCurrentChainId(nextId); + localStorage.setItem(STORAGE_CURRENT_KEY, nextId); + saveAllToLocalStorage(filtered); + triggerToast("删除成功", "success"); + }; + + // Reorder commands: Drag & Drop + const handleDragStart = (index: number) => { + setDraggedIndex(index); + }; + + const handleDragOver = (e: React.DragEvent, index: number) => { + e.preventDefault(); + setDragOverIndex(index); + }; + + const handleDrop = (targetIndex: number) => { + if (draggedIndex === null) return; + const reordered = [...activeChain.seats]; + const [movedItem] = reordered.splice(draggedIndex, 1); + reordered.splice(targetIndex, 0, movedItem); + updateActiveSeats(reordered); + setDraggedIndex(null); + setDragOverIndex(null); + }; + + // Fallback reordering commands: Move Up/Down button click + const moveDirection = (index: number, direction: "up" | "down") => { + const targetIdx = direction === "up" ? index - 1 : index + 1; + if (targetIdx < 0 || targetIdx >= activeChain.seats.length) return; + const items = [...activeChain.seats]; + const temp = items[index]; + items[index] = items[targetIdx]; + items[targetIdx] = temp; + updateActiveSeats(items); + }; + + // Create customized user agents + const handleAddCustomAgent = (e: React.FormEvent) => { + e.preventDefault(); + if (!newAgentName.trim()) { + triggerToast("请输入智能体名称", "error"); + return; + } + + const newAgent: Agent = { + id: `custom-agent-${Date.now()}`, + name: newAgentName, + description: newAgentDesc || "自定义创建的专属智能体角色。", + isCustom: true + }; + + const updatedCustom = [...customAgents, newAgent]; + setCustomAgents(updatedCustom); + localStorage.setItem(STORAGE_CUSTOM_AGENTS_KEY, JSON.stringify(updatedCustom)); + setNewAgentName(""); + setNewAgentDesc(""); + setShowAgentModal(false); + triggerToast(`智能体 '${newAgent.name}' 创建成功!`); + + // Optionally auto-add to the chain seats if space available + if (activeChain.seats.length < 8) { + updateActiveSeats([...activeChain.seats, newAgent.id]); + } + }; + + // Clear a custom agent + const handleDeleteCustomAgent = (e: React.MouseEvent, agentId: string) => { + e.stopPropagation(); // Avoid triggering seat addition + const updatedCustom = customAgents.filter(a => a.id !== agentId); + setCustomAgents(updatedCustom); + localStorage.setItem(STORAGE_CUSTOM_AGENTS_KEY, JSON.stringify(updatedCustom)); + // Also remove from any seats as well + if (activeChain.seats.includes(agentId)) { + updateActiveSeats(activeChain.seats.filter(id => id !== agentId)); + } + triggerToast("已彻底删除该自定义智能体"); + }; + + // Filter conditions + const filteredCandidates = allAgents.filter(agent => { + const text = (agent.name + " " + agent.description).toLowerCase(); + return text.includes(searchTerm.toLowerCase()); + }); + + return ( +
+ {/* 1. Header Toolbar */} + + + {/* 2. Top Config Form Cards (Title, Description) */} +
+
+ + {/* Title input field */} +
+ + 标题 + + handleTitleChange(e.target.value)} + placeholder="如:应急方案三段链" + className="w-full px-4 py-2.5 text-sm text-[#1f2937] focus:outline-none placeholder-[#9ca3af] font-medium" + /> +
+ + {/* Description input field */} +
+ + 描述 + + handleDescChange(e.target.value)} + placeholder="链条用途说明(可选)" + className="w-full px-4 py-2.5 text-sm text-[#1f2937] focus:outline-none placeholder-[#9ca3af] font-medium" + /> +
+ +
+
+ + {/* 3. Main Split Workspace Grid */} +
+ + {/* Column Left: 候选智能体 (Grid weight 4 of 12) */} +
+ {/* Candidate Header */} +
+
+ + + +

候选智能体

+ + {filteredCandidates.length} + +
+ 拖到右侧 / 点击加入 +
+ + {/* Search Bar with Creation Utility */} +
+
+ + + + setSearchTerm(e.target.value)} + placeholder="按名称搜索智能体..." + className="w-full pl-9 pr-3 py-2 text-xs border border-[#ede9e9] rounded-md bg-white focus:outline-none focus:ring-1 focus:ring-[#c25555] focus:border-[#c25555] placeholder-slate-400 font-medium" + /> + {searchTerm && ( + + )} +
+ + {/* Inline Custom Agent Launcher */} + +
+ + {/* Candidates Scroll List */} +
+ {filteredCandidates.length === 0 ? ( +
+

未找到符合搜索条件的智能体

+ {searchTerm && ( + + )} +
+ ) : ( + filteredCandidates.map((agent) => { + const isAdded = activeChain.seats.includes(agent.id); + return ( +
handleToggleAgent(agent.id)} + className={`group relative flex items-start gap-3 p-3.5 bg-white border rounded-lg cursor-pointer transition-all duration-150 ${ + isAdded + ? "border-[#fcdbda] bg-[#fdfdfd] shadow-[0_1px_3px_rgba(194,85,85,0.02)]" + : "border-[#ede9e9] hover:border-[#c25555] hover:shadow-[0_2px_8px_rgba(0,0,0,0.03)]" + }`} + > + {/* Left Icon status badge */} +
+
+ +
+
+ + {/* Agent Details */} +
+
+

{agent.name}

+ {agent.isCustom && ( + 自定义 + )} +
+

+ {agent.description} +

+
+ + {/* Status Badge Action on Far Right */} +
+ {isAdded ? ( + + 已加入 + + ) : ( + + 点击加入 + + )} + + {/* Custom agents get an extra trash deletion trigger */} + {agent.isCustom && ( + + )} +
+
+ ); + }) + )} +
+
+ + {/* Column Right: 链条席位 (Grid weight 8 of 12) */} +
+ {/* Seats Header */} +
+
+ + + +

链条席位

+ 按顺序发言 +
+ + {/* Limit counter badge */} +
+ 配置席位 + 0 + ? "bg-[#fef4f3] border-[#fcdbda] text-[#c25555]" + : "bg-slate-50 border-slate-200 text-slate-400" + }`}> + {activeChain.seats.length} / 8 + +
+
+ + {/* Seats Content Canvas */} +
+ {activeChain.seats.length === 0 ? ( +
+
+ +
+

暂无席位

+

+ 该业务链条当前为空。请从左侧“候选智能体”列表中点击或者选择,将其按顺序编排加入此处。 +

+
+ {allAgents.slice(0, 3).map(agent => ( + + ))} +
+
+ ) : ( +
+ {activeChain.seats.map((agentId, index) => { + const agent = allAgents.find(a => a.id === agentId); + if (!agent) return null; + + const theme = SEAT_COLORS[index] || { bg: "bg-slate-50 border border-slate-200 text-slate-600" }; + const isDragged = index === draggedIndex; + const isHoveredDrop = index === dragOverIndex; + + return ( +
handleDragStart(index)} + onDragOver={(e) => handleDragOver(e, index)} + onDragEnd={() => { setDraggedIndex(null); setDragOverIndex(null); }} + onDrop={() => handleDrop(index)} + className={`flex items-stretch bg-white border rounded-lg shadow-sm transition-all duration-200 select-none ${ + isDragged ? "opacity-35 scale-[0.98] border-dashed border-[#c25555]" : "" + } ${ + isHoveredDrop ? "border-2 border-[#c25555] bg-[#fef4f3] scale-[1.01]" : "border-[#ede9e9]" + }`} + > + {/* Left Grip Handle */} +
+
+ +
+
+ + {/* Number Indicator Badge - Placed to the LEFT of details */} +
+
+ {index + 1} +
+
+ + {/* Seat info details */} +
+

+ {agent.name} + {agent.isCustom && ( + Custom + )} +

+

+ {agent.description} +

+
+ + {/* Seat controller Actions (Move up/down & close delete) */} +
+ {/* Up button */} + + + {/* Down button */} + + + {/* Cross Delete button */} + +
+ +
+ ); + })} +
+ )} +
+
+ +
+ + {/* 4. Float Helper Footer decoration */} +
+ 编排有序的业务链条,让智能体决策更有深度。保存后,配置将自动同步至圆桌。 +
+ + {/* 5. Custom Agent Modal (Popup Card) */} + {showAgentModal && ( +
+
+ {/* Modal Header */} +
+
+ 🧠 +

添加自定义候选智能体

+
+ +
+ + {/* Modal Form */} +
+
+ + setNewAgentName(e.target.value)} + className="w-full px-3 py-2 text-xs border border-[#ede9e9] rounded-md focus:outline-none focus:ring-1 focus:ring-[#c25555] focus:border-[#c25555] placeholder-slate-400 font-medium" + /> +
+ +
+ +