HEX
Server: LiteSpeed
System: Linux server240.web-hosting.com 4.18.0-553.45.1.lve.el8.x86_64 #1 SMP Wed Mar 26 12:08:09 UTC 2025 x86_64
User: creaqbdc (8964)
PHP: 8.0.30
Disabled: NONE
Upload Files
File: //proc/self/cwd/wp-content/plugins/essential-blocks/src/write-with-ai/WriteAiButton.js
/**
 * WordPress dependencies
 */
import { useState, useEffect } from "@wordpress/element";
import { __ } from "@wordpress/i18n";
import { Button, Popover, TextControl, SelectControl, TextareaControl, ToggleControl } from "@wordpress/components";
import { createBlock } from "@wordpress/blocks";
import { dispatch, useDispatch, select, useSelect } from "@wordpress/data";

import AiSvgIcon from "./ai-write-icon";

const WriteAIButton = () => {
    const [isVisible, setIsVisible] = useState(false);
    const [loading, setLoading] = useState(false);
    const [error, setError] = useState(false);
    const [topic, setTopic] = useState('');
    const [prompt, setPrompt] = useState('');
    const [keywords, setKeywords] = useState('');
    const [contentLength, setContentLength] = useState('medium');
    const [tone, setTone] = useState('informative');
    const [overwriteContent, setOverwriteContent] = useState(false);
    const [isPromptGenerated, setIsPromptGenerated] = useState(false);

    const handleClick = () => {
        setIsVisible(!isVisible);
    };

    /**
     * Get tone instructions based on selected tone
     *
     * @param {string} tone - The selected tone
     * @return {string} - Tone instructions
     */
    const getToneInstructions = (tone) => {
        switch (tone) {
            case 'casual':
                return "conversational and friendly, using a casual tone";
            case 'formal':
                return "professional and formal, using proper language";
            case 'persuasive':
                return "persuasive and compelling, designed to convince the reader";
            case 'informative':
            default:
                return "informative and educational, focusing on providing valuable information";
        }
    };

    /**
     * Get length instructions based on selected length
     *
     * @param {string} length - The selected length
     * @return {string} - Length instructions
     */
    const getLengthInstructions = (length) => {
        switch (length) {
            case 'short':
                return "Keep the content concise and to the point, around 150-250 words.";
            case 'long':
                return "Create comprehensive content with detailed explanations, around 500-800 words.";
            case 'medium':
            default:
                return "Write a moderate-length content of approximately 300-500 words.";
        }
    };



    /**
     * Generate the full prompt based on topic, keywords, tone, and length
     *
     * @param {string} topic - The topic to write about
     * @param {string} keywords - Optional keywords to include
     * @param {string} tone - The selected tone
     * @param {string} length - The selected length
     * @return {string} - Full prompt
     */
    const generateFullPrompt = (topic, keywords, tone, length) => {
        if (!topic || !topic.trim()) {
            return '';
        }

        const toneInstructions = getToneInstructions(tone);
        const lengthInstructions = getLengthInstructions(length);

        let prompt = `Generate content using proper HTML structure for '${topic}'. `;

        if (keywords && keywords.trim()) {
            prompt += `Include relevant details based on ${keywords} throughout the content. `;
        } else {
            prompt += `Include relevant details throughout the content. `;
        }

        prompt += `Ensure all text is enclosed with <p> tags, use appropriate heading tags (<h1>, <h2>, etc.) for structure, and apply a <span> tag with the class 'highlight' to important terms for emphasis. `;

        prompt += `The content should be ${toneInstructions}. ${lengthInstructions} `;

        return prompt;
    };

    /**
     * Parse content into blocks based on headings and paragraphs
     *
     * @param {string} content - The content to parse
     * @return {Array} - Array of blocks
     */
    const parseContentIntoBlocks = (content) => {

        // Split content by line breaks
        const lines = content.split(/\r?\n/);
        const blocks = [];
        let currentParagraph = '';

        // Helper function to process bold text marked with **
        const processBoldText = (text) => {
            // Regular expression to match text between ** (but not including the **)
            const boldRegex = /\*\*(.*?)\*\*/g;

            // Replace all occurrences of **text** with <strong>text</strong>
            return text.replace(boldRegex, '<strong>$1</strong>');
        };

        // Helper function to add a paragraph block
        const addParagraphBlock = (text) => {
            if (text.trim()) {
                // Process bold text before creating the block
                const processedText = processBoldText(text.trim());

                blocks.push(
                    createBlock('essential-blocks/text', {
                        tagName: 'p',
                        text: processedText,
                        source: 'custom'
                    })
                );
            }
        };

        // Process each line
        for (let i = 0; i < lines.length; i++) {
            const line = lines[i].trim();

            // Skip empty lines
            if (!line) {
                // If we have accumulated paragraph content, add it as a block
                if (currentParagraph) {
                    addParagraphBlock(currentParagraph);
                    currentParagraph = '';
                }
                continue;
            }

            // Check for HTML-style headings (<h1>, <h2>, etc.)
            const htmlHeadingMatch = line.match(/<h([1-6])>(.*?)<\/h\1>/i);
            if (htmlHeadingMatch) {
                // If we have accumulated paragraph content, add it as a block first
                if (currentParagraph) {
                    addParagraphBlock(currentParagraph);
                    currentParagraph = '';
                }

                // Add heading block - process bold text in headings
                blocks.push(
                    createBlock('essential-blocks/advanced-heading', {
                        tagName: 'h' + htmlHeadingMatch[1],
                        titleText: processBoldText(htmlHeadingMatch[2].trim()),
                        source: 'custom'
                    })
                );
                continue;
            }

            // Check if line is a heading (starts with # or ##)
            const markdownHeadingMatch = line.match(/^(#{1,6})\s+(.+)$/);
            if (markdownHeadingMatch) {
                // If we have accumulated paragraph content, add it as a block first
                if (currentParagraph) {
                    addParagraphBlock(currentParagraph);
                    currentParagraph = '';
                }

                // Add heading block
                blocks.push(
                    createBlock('essential-blocks/advanced-heading', {
                        tagName: 'h' + markdownHeadingMatch[1].length, // h1, h2, etc. based on number of #
                        titleText: processBoldText(markdownHeadingMatch[2]),
                        source: 'custom'
                    })
                );
                continue;
            }

            // Check if line starts and ends with ** (treat as heading)
            const boldHeadingMatch = line.match(/^\*\*(.*)\*\*$/);
            if (boldHeadingMatch) {
                // If we have accumulated paragraph content, add it as a block first
                if (currentParagraph) {
                    addParagraphBlock(currentParagraph);
                    currentParagraph = '';
                }

                // Add heading block (h3 level for ** headings)
                blocks.push(
                    createBlock('essential-blocks/advanced-heading', {
                        tagName: 'h3',
                        titleText: boldHeadingMatch[1], // Use the content between ** without processing
                        source: 'custom'
                    })
                );
                continue;
            }

            // Check for numbered paragraph (1. Title, 2. Title, etc.)
            const numberedHeadingMatch = line.match(/^(\d+)\.?\s+(.+)$/);
            if (numberedHeadingMatch &&
                // Make sure it's not just a regular numbered list in the middle of content
                (i === 0 || !lines[i - 1].trim() || lines[i - 1].match(/^(\d+)\.?\s+/))) {

                // If we have accumulated paragraph content, add it as a block first
                if (currentParagraph) {
                    addParagraphBlock(currentParagraph);
                    currentParagraph = '';
                }

                // Add paragraph block
                addParagraphBlock(line);
                continue;
            }

            // Check if line is a list item or other formatted content
            if (line.match(/^[*-]\s+/) || line.match(/^\d+\.\s+/)) {
                // If we have accumulated paragraph content, add it as a block first
                if (currentParagraph) {
                    addParagraphBlock(currentParagraph);
                    currentParagraph = '';
                }

                // Remove the leading special character(s) (*, -, 1., 2., etc.)
                const cleanedLine = line.replace(/^[*-]\s+|^\d+\.\s+/, '').trim();

                // Wrap the cleaned line inside <li>...</li>
                // const listItem = `<li>${cleanedLine}</li>`;
                const listItem = `${cleanedLine}`;

                // Add it as a paragraph block (or list item depending on your structure)
                addParagraphBlock(listItem);
                continue;
            }

            // Regular paragraph content
            // Check if this line looks like a complete sentence and the next line is empty or starts a new thought
            const isEndOfParagraph =
                line.match(/[.!?]$/) && // Ends with punctuation
                (i === lines.length - 1 || // Last line
                    !lines[i + 1].trim() || // Next line is empty
                    lines[i + 1].match(/^[A-Z]/) || // Next line starts with capital letter
                    lines[i + 1].match(/^[*#\-]/) || // Next line is a list item or heading
                    lines[i + 1].match(/^\d+\./)); // Next line is a numbered item

            if (isEndOfParagraph) {
                currentParagraph += (currentParagraph ? ' ' : '') + line;
                addParagraphBlock(currentParagraph);
                currentParagraph = '';
            } else {
                // Add to current paragraph
                currentParagraph += (currentParagraph ? ' ' : '') + line;
            }
        }

        // Add any remaining paragraph content
        if (currentParagraph) {
            blocks.push(
                createBlock('essential-blocks/text', {
                    tagName: 'p',
                    text: currentParagraph,
                    source: 'custom'
                })
            );
        }

        // If no blocks were created, create a single paragraph block with the entire content
        if (blocks.length === 0 && content.trim()) {
            blocks.push(
                createBlock('essential-blocks/text', {
                    tagName: 'p',
                    text: content.trim(),
                    source: 'custom'
                })
            );
        }

        return blocks;
    };

    const generateAIContent = () => {
        if (!prompt) {
            alert(__("Please enter a prompt", "essential-blocks"));
            return;
        }

        setLoading(true);
        setError(false);

        // Add additional instruction to not include HTML or header tags
        const modifiedPrompt = prompt + "\n\nIMPORTANT: Do not include any <html> or <head> or <body> tag in your response. Provide content only for body. Do not add \\n for line breaks. Our system will handle the formatting.";

        // AJAX call to OpenAI API
        let data = new FormData();
        data.append("action", "write_with_ai");
        data.append("admin_nonce", EssentialBlocksLocalize.admin_nonce);
        data.append("prompt", modifiedPrompt); // Use the modified prompt with additional instructions
        data.append("overwrite", overwriteContent);
        data.append("content_for", "writePageContent");


        fetch(EssentialBlocksLocalize?.ajax_url, {
            method: "POST",
            body: data,
        }) // wrapped
            .then((res) => {
                return res.json();
            })
            .then((response) => {
                setLoading(false);

                if (response.success) {
                    // Insert the generated content into the editor
                    const content = response.data.content;

                    // Get the block editor dispatch
                    const blockEditor = dispatch("core/block-editor");

                    // If overwrite is enabled, remove all existing blocks first
                    if (overwriteContent) {
                        const { getBlocks } = select("core/block-editor");
                        const allBlocks = getBlocks();

                        if (allBlocks.length > 0) {
                            blockEditor.removeBlocks(allBlocks.map(block => block.clientId));
                        }
                    }

                    // Parse content to identify paragraphs and headings
                    const blocks = parseContentIntoBlocks(content);

                    // Insert the blocks
                    blockEditor.insertBlocks(blocks);

                    // Close the popover
                    setIsVisible(false);
                } else {
                    setError(true);
                    console.error("Error generating content:", response.data.message);
                    alert(__("Error generating content. Please try again.", "essential-blocks"));
                }
            })
            .catch((error) => {
                setLoading(false);
                setError(true);
                console.error("AJAX Error:", error);
                alert(__("Error connecting to the server. Please try again.", "essential-blocks"));
            });
    }

    const isBlank = select('core/block-editor').getBlocks().length === 0 ? true : false;

    if (!EssentialBlocksLocalize?.hasOpenAiApiKey) {
        return "";
    }

    return EssentialBlocksLocalize?.enableWriteAIPageContent !== "1" ? "" : (
        <>
            <Button
                onClick={() => handleClick()}
                className={"eb-write-ai-button"}
            >
                <img
                    src={`${EssentialBlocksLocalize?.eb_plugins_url}assets/images/eb-logo.svg`}
                    alt={"Essential Blocks Icon"}
                />{" "}
                {__("Write With AI", "essential-blocks")}
            </Button>
            {isVisible && (
                <Popover className={"eb-write-ai-popover"}>
                    <div className="eb-write-ai-popover-content">
                        <div className="ai-content-generator">
                            <div className="eb-write-ai-header">
                                <h2 className="eb-write-ai-heading">
                                    <AiSvgIcon />
                                    {__("Write Content with Essential Blocks AI", "essential-blocks")}
                                </h2>
                                <p className="eb-write-ai-description">
                                    {__("Generate high-quality posts, pages, custom post types and product descriptions effortlessly with Essential Blocks AI. Simply input your title, keywords, prompt, and let the system automatically generate engaging and structured content tailored to your needs.", "essential-blocks")}
                                </p>
                            </div>

                            <div className="eb-write-ai-form">
                                {!EssentialBlocksLocalize?.hasOpenAiApiKey && (
                                    <div className="eb-write-ai-api-key-warning">
                                        <p>
                                            {__("Please Insert your OpenAI API Key to use this Write with AI feature.", "essential-blocks")}
                                        </p>
                                        <a href={`${EssentialBlocksLocalize?.eb_admin_url}admin.php?page=essential-blocks&tab=ai-suite`} target="_blank" rel="noopener noreferrer">
                                            {__("OpenAI API Key will redirect to our Write with AI Dashboard", "essential-blocks")}
                                        </a>
                                    </div>
                                )}


                                <TextControl
                                    label={__("Content Title:", "essential-blocks")}
                                    value={topic}
                                    onChange={(value) => {
                                        setTopic(value);
                                        // Generate prompt when topic changes
                                        if (value.trim()) {
                                            const newPrompt = generateFullPrompt(value, keywords, tone, contentLength);
                                            setPrompt(newPrompt);
                                            setIsPromptGenerated(true);
                                        } else {
                                            setPrompt('');
                                            setIsPromptGenerated(false);
                                        }
                                    }}
                                    placeholder={__("Enter a descriptive title for your post, page or custom post type.", "essential-blocks")}
                                    className="eb-write-ai-topic"
                                />

                                <TextControl
                                    label={__("Keywords:", "essential-blocks")}
                                    value={keywords}
                                    onChange={(value) => {
                                        setKeywords(value);
                                        // Update prompt if topic exists
                                        if (topic.trim()) {
                                            const newPrompt = generateFullPrompt(topic, value, tone, contentLength);
                                            setPrompt(newPrompt);
                                            setIsPromptGenerated(true);
                                        }
                                    }}
                                    placeholder={__("Add keywords to generate precise & relevant content (comma-separated).", "essential-blocks")}
                                    className="eb-write-ai-keywords"
                                />

                                <TextareaControl
                                    label={__("Prompt:", "essential-blocks")}
                                    value={prompt}
                                    onChange={(value) => {
                                        setPrompt(value);
                                        // If user edits the prompt, mark it as manually edited
                                        if (isPromptGenerated) {
                                            setIsPromptGenerated(false);
                                        }
                                    }}
                                    placeholder={__("Generate content using proper HTML structure for '{Content Title}'. Include relevant details based on {Content Keywords} throughout the content. Ensure all text is enclosed with <p> tags, use appropriate heading tags (<h1>, <h2>, etc.) for structure, and apply a <span> tag with the class 'highlight' to important terms for emphasis.", "essential-blocks")}
                                    rows={6}
                                    className="eb-write-ai-prompt"
                                    help={__("Ensure you include a clear and detailed prompt to receive the best possible output. Follow the provided guidelines for optimal results.", "essential-blocks")}
                                />

                                <SelectControl
                                    label={__("Content Tone:", "essential-blocks")}
                                    value={tone}
                                    options={[
                                        { label: __("Informative", "essential-blocks"), value: "informative" },
                                        { label: __("Casual", "essential-blocks"), value: "casual" },
                                        { label: __("Formal", "essential-blocks"), value: "formal" },
                                        { label: __("Persuasive", "essential-blocks"), value: "persuasive" }
                                    ]}
                                    onChange={(value) => {
                                        setTone(value);
                                        // Update prompt if topic exists
                                        if (topic.trim()) {
                                            const newPrompt = generateFullPrompt(topic, keywords, value, contentLength);
                                            setPrompt(newPrompt);
                                            setIsPromptGenerated(true);
                                        }
                                    }}
                                    className="eb-write-ai-tone"
                                />

                                <SelectControl
                                    label={__("Desired Content Length:", "essential-blocks")}
                                    value={contentLength}
                                    options={[
                                        { label: __("Short (around 150-250 words)", "essential-blocks"), value: "short" },
                                        { label: __("Medium (around 300-500 words)", "essential-blocks"), value: "medium" },
                                        { label: __("Long (around 500-800 words)", "essential-blocks"), value: "long" }
                                    ]}
                                    onChange={(value) => {
                                        setContentLength(value);
                                        // Update prompt if topic exists
                                        if (topic.trim()) {
                                            const newPrompt = generateFullPrompt(topic, keywords, tone, value);
                                            setPrompt(newPrompt);
                                            setIsPromptGenerated(true);
                                        }
                                    }}
                                    className="eb-write-ai-length"
                                />

                                {!isBlank && (
                                    <ToggleControl
                                        label={__("Overwrite existing content", "essential-blocks")}
                                        checked={overwriteContent}
                                        onChange={(value) => setOverwriteContent(value)}
                                        help={__("If enabled, all existing content will be removed before inserting the generated content.", "essential-blocks")}
                                        className="eb-write-ai-overwrite"
                                    />
                                )}

                                <div className="eb-write-ai-generate-button-wrapper">
                                    <Button
                                        className={`eb-write-ai-generate-button ${error ? 'eb-button-error' : ''}`}
                                        onClick={() => generateAIContent()}
                                        isPrimary
                                        disabled={loading || !prompt || !EssentialBlocksLocalize?.hasOpenAiApiKey}
                                    >
                                        <AiSvgIcon />
                                        {!error && !loading && __("Generate Content", "essential-blocks")}
                                        {!error && loading && (
                                            <>
                                                {__("Generating...", "essential-blocks")}
                                                <img
                                                    className="eb-install-loader"
                                                    src={`${EssentialBlocksLocalize.eb_plugins_url}/assets/images/loading.svg`}
                                                    alt={__("Loading", "essential-blocks")}
                                                />
                                            </>
                                        )}
                                        {error && __("Something went wrong!", "essential-blocks")}
                                    </Button>
                                </div>

                            </div>
                        </div>
                    </div>
                    <a
                        className="eb-write-ai-close-btn"
                        href="#"
                        onClick={() => handleClick()}
                    >
                        <svg
                            width="20"
                            height="20"
                            viewBox="0 0 20 20"
                            fill="none"
                            xmlns="http://www.w3.org/2000/svg"
                        >
                            <g clip-path="url(#clip0_435_1560)">
                                <path
                                    d="M15 4.75L5 14.75"
                                    stroke="#667085"
                                    strokeWidth="1.2"
                                    strokeLinecap="round"
                                    strokeLinejoin="round"
                                />
                                <path
                                    d="M5 4.75L15 14.75"
                                    stroke="#667085"
                                    strokeWidth="1.2"
                                    strokeLinecap="round"
                                    strokeLinejoin="round"
                                />
                            </g>
                            <defs>
                                <clipPath id="clip0_435_1560">
                                    <rect width="20" height="20" fill="white" />
                                </clipPath>
                            </defs>
                        </svg>
                    </a>
                </Popover>
            )}
        </>
    )
};

export default WriteAIButton;