HEX
Server: Apache/2.4.68 (Debian)
System: Linux as-cs-widget-demo-us-central1 6.1.0-44-cloud-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.164-1 (2026-03-09) x86_64
User: root (0)
PHP: 8.2.32
Disabled: NONE
Upload Files
File: /var/www/html/wp-content/plugins/wp-recipe-maker/assets/js/shared/AiRecipeImport.js
import React, { useEffect, useState } from 'react';

import '../../css/admin/modal/ai-text-import.scss';

import Api from './Api';
import Loader from './Loader';
import { __wprm } from './Translations';
import { convertTermNamesToObjects } from './CategoryTerms';

import FieldContainer from '../admin-modal/fields/FieldContainer';
import FieldTextarea from '../admin-modal/fields/FieldTextarea';

const formatTime = ( minutes ) => {
    if ( ! minutes ) {
        return '';
    }

    const mins = parseInt( minutes );

    if ( mins < 60 ) {
        return `${ mins } min`;
    }

    const hours = Math.floor( mins / 60 );
    const remainingMins = mins % 60;

    return remainingMins > 0 ? `${ hours } hr ${ remainingMins } min` : `${ hours } hr`;
};

const getTagName = ( tag ) => {
    if ( 'string' === typeof tag ) {
        return tag.trim();
    }

    if ( tag && 'object' === typeof tag ) {
        if ( 'string' === typeof tag.name ) {
            return tag.name.trim();
        }

        if ( 'string' === typeof tag.term_id ) {
            return tag.term_id.trim();
        }
    }

    return '';
};

const normalizeRecipeTags = ( recipe ) => {
    if ( ! recipe || ! recipe.tags || 'object' !== typeof recipe.tags ) {
        return recipe;
    }

    const normalizedTags = Object.keys( recipe.tags ).reduce( ( tags, key ) => {
        const values = Array.isArray( recipe.tags[ key ] ) ? recipe.tags[ key ] : [];
        const normalizedValues = [];
        const namesToConvert = [];

        values.forEach( ( value ) => {
            if ( value && 'object' === typeof value && value.name ) {
                normalizedValues.push( value );
                return;
            }

            const tagName = getTagName( value );

            if ( tagName ) {
                namesToConvert.push( tagName );
            }
        } );

        if ( namesToConvert.length ) {
            const convertedValues = convertTermNamesToObjects( key, namesToConvert );

            if ( convertedValues.length ) {
                normalizedValues.push( ...convertedValues );
            } else {
                normalizedValues.push( ...namesToConvert.map( ( name ) => ( {
                    term_id: name,
                    name,
                } ) ) );
            }
        }

        tags[ key ] = normalizedValues;
        return tags;
    }, {} );

    return {
        ...recipe,
        tags: normalizedTags,
    };
};

const getTagItems = ( recipe ) => {
    if ( ! recipe || ! recipe.tags || 'object' !== typeof recipe.tags ) {
        return [];
    }

    const labels = {
        course: __wprm( 'Courses' ),
        cuisine: __wprm( 'Cuisines' ),
        keyword: __wprm( 'Keywords' ),
        difficulty: __wprm( 'Difficulty' ),
    };

    return Object.keys( labels ).map( ( key ) => {
        const value = Array.isArray( recipe.tags[ key ] )
            ? recipe.tags[ key ].map( getTagName ).filter( Boolean )
            : [];

        if ( ! value.length ) {
            return false;
        }

        return {
            key,
            label: labels[ key ],
            value: value.join( ', ' ),
        };
    }).filter( Boolean );
};

const IngredientPreview = ( props ) => {
    const ingredients = props.ingredients || [];

    if ( ! ingredients.length ) {
        return null;
    }

    return (
        <div className="wprm-ai-preview-section">
            <h4>{ __wprm( 'Ingredients' ) }</h4>
            <ul className="wprm-ai-preview-ingredients">
                { ingredients.map( ( ingredient, index ) => {
                    if ( 'group' === ingredient.type ) {
                        return (
                            <li key={ index }>
                                <strong>{ ingredient.name }</strong>
                            </li>
                        );
                    }

                    return (
                        <li key={ index }>
                            { ( ingredient.amount || ingredient.unit ) && (
                                <span className="wprm-ai-preview-ingredient-amount">
                                    { [ ingredient.amount, ingredient.unit ].filter( Boolean ).join( ' ' ) }
                                </span>
                            ) }
                            <span className="wprm-ai-preview-ingredient-name">{ ingredient.name }</span>
                            { ingredient.notes && (
                                <span className="wprm-ai-preview-ingredient-notes">({ ingredient.notes })</span>
                            ) }
                        </li>
                    );
                } ) }
            </ul>
        </div>
    );
};

