import React, { useState, useEffect, useRef } from 'react'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Switch } from "@/components/ui/switch"; import { Button } from "@/components/ui/button"; import { Progress } from "@/components/ui/progress"; import { Checkbox } from "@/components/ui/checkbox"; import { Textarea } from "@/components/ui/textarea"; import { MultiSelect } from "@/components/ui/multi-select"; import { Alert, AlertDescription } from "@/components/ui/alert"; import { __ } from '@wordpress/i18n'; // Import the separated components import UrlProcessing from '@/components/aiconfig/UrlProcessing'; import PdfProcessing from '@/components/aiconfig/PdfProcessing'; import HelpQuestions from '@/components/aiconfig/HelpQuestions'; const AISK_API = (window.AiskSettings?.apiUrl || '/wp-json/aisk/v1').replace(/\/+$/, ''); const AiConfigSettings = ({ settings, updateSettings }) => { const [processing, setProcessing] = useState(false); const [progress, setProgress] = useState(0); const [processMessage, setProcessMessage] = useState(''); const [unprocessedCount, setUnprocessedCount] = useState(0); const [cleanupStatus, setCleanupStatus] = useState(''); const [posts, setPosts] = useState([]); const [pages, setPages] = useState([]); const [products, setProducts] = useState([]); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(''); const [isWooCommerceActive, setIsWooCommerceActive] = useState(false); const [isCleaningUp, setIsCleaningUp] = useState(false); const [successMessage, setSuccessMessage] = useState(null); // Track original values for contact info and custom content const [originalContactInfo, setOriginalContactInfo] = useState(''); const [originalCustomContent, setOriginalCustomContent] = useState(''); const [hasSettingsChanges, setHasSettingsChanges] = useState(false); // Use ref to track if original values have been initialized const hasInitialized = useRef(false); // Initialize REST URL and nonce const restUrl = window?.wpApiSettings?.root || window?.AiskSettings?.restUrl || '/wp-json/'; const nonce = window?.wpApiSettings?.nonce || window?.AiskSettings?.nonce; useEffect(() => { if (!nonce) { setError(__('Authentication token missing. Please refresh the page.', 'aisk-ai-chat')); return; } if (!restUrl) { setError(__('API endpoint not configured. Please refresh the page.', 'aisk-ai-chat')); return; } loadUnprocessedCount(); loadPostsAndPages(); // Check if WooCommerce is active using the global variable setIsWooCommerceActive(!!window?.AiskSettings?.isWooCommerceActive); }, []); // Set original values when settings are loaded (only once) useEffect(() => { if (!hasInitialized.current && settings.ai_config) { setOriginalContactInfo(settings.ai_config.contact_info || ''); setOriginalCustomContent(settings.ai_config.custom_content || ''); hasInitialized.current = true; } }, [settings.ai_config]); // Track changes in contact info and custom content useEffect(() => { if (hasInitialized.current) { const contactChanged = settings.ai_config.contact_info !== originalContactInfo; const contentChanged = settings.ai_config.custom_content !== originalCustomContent; if (contactChanged || contentChanged) { setHasSettingsChanges(true); } else { setHasSettingsChanges(false); } } }, [settings.ai_config.contact_info, settings.ai_config.custom_content, originalContactInfo, originalCustomContent]); const loadUnprocessedCount = async () => { try { if (!nonce) { throw new Error(__('Authentication token missing. Please refresh the page.', 'aisk-ai-chat')); } if (!restUrl) { throw new Error(__('API endpoint not configured. Please refresh the page.', 'aisk-ai-chat')); } const response = await fetch(`${restUrl}aisk/v1/get-unprocessed-count`, { method: 'GET', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': nonce, 'X-WP-Nonce-Valid': 'true' }, credentials: 'same-origin' }); // Check if we got redirected to login page const responseText = await response.text(); if (responseText.includes('wp-login.php') || responseText.includes('login')) { throw new Error(__('Authentication required. Please refresh the page and try again.', 'aisk-ai-chat')); } if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } // Try to parse as JSON let data; try { data = JSON.parse(responseText); } catch (e) { throw new Error(__('Invalid response format from server', 'aisk-ai-chat')); } if (data && typeof data === 'object') { if ('success' in data && 'count' in data) { setUnprocessedCount(data.count); } else if (data.message === __('No content available to process', 'aisk-ai-chat')) { setUnprocessedCount(0); } else { throw new Error(__('Invalid response structure from server', 'aisk-ai-chat')); } } else { throw new Error(__('Invalid response data from server', 'aisk-ai-chat')); } } catch (error) { setError(error.message || __('Failed to load unprocessed count. Please try again.', 'aisk-ai-chat')); setUnprocessedCount(0); } }; const loadPostsAndPages = async () => { setIsLoading(true); setError(''); try { // Separate promises for posts, pages, and products const postsPromise = fetch(`${restUrl}wp/v2/posts?per_page=100`, { headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': nonce }, credentials: 'same-origin' }); const pagesPromise = fetch(`${restUrl}wp/v2/pages?per_page=100`, { headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': nonce }, credentials: 'same-origin' }); // Handle posts and pages separately from products const [postsResponse, pagesResponse] = await Promise.all([postsPromise, pagesPromise]); const postsData = await postsResponse.json(); const pagesData = await pagesResponse.json(); // Set posts and pages regardless of WooCommerce setPosts(postsData.map(post => ({ value: post.id.toString(), label: post.title.rendered, type: 'post' }))); setPages(pagesData.map(page => ({ value: page.id.toString(), label: page.title.rendered, type: 'page' }))); // Only try to load products if WooCommerce is active if (window?.AiskSettings?.isWooCommerceActive) { try { const productsResponse = await fetch(`${restUrl}wc/v3/products?per_page=100`, { headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': nonce }, credentials: 'same-origin' }); const productsData = await productsResponse.json(); setProducts(productsData.map(product => ({ value: product.id.toString(), label: product.name, type: 'product' }))); } catch (error) { console.error('Error loading WooCommerce products:', error); // Don't set main error for WooCommerce issues } } // Only show no content error if both posts and pages are empty if (!postsData.length && !pagesData.length) { setError(__('No posts or pages found. Please create some content first.', 'aisk-ai-chat')); } } catch (error) { console.error('Error loading content:', error); setError(__('Failed to load posts and pages. Please try again.', 'aisk-ai-chat')); } finally { setIsLoading(false); } }; const processContent = async () => { try { setProcessing(true); setError(null); setSuccessMessage(null); if (!nonce) { throw new Error(__('Authentication token missing. Please refresh the page.', 'aisk-ai-chat')); } if (!restUrl) { throw new Error(__('API endpoint not configured. Please refresh the page.', 'aisk-ai-chat')); } // If there are settings changes, update the original values first if (hasSettingsChanges) { setOriginalContactInfo(settings.ai_config.contact_info || ''); setOriginalCustomContent(settings.ai_config.custom_content || ''); setHasSettingsChanges(false); } const response = await fetch(`${restUrl}aisk/v1/process-content`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': nonce, 'X-WP-Nonce-Valid': 'true' }, credentials: 'same-origin' }); if (!response.ok) { const errorText = await response.text(); console.error('Error response:', errorText); throw new Error(`Server returned status ${response.status}: ${errorText}`); } const contentType = response.headers.get('content-type'); if (!contentType || !contentType.includes('application/json')) { const text = await response.text(); console.error('Invalid content type:', contentType); console.error('Response text:', text); throw new Error(`Invalid content type: ${contentType}. Expected application/json`); } const data = await response.json(); if (data.success) { let message = __('Successfully processed content', 'aisk-ai-chat'); if (data.processed > 0) { message += `: ${data.processed} ${__('items processed', 'aisk-ai-chat')}`; if (data.total > 0) { message += `, ${data.total} ${__('remaining', 'aisk-ai-chat')}`; } } else { message += `: ${__('No items to process', 'aisk-ai-chat')}`; } setSuccessMessage(message); if (data.errors && data.errors.length > 0) { console.warn('Processing completed with errors:', data.errors); // Show errors in a non-blocking way data.errors.forEach(error => { console.error('Processing error:', error); }); } // Refresh unprocessed count after processing await loadUnprocessedCount(); } else { throw new Error(data.message || __('Failed to process content', 'aisk-ai-chat')); } } catch (error) { console.error('Error processing content:', error); setError(error.message || __('Error processing content', 'aisk-ai-chat')); } finally { setProcessing(false); } }; // Function to handle content exclusion updates with cleanup const handleExclusionUpdate = async (section, field, selected) => { // First update the settings in state updateSettings(section, field, selected); // Then clean up the excluded content from embeddings await cleanupExcludedEmbeddings(); }; // Function to clean up excluded content embeddings const cleanupExcludedEmbeddings = async () => { setIsCleaningUp(true); setCleanupStatus(__('Removing embeddings for excluded content...', 'aisk-ai-chat')); try { const response = await fetch(`${AISK_API}/cleanup-excluded-embeddings`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': nonce } }); const data = await response.json(); if (data.success) { setCleanupStatus(data.message || __('Successfully removed embeddings for excluded content.', 'aisk-ai-chat')); // Refresh unprocessed count after cleanup await loadUnprocessedCount(); } else { setCleanupStatus(`${__('Error:', 'aisk-ai-chat')} ${data.message || __('Failed to remove embeddings for excluded content.', 'aisk-ai-chat')}`); } } catch (error) { console.error('Error cleaning up excluded embeddings:', error); setCleanupStatus(`${__('Error:', 'aisk-ai-chat')} ${error.message || __('Failed to remove embeddings for excluded content.', 'aisk-ai-chat')}`); } finally { // Clear status message after a delay setTimeout(() => { setCleanupStatus(''); setIsCleaningUp(false); }, 3000); } }; return ( {__('AI Configuration', 'aisk-ai-chat')} {__('Configure AI settings and process your content for the chatbot', 'aisk-ai-chat')} {/* WooCommerce Integration */}

