import { useState, useRef, useEffect } from 'react'; import { Send, Sparkles, Wrench, ShieldCheck, Route, FileText, Landmark, AlertTriangle, BookOpen, ChevronDown, Plus, Zap, RefreshCw, FileOutput, MessageSquare, History, Target } from 'lucide-react'; import type { Agent, ChatMessage, ConversationScope, ComponentItem } from '../types'; import { sampleMessages, sampleThreads } from '../data/sampleData'; import { componentItems } from '../data/components'; import type { Node, Edge } from '@xyflow/react'; const agents: { id: Agent; icon: typeof Sparkles; color: string }[] = [ { id: 'Builder', icon: Sparkles, color: '#3b82f6' }, { id: 'Compliance', icon: ShieldCheck, color: '#22c55e' }, { id: 'Routing', icon: Route, color: '#f97316' }, { id: 'ISO-20022', icon: FileText, color: '#a855f7' }, { id: 'Settlement', icon: Landmark, color: '#eab308' }, { id: 'Risk', icon: AlertTriangle, color: '#ef4444' }, { id: 'Documentation', icon: BookOpen, color: '#6b7280' }, ]; interface RightPanelProps { width: number; nodes: Node[]; edges: Edge[]; selectedNodes: Node[]; chatInputRef: React.RefObject; onInsertBlock: (item: ComponentItem, position: { x: number; y: number }) => void; onRunValidation: () => void; onOptimizeRoute: () => void; onRunCompliance: () => void; onGenerateSettlement: () => void; } export default function RightPanel({ width, nodes, edges, selectedNodes, chatInputRef, onInsertBlock, onRunValidation, onOptimizeRoute, onRunCompliance, onGenerateSettlement, }: RightPanelProps) { const [activeAgent, setActiveAgent] = useState('Builder'); const [messages, setMessages] = useState(sampleMessages); const [input, setInput] = useState(''); const [showAgentMenu, setShowAgentMenu] = useState(false); const [showContext, setShowContext] = useState(false); const [showThreads, setShowThreads] = useState(false); const [scope, setScope] = useState('full-transaction'); const [showScopeMenu, setShowScopeMenu] = useState(false); const messagesEnd = useRef(null); useEffect(() => { messagesEnd.current?.scrollIntoView({ behavior: 'smooth' }); }, [messages]); const getCanvasContext = () => { const nodeLabels = nodes.map(n => (n.data as Record).label as string); const categories = [...new Set(nodes.map(n => (n.data as Record).category as string))]; const hasCompliance = categories.includes('compliance'); const hasRouting = categories.includes('routing'); const disconnected = nodes.filter(n => !edges.some(e => e.source === n.id || e.target === n.id)); const selectedLabels = selectedNodes.map(n => (n.data as Record).label as string); return { nodeLabels, categories, hasCompliance, hasRouting, disconnected, selectedLabels, nodeCount: nodes.length, edgeCount: edges.length }; }; const sendMessage = () => { if (!input.trim()) return; const userMsg: ChatMessage = { id: Date.now().toString(), agent: 'User', content: input, timestamp: new Date(), type: 'user', }; setMessages(prev => [...prev, userMsg]); const capturedInput = input; setInput(''); setTimeout(() => { const ctx = getCanvasContext(); const buildResponse = (): string => { const lowerInput = capturedInput.toLowerCase(); switch (activeAgent) { case 'Builder': { if (selectedNodes.length > 0) { const sel = (selectedNodes[0].data as Record).label as string; if (lowerInput.includes('explain')) return `The "${sel}" block ${getBlockExplanation(sel)}. It currently has ${edges.filter(e => e.source === selectedNodes[0].id || e.target === selectedNodes[0].id).length} connection(s).`; if (lowerInput.includes('next') || lowerInput.includes('suggest')) return `After "${sel}", I recommend adding a ${suggestNextBlock(sel)}. This would complete the ${(selectedNodes[0].data as Record).category} flow.`; } if (lowerInput.includes('build') || lowerInput.includes('create') || lowerInput.includes('set up') || lowerInput.includes('payment')) { if (ctx.nodeCount === 0) return `To build a transaction flow, start by dragging a "Fiat Account" or "Stablecoin Wallet" from the left panel as your source. Then add a "${capturedInput.includes('swap') ? 'Swap' : 'Transfer'}" action and connect them.`; return `Your graph has ${ctx.nodeCount} nodes. Try dragging a "${capturedInput.includes('swap') ? 'Swap' : 'Transfer'}" block onto the canvas and connecting it to your source.`; } if (ctx.disconnected.length > 0) return `I notice ${ctx.disconnected.length} disconnected node(s) in your graph: ${ctx.disconnected.map(n => (n.data as Record).label).join(', ')}. Connect them to complete the flow.`; return `I can help you build that flow. Try dragging a "${capturedInput.includes('swap') ? 'Swap' : 'Transfer'}" block onto the canvas and connecting it to your source. Your graph currently has ${ctx.nodeCount} nodes and ${ctx.edgeCount} connections.`; } case 'Compliance': { if (lowerInput.includes('check') || lowerInput.includes('compliance') || lowerInput.includes('review')) { if (!ctx.hasCompliance && ctx.nodeCount > 0) return `WARNING: Your transaction graph has ${ctx.nodeCount} nodes but no compliance checks. I recommend adding KYC and AML nodes before the settlement step. This is required for cross-border transactions.`; if (ctx.hasCompliance) return `Running compliance check on the current graph. No policy violations detected for the selected jurisdiction. ${ctx.nodeCount} nodes verified against 47 compliance rules.`; return `Running compliance check on the current graph. No policy violations detected for the selected jurisdiction.`; } if (lowerInput.includes('violation') || lowerInput.includes('failure')) return `Scanning graph for policy violations... ${ctx.hasCompliance ? 'All compliance nodes are properly configured. No violations found.' : 'No compliance nodes found in graph. Consider adding KYC/AML checks.'}`; return `Running compliance check on the current graph. No policy violations detected for the selected jurisdiction.`; } case 'Routing': { if (ctx.hasRouting) return `Analyzing ${ctx.nodeCount} nodes with routing configuration. Found optimal path via ${ctx.nodeLabels.find(l => l.includes('Route') || l.includes('Router')) || 'Banking Rail'}. Estimated fee: 0.02%, latency: 230ms.`; return `Analyzing optimal routes... Found 3 execution paths. The best route via Banking Rail offers lowest fees at 0.02%. Your graph has ${ctx.nodeCount} nodes across ${ctx.edgeCount} connections.`; } case 'ISO-20022': { if (ctx.nodeCount > 0) return `Based on your graph with ${ctx.nodeCount} nodes, I can generate a pain.001 message. The required fields from your current configuration: debtor (${ctx.nodeLabels[0] || 'source'}), creditor (${ctx.nodeLabels[ctx.nodeLabels.length - 1] || 'destination'}), amount, and currency.`; return `I can generate a pain.001 message for this transfer. The required fields based on your current graph are: debtor, creditor, amount, and currency.`; } case 'Settlement': { return `Current settlement window for this transaction type is T+1. ${ctx.nodeCount > 0 ? `Your graph has ${ctx.nodeCount} nodes ready for settlement processing.` : 'I recommend adding a settlement instruction block to specify your preferred CSD.'}`; } case 'Risk': { if (ctx.nodeCount > 0) { const riskLevel = ctx.hasCompliance ? 'LOW' : 'MEDIUM'; return `Risk assessment: ${riskLevel}. ${ctx.nodeCount} nodes evaluated. ${ctx.hasCompliance ? 'Compliance checks present.' : 'No compliance nodes โ€” risk elevated.'} ${ctx.disconnected.length > 0 ? `${ctx.disconnected.length} disconnected node(s) detected.` : 'All nodes connected.'}`; } return `Risk assessment: LOW. Transaction amount is within normal parameters. No counterparty risk flags detected.`; } case 'Documentation': { if (ctx.nodeCount > 0) return `Generating deal memo for "${ctx.nodeLabels[0]}" flow with ${ctx.nodeCount} nodes. Categories: ${ctx.categories.join(', ')}. ${ctx.edgeCount} connections mapped. ${ctx.hasCompliance ? 'Compliance: verified.' : 'Compliance: not yet added.'}`; return `I'll generate a deal memo for this transaction. It will include the execution path, compliance checks, and settlement instructions.`; } default: return 'How can I assist you?'; } }; const reply: ChatMessage = { id: (Date.now() + 1).toString(), agent: activeAgent, content: buildResponse(), timestamp: new Date(), type: 'agent', }; setMessages(prev => [...prev, reply]); }, 800); }; const currentAgentDef = agents.find(a => a.id === activeAgent)!; const CurrentIcon = currentAgentDef.icon; const scopeLabels: Record = { 'current-node': 'Current Node', 'current-flow': 'Current Flow', 'full-transaction': 'Full Transaction', 'terminal': 'Terminal', 'compliance': 'Compliance Only', }; // Context from canvas const ctx = getCanvasContext(); const handleInsertBlock = () => { const suggestions = ['transfer', 'kyc', 'banking-rail']; const item = componentItems.find(c => c.id === suggestions[Math.floor(Math.random() * suggestions.length)]); if (item) onInsertBlock(item, { x: 250 + Math.random() * 200, y: 150 + Math.random() * 200 }); }; return (
setShowAgentMenu(!showAgentMenu)}> {activeAgent} Agent {showAgentMenu && (
{agents.map(a => { const Icon = a.icon; return (
{ e.stopPropagation(); setActiveAgent(a.id); setShowAgentMenu(false); }} > {a.id}
); })}
)}
setShowScopeMenu(!showScopeMenu)}> {scopeLabels[scope]} {showScopeMenu && (
{(Object.keys(scopeLabels) as ConversationScope[]).map(s => (
{ e.stopPropagation(); setScope(s); setShowScopeMenu(false); }} > {scopeLabels[s]}
))}
)}
{agents.map(a => { const Icon = a.icon; return ( ); })}
{showThreads && (
Thread History
{sampleThreads.map(t => (
setShowThreads(false)}> a.id === t.agent)?.color} />
{t.title} {t.agent} ยท {t.messageCount} messages
))}
)} {showContext && (
Selected {ctx.selectedLabels.length > 0 ? ctx.selectedLabels.join(', ') : 'None'}
Nodes {ctx.nodeCount}
Connections {ctx.edgeCount}
Jurisdiction Multi
Counterparties {ctx.nodeCount > 0 ? Math.max(1, Math.floor(ctx.nodeCount / 3)) : 0}
Compliance 0 ? 'warn' : '')}`}> {ctx.hasCompliance ? 'Pass' : (ctx.nodeCount > 0 ? 'Missing' : 'N/A')}
Categories {ctx.categories.length > 0 ? ctx.categories.join(', ') : 'โ€”'}
Est. Fees {ctx.nodeCount > 0 ? '$0.02%' : 'โ€”'}
)}
{messages.map(msg => (
{msg.agent} {msg.timestamp.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
{msg.content}
))}
setInput(e.target.value)} onKeyDown={e => e.key === 'Enter' && sendMessage()} />
); } function getBlockExplanation(label: string): string { const explanations: Record = { 'Fiat Account': 'represents a traditional fiat currency account, typically used as a source or destination for fund transfers', 'Transfer': 'moves value from one account to another along a defined path', 'KYC': 'performs Know Your Customer verification before allowing the transaction to proceed', 'AML': 'runs Anti-Money Laundering screening against watchlists', 'Swap': 'exchanges one asset type for another at the current market rate', 'Banking Rail': 'routes the transaction through traditional banking infrastructure', }; return explanations[label] || 'is a transaction primitive used in flow composition'; } function suggestNextBlock(label: string): string { const suggestions: Record = { 'Fiat Account': 'Transfer or Convert block', 'Transfer': 'KYC compliance check', 'KYC': 'AML screening node', 'AML': 'Banking Rail or DEX Route', 'Swap': 'Settlement instruction', 'Banking Rail': 'Settlement instruction', }; return suggestions[label] || 'compliance or routing node'; }