PluginProbe
WPBot – AI ChatBot for Live Support, Lead Generation, WordPress Automation, AI Services / 8.8.1
WPBot – AI ChatBot for Live Support, Lead Generation, WordPress Automation, AI Services v8.8.1
8.8.1 8.8.0 8.7.9 8.7.8 8.7.7 8.7.6 8.7.5 8.7.4 8.7.3 8.7.2 8.7.1 8.7.0 8.6.9 8.6.8 8.6.7 8.6.6 8.6.5 8.6.4 8.6.2 8.6.1 8.6.0 8.5.9 8.5.8 8.5.7 8.5.6 All 537 releases
chatbot / addons / automator / src / components / AiWorkflowGenerator.jsx

AiWorkflowGenerator.jsx in WPBot – AI ChatBot for Live Support, Lead Generation, WordPress Automation, AI Services 8.8.1, at addons/automator/src/components/AiWorkflowGenerator.jsx

312 lines 12.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import React, { useState } from 'react';
2 import { Sparkles, Loader2, ArrowRight, RotateCcw } from 'lucide-react';
3 import { appsMetadata } from '../constants/appsMetadata';
4
5 const LS_KEY = 'wpbot_ai_gen_settings';
6
7 const findApp = (id) => appsMetadata.find(a => a.id === id);
8
9 const findActionName = (appId, actionId) => {
10 const app = findApp(appId);
11 if (!app) return actionId;
12 if (app.categories) {
13 for (const cat of app.categories) {
14 const found = cat.actions?.find(a => a.id === actionId);
15 if (found) return found.name;
16 }
17 }
18 if (app.actions) {
19 const found = app.actions.find(a => a.id === actionId);
20 if (found) return found.name;
21 }
22 return actionId;
23 };
24
25 const APP_VISUAL = {
26 wordpress: { title: 'WordPress', icon: 'W', iconBg: 'bg-blue-600' },
27 woocommerce: { title: 'WooCommerce', icon: 'WC', iconBg: 'bg-purple-600' },
28 wpforms: { title: 'WPForms', icon: 'F', iconBg: 'bg-orange-500' },
29 cf7: { title: 'Contact Form 7', icon: 'C7', iconBg: 'bg-black' },
30 fluent_forms: { title: 'Fluent Forms', icon: 'FF', iconBg: 'bg-green-600' },
31 webhook: { title: 'Webhook', icon: 'Wh', iconBg: 'bg-violet-600' },
32 facebook: { title: 'Facebook', icon: 'fb', iconBg: 'bg-blue-600' },
33 google_sheets: { title: 'Google Sheets', icon: 'GS', iconBg: 'bg-green-600' },
34 mail: { title: 'Mail', icon: 'M', iconBg: 'bg-gray-700' },
35 telegram: { title: 'Telegram', icon: 'Tg', iconBg: 'bg-blue-500' },
36 whatsapp: { title: 'WhatsApp', icon: 'WA', iconBg: 'bg-green-500' },
37 instagram: { title: 'Instagram', icon: 'Ig', iconBg: 'bg-pink-500' },
38 linkedin: { title: 'LinkedIn', icon: 'In', iconBg: 'bg-sky-600' },
39 openai: { title: 'OpenAI', icon: 'OA', iconBg: 'bg-emerald-600' },
40 claude: { title: 'Claude', icon: 'CL', iconBg: 'bg-amber-700' },
41 gemini: { title: 'Gemini', icon: 'GE', iconBg: 'bg-blue-500' },
42 grok: { title: 'Grok AI', icon: 'GK', iconBg: 'bg-slate-900' },
43 mistral: { title: 'Mistral AI', icon: 'MI', iconBg: 'bg-orange-600' },
44 openrouter: { title: 'OpenRouter', icon: 'OR', iconBg: 'bg-indigo-600' },
45 fluentcrm: { title: 'FluentCRM', icon: 'FC', iconBg: 'bg-blue-700' },
46 filters: { title: 'Filters', icon: 'FT', iconBg: 'bg-yellow-500' },
47 iterator: { title: 'Iterator', icon: '↻', iconBg: 'bg-amber-500' },
48 iterator_end: { title: 'Iterator End', icon: '⏹', iconBg: 'bg-amber-700' },
49 api: { title: 'API', icon: 'A', iconBg: 'bg-slate-700' },
50 mailboxlayer: { title: 'MailboxLayer', icon: 'ML', iconBg: 'bg-blue-600' },
51 mailrefine: { title: 'MailRefine', icon: 'MR', iconBg: 'bg-violet-600' },
52 };
53
54 const PROVIDERS = [
55 { id: 'openai', name: 'OpenAI', models: ['gpt-4o-mini', 'gpt-4o', 'gpt-4-turbo'] },
56 { id: 'claude', name: 'Claude', models: ['claude-3-5-sonnet-20240620', 'claude-3-haiku-20240307'] },
57 { id: 'gemini', name: 'Gemini', models: ['gemini-1.5-flash', 'gemini-2.0-flash', 'gemini-1.5-pro'] },
58 ];
59
60 function computePositions(nodes, connections) {
61 const childrenOf = {};
62 connections.forEach(({ from, to }) => {
63 if (!childrenOf[from]) childrenOf[from] = [];
64 childrenOf[from].push(to);
65 });
66
67 const pos = {};
68 const visited = new Set();
69 const queue = [{ idx: 0, x: 100, y: 200 }];
70
71 while (queue.length) {
72 const { idx, x, y } = queue.shift();
73 if (visited.has(idx)) continue;
74 visited.add(idx);
75 pos[idx] = { x, y };
76 const kids = childrenOf[idx] || [];
77 const spread = (kids.length - 1) * 160;
78 kids.forEach((child, i) => {
79 if (!visited.has(child)) {
80 queue.push({ idx: child, x: x + 300, y: y - spread / 2 + i * 160 });
81 }
82 });
83 }
84 return pos;
85 }
86
87 function buildWorkflowData(aiResult) {
88 const { nodes: aiNodes, connections: aiConns } = aiResult;
89 const positions = computePositions(aiNodes, aiConns);
90
91 const nodes = aiNodes.map((n, idx) => {
92 const visual = APP_VISUAL[n.appId] || { title: n.appId, icon: '?', iconBg: 'bg-gray-500' };
93 return {
94 id: idx === 0 ? 'node-trigger-1' : `node-action-${idx}`,
95 type: n.type,
96 title: visual.title,
97 subtitle: findActionName(n.appId, n.actionId),
98 position: positions[idx] || { x: 100 + idx * 300, y: 200 },
99 icon: visual.icon,
100 iconBg: visual.iconBg,
101 actionNumber: idx + 1,
102 appData: findApp(n.appId) || null,
103 };
104 });
105
106 const connections = aiConns.map((c, idx) => ({
107 id: `conn-${idx + 1}`,
108 from: c.from === 0 ? 'node-trigger-1' : `node-action-${c.from}`,
109 to: c.to === 0 ? 'node-trigger-1' : `node-action-${c.to}`,
110 fromHandle: 'right',
111 toHandle: 'left',
112 }));
113
114 return { nodes, connections };
115 }
116
117 const AiWorkflowGenerator = ({ onSelect }) => {
118 const [settings, setSettings] = useState(() => {
119 try { return JSON.parse(localStorage.getItem(LS_KEY) || '{}'); }
120 catch { return {}; }
121 });
122
123 const [prompt, setPrompt] = useState('');
124 const [loading, setLoading] = useState(false);
125 const [error, setError] = useState('');
126 const [result, setResult] = useState(null);
127
128 const provider = settings.provider || 'openai';
129 const apiKey = settings.api_key || '';
130 const models = PROVIDERS.find(p => p.id === provider)?.models || [];
131 const model = settings.model && models.includes(settings.model) ? settings.model : models[0];
132
133 const updateSettings = (patch) => {
134 const next = { ...settings, ...patch };
135 setSettings(next);
136 localStorage.setItem(LS_KEY, JSON.stringify(next));
137 };
138
139 const handleGenerate = async () => {
140 if (!prompt.trim()) { setError('Please describe your workflow.'); return; }
141 if (!apiKey.trim()) { setError('Please enter your API key.'); return; }
142
143 setError('');
144 setResult(null);
145 setLoading(true);
146
147 try {
148 const response = await fetch(`${window.wpbotAutomator.apiUrl}/generate-workflow`, {
149 method: 'POST',
150 headers: {
151 'Content-Type': 'application/json',
152 'X-WP-Nonce': window.wpbotAutomator.nonce,
153 },
154 body: JSON.stringify({ provider, api_key: apiKey, model, prompt: prompt.trim() }),
155 });
156
157 const data = await response.json();
158 if (!response.ok || !data.success) {
159 setError(data.message || data.code || 'Failed to generate. Please try again.');
160 return;
161 }
162 setResult(data.workflow);
163 } catch {
164 setError('Network error. Please try again.');
165 } finally {
166 setLoading(false);
167 }
168 };
169
170 const handleUse = () => {
171 if (!result) return;
172 onSelect({
173 id: null,
174 name: result.name,
175 description: result.description,
176 workflow_data: buildWorkflowData(result),
177 });
178 };
179
180 return (
181 <div className="space-y-4">
182 <p className="text-sm text-muted-foreground -mt-1">
183 Describe your automation in plain English and AI will build the workflow for you.
184 </p>
185
186 {/* Prompt */}
187 <div>
188 <label className="block text-sm font-medium mb-1.5">Describe your automation</label>
189 <textarea
190 rows={4}
191 value={prompt}
192 onChange={e => setPrompt(e.target.value)}
193 placeholder="e.g. When a WPForms form is submitted, save the data to Google Sheets and send a welcome email to the user"
194 className="w-full border border-border rounded-lg px-3 py-2.5 text-sm bg-background resize-none focus:outline-none focus:ring-2 focus:ring-primary/30"
195 disabled={loading}
196 />
197 </div>
198
199 {/* Provider + Model + API Key */}
200 <div className="grid grid-cols-3 gap-3">
201 <div>
202 <label className="block text-xs font-medium text-muted-foreground mb-1">AI Provider</label>
203 <select
204 value={provider}
205 onChange={e => updateSettings({ provider: e.target.value, model: '' })}
206 disabled={loading}
207 className="w-full border border-border rounded-lg px-2.5 py-2 text-sm bg-background focus:outline-none"
208 >
209 {PROVIDERS.map(p => <option key={p.id} value={p.id}>{p.name}</option>)}
210 </select>
211 </div>
212 <div>
213 <label className="block text-xs font-medium text-muted-foreground mb-1">Model</label>
214 <select
215 value={model}
216 onChange={e => updateSettings({ model: e.target.value })}
217 disabled={loading}
218 className="w-full border border-border rounded-lg px-2.5 py-2 text-sm bg-background focus:outline-none"
219 >
220 {models.map(m => <option key={m} value={m}>{m}</option>)}
221 </select>
222 </div>
223 <div>
224 <label className="block text-xs font-medium text-muted-foreground mb-1">API Key</label>
225 <input
226 type="password"
227 value={apiKey}
228 onChange={e => updateSettings({ api_key: e.target.value })}
229 placeholder="sk-..."
230 disabled={loading}
231 className="w-full border border-border rounded-lg px-2.5 py-2 text-sm bg-background focus:outline-none"
232 />
233 </div>
234 </div>
235
236 {/* Error */}
237 {error && (
238 <p className="text-sm text-rose-600 bg-rose-50 border border-rose-200 rounded-lg px-3 py-2">
239 {error}
240 </p>
241 )}
242
243 {/* Generate button (shown when no result yet) */}
244 {!result && (
245 <button
246 onClick={handleGenerate}
247 disabled={loading}
248 className="w-full flex items-center justify-center gap-2 px-4 py-2.5 bg-primary text-primary-foreground rounded-lg font-medium text-sm hover:bg-primary/90 transition-colors disabled:opacity-60"
249 >
250 {loading ? (
251 <><Loader2 className="w-4 h-4 animate-spin" /> Generating...</>
252 ) : (
253 <><Sparkles className="w-4 h-4" /> Generate Workflow</>
254 )}
255 </button>
256 )}
257
258 {/* Result preview */}
259 {result && (
260 <div className="border border-border rounded-xl overflow-hidden">
261 <div className="bg-muted/40 px-4 py-3 border-b border-border">
262 <p className="font-semibold text-sm">{result.name}</p>
263 {result.description && (
264 <p className="text-xs text-muted-foreground mt-0.5">{result.description}</p>
265 )}
266 </div>
267 <div className="px-4 py-3 space-y-2.5">
268 {result.nodes?.map((node, idx) => {
269 const visual = APP_VISUAL[node.appId] || { icon: '?', iconBg: 'bg-gray-500', title: node.appId };
270 return (
271 <div key={idx} className="flex items-center gap-3">
272 {idx > 0 && (
273 <div className="absolute ml-3.5 -mt-4 w-px h-3 bg-border" style={{ marginTop: '-10px', position: 'relative', left: '13px', top: '-8px', height: '8px' }} />
274 )}
275 <div className={`w-8 h-8 rounded-lg ${visual.iconBg} flex items-center justify-center text-white text-[10px] font-bold shrink-0`}>
276 {visual.icon}
277 </div>
278 <div className="flex-1 min-w-0">
279 <span className="text-xs font-semibold text-foreground">{visual.title}</span>
280 <span className="text-xs text-muted-foreground ml-1.5">{findActionName(node.appId, node.actionId)}</span>
281 </div>
282 {idx === 0 && (
283 <span className="text-[10px] bg-primary/10 text-primary font-semibold px-1.5 py-0.5 rounded shrink-0">
284 Trigger
285 </span>
286 )}
287 </div>
288 );
289 })}
290 </div>
291 <div className="flex gap-2 px-4 py-3 border-t border-border bg-muted/20">
292 <button
293 onClick={() => setResult(null)}
294 className="flex items-center gap-1.5 px-3 py-2 text-sm border border-border rounded-lg hover:bg-muted transition-colors"
295 >
296 <RotateCcw className="w-3.5 h-3.5" /> Regenerate
297 </button>
298 <button
299 onClick={handleUse}
300 className="flex-1 flex items-center justify-center gap-1.5 px-3 py-2 text-sm bg-primary text-primary-foreground rounded-lg font-medium hover:bg-primary/90 transition-colors"
301 >
302 Use This Workflow <ArrowRight className="w-3.5 h-3.5" />
303 </button>
304 </div>
305 </div>
306 )}
307 </div>
308 );
309 };
310
311 export default AiWorkflowGenerator;
312