{__('Enable AI processing for WooCommerce products', 'aisk-ai-chat')}

{ updateSettings('ai_config', 'woocommerce_enabled', checked); }} /> {!isWooCommerceActive && ( {__('Install WooCommerce', 'aisk-ai-chat')} )} {processing && processMessage && (

{processMessage}

)}
{settings.ai_config.woocommerce_enabled && isWooCommerceActive && (
handleExclusionUpdate('ai_config', 'excluded_products', selected)} placeholder={isLoading ? __('Loading products...', 'aisk-ai-chat') : products.length ? settings.ai_config.excluded_products?.length ? '' : __('Select products to exclude...', 'aisk-ai-chat') : __('No products found', 'aisk-ai-chat') } disabled={isLoading || !products.length || isCleaningUp} type="product" />
)} {/* Content Types */}
{ const types = checked ? [...settings.ai_config.included_post_types, 'post'] : settings.ai_config.included_post_types.filter(t => t !== 'post'); updateSettings('ai_config', 'included_post_types', types); }} />
{ const types = checked ? [...settings.ai_config.included_post_types, 'page'] : settings.ai_config.included_post_types.filter(t => t !== 'page'); updateSettings('ai_config', 'included_post_types', types); }} />
{error ? ( {error} ) : (
{settings.ai_config.included_post_types.includes('post') && (
handleExclusionUpdate('ai_config', 'excluded_posts', selected)} placeholder={isLoading ? __('Loading posts...', 'aisk-ai-chat') : posts.length ? settings.ai_config.excluded_posts?.length ? '' : __('Select posts to exclude...', 'aisk-ai-chat') : __('No posts found', 'aisk-ai-chat') } disabled={isLoading || !posts.length || isCleaningUp} type="post" />
)} {settings.ai_config.included_post_types.includes('page') && (
handleExclusionUpdate('ai_config', 'excluded_pages', selected)} placeholder={isLoading ? __('Loading pages...', 'aisk-ai-chat') : pages.length ? settings.ai_config.excluded_pages?.length ? '' : __('Select pages to exclude...', 'aisk-ai-chat') : __('No pages found', 'aisk-ai-chat') } disabled={isLoading || !pages.length || isCleaningUp} type="page" />
)}
)}
{/* External Sources Section - Using the separated UrlProcessing component */} {/* PDF Sources Section - Using the separated PdfProcessing component */} {/* Contact Information */}
{settings.ai_config.contact_info !== originalContactInfo && ( {__('Modified', 'aisk-ai-chat')} )}