feat(frontend): add advanced node components to FlowBuilder

Add visual components for all new Flow Engine node types including:
- Basic nodes: Buttons, SetVariable
- Control nodes: Switch, Delay, Random, Loop, GoTo
- Validation nodes: Email, Phone, Number, Date, Regex, Options
- Script nodes: JavaScript, HTTP Request
- AI nodes: AI Response, AI Sentiment

Reorganize toolbar into categorized dropdown menus for better UX.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Claude AI
2026-01-29 11:18:57 +00:00
parent b458b8e8dd
commit 0230ab4e36

View File

@@ -12,7 +12,7 @@ import ReactFlow, {
NodeTypes,
} from 'reactflow';
import 'reactflow/dist/style.css';
import { Button, message, Space } from 'antd';
import { Button, message, Space, Dropdown } from 'antd';
import { SaveOutlined, ArrowLeftOutlined } from '@ant-design/icons';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { apiClient } from '../api/client';
@@ -42,11 +42,140 @@ const WaitInputNode = ({ data }: { data: any }) => (
</div>
);
const ButtonsNode = ({ data }: { data: any }) => (
<div style={{ padding: 10, border: '2px solid #13c2c2', borderRadius: 8, background: '#e6fffb' }}>
<strong>🔘 Botones</strong>
<div style={{ fontSize: 12, marginTop: 4 }}>{data.config?.buttons?.length || 0} opciones</div>
</div>
);
const SetVariableNode = ({ data }: { data: any }) => (
<div style={{ padding: 10, border: '2px solid #eb2f96', borderRadius: 8, background: '#fff0f6' }}>
<strong>📝 Variable</strong>
<div style={{ fontSize: 12, marginTop: 4 }}>{data.config?.variable || 'Sin nombre'}</div>
</div>
);
const SwitchNode = ({ data }: { data: any }) => (
<div style={{ padding: 10, border: '2px solid #fa8c16', borderRadius: 8, background: '#fff7e6' }}>
<strong>🔀 Switch</strong>
<div style={{ fontSize: 12, marginTop: 4 }}>{data.config?.cases?.length || 0} casos</div>
</div>
);
const DelayNode = ({ data }: { data: any }) => (
<div style={{ padding: 10, border: '2px solid #a0d911', borderRadius: 8, background: '#fcffe6' }}>
<strong> Delay</strong>
<div style={{ fontSize: 12, marginTop: 4 }}>{data.config?.seconds || 0}s</div>
</div>
);
const RandomNode = ({ data }: { data: any }) => (
<div style={{ padding: 10, border: '2px solid #2f54eb', borderRadius: 8, background: '#f0f5ff' }}>
<strong>🎲 Random</strong>
<div style={{ fontSize: 12, marginTop: 4 }}>A/B Test</div>
</div>
);
const LoopNode = ({ data }: { data: any }) => (
<div style={{ padding: 10, border: '2px solid #52c41a', borderRadius: 8, background: '#f6ffed' }}>
<strong>🔄 Loop</strong>
<div style={{ fontSize: 12, marginTop: 4 }}>Max: {data.config?.max_iterations || 10}</div>
</div>
);
const GoToNode = ({ data }: { data: any }) => (
<div style={{ padding: 10, border: '2px solid #595959', borderRadius: 8, background: '#fafafa' }}>
<strong> GoTo</strong>
<div style={{ fontSize: 12, marginTop: 4 }}>{data.config?.target_node || 'Sin destino'}</div>
</div>
);
const ValidateEmailNode = () => (
<div style={{ padding: 10, border: '2px solid #1890ff', borderRadius: 8, background: '#e6f7ff' }}>
<strong> Validar Email</strong>
</div>
);
const ValidatePhoneNode = () => (
<div style={{ padding: 10, border: '2px solid #1890ff', borderRadius: 8, background: '#e6f7ff' }}>
<strong>📱 Validar Teléfono</strong>
</div>
);
const ValidateNumberNode = () => (
<div style={{ padding: 10, border: '2px solid #1890ff', borderRadius: 8, background: '#e6f7ff' }}>
<strong>🔢 Validar Número</strong>
</div>
);
const ValidateDateNode = () => (
<div style={{ padding: 10, border: '2px solid #1890ff', borderRadius: 8, background: '#e6f7ff' }}>
<strong>📅 Validar Fecha</strong>
</div>
);
const ValidateRegexNode = () => (
<div style={{ padding: 10, border: '2px solid #1890ff', borderRadius: 8, background: '#e6f7ff' }}>
<strong>🔍 Validar Regex</strong>
</div>
);
const ValidateOptionsNode = () => (
<div style={{ padding: 10, border: '2px solid #1890ff', borderRadius: 8, background: '#e6f7ff' }}>
<strong>📋 Validar Opciones</strong>
</div>
);
const JavaScriptNode = ({ data }: { data: any }) => (
<div style={{ padding: 10, border: '2px solid #fadb14', borderRadius: 8, background: '#fffb8f' }}>
<strong>📜 JavaScript</strong>
<div style={{ fontSize: 12, marginTop: 4 }}>Script</div>
</div>
);
const HttpRequestNode = ({ data }: { data: any }) => (
<div style={{ padding: 10, border: '2px solid #722ed1', borderRadius: 8, background: '#f9f0ff' }}>
<strong>🌐 HTTP Request</strong>
<div style={{ fontSize: 12, marginTop: 4 }}>{data.config?.method || 'GET'} {data.config?.url?.slice(0, 20) || ''}</div>
</div>
);
const AIResponseNode = ({ data }: { data: any }) => (
<div style={{ padding: 10, border: '2px solid #eb2f96', borderRadius: 8, background: '#fff0f6' }}>
<strong>🤖 AI Response</strong>
<div style={{ fontSize: 12, marginTop: 4 }}>GPT</div>
</div>
);
const AISentimentNode = () => (
<div style={{ padding: 10, border: '2px solid #eb2f96', borderRadius: 8, background: '#fff0f6' }}>
<strong>💭 AI Sentiment</strong>
</div>
);
const nodeTypes: NodeTypes = {
trigger: TriggerNode,
message: MessageNode,
condition: ConditionNode,
wait_input: WaitInputNode,
buttons: ButtonsNode,
set_variable: SetVariableNode,
switch: SwitchNode,
delay: DelayNode,
random: RandomNode,
loop: LoopNode,
goto: GoToNode,
validate_email: ValidateEmailNode,
validate_phone: ValidatePhoneNode,
validate_number: ValidateNumberNode,
validate_date: ValidateDateNode,
validate_regex: ValidateRegexNode,
validate_options: ValidateOptionsNode,
javascript: JavaScriptNode,
http_request: HttpRequestNode,
ai_response: AIResponseNode,
ai_sentiment: AISentimentNode,
};
interface Flow {
@@ -124,10 +253,59 @@ export default function FlowBuilder() {
<span style={{ fontSize: 18, fontWeight: 'bold' }}>{flow?.name}</span>
</Space>
<Space>
<Button onClick={() => addNode('trigger')}>+ Trigger</Button>
<Button onClick={() => addNode('message')}>+ Mensaje</Button>
<Button onClick={() => addNode('condition')}>+ Condición</Button>
<Button onClick={() => addNode('wait_input')}>+ Esperar Input</Button>
<Dropdown
menu={{
items: [
{ key: 'trigger', label: '🚀 Trigger', onClick: () => addNode('trigger') },
{ key: 'message', label: '💬 Mensaje', onClick: () => addNode('message') },
{ key: 'buttons', label: '🔘 Botones', onClick: () => addNode('buttons') },
{ key: 'wait_input', label: '⏳ Esperar Input', onClick: () => addNode('wait_input') },
{ key: 'set_variable', label: '📝 Variable', onClick: () => addNode('set_variable') },
],
}}
>
<Button>+ Básicos</Button>
</Dropdown>
<Dropdown
menu={{
items: [
{ key: 'condition', label: '❓ Condición', onClick: () => addNode('condition') },
{ key: 'switch', label: '🔀 Switch', onClick: () => addNode('switch') },
{ key: 'delay', label: '⏱️ Delay', onClick: () => addNode('delay') },
{ key: 'random', label: '🎲 Random', onClick: () => addNode('random') },
{ key: 'loop', label: '🔄 Loop', onClick: () => addNode('loop') },
{ key: 'goto', label: '➡️ GoTo', onClick: () => addNode('goto') },
],
}}
>
<Button>+ Control</Button>
</Dropdown>
<Dropdown
menu={{
items: [
{ key: 'validate_email', label: '✉️ Email', onClick: () => addNode('validate_email') },
{ key: 'validate_phone', label: '📱 Teléfono', onClick: () => addNode('validate_phone') },
{ key: 'validate_number', label: '🔢 Número', onClick: () => addNode('validate_number') },
{ key: 'validate_date', label: '📅 Fecha', onClick: () => addNode('validate_date') },
{ key: 'validate_regex', label: '🔍 Regex', onClick: () => addNode('validate_regex') },
{ key: 'validate_options', label: '📋 Opciones', onClick: () => addNode('validate_options') },
],
}}
>
<Button>+ Validación</Button>
</Dropdown>
<Dropdown
menu={{
items: [
{ key: 'javascript', label: '📜 JavaScript', onClick: () => addNode('javascript') },
{ key: 'http_request', label: '🌐 HTTP Request', onClick: () => addNode('http_request') },
{ key: 'ai_response', label: '🤖 AI Response', onClick: () => addNode('ai_response') },
{ key: 'ai_sentiment', label: '💭 AI Sentiment', onClick: () => addNode('ai_sentiment') },
],
}}
>
<Button>+ Avanzados</Button>
</Dropdown>
<Button
type="primary"
icon={<SaveOutlined />}