const InstructionPreview = ( props ) => {
    const instructions = props.instructions || [];

    if ( ! instructions.length ) {
        return null;
    }

    return (
        <div className="wprm-ai-preview-section">
            <h4>{ __wprm( 'Instructions' ) }</h4>
            <ol className="wprm-ai-preview-instructions">
                { instructions.map( ( instruction, index ) => {
                    if ( 'group' === instruction.type ) {
                        return (
                            <li key={ index }>
                                <strong>{ instruction.name }</strong>
                            </li>
                        );
                    }

                    if ( 'tip' === instruction.type ) {
                        return (
                            <li key={ index }>
                                <strong>{ __wprm( 'Tip' ) }:</strong> { instruction.text }
                            </li>
                        );
                    }

                    return <li key={ index }>{ instruction.text }</li>;
                } ) }
            </ol>
        </div>
    );
};

const EquipmentPreview = ( props ) => {
    const equipment = props.equipment || [];

    if ( ! equipment.length ) {
        return null;
    }

    return (
        <div className="wprm-ai-preview-section">
            <h4>{ __wprm( 'Equipment' ) }</h4>
            <ul className="wprm-ai-preview-ingredients">
                { equipment.map( ( item, index ) => (
                    <li key={ index }>
                        { item.amount && <span className="wprm-ai-preview-ingredient-amount">{ item.amount }</span> }
                        <span className="wprm-ai-preview-ingredient-name">{ item.name }</span>
                        { item.notes && <span className="wprm-ai-preview-ingredient-notes">({ item.notes })</span> }
                    </li>
                ) ) }
            </ul>
        </div>
    );
};

export const AIRecipeImportPreview = ( props ) => {
    const recipe = props.recipe;

    if ( ! recipe ) {
        return null;
    }

    const ingredients = Array.isArray( recipe.ingredients_flat ) ? recipe.ingredients_flat : [];
    const instructions = Array.isArray( recipe.instructions_flat ) ? recipe.instructions_flat : [];
    const equipment = Array.isArray( recipe.equipment ) ? recipe.equipment.filter( ( item ) => item && item.name ) : [];
    const summary = recipe.summary ? recipe.summary : '';
    const notes = recipe.notes ? recipe.notes : '';
    const servings = recipe.servings ? `${ recipe.servings }${ recipe.servings_unit ? ` ${ recipe.servings_unit }` : '' }` : '';
    const tagItems = getTagItems( recipe );
    const timeItems = [
        recipe.prep_time ? {
            label: __wprm( 'Prep Time' ),
            value: formatTime( recipe.prep_time ),
        } : false,
        recipe.cook_time ? {
            label: __wprm( 'Cook Time' ),
            value: formatTime( recipe.cook_time ),
        } : false,
        recipe.total_time ? {
            label: __wprm( 'Total Time' ),
            value: formatTime( recipe.total_time ),
        } : false,
        recipe.custom_time ? {
            label: recipe.custom_time_label || __wprm( 'Custom Time' ),
            value: formatTime( recipe.custom_time ),
        } : false,
    ].filter( Boolean );

    return (
        <div className="wprm-admin-modal-ai-text-import-preview">
            <div className="wprm-ai-preview-header">
                <h3>{ recipe.name }</h3>
                { summary && <p className="wprm-ai-preview-summary">{ summary }</p> }
            </div>
            { ( timeItems.length > 0 || servings ) && (
                <div className="wprm-ai-preview-meta-grid">
                    { timeItems.map( ( time, index ) => (
                        <div key={ index } className="wprm-ai-preview-meta-item">
                            <span className="wprm-ai-preview-meta-label">{ time.label }</span>
                            <span className="wprm-ai-preview-meta-value">{ time.value }</span>
                        </div>
                    ) ) }
                    { servings && (
                        <div className="wprm-ai-preview-meta-item">
                            <span className="wprm-ai-preview-meta-label">{ __wprm( 'Servings' ) }</span>
                            <span className="wprm-ai-preview-meta-value">{ servings }</span>
                        </div>
                    ) }
                </div>
            ) }
            { tagItems.length > 0 && (
                <div className="wprm-ai-preview-section">
                    <h4>{ __wprm( 'Tags' ) }</h4>
                    <ul className="wprm-ai-preview-ingredients">
                        { tagItems.map( ( tag ) => (
                            <li key={ tag.key }>
                                <strong>{ tag.label }:</strong> { tag.value }
                            </li>
                        ) ) }
                    </ul>
                </div>
            ) }
            <EquipmentPreview equipment={ equipment } />
            <IngredientPreview ingredients={ ingredients } />
            <InstructionPreview instructions={ instructions } />
            { notes && (
                <div className="wprm-ai-preview-section">
                    <h4>{ __wprm( 'Notes' ) }</h4>
                    <p className="wprm-ai-preview-summary">{ notes }</p>
                </div>
            ) }
        </div>
    );
};

