import React, { useEffect, useMemo, useState } from 'react'; import { Check, Megaphone, Target, Users, X, Zap } from 'lucide-react'; import { useTheme } from '../../contexts/ThemeContext'; import type { ExpertCard, SelectableMcpKey } from './types'; interface McpOption { key: SelectableMcpKey; name: string; description: string; icon: React.ComponentType<{ className?: string }>; accent: 'purpose' | 'actor' | 'behavior' | 'narrative'; } const MCP_OPTIONS: McpOption[] = [ { key: 'purpose', name: '目的', description: '识别本次推演的核心目的,构成后续推理的锚点。', icon: Target, accent: 'purpose' }, { key: 'actor', name: '行为体', description: '推演支撑目的所需的关键行为体角色。', icon: Users, accent: 'actor' }, { key: 'behavior_factor', name: '预期行为和驱动因素', description: '为每个行为体推演预期行为及背后的驱动因素。', icon: Zap, accent: 'behavior' }, { key: 'narrative', name: '叙事', description: '基于驱动因素构建可被复用的叙事框架。', icon: Megaphone, accent: 'narrative' }, ]; const ACCENT_LIGHT: Record = { purpose: { selected: 'border-orange-300 bg-orange-50/70 ring-1 ring-orange-200', idle: 'border-gray-200 bg-white hover:border-orange-200', chip: 'bg-orange-50 text-orange-600', }, actor: { selected: 'border-red-300 bg-red-50/70 ring-1 ring-red-200', idle: 'border-gray-200 bg-white hover:border-red-200', chip: 'bg-red-50 text-red-600', }, behavior: { selected: 'border-emerald-300 bg-emerald-50/70 ring-1 ring-emerald-200', idle: 'border-gray-200 bg-white hover:border-emerald-200', chip: 'bg-emerald-50 text-emerald-600', }, narrative: { selected: 'border-amber-300 bg-amber-50/70 ring-1 ring-amber-200', idle: 'border-gray-200 bg-white hover:border-amber-200', chip: 'bg-amber-50 text-amber-600', }, }; const ACCENT_DARK: Record = { purpose: { selected: 'border-violet-300/50 bg-violet-500/15 ring-1 ring-violet-300/30', idle: 'border-slate-700 bg-slate-800/60 hover:border-violet-300/40', chip: 'bg-violet-500/20 text-violet-200', }, actor: { selected: 'border-cyan-300/50 bg-cyan-500/15 ring-1 ring-cyan-300/30', idle: 'border-slate-700 bg-slate-800/60 hover:border-cyan-300/40', chip: 'bg-cyan-500/20 text-cyan-200', }, behavior: { selected: 'border-emerald-300/50 bg-emerald-500/15 ring-1 ring-emerald-300/30', idle: 'border-slate-700 bg-slate-800/60 hover:border-emerald-300/40', chip: 'bg-emerald-500/20 text-emerald-200', }, narrative: { selected: 'border-amber-300/50 bg-amber-500/15 ring-1 ring-amber-300/30', idle: 'border-slate-700 bg-slate-800/60 hover:border-amber-300/40', chip: 'bg-amber-500/20 text-amber-200', }, }; interface Props { open: boolean; expert: ExpertCard | null; initialSelected?: SelectableMcpKey[]; onCancel: () => void; onConfirm: (selected: SelectableMcpKey[]) => void; } const McpSelectModal: React.FC = ({ open, expert, initialSelected, onCancel, onConfirm }) => { const { theme } = useTheme(); const isDark = theme === 'dark'; const defaultAll = useMemo(() => MCP_OPTIONS.map((m) => m.key), []); const [selected, setSelected] = useState( initialSelected && initialSelected.length > 0 ? initialSelected : defaultAll, ); const [error, setError] = useState(null); useEffect(() => { if (open) { setSelected(initialSelected && initialSelected.length > 0 ? initialSelected : defaultAll); setError(null); } }, [defaultAll, initialSelected, open]); if (!open) return null; const toggle = (key: SelectableMcpKey) => { setSelected((prev) => { const has = prev.includes(key); if (has) { if (prev.length <= 1) { setError('至少选择一个生成步骤'); return prev; } setError(null); return prev.filter((k) => k !== key); } setError(null); return [...prev, key]; }); }; const handleConfirm = () => { if (selected.length === 0) { setError('至少选择一个生成步骤'); return; } const ordered = MCP_OPTIONS.map((m) => m.key).filter((k) => selected.includes(k)); onConfirm(ordered); }; return (
选择本次执行的 MCP
{expert ? `专家:${expert.name}` : '默认全部勾选,至少保留 1 个步骤'}
{MCP_OPTIONS.map((opt) => { const isSelected = selected.includes(opt.key); const palette = isDark ? ACCENT_DARK[opt.accent] : ACCENT_LIGHT[opt.accent]; const Icon = opt.icon; return ( ); })} {error && (
{error}
)}
后端固定执行顺序:目的 → 行为体 → 预期行为和驱动因素 → 叙事
); }; export default McpSelectModal;