PluginProbe
Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor / 2.0.14
Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor v2.0.14
2.0.14 2.0.13 2.0.12 2.0.11 2.0.10 2.0.9 trunk 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.0.8
blockenberg / assets / js / ai-external.js

ai-external.js in Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor 2.0.14, at assets/js/ai-external.js

384 lines 27.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /** Temporary external sessions: the open editor is the only command executor. */
2 (function (wp) {
3 'use strict';
4 if (!wp || !wp.element) { return; }
5 var el = wp.element.createElement, useState = wp.element.useState, useRef = wp.element.useRef, useEffect = wp.element.useEffect;
6 var __ = wp.i18n.__;
7 var cfg = window.bkbgAI || {};
8 var CLIENTS = [
9 { id: 'codex', label: __( 'Codex', 'blockenberg' ) },
10 { id: 'claude', label: __( 'Claude Code', 'blockenberg' ) },
11 { id: 'cursor', label: __( 'Cursor', 'blockenberg' ) }
12 ];
13 function clientLabel(id) {
14 for (var i = 0; i < CLIENTS.length; i++) {
15 if (CLIENTS[i].id === id) { return CLIENTS[i].label; }
16 }
17 return CLIENTS[0].label;
18 }
19 function promptFor(session, client) {
20 if (!session) { return ''; }
21 if (session.prompts && session.prompts[client]) { return session.prompts[client]; }
22 return session.prompt || '';
23 }
24 function request(path, body, session, keepalive) {
25 return window.fetch(cfg.restBase + '/ai/external' + path, {
26 method: 'POST', credentials: 'same-origin', keepalive: !!keepalive,
27 headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': cfg.nonce, 'X-Blockenberg-Editor': session ? session.editor_token : '' },
28 body: JSON.stringify(body || {})
29 }).then(function (r) { return r.json().then(function (json) {
30 if (!r.ok) { var error = new Error(json.message || 'Connection failed'); error.status = r.status; throw error; }
31 return json;
32 }); });
33 }
34 function Duration(props) {
35 var tick = useState(0), setTick = tick[1];
36 useEffect(function () {
37 if (!props.startedAt || props.finishedAt) { return; }
38 var timer, disposed = false;
39 function update() {
40 if (disposed) { return; }
41 setTick(function (value) { return value + 1; });
42 timer = setTimeout(update, 1000);
43 }
44 timer = setTimeout(update, 1000);
45 return function () { disposed = true; clearTimeout(timer); };
46 }, [props.startedAt, props.finishedAt]);
47 if (!props.startedAt) { return null; }
48 var seconds = Math.max(0, Math.floor(((props.finishedAt || Date.now()) - props.startedAt) / 1000));
49 var hours = Math.floor(seconds / 3600), minutes = Math.floor(seconds % 3600 / 60);
50 var value = (hours ? hours + 'h ' : '') + (hours || minutes ? minutes + 'm ' : '') + seconds % 60 + 's';
51 return el('div', { className: 'bkbg-ai-duration', 'aria-live': 'off' },
52 (props.finishedAt ? __('Worked for %s', 'blockenberg') : __('Working for %s', 'blockenberg')).replace('%s', value));
53 }
54 window.bkbgDuration = Duration;
55 function phaseLabels() {
56 return {
57 planning: __( 'Planning', 'blockenberg' ),
58 building: __( 'Building the page', 'blockenberg' ),
59 generating_image: __( 'Generating an image', 'blockenberg' ),
60 reviewing: __( 'Checking the result', 'blockenberg' ),
61 waiting: __( 'Waiting for an update', 'blockenberg' ),
62 done: __( 'Finished', 'blockenberg' ),
63 error: __( 'Needs attention', 'blockenberg' )
64 };
65 }
66 function completedPlan( plan ) {
67 return ( plan || [] ).map( function ( step ) {
68 return Object.assign( {}, step, { status: 'done' } );
69 } );
70 }
71 function renderExternalWork( live, options ) {
72 if ( ! live ) { return null; }
73 options = options || {};
74 var finished = !!( options.complete || live.phase === 'done' );
75 var plan = finished ? completedPlan( live.plan ) : ( live.plan || [] );
76 return el( 'div', { className: 'bkbg-ai-external-work' },
77 live.error ? el( 'p', { role: 'alert', className: 'bkbg-ai-activity__error' }, live.error ) : null,
78 live.stale && live.phase !== 'done' && ! live.current ? el( 'p', { className: 'bkbg-ai-muted' }, __( 'No recent update. The agent may still be working; check its chat.', 'blockenberg' ) ) : null,
79 plan.length ? el( 'ol', { className: 'bkbg-ai-external-plan' }, plan.map( function ( step ) {
80 return el( 'li', { key: step.id, className: 'is-' + step.status, 'aria-current': step.status === 'active' ? 'step' : undefined },
81 el( 'span', { 'aria-hidden': true }, step.status === 'done' ? '✓' : step.status === 'active' ? '◉' : '○' ), step.label,
82 el( 'span', { className: 'screen-reader-text' }, ' — ' + ( { pending: __( 'Pending', 'blockenberg' ), active: __( 'In progress', 'blockenberg' ), done: __( 'Completed', 'blockenberg' ) } )[ step.status ] ) );
83 } ) ) : null,
84 options.renderActivity ? options.renderActivity( live.log, !! live.moving ) : null,
85 live.captures && live.captures.length ? el( 'div', { className: 'bkbg-ai-section-previews', 'aria-label': __( 'Section screenshots for visual review', 'blockenberg' ) }, live.captures.map( function ( shot ) {
86 return el( 'figure', { key: shot.id }, el( 'img', { src: shot.data, alt: __( 'Section preview', 'blockenberg' ) + ' · ' + shot.viewport } ), el( 'figcaption', null, shot.viewport === 'mobile' ? __( 'Mobile', 'blockenberg' ) : __( 'Desktop', 'blockenberg' ) ) );
87 } ) ) : null,
88 live.visualReview ? el( 'p', { className: 'bkbg-ai-muted' }, ( live.visualReview.decision === 'passed' ? __( 'Visual review passed', 'blockenberg' ) : __( 'Visual corrections needed', 'blockenberg' ) ) + ': ' + live.visualReview.notes ) : null,
89 live.images && live.images.length ? el( 'div', { className: 'bkbg-ai-external-images', 'aria-label': __( 'Generated images in the Media Library', 'blockenberg' ) }, live.images.map( function ( img ) {
90 return el( 'a', { key: img.id, href: img.url, target: '_blank', rel: 'noopener noreferrer', title: img.alt || __( 'Generated image', 'blockenberg' ) }, el( 'img', { src: img.url, alt: img.alt || __( 'Generated image', 'blockenberg' ), loading: 'lazy' } ) );
91 } ) ) : null,
92 live.counts && ( live.counts.actions || live.counts.images || live.counts.sections ) ? el( 'div', { className: 'bkbg-ai-external-progress__counts' },
93 live.counts.actions + ' ' + ( live.counts.actions === 1 ? __( 'action', 'blockenberg' ) : __( 'actions', 'blockenberg' ) ),
94 live.counts.sections ? ' · ' + live.counts.sections + ' ' + ( live.counts.sections === 1 ? __( 'section', 'blockenberg' ) : __( 'sections', 'blockenberg' ) ) : '',
95 ' · ',
96 live.counts.images + ' ' + ( live.counts.images === 1 ? __( 'image', 'blockenberg' ) : __( 'images', 'blockenberg' ) )
97 ) : null
98 );
99 }
100 window.bkbgRenderExternalWork = renderExternalWork;
101 function Panel(props) {
102 var state = useState(null), session = state[0], setSession = state[1];
103 var statusState = useState(''), status = statusState[0], setStatus = statusState[1];
104 var errorState = useState(''), error = errorState[0], setError = errorState[1];
105 var busyState = useState(false), busy = busyState[0], setBusy = busyState[1];
106 var logState = useState([]), log = logState[0], setLog = logState[1];
107 var logRef = useRef(log); logRef.current = log;
108 var progressState = useState(null), progress = progressState[0], setProgress = progressState[1];
109 var currentState = useState(''), current = currentState[0], setCurrent = currentState[1];
110 var copyState = useState(''), copyStatus = copyState[0], setCopyStatus = copyState[1];
111 var promptField = useRef(null), pendingMessage = useRef(null);
112 var captureState = useState([]), captures = captureState[0], setCaptures = captureState[1];
113 var runState = useState(null), run = runState[0], setRun = runState[1];
114 var clientState = useState('codex'), promptClient = clientState[0], setPromptClient = clientState[1];
115 var active = useRef(null), mounted = useRef(true), callbacks = useRef(props);
116 var progressRef = useRef(null), capturesRef = useRef([]);
117 var disconnectRef = useRef(function () {});
118 var liveKeyRef = useRef( '' );
119 callbacks.current = props;
120 progressRef.current = progress;
121 capturesRef.current = captures;
122 function finishRun(owner, clock, message, options) {
123 if (!clock || owner.reportedRun === clock.id) { return; }
124 owner.reportedRun = clock.id;
125 if (!callbacks.current.onCompleted) { return; }
126 var report = ( options && options.report ) || progressRef.current || {};
127 var update = report.progress || {};
128 callbacks.current.onCompleted({
129 text: message,
130 activity: logRef.current,
131 meta: { startedAt: clock.startedAt, finishedAt: clock.finishedAt || Date.now() },
132 runId: clock.id,
133 agent: report.agent || '',
134 plan: options && options.complete ? completedPlan( update.plan ) : ( update.plan || [] ),
135 images: report.images || [],
136 visualReview: report.visual_review || null,
137 counts: report.counts || { actions: 0, images: 0 }
138 });
139 }
140 function sendMessage(text) {
141 var owner = active.current;
142 if (!owner || !owner.session) { return Promise.reject(new Error(__('Connect an agent first.', 'blockenberg'))); }
143 if (!pendingMessage.current || pendingMessage.current.text !== text) { pendingMessage.current = {id:'msg-' + Date.now().toString(36) + '-' + Math.random().toString(36).slice(2,12),text:text}; }
144 var message = pendingMessage.current;
145 return request('/' + owner.session.id + '/outbox', message, owner.session).then(function (saved) {
146 if (active.current !== owner) { throw new Error('Connection changed. Check the saved conversation before resending.'); }
147 if (callbacks.current.onMessages) { callbacks.current.onMessages([saved]); }
148 pendingMessage.current = null;
149 });
150 }
151 function disconnect(message) {
152 var old = active.current;
153 active.current = null;
154 if (old) {
155 finishRun(old, old.run, __('External agent stopped.', 'blockenberg'));
156 if (mounted.current && old.run) { setRun(Object.assign({}, old.run, { finishedAt: old.run.finishedAt || Date.now() })); }
157 old.controller.abort();
158 clearTimeout(old.timer);
159 if (old.session) { request('/' + old.session.id + '/revoke', {}, old.session, true).catch(function () {}); }
160 }
161 if (mounted.current) {
162 setSession(null); setBusy(false); setCurrent(''); setCopyStatus(''); setStatus(message || __('Disconnected', 'blockenberg'));
163 callbacks.current.onActive(false);
164 if (callbacks.current.onProgress) { callbacks.current.onProgress(null); }
165 liveKeyRef.current = '';
166 }
167 }
168 disconnectRef.current = disconnect;
169 useEffect(function () {
170 mounted.current = true;
171 if (callbacks.current.onSender) { callbacks.current.onSender(sendMessage); }
172 if (callbacks.current.onControls) { callbacks.current.onControls({ disconnect: function () { disconnectRef.current(); } }); }
173 var leave = function () { disconnectRef.current(); };
174 window.addEventListener('pagehide', leave);
175 return function () {
176 mounted.current = false;
177 disconnectRef.current();
178 if (callbacks.current.onSender) { callbacks.current.onSender(null); }
179 if (callbacks.current.onControls) { callbacks.current.onControls(null); }
180 window.removeEventListener('pagehide', leave);
181 };
182 }, []);
183 function start() {
184 if (active.current || busy || props.disabled) { return; }
185 setError(''); setBusy(true); setLog([]); setProgress(null); setCurrent(''); setCopyStatus(''); setRun(null); setCaptures([]); setPromptClient('codex'); callbacks.current.onActive(true);
186 var scope = { mode: props.scope.mode, rootIds: props.scope.rootIds.slice() };
187 var owner = { controller: new window.AbortController(), session: null, timer: null, scope: scope, post: wp.data.select('core/editor').getCurrentPostId(), ack: null, seen: Object.create(null) };
188 active.current = owner;
189 var manifest;
190 try { manifest = callbacks.current.manifest(scope); }
191 catch (e) { active.current = null; setBusy(false); callbacks.current.onActive(false); setError(e.message); return; }
192 request('', manifest).then(function (value) {
193 owner.session = value;
194 if (active.current !== owner || !mounted.current) { request('/' + value.id + '/revoke', {}, value, true).catch(function () {}); return; }
195 setSession(value); setBusy(false); setStatus(__('Waiting for an agent…', 'blockenberg'));
196 poll(owner);
197 }).catch(function (e) {
198 if (active.current !== owner || !mounted.current) { return; }
199 active.current = null; setBusy(false); callbacks.current.onActive(false); setError(e.message);
200 });
201 }
202 async function captureSection(owner, commandId, clientId, viewport) {
203 setCurrent(__('Capturing section preview', 'blockenberg') + ' · ' + viewport);
204 var shot = await callbacks.current.capture({ clientId: clientId, viewport: viewport }, { scope: owner.scope, signal: owner.controller.signal, render: function (content) { return request('/' + owner.session.id + '/render-section', {content:content}, owner.session); } });
205 if (active.current !== owner) { throw new Error('Disconnected during capture.'); }
206 var id = commandId + '-' + viewport;
207 var result = await request('/' + owner.session.id + '/captures/' + id, Object.assign({}, shot, { clientId: clientId }), owner.session);
208 if (active.current !== owner) { throw new Error('Disconnected during capture.'); }
209 setCaptures(function (previous) { return previous.filter(function (item) { return item.viewport !== viewport; }).concat([{ id: id, viewport: viewport, data: shot.data }]); });
210 return result;
211 }
212 function poll(owner) {
213 if (active.current !== owner || owner.controller.signal.aborted) { return; }
214 if (Date.now() >= owner.session.expires * 1000) { disconnect(__('Session expired. Create a new connection.', 'blockenberg')); return; }
215 var ack = owner.ack;
216 request('/' + owner.session.id + '/poll', { ack: ack }, owner.session).then(function (response) {
217 if (active.current !== owner || owner.controller.signal.aborted) { return; }
218 if (ack === owner.ack) {
219 if (ack) { owner.seen[ack.id] = { id: ack.id, result: { error: 'Already executed and acknowledged. Read the current page before planning a new change.' } }; }
220 owner.ack = null;
221 }
222 setError('');
223 setProgress(response);
224 if (response.messages && callbacks.current.onMessages) { callbacks.current.onMessages(response.messages); }
225 if (response.run) {
226 var reported = response.run;
227 // Translate the server clock once per task; local ticks then remain smooth.
228 if (!owner.run || owner.run.id !== reported.id) {
229 owner.clockOffset = Date.now() - response.server_time * 1000;
230 owner.run = { id: reported.id, startedAt: reported.started_at * 1000 + owner.clockOffset };
231 }
232 if (reported.finished_at) { owner.run.finishedAt = reported.finished_at * 1000 + owner.clockOffset; }
233 setRun(Object.assign({}, owner.run));
234 if (owner.run.finishedAt) { finishRun(owner, owner.run, response.progress && response.progress.message || __('External agent finished.', 'blockenberg'), { complete: true, report: response }); }
235 }
236 setStatus(response.agent ? response.agent + ' · ' + __('Connected', 'blockenberg') : __('Waiting for an agent…', 'blockenberg'));
237 if (response.agent) { setSession(function (previous) { return previous && previous.prompt ? Object.assign({}, previous, { prompt: '' }) : previous; }); }
238 var command = response.command;
239 if (!command) { return; }
240 if (owner.seen[command.id]) { owner.ack = owner.seen[command.id]; return; }
241 var label = callbacks.current.describe(command.tool, command.args);
242 setCurrent(label);
243 setLog(function (previous) { return previous.concat([{ key: command.id, text: label, state: 'run' }]).slice(-100); });
244 return Promise.resolve().then(function () {
245 if (active.current !== owner || owner.controller.signal.aborted) { return { error: 'Disconnected before execution.' }; }
246 if (wp.data.select('core/editor').getCurrentPostId() !== owner.post) { return { error: 'The editor is no longer on the connected page.' }; }
247 var args = Object.assign({}, command.args);
248 if (command.tool === 'create_image') { args.source = 'search'; }
249 if (command.tool === 'capture_section') { return captureSection(owner, command.id, args.clientId, args.viewport); }
250 return callbacks.current.execute(command.tool, args, { signal: owner.controller.signal, scope: owner.scope });
251 }).then(async function (result) {
252 if (command.tool === 'build_section' && result && !result.error && result.focus && callbacks.current.capture) {
253 result.captures = [];
254 for (var viewport of ['desktop', 'mobile']) {
255 if (active.current !== owner) { break; }
256 try { result.captures.push(await captureSection(owner, command.id, result.focus, viewport)); }
257 catch (error) { result.warnings = (result.warnings || []).concat(['Visual capture (' + viewport + ') failed: ' + error.message + '. Retry capture_section; do not claim the layout was visually checked.']); }
258 }
259 result.visual_review_required = true;
260 }
261 return result;
262 }).catch(function (e) { return { error: e.message || String(e) }; }).then(function (result) {
263 if (active.current !== owner) { return; }
264 if (JSON.stringify(result).length > 500000) { result = { error: 'Result is too large. Request a shallower structure or individual block details.' }; }
265 setCurrent('');
266 owner.ack = { id: command.id, result: result };
267 owner.seen[command.id] = owner.ack;
268 setLog(function (previous) { return previous.map(function (item) {
269 return item.key === command.id ? Object.assign({}, item, { state: result.error ? 'error' : 'done', error: result.error, warn: result.warnings && result.warnings[0] }) : item;
270 }); });
271 });
272 }).catch(function (e) {
273 if (active.current !== owner) { return; }
274 if (e.status === 403 || e.status === 410) { disconnect(e.message); return; }
275 setError(__('Connection interrupted; retrying. Commands are not replayed.', 'blockenberg'));
276 }).then(function () {
277 if (active.current === owner) { owner.timer = setTimeout(function () { poll(owner); }, owner.ack ? 50 : 1500); }
278 });
279 }
280 function copy() {
281 if (!session || !promptFor(session, promptClient)) { return; }
282 var owner = active.current, field = promptField.current;
283 var text = field ? field.value : promptFor(session, promptClient);
284 setCopyStatus('');
285 function report(value) {
286 if (mounted.current && active.current === owner) { setCopyStatus(value); }
287 }
288 function fallback() {
289 if (!mounted.current || active.current !== owner) { return; }
290 var copied = false;
291 try {
292 if (field && field.isConnected) {
293 var doc = field.ownerDocument, previousFocus = doc.activeElement;
294 field.focus(); field.select(); field.setSelectionRange(0, text.length);
295 // Clipboard API is unavailable on local HTTP sites. Keep this
296 // synchronous with the click so the browser permits copying.
297 copied = !!doc.execCommand('copy');
298 if (copied && previousFocus && previousFocus.focus) { previousFocus.focus(); }
299 }
300 } catch (e) { /* Leave the prompt selected for manual copying. */ }
301 report(copied ? 'copied' : 'error');
302 }
303 try {
304 if (window.navigator.clipboard && typeof window.navigator.clipboard.writeText === 'function') {
305 Promise.resolve(window.navigator.clipboard.writeText(text)).then(function () { report('copied'); }, fallback);
306 return;
307 }
308 } catch (e) { /* A denied Clipboard API can still allow the selection fallback. */ }
309 fallback();
310 }
311 var update = progress && progress.progress;
312 var phase = update ? update.phase : 'waiting';
313 var stale = !!(progress && progress.agent_seen && progress.server_time - progress.agent_seen > 90);
314 var moving = !!current || (!!update && !stale && !error && ['planning', 'building', 'generating_image', 'reviewing'].indexOf(phase) !== -1);
315 var labels = phaseLabels();
316 var paired = !!(progress && progress.agent);
317 var needsPrompt = !!(session && !paired && promptFor(session, promptClient));
318 var live = {
319 session: !!session,
320 paired: paired,
321 agent: (progress && progress.agent) || '',
322 phase: phase,
323 phaseLabel: current ? labels.building : (labels[phase] || labels.waiting),
324 moving: moving,
325 stale: stale,
326 error: error,
327 current: current,
328 message: current || (update && update.message) || '',
329 plan: (update && update.plan) || [],
330 log: log,
331 run: run,
332 counts: (progress && progress.counts) || { actions: 0, images: 0 },
333 captures: captures,
334 images: (progress && progress.images) || [],
335 visualReview: (progress && progress.visual_review) || null
336 };
337 var last = live.log && live.log.length ? live.log[ live.log.length - 1 ] : null;
338 var key = session || busy ? [
339 session && session.id, paired, live.agent, live.phase, live.moving, live.stale, live.error,
340 live.current, live.message, ( live.log || [] ).length, last && last.key, last && last.state,
341 live.run && live.run.id, live.run && live.run.finishedAt,
342 live.counts && live.counts.actions, live.counts && live.counts.images, live.counts && live.counts.sections,
343 ( live.plan || [] ).map( function ( step ) { return step.id + ':' + step.status; } ).join( ',' ),
344 live.captures && live.captures.length, live.images && live.images.length
345 ].join( '\x1e' ) : '';
346 if ( key !== liveKeyRef.current ) {
347 liveKeyRef.current = key;
348 if ( callbacks.current.onProgress ) { callbacks.current.onProgress( session || busy ? live : null ); }
349 }
350 return el('div', { className: 'bkbg-ai-external-wrap' + (paired ? ' is-paired' : '') },
351 paired ? null : el('details', { className: 'bkbg-ai-external' + (session ? ' is-ready' : ''), open: needsPrompt ? true : undefined },
352 el('summary', null, session ? (needsPrompt ? __('Copy pairing prompt', 'blockenberg') : __('Connection', 'blockenberg')) : [el('span', { className: 'bkbg-ai-external-lead' }, __('Connect', 'blockenberg')), ' ', __('Codex, Claude or Cursor', 'blockenberg')], session ? el('span', { className: 'bkbg-ai-external-dot', 'aria-label': __('Session active', 'blockenberg') }) : null),
353 el('div', { className: 'bkbg-ai-external-body' },
354 el('p', null, session
355 ? __('Paste into your local Codex, Claude Code, or Cursor chat. The session lasts one hour.', 'blockenberg')
356 : __('Use a local Codex, Claude Code, or Cursor session. No OpenRouter key is needed.', 'blockenberg')),
357 status && ! session ? el('p', { role: 'status' }, status) : null,
358 error && ! session ? el('p', { role: 'alert', className: 'bkbg-ai-activity__error' }, error) : null,
359 needsPrompt ? el.apply(null, ['div', { className: 'bkbg-ai-external-clients', role: 'group', 'aria-label': __('Agent prompt', 'blockenberg') }].concat(CLIENTS.map(function (client) {
360 return el(wp.components.Button, {
361 key: client.id,
362 className: 'bkbg-ai-external-client',
363 variant: promptClient === client.id ? 'primary' : 'secondary',
364 'aria-pressed': promptClient === client.id,
365 onClick: function () { setPromptClient(client.id); setCopyStatus(''); }
366 }, client.label);
367 }))) : null,
368 needsPrompt ? el('textarea', { ref: promptField, className: 'bkbg-ai-external-prompt', readOnly: true, rows: 3, value: promptFor(session, promptClient), 'aria-label': __('Connection prompt', 'blockenberg'), onFocus: function (e) { e.target.select(); } }) : null,
369 needsPrompt && copyStatus ? el('p', { role: copyStatus === 'error' ? 'alert' : 'status', className: copyStatus === 'error' ? 'bkbg-ai-activity__error' : 'bkbg-ai-muted' }, copyStatus === 'error' ? __('Automatic copying is blocked. Select the prompt and press ⌘C on Mac or Ctrl+C on Windows/Linux.', 'blockenberg') : __('Copied. Paste into %s.', 'blockenberg').replace('%s', clientLabel(promptClient))) : null,
370 el('div', { className: 'bkbg-ai-external-buttons' },
371 !session ? el(wp.components.Button, { variant: 'secondary', disabled: busy || props.disabled, onClick: start }, busy ? __('Connecting…', 'blockenberg') : __('Create connection', 'blockenberg')) : null,
372 needsPrompt ? el(wp.components.Button, { variant: 'primary', onClick: copy }, copyStatus === 'copied' ? __('Copied!', 'blockenberg') : __('Copy prompt', 'blockenberg')) : null,
373 session ? el(wp.components.Button, { variant: 'tertiary', onClick: function () { disconnect(); } }, __('Cancel', 'blockenberg')) : null
374 )
375 )
376 ),
377 paired ? el('div', { className: 'bkbg-ai-external-buttons bkbg-ai-external-buttons--session' },
378 el(wp.components.Button, { className: 'bkbg-ai-external-disconnect', variant: 'tertiary', isDestructive: true, onClick: function () { disconnect(); } }, __('Disconnect', 'blockenberg'))
379 ) : null
380 );
381 }
382 window.bkbgExternalPanel = Panel;
383 })(window.wp);
384