export const AIRecipeImportContent = ( props ) => {
    const hasImportedRecipe = !! props.importedRecipe;

    return (
        <div className={ `wprm-admin-modal-ai-text-import-container${ hasImportedRecipe ? ' wprm-has-preview' : '' }` }>
            <FieldContainer label={ __wprm( 'Recipe Text' ) }>
                <FieldTextarea
                    value={ props.text }
                    placeholder={ __wprm( 'Paste or type recipe and click the import button' ) }
                    onChange={ props.onTextChange }
                />
            </FieldContainer>
            { props.importing && (
                <div className="wprm-admin-modal-ai-text-import-loading">
                    <Loader />
                    <p>{ __wprm( 'Importing recipe with AI...' ) }</p>
                </div>
            ) }
            { props.error && (
                <div className="wprm-admin-modal-ai-text-import-error">
                    <p><strong>{ __wprm( 'Error' ) }</strong></p>
                    <p>{ props.error }</p>
                </div>
            ) }
            { hasImportedRecipe && <AIRecipeImportPreview recipe={ props.importedRecipe } /> }
            { ! props.importing && ! hasImportedRecipe && ! props.error && (
                <p className="wprm-admin-modal-ai-text-import-help">
                    { __wprm( 'Paste recipe text and let AI extract the recipe fields for you. You can review the imported values before applying them.' ) }
                </p>
            ) }
        </div>
    );
};

export const useAIRecipeImport = ( options = {} ) => {
    const initialText = options.initialText || '';
    const autoImport = !! options.autoImport;

    const [ text, setText ] = useState( initialText );
    const [ importing, setImporting ] = useState( false );
    const [ error, setError ] = useState( '' );
    const [ importedRecipe, setImportedRecipe ] = useState( false );

    const onTextChange = ( value ) => {
        setText( value );
        setError( '' );
        setImportedRecipe( false );
    };

    const editImportedRecipe = () => {
        setError( '' );
        setImportedRecipe( false );
    };

    const importRecipe = ( textToImport = text ) => {
        const trimmedText = textToImport.trim();

        if ( ! trimmedText ) {
            setError( __wprm( 'Text is required' ) );
            setImportedRecipe( false );

            return Promise.resolve( false );
        }

        setImporting( true );
        setError( '' );
        setImportedRecipe( false );

        return Api.import.aiImportRecipe( trimmedText ).then( ( response ) => {
            if ( response && response.success && response.recipe ) {
                const importedRecipe = normalizeRecipeTags( response.recipe );

                setImporting( false );
                setError( '' );
                setImportedRecipe( importedRecipe );

                return importedRecipe;
            }

            setImporting( false );
            setError( response?.error || __wprm( 'Failed to import recipe with AI. Please try again.' ) );
            setImportedRecipe( false );

            return false;
        } ).catch( ( importError ) => {
            console.error( 'Error importing recipe with AI:', importError );

            setImporting( false );
            setError( __wprm( 'An error occurred while importing with AI. Please try again.' ) );
            setImportedRecipe( false );

            return false;
        } );
    };

    useEffect( () => {
        if ( autoImport && initialText ) {
            importRecipe( initialText );
        }
        // Only run once on mount for optional auto-import behavior.
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [] );

    return {
        text,
        importing,
        error,
        importedRecipe,
        onTextChange,
        editImportedRecipe,
        importRecipe,
        setError,
    };
};