PluginProbe
AI Builder – Generate pages, blocks, images & translate with AI / 2.1.2
AI Builder – Generate pages, blocks, images & translate with AI v2.1.2
2.7.10 2.7.9 2.7.8 2.0.8 2.0.9 2.1.0 2.1.1 2.1.10 2.1.11 2.1.12 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 2.1.7 2.1.8 2.1.9 2.2.0 2.2.1 2.2.2 2.2.3 2.2.4 2.3.0 2.3.10 All 122 releases
ai-builder / assets / js / multi-page.js

multi-page.js in AI Builder – Generate pages, blocks, images & translate with AI 2.1.2, at assets/js/multi-page.js

725 lines 27.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 let currentPageCount = 1;
2 let currentContentType = 'page';
3 let generationInProgress = false;
4 let generatedPages = [];
5 let isPremiumUser = false;
6
7 function showMessage(message, type = "info") {
8 console.log(`Showing message: ${message} (type: ${type})`);
9
10 let container = document.getElementById("message-container");
11
12 // Si le container n'existe pas, le créer
13 if (!container) {
14 container = document.createElement("div");
15 container.id = "message-container";
16 const multiPageContainer = document.querySelector(".ai-multi-page-container");
17 if (multiPageContainer) {
18 multiPageContainer.appendChild(container);
19 } else {
20 console.error('Multi-page container not found');
21 return;
22 }
23 }
24
25 container.innerHTML = `<div class="ai-message ${type}">${message}</div>`;
26 container.scrollIntoView({ behavior: "smooth" });
27
28 // Auto-hide after 5 seconds for info messages
29 if (type === "info") {
30 setTimeout(() => {
31 if (container && container.parentNode) {
32 container.innerHTML = '';
33 }
34 }, 5000);
35 }
36 }
37
38
39 function getCreditsPageUrl() {
40 const base = (typeof aiBuilderVars !== 'undefined' && aiBuilderVars.adminBaseUrl)
41 ? aiBuilderVars.adminBaseUrl
42 : '/wp-admin/';
43 return `${base}admin.php?page=aibui-credits`;
44 }
45
46 function renderPremiumWarning(message) {
47 const warningContainer = document.getElementById('premium-warning-container');
48 if (!warningContainer) {
49 return;
50 }
51
52 warningContainer.innerHTML = `
53 <div class="ai-premium-warning">
54 <strong>${message}</strong>
55 <a class="ai-primary-btn" href="${getCreditsPageUrl()}">Go to Credits</a>
56 </div>
57 `;
58 }
59
60 function disableMultiPageAccess(message) {
61 const content = document.querySelector('.ai-multi-page-content');
62 if (content) {
63 content.classList.add('ai-locked');
64 const interactiveElements = content.querySelectorAll('button, select, textarea, input');
65 interactiveElements.forEach((element) => {
66 element.setAttribute('disabled', 'disabled');
67 element.setAttribute('aria-disabled', 'true');
68 });
69 }
70
71 renderPremiumWarning(message);
72 showMessage(message, 'error');
73 }
74
75 async function ensurePremiumAccess() {
76 try {
77 const tokenResponse = await fetch(ajaxurl, {
78 method: "POST",
79 headers: { "Content-Type": "application/x-www-form-urlencoded" },
80 body: "action=aibui_get_token&nonce=" + aiBuilderVars.nonce,
81 });
82
83 const tokenData = await tokenResponse.json();
84 if (!tokenData.success || !tokenData.data || !tokenData.data.token) {
85 disableMultiPageAccess("Please sign in to access the Multi Page Generator.");
86 return false;
87 }
88
89 const jwtToken = tokenData.data.token;
90 const response = await fetch(`${window.config.apiUrl}/user/profile`, {
91 method: "GET",
92 headers: {
93 Authorization: `Bearer ${jwtToken}`,
94 "Content-Type": "application/json",
95 },
96 });
97
98 if (!response.ok) {
99 disableMultiPageAccess("Unable to verify your subscription. Please refresh the page.");
100 return false;
101 }
102
103 const profile = await response.json();
104 const user = profile?.user || profile?.data || profile;
105 const plan = (user?.plan || '').toLowerCase();
106
107 if (plan !== 'premium' && plan !== 'creator') {
108 disableMultiPageAccess("Multi Page Generator is available for Premium subscribers only.");
109 return false;
110 }
111
112 isPremiumUser = true;
113 return true;
114 } catch (error) {
115 console.error("Error verifying premium access:", error);
116 disableMultiPageAccess("Unable to verify your subscription at this time. Please try again later.");
117 return false;
118 }
119 }
120
121 function setLoading(buttonId, isLoading) {
122 const button = document.getElementById(buttonId);
123
124 if (!button) {
125 console.error(`Button with id '${buttonId}' not found`);
126 return;
127 }
128
129 let loading = button.querySelector(".ai-loading");
130
131 // Si l'élément loading n'existe pas, le créer
132 if (!loading) {
133 loading = document.createElement("span");
134 loading.className = "ai-loading";
135 loading.style.display = "none";
136 button.appendChild(loading);
137 }
138
139 // Stocker le texte original dans un attribut data si pas déjà fait
140 if (!button.dataset.originalText) {
141 button.dataset.originalText = button.textContent.trim();
142 }
143
144 if (isLoading) {
145 // Ensure layout centers spinner and text
146 button.style.display = 'inline-flex';
147 button.style.alignItems = 'center';
148 button.style.justifyContent = 'center';
149
150 loading.style.display = "inline-block";
151 loading.style.marginRight = '8px';
152
153 button.disabled = true;
154 button.textContent = "";
155 button.appendChild(loading);
156 const textSpan = document.createElement('span');
157 textSpan.className = 'ai-loading-text';
158 textSpan.style.marginLeft = '4px';
159 textSpan.textContent = button.dataset.originalText;
160 button.appendChild(textSpan);
161 } else {
162 loading.style.display = "none";
163 button.disabled = false;
164 button.style.display = '';
165 button.style.alignItems = '';
166 button.style.justifyContent = '';
167 button.textContent = button.dataset.originalText;
168 }
169 }
170
171 function updateTotalCost() {
172 const pageCountSelect = document.getElementById('page-count');
173 if (!pageCountSelect) {
174 console.error('Page count select element not found');
175 return;
176 }
177
178 const pageCount = parseInt(pageCountSelect.value);
179 const totalCost = pageCount * 75;
180
181 const totalCostElement = document.getElementById('total-cost');
182 if (!totalCostElement) {
183 console.error('Total cost element not found');
184 return;
185 }
186
187 totalCostElement.textContent = `${totalCost} credits`;
188 console.log(`Updating cost: ${pageCount} pages × 75 = ${totalCost} credits`);
189 }
190
191 function generatePagePrompts() {
192 if (!isPremiumUser) {
193 showMessage('Premium subscription required to use the Multi Page Generator.', 'error');
194 return;
195 }
196
197 const pageCount = parseInt(document.getElementById('page-count').value);
198 const container = document.getElementById('pages-prompts-container');
199
200 container.innerHTML = '';
201
202 for (let i = 1; i <= pageCount; i++) {
203 const pageDiv = document.createElement('div');
204 pageDiv.className = 'ai-page-prompt-item';
205 pageDiv.innerHTML = `
206 <div class="ai-form-group">
207 <label for="page-${i}-prompt">Page ${i} Prompt</label>
208 <textarea
209 id="page-${i}-prompt"
210 name="page_${i}_prompt"
211 placeholder="Describe what you want for page ${i}..."
212 maxlength="1500"
213 required
214 ></textarea>
215 <div class="ai-char-counter">
216 <span id="page-${i}-counter">0</span>/1500 characters
217 </div>
218 </div>
219 `;
220 container.appendChild(pageDiv);
221
222 // Ajouter le listener pour le compteur de caractères
223 const textarea = pageDiv.querySelector('textarea');
224 const counter = pageDiv.querySelector('.ai-char-counter span');
225
226 textarea.addEventListener('input', function () {
227 counter.textContent = this.value.length;
228 if (this.value.length > 1400) {
229 counter.style.color = '#ff6b6b';
230 } else if (this.value.length > 1200) {
231 counter.style.color = '#ffa726';
232 } else {
233 counter.style.color = '#666';
234 }
235 });
236 }
237
238 document.getElementById('pages-config-section').style.display = 'block';
239 document.getElementById('generate-pages-btn').style.display = 'block';
240 }
241
242 function validatePrompts() {
243 const pageCount = parseInt(document.getElementById('page-count').value);
244
245 for (let i = 1; i <= pageCount; i++) {
246 const prompt = document.getElementById(`page-${i}-prompt`).value.trim();
247 if (!prompt) {
248 showMessage(`Please enter a prompt for page ${i}`, 'error');
249 return false;
250 }
251 if (prompt.length > 1500) {
252 showMessage(`Prompt for page ${i} is too long (max 1500 characters)`, 'error');
253 return false;
254 }
255 }
256
257 return true;
258 }
259
260 async function generatePages() {
261 if (!isPremiumUser) {
262 showMessage('Premium subscription required to use the Multi Page Generator.', 'error');
263 return;
264 }
265
266 if (generationInProgress) return;
267
268 if (!validatePrompts()) return;
269
270 generationInProgress = true;
271 setLoading('generate-pages-btn', true);
272
273 const pageCount = parseInt(document.getElementById('page-count').value);
274 const contentType = document.getElementById('content-type').value;
275
276 // Collect prompts
277 const prompts = [];
278 for (let i = 1; i <= pageCount; i++) {
279 prompts.push(document.getElementById(`page-${i}-prompt`).value.trim());
280 }
281
282 // Show progress section
283 document.getElementById('generation-progress-section').style.display = 'block';
284 const progressContainer = document.getElementById('generation-progress');
285 progressContainer.innerHTML = '';
286
287 // Create progress items
288 for (let i = 1; i <= pageCount; i++) {
289 const progressItem = document.createElement('div');
290 progressItem.className = 'ai-progress-item';
291 progressItem.id = `progress-${i}`;
292 progressItem.innerHTML = `
293 <div class="ai-progress-header">
294 <span class="ai-progress-title">Page ${i}</span>
295 <span class="ai-progress-status" id="status-${i}">Waiting...</span>
296 </div>
297 <div class="ai-progress-bar">
298 <div class="ai-progress-fill" id="fill-${i}"></div>
299 </div>
300 `;
301 progressContainer.appendChild(progressItem);
302 }
303
304 // Generate pages one by one
305 const results = [];
306
307 for (let i = 0; i < pageCount; i++) {
308 const pageNumber = i + 1;
309 const statusElement = document.getElementById(`status-${pageNumber}`);
310 const fillElement = document.getElementById(`fill-${pageNumber}`);
311
312 try {
313 statusElement.textContent = 'Generating...';
314 statusElement.style.color = '#ffa726';
315 fillElement.style.width = '0%';
316 fillElement.style.backgroundColor = '#ffa726';
317
318 const result = await generateSinglePage(prompts[i], contentType, pageNumber);
319
320 if (result.success) {
321 statusElement.textContent = 'Completed';
322 statusElement.style.color = '#4caf50';
323 fillElement.style.width = '100%';
324 fillElement.style.backgroundColor = '#4caf50';
325 results.push(result);
326 } else {
327 statusElement.textContent = result.error || 'Failed';
328 statusElement.style.color = '#f44336';
329 fillElement.style.backgroundColor = '#f44336';
330 results.push({ success: false, error: result.error });
331 }
332 } catch (error) {
333 console.error(`Error generating page ${pageNumber}:`, error);
334 statusElement.textContent = 'Error';
335 statusElement.style.color = '#f44336';
336 fillElement.style.backgroundColor = '#f44336';
337 results.push({ success: false, error: error.message });
338 }
339
340 // Small delay between pages
341 if (i < pageCount - 1) {
342 await new Promise(resolve => setTimeout(resolve, 1000));
343 }
344 }
345
346 // Show results
347 showResults(results);
348
349 generationInProgress = false;
350 setLoading('generate-pages-btn', false);
351 }
352
353 // Fonction pour simuler la barre de progression
354 function simulateProgress(fillElement, durationMs = 45000) {
355 let currentProgress = 0;
356 const targetProgress = 90;
357 const startTime = Date.now();
358 let progressInterval;
359 let slowProgressInterval;
360 let isPhase2 = false;
361
362 // Phase 1: 0% à 90% en 30 secondes de manière saccadée
363 progressInterval = setInterval(() => {
364 if (isPhase2) return;
365
366 const elapsed = Date.now() - startTime;
367 const baseProgress = Math.min((elapsed / durationMs) * targetProgress, targetProgress);
368
369 // Ajouter des variations saccadées (uniquement positives pour ne pas reculer)
370 const jitter = Math.random() * 2; // Entre 0% et 2%
371 currentProgress = Math.min(baseProgress + jitter, targetProgress);
372
373 // S'assurer que la progression ne recule jamais
374 const currentWidth = parseFloat(fillElement.style.width) || 0;
375 currentProgress = Math.max(currentProgress, currentWidth);
376
377 fillElement.style.width = currentProgress + '%';
378
379 // Quand on arrive à 90%, passer à la phase lente
380 if (currentProgress >= targetProgress) {
381 clearInterval(progressInterval);
382 isPhase2 = true;
383
384 // Phase 2: Avancer très lentement de 90% vers ~95%
385 slowProgressInterval = setInterval(() => {
386 currentProgress = Math.min(currentProgress + 0.05, 95);
387 fillElement.style.width = currentProgress + '%';
388 }, 1000); // +0.05% toutes les secondes (très lent)
389 }
390 }, 200); // Mise à jour toutes les 200ms pour un effet fluide mais saccadé
391
392 // Fonction pour compléter à 100%
393 return {
394 complete: () => {
395 clearInterval(progressInterval);
396 clearInterval(slowProgressInterval);
397 fillElement.style.width = '100%';
398 }
399 };
400 }
401
402 async function generateSinglePage(prompt, contentType, pageNumber) {
403 if (!isPremiumUser) {
404 return { success: false, error: 'Premium subscription required' };
405 }
406
407 // Démarrer la simulation de progression
408 const fillElement = document.getElementById(`fill-${pageNumber}`);
409 const progressSimulator = simulateProgress(fillElement);
410
411 try {
412 // Get JWT token
413 const tokenResponse = await fetch(ajaxurl, {
414 method: "POST",
415 headers: { "Content-Type": "application/x-www-form-urlencoded" },
416 body: "action=aibui_get_token&nonce=" + aiBuilderVars.nonce,
417 });
418
419 const tokenData = await tokenResponse.json();
420
421 if (!tokenData.success || !tokenData.data.token) {
422 progressSimulator.complete();
423 throw new Error("No authentication token found");
424 }
425
426 const jwtToken = tokenData.data.token;
427
428 // Call the AI API (JSON blocks response)
429 const response = await fetch(`${window.config.apiUrl}/ai-transform-page/v2-page-generation`, {
430 method: "POST",
431 headers: {
432 "Content-Type": "application/json",
433 Authorization: `Bearer ${jwtToken}`,
434 },
435 body: JSON.stringify({
436 userPrompt: prompt,
437 pageContent: "", // Kept for compatibility
438 }),
439 });
440
441 const data = await response.json();
442
443 if (response.status === 402) {
444 progressSimulator.complete();
445 return { success: false, error: "Not enough credits" };
446 }
447
448 if (data.error === "not-enough-credits") {
449 progressSimulator.complete();
450 return { success: false, error: "Not enough credits" };
451 }
452
453 if (!data.pageContent) {
454 progressSimulator.complete();
455 return { success: false, error: "No content generated" };
456 }
457 // Save generation (Pending review)
458 const generationPayload = {
459 id: `${Date.now()}_${Math.random().toString(36).slice(2, 10)}`,
460 title: data.postTitle || `Generated Page ${pageNumber}`,
461 metaDesc: data.postMetaDesc || '',
462 cssContent: data.cssContent || '',
463 blocksJson: Array.isArray(data.pageContent) ? data.pageContent : [],
464 };
465 const saveRes = await fetch(ajaxurl, {
466 method: "POST",
467 headers: { "Content-Type": "application/x-www-form-urlencoded" },
468 body: `action=aibui_save_generation&nonce=${aiBuilderVars.nonce}&payload=${encodeURIComponent(JSON.stringify(generationPayload))}`,
469 });
470 const saveData = await saveRes.json();
471 progressSimulator.complete();
472 if (!saveData.success) {
473 return { success: false, error: saveData.data || 'Failed to save generation' };
474 }
475 return {
476 success: true,
477 generation: saveData.data,
478 pageTitle: generationPayload.title,
479 };
480
481 } catch (error) {
482 console.error("Error generating page:", error);
483 progressSimulator.complete();
484 return { success: false, error: error.message };
485 }
486 }
487
488 function showResults(results) {
489 const resultsSection = document.getElementById('results-section');
490 const resultsList = document.getElementById('generated-pages-list');
491
492 resultsSection.style.display = 'block';
493 resultsList.innerHTML = '';
494
495 let successCount = 0;
496
497 results.forEach((result, index) => {
498 const pageNumber = index + 1;
499 const resultItem = document.createElement('div');
500 resultItem.className = 'ai-result-item';
501
502 if (result.success && result.generation) {
503 successCount++;
504 const gen = result.generation;
505 const base = (typeof aiBuilderVars !== 'undefined' && aiBuilderVars.adminBaseUrl) ? aiBuilderVars.adminBaseUrl : '/wp-admin/';
506 const applyUrl = `${base}post-new.php?post_type=${currentContentType}&aibui_gen_id=${encodeURIComponent(gen.id)}`;
507 resultItem.innerHTML = `
508 <div class="ai-result-success">
509 <svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
510 <path d="M9 12L11 14L15 10" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
511 <circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="2"/>
512 </svg>
513 <div class="ai-result-content">
514 <h4>Page ${pageNumber}: ${gen.title}</h4>
515 <div class="ai-actions">
516 <span class="ai-status">Status: Pending review</span>
517 <a href="${applyUrl}" class="ai-page-link" target="_blank" rel="noopener">Open and create page</a>
518 </div>
519 </div>
520 </div>
521 `;
522 } else {
523 resultItem.innerHTML = `
524 <div class="ai-result-error">
525 <svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
526 <circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="2"/>
527 <path d="M15 9L9 15" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
528 <path d="M9 9L15 15" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
529 </svg>
530 <div class="ai-result-content">
531 <h4>Page ${pageNumber}: Failed</h4>
532 <p class="ai-error-message">${result.error}</p>
533 </div>
534 </div>
535 `;
536 }
537
538 resultsList.appendChild(resultItem);
539 });
540
541 // Show summary message
542 if (successCount > 0) {
543 showMessage(`Successfully generated ${successCount} out of ${results.length} pages!`, 'success');
544 } else {
545 showMessage('No pages were generated successfully.', 'error');
546 }
547 }
548
549 function resetForm() {
550 document.getElementById('multi-page-form').reset();
551 document.getElementById('pages-config-section').style.display = 'none';
552 document.getElementById('generation-progress-section').style.display = 'none';
553 document.getElementById('results-section').style.display = 'none';
554 document.getElementById('generate-pages-btn').style.display = 'none';
555 updateTotalCost();
556 }
557
558 // Event listeners
559 document.addEventListener("DOMContentLoaded", function () {
560 (async () => {
561 console.log('Multi-page generator loaded');
562
563 // Debug: Check if config is loaded
564 console.log('window.config:', window.config);
565 console.log('window.config.apiUrl:', window.config?.apiUrl);
566
567 if (!window.config || !window.config.apiUrl) {
568 console.warn('Config not loaded, using fallback configuration');
569 // Fallback configuration
570 window.config = {
571 apiUrl: "https://api.wordpress-ai-builder.com/api"
572 };
573 console.log('Using fallback config:', window.config);
574 }
575
576 // Debug: Check if CSS is loaded
577 const testElement = document.createElement('div');
578 testElement.className = 'ai-multi-page-container';
579 testElement.style.display = 'none';
580 document.body.appendChild(testElement);
581 const computedStyle = window.getComputedStyle(testElement);
582 console.log('CSS loaded:', computedStyle.fontFamily !== '');
583 document.body.removeChild(testElement);
584
585 // Debug: Check if AJAX variables are available
586 console.log('ajaxurl:', window.ajaxurl);
587 console.log('aiBuilderVars:', window.aiBuilderVars);
588
589 if (!window.ajaxurl || !window.aiBuilderVars) {
590 console.error('AJAX variables not loaded!');
591 showMessage('AJAX configuration not loaded. Please refresh the page.', 'error');
592 return;
593 }
594
595 const hasPremiumAccess = await ensurePremiumAccess();
596 if (!hasPremiumAccess) {
597 return;
598 }
599
600 // Debug: Check if elements exist
601 const pageCountSelect = document.getElementById('page-count');
602 const totalCostElement = document.getElementById('total-cost');
603 const startBtn = document.getElementById('start-generation-btn');
604
605 console.log('Page count select found:', !!pageCountSelect);
606 console.log('Total cost element found:', !!totalCostElement);
607 console.log('Start button found:', !!startBtn);
608
609 if (pageCountSelect) {
610 // Page count change
611 pageCountSelect.addEventListener('change', function () {
612 console.log('Page count changed to:', this.value);
613 updateTotalCost();
614 });
615 } else {
616 console.error('Page count select element not found');
617 }
618
619 // Content type change
620 const contentTypeSelect = document.getElementById('content-type');
621 if (contentTypeSelect) {
622 contentTypeSelect.addEventListener('change', function () {
623 currentContentType = this.value;
624 console.log('Content type changed to:', this.value);
625 });
626 }
627
628 // Start generation button
629 if (startBtn) {
630 startBtn.addEventListener('click', function (e) {
631 e.preventDefault();
632 console.log('Start generation button clicked');
633 generatePagePrompts();
634 });
635 } else {
636 console.error('Start generation button not found');
637 }
638
639 // Generate pages button
640 const generateBtn = document.getElementById('generate-pages-btn');
641 if (generateBtn) {
642 generateBtn.addEventListener('click', generatePages);
643 }
644
645 // Generate more button
646 const generateMoreBtn = document.getElementById('generate-more-btn');
647 if (generateMoreBtn) {
648 generateMoreBtn.addEventListener('click', resetForm);
649 }
650
651 // Initialize
652 console.log('Initializing total cost...');
653 updateTotalCost();
654
655 // Load existing generations history and render sections
656 (async function loadGenerationsHistory() {
657 try {
658 const res = await fetch(ajaxurl, {
659 method: 'POST',
660 headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
661 body: `action=aibui_get_generations&nonce=${aiBuilderVars.nonce}`,
662 });
663 const data = await res.json();
664 if (!data.success) return;
665 const gens = Array.isArray(data.data) ? data.data : [];
666
667 let history = document.getElementById('history-section');
668 if (!history) {
669 history = document.createElement('div');
670 history.id = 'history-section';
671 history.innerHTML = `
672 <h3 style="margin-top:24px;">Generations history</h3>
673 <div class="ai-history">
674 <div class="ai-history-pending">
675 <h4>Pending review</h4>
676 <div id="history-pending-list"></div>
677 </div>
678 <div class="ai-history-applied" style="margin-top:16px;">
679 <h4>Applied</h4>
680 <div id="history-applied-list"></div>
681 </div>
682 </div>`;
683 const container = document.querySelector('.ai-multi-page-container') || document.body;
684 container.appendChild(history);
685 }
686 const pendingList = document.getElementById('history-pending-list');
687 const appliedList = document.getElementById('history-applied-list');
688 if (!pendingList || !appliedList) return;
689 pendingList.innerHTML = '';
690 appliedList.innerHTML = '';
691
692 const adminBase = (typeof aiBuilderVars !== 'undefined' && aiBuilderVars.adminBaseUrl) ? aiBuilderVars.adminBaseUrl : '/wp-admin/';
693
694 gens.forEach((g) => {
695 const wrap = document.createElement('div');
696 wrap.className = 'ai-history-item';
697 wrap.style.cssText = 'display:flex;align-items:center;padding:10px;border:1px solid #e5e7eb;border-radius:6px;margin-bottom:8px;gap:12px;';
698 const title = document.createElement('div');
699 title.innerHTML = `<strong>${g.title || 'Untitled'}</strong><br><small>${g.createdAt || ''}</small>`;
700 const actions = document.createElement('div');
701 actions.style.cssText = 'margin-left:auto;display:flex;align-items:center;gap:10px;justify-content:flex-end;';
702
703 if (g.applied && g.pageId) {
704 const editUrl = `${adminBase}post.php?post=${encodeURIComponent(g.pageId)}&action=edit`;
705 actions.innerHTML = `<span class="ai-status">Status: Applied</span>
706 <a href="${editUrl}" target="_blank" rel="noopener" class="ai-page-link">Open page</a>`;
707 wrap.appendChild(title);
708 wrap.appendChild(actions);
709 appliedList.appendChild(wrap);
710 } else {
711 const applyUrl = `${adminBase}post-new.php?post_type=${currentContentType}&aibui_gen_id=${encodeURIComponent(g.id)}`;
712 actions.innerHTML = `<span class="ai-status">Status: Pending review</span>
713 <a href="${applyUrl}" target="_blank" rel="noopener" class="ai-page-link">Open and create page</a>`;
714 wrap.appendChild(title);
715 wrap.appendChild(actions);
716 pendingList.appendChild(wrap);
717 }
718 });
719 } catch (e) {
720 // silent
721 }
722 })();
723 })();
724 });
725