From 951ca340e0763b45f6d2c93d6376794309c60e2b Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Wed, 15 May 2024 20:21:04 +0530 Subject: [PATCH 001/138] Updated current setting page slug to classifai_old. --- includes/Classifai/Admin/templates/classifai-header.php | 4 ++-- includes/Classifai/Services/Service.php | 2 +- includes/Classifai/Services/ServicesManager.php | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/includes/Classifai/Admin/templates/classifai-header.php b/includes/Classifai/Admin/templates/classifai-header.php index ea29a7fde..a129d8040 100644 --- a/includes/Classifai/Admin/templates/classifai-header.php +++ b/includes/Classifai/Admin/templates/classifai-header.php @@ -22,7 +22,7 @@ if ( $is_setup_page ) { ?>
- + @@ -70,7 +70,7 @@ $value ) { ?> - + 'classifai', + 'page' => 'classifai_old', 'tab' => $this->get_menu_slug(), ), admin_url( 'tools.php' ) diff --git a/includes/Classifai/Services/ServicesManager.php b/includes/Classifai/Services/ServicesManager.php index f15a796ac..0fe6f254d 100644 --- a/includes/Classifai/Services/ServicesManager.php +++ b/includes/Classifai/Services/ServicesManager.php @@ -270,14 +270,14 @@ protected function register_services() { */ protected function get_menu_title() { $registration_settings = get_option( 'classifai_settings' ); - $this->title = esc_html__( 'ClassifAI', 'classifai' ); + $this->title = esc_html__( 'ClassifAI Old', 'classifai' ); $this->menu_title = $this->title; if ( ! isset( $registration_settings['valid_license'] ) || ! $registration_settings['valid_license'] ) { /* * Translators: Main title. */ - $this->menu_title = sprintf( __( 'ClassifAI %s', 'classifai' ), '!' ); + $this->menu_title = sprintf( __( 'ClassifAI Old %s', 'classifai' ), '!' ); } } @@ -299,7 +299,7 @@ public function register_admin_menu_item() { $this->title, $this->menu_title, 'manage_options', - 'classifai', + 'classifai_old', [ $this, 'render_settings_page' ] ); } From f16a7ba5607610ebd71209fab4af383c551425ac Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Wed, 15 May 2024 23:46:33 +0530 Subject: [PATCH 002/138] Initial base react setting page. --- includes/Classifai/Admin/Settings.php | 217 ++++++++++++++++++ includes/Classifai/Features/Feature.php | 16 ++ includes/Classifai/Plugin.php | 4 + .../settings/components/feature-settings.js | 124 ++++++++++ src/js/settings/components/header.js | 101 ++++++++ src/js/settings/components/index.js | 3 + .../settings/components/settings-wrapper.js | 80 +++++++ src/js/settings/hooks/use-settings.js | 28 +++ src/js/settings/index.js | 73 ++++++ src/js/settings/provider.js | 34 +++ src/js/settings/utils/icons.js | 52 +++++ src/js/settings/utils/utils.js | 20 ++ src/scss/settings.scss | 132 +++++++++++ webpack.config.js | 1 + 14 files changed, 885 insertions(+) create mode 100644 includes/Classifai/Admin/Settings.php create mode 100644 src/js/settings/components/feature-settings.js create mode 100644 src/js/settings/components/header.js create mode 100644 src/js/settings/components/index.js create mode 100644 src/js/settings/components/settings-wrapper.js create mode 100644 src/js/settings/hooks/use-settings.js create mode 100644 src/js/settings/index.js create mode 100644 src/js/settings/provider.js create mode 100644 src/js/settings/utils/icons.js create mode 100644 src/js/settings/utils/utils.js create mode 100644 src/scss/settings.scss diff --git a/includes/Classifai/Admin/Settings.php b/includes/Classifai/Admin/Settings.php new file mode 100644 index 000000000..7465556cc --- /dev/null +++ b/includes/Classifai/Admin/Settings.php @@ -0,0 +1,217 @@ + +
+ $this->get_features(), + 'services' => get_services_menu(), + ); + + wp_add_inline_script( + 'classifai-settings', + sprintf( + 'var classifAISettings = %s;', + wp_json_encode( $data ) + ), + 'before' + ); + + wp_enqueue_style( + 'classifai-settings', + CLASSIFAI_PLUGIN_URL . 'dist/settings.css', + array_filter( + get_asset_info( 'settings', 'dependencies' ), + function ( $style ) { + return wp_style_is( $style, 'registered' ); + } + ), + get_asset_info( 'settings', 'version' ), + 'all' + ); + } + + /** + * Get features for the settings page. + * + * @param bool $with_instance Whether to include the instance of the feature. + */ + public function get_features( $with_instance = false ) { + $services = get_plugin()->services; + if ( empty( $services ) || empty( $services['service_manager'] ) || ! $services['service_manager'] instanceof ServicesManager ) { + return []; + } + + /** @var ServicesManager $service_manager Instance of the services manager class. */ + $service_manager = $services['service_manager']; + $services = []; + + if ( empty( $service_manager->service_classes ) ) { + return []; + } + + if ( $with_instance ) { + foreach ( $service_manager->service_classes as $service ) { + foreach ( $service->feature_classes as $feature ) { + $services[ $feature::ID ] = $feature; + } + } + return $services; + } + + foreach ( $service_manager->service_classes as $service ) { + $services[ $service->get_menu_slug() ] = array(); + + foreach ( $service->feature_classes as $feature ) { + $services[ $service->get_menu_slug() ][ $feature::ID ] = array( + 'label' => $feature->get_label(), + ); + } + } + return $services; + } + + /** + * Register the REST API routes for the settings. + * + * @return void + */ + public function register_routes() { + register_rest_route( + 'classifai/v1', + 'settings', + [ + [ + 'methods' => \WP_REST_Server::READABLE, + 'callback' => [ $this, 'get_settings' ], + 'permission_callback' => [ $this, 'get_settings_permissions_check' ], + ], + [ + 'methods' => \WP_REST_Server::EDITABLE, + 'callback' => [ $this, 'update_settings' ], + 'permission_callback' => [ $this, 'update_settings_permissions_check' ], + ], + ] + ); + } + + /** + * Get the settings. + * + * @return \WP_REST_Response + */ + public function get_settings() { + $features = $this->get_features( true ); + $settings = []; + + foreach ( $features as $feature ) { + $settings[ $feature::ID ] = $feature->get_settings(); + } + + return rest_ensure_response( $settings ); + } + + /** + * Check if a given request has access to get settings. + * + * @return bool|\WP_Error + */ + public function get_settings_permissions_check() { + return current_user_can( 'manage_options' ); + } + + /** + * Update the settings. + * + * @param \WP_REST_Request $request Full data about the request. + * @return \WP_REST_Response + */ + public function update_settings( $request ) { + $settings = $request->get_json_params(); + + $feature_key = key( $settings ); + $features = $this->get_features( true ); + $feature = $features[ $feature_key ]; + + if ( ! $feature ) { + return new \WP_Error( 'invalid_feature', __( 'Invalid feature.', 'classifai' ), [ 'status' => 400 ] ); + } + + $feature->update_settings( $settings[ $feature_key ] ); + + return $this->get_settings(); + } + + /** + * Check if a given request has access to update settings. + * + * @return bool|\WP_Error + */ + public function update_settings_permissions_check() { + return current_user_can( 'manage_options' ); + } +} diff --git a/includes/Classifai/Features/Feature.php b/includes/Classifai/Features/Feature.php index 1e69ba022..9554f327a 100644 --- a/includes/Classifai/Features/Feature.php +++ b/includes/Classifai/Features/Feature.php @@ -437,6 +437,22 @@ public function reset_settings() { update_option( $this->get_option_name(), $this->get_default_settings() ); } + /** + * Updates the settings for the feature. + * + * @param array $new_settings New settings to update. + */ + public function update_settings( array $new_settings ) { + $settings = $this->get_settings(); + if ( empty( $new_settings ) ) { + return; + } + + // Update the settings with the new values. + $new_settings = array_merge( $settings, $new_settings ); + update_option( $this->get_option_name(), $new_settings ); + } + /** * Add settings fields for Role/User based access. */ diff --git a/includes/Classifai/Plugin.php b/includes/Classifai/Plugin.php index 1cfd79caf..e4977cf4a 100644 --- a/includes/Classifai/Plugin.php +++ b/includes/Classifai/Plugin.php @@ -57,6 +57,10 @@ public function init() { // Initialize the services; each service handles their features. $this->init_services(); + // Initialize the ClassifAI Settings. + $settings = new Admin\Settings(); + $settings->init(); + // Initialize the ClassifAI Onboarding. $onboarding = new Admin\Onboarding(); $onboarding->init(); diff --git a/src/js/settings/components/feature-settings.js b/src/js/settings/components/feature-settings.js new file mode 100644 index 000000000..b23503842 --- /dev/null +++ b/src/js/settings/components/feature-settings.js @@ -0,0 +1,124 @@ +/** + * WordPress dependencies + */ +import { __, sprintf } from '@wordpress/i18n'; +import { + withFilters, + Slot, + ToggleControl, + SelectControl, + Button, +} from '@wordpress/components'; +import { useState } from '@wordpress/element'; + +/** + * Internal dependencies + */ + +// Provides an entry point to slot in additional settings. Must be placed +// outside of function to avoid unnecessary rerenders. +const AdditionalSettings = withFilters( 'classifai.PluginSettings' )( + // eslint-disable-next-line no-unused-vars + ( props ) => <> +); + +/** + * Renders the plugin Settings tab of the Block Visibility settings page + * + * @since 1.0.0 + * @param {Object} props All the props passed to this function + */ +export const FeatureSettings = ( props ) => { + const { feature, featureSettings, setFeatureSettings, saveSettings } = + props; + const featureName = feature?.name; + const featureTitle = feature?.title || __( 'Feature', 'classifai' ); + const [ hasUpdates, setHasUpdates ] = useState( false ); + + function setSettings( newSettings ) { + setFeatureSettings( { + ...featureSettings, + ...newSettings, + } ); + setHasUpdates( true ); + } + + function saveFeatureSettings() { + saveSettings( { [ featureName ]: featureSettings } ); + setHasUpdates( false ); + } + + return ( + <> +

+ { + // translators: %s: Feature title + sprintf( __( '%s Settings', 'classifai' ), featureTitle ) + } +

+
+
+
+
+ { __( 'Enable feature', 'classifai' ) } +
+
+ + setSettings( { + status: status ? '1' : '0', + } ) + } + /> +
+ +
+ + { __( 'Select a provider', 'classifai' ) } + +
+
+ + setSettings( { provider } ) + } + value={ featureSettings.provider } + options={ [ + { + label: 'Option A', + value: 'a', + }, + { + label: 'Option B', + value: 'b', + }, + { + label: 'Option C', + value: 'c', + }, + ] } + /> +
+ + +
+ + +
+
+ + + ); +}; diff --git a/src/js/settings/components/header.js b/src/js/settings/components/header.js new file mode 100644 index 000000000..c3c9d224f --- /dev/null +++ b/src/js/settings/components/header.js @@ -0,0 +1,101 @@ +/** + * External dependencies + */ +import { + DropdownMenu, + MenuGroup, + MenuItem, + VisuallyHidden, + Button, +} from '@wordpress/components'; +import { external, help, cog, tool } from '@wordpress/icons'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import { ClassifAILogo } from '../utils/icons'; + +export const Header = ( props ) => { + const { isSetupPage } = props; + + return ( +
+
+
+ +
+
+ { isSetupPage && ( + + ) } + { ! isSetupPage && ( + + ) } + + { ( { onClose } ) => ( + + + { __( 'FAQs', 'classifai' ) } + + { + /* translators: accessibility text */ + __( + '(opens in a new tab)', + 'classifai' + ) + } + + + + { __( + 'Report issue/enhancement', + 'classifai' + ) } + + { + /* translators: accessibility text */ + __( + '(opens in a new tab)', + 'classifai' + ) + } + + + + ) } + +
+
+
+ ); +}; diff --git a/src/js/settings/components/index.js b/src/js/settings/components/index.js new file mode 100644 index 000000000..b899466a0 --- /dev/null +++ b/src/js/settings/components/index.js @@ -0,0 +1,3 @@ +export * from './header'; +export * from './settings-wrapper'; +export * from './feature-settings'; diff --git a/src/js/settings/components/settings-wrapper.js b/src/js/settings/components/settings-wrapper.js new file mode 100644 index 000000000..4cb770194 --- /dev/null +++ b/src/js/settings/components/settings-wrapper.js @@ -0,0 +1,80 @@ +/** + * External dependencies + */ +import { TabPanel } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +import { FeatureSettings } from './feature-settings'; +import { useSettings } from '../hooks/use-settings'; +import { updateUrl } from '../utils/utils'; + +/** + * Internal dependencies + */ +const { features } = window.classifAISettings; + +export const SettingsWrapper = ( props ) => { + const { tab } = props; + const servicefeatures = features[ tab ]; + const { settings, setSettings, saveSettings } = useSettings(); + + const urlParams = new URLSearchParams( window.location.search ); + const requestedFeature = urlParams.get( 'feature' ); + const initialFeature = Object.keys( servicefeatures ).includes( + requestedFeature + ) + ? requestedFeature + : Object.keys( servicefeatures )[ 0 ]; + + const featureOptions = Object.keys( servicefeatures ).map( ( feature ) => { + return { + name: feature, + title: + servicefeatures[ feature ]?.label || + __( 'Feature', 'classifai' ), + className: feature, + }; + } ); + + return ( +
+ + updateUrl( 'feature', featureName ) + } + > + { ( feature ) => { + return ( + <> + { featureOptions.map( ( key ) => { + if ( key.name !== feature.name ) { + return null; + } + + return ( + + setSettings( { + ...settings, + [ feature.name ]: newSettings, + } ) + } + saveSettings={ saveSettings } + /> + ); + } ) } + + ); + } } + +
+ ); +}; diff --git a/src/js/settings/hooks/use-settings.js b/src/js/settings/hooks/use-settings.js new file mode 100644 index 000000000..9dd061574 --- /dev/null +++ b/src/js/settings/hooks/use-settings.js @@ -0,0 +1,28 @@ +import apiFetch from '@wordpress/api-fetch'; +import { useEffect, useState } from '@wordpress/element'; + +export const useSettings = () => { + const [ settings, setSettings ] = useState( {} ); // TODO: Set default settings here + + useEffect( () => { + apiFetch( { path: '/classifai/v1/settings' } ).then( ( res ) => { + setSettings( res ); + } ); + }, [] ); + + const saveSettings = ( featureSettings ) => { + apiFetch( { + path: '/classifai/v1/settings', + method: 'POST', + data: featureSettings, + } ).then( ( res ) => { + setSettings( res ); + } ); + }; + + return { + settings, + setSettings, + saveSettings, + }; +}; diff --git a/src/js/settings/index.js b/src/js/settings/index.js new file mode 100644 index 000000000..549ea41b8 --- /dev/null +++ b/src/js/settings/index.js @@ -0,0 +1,73 @@ +import domReady from '@wordpress/dom-ready'; +import { createRoot } from '@wordpress/element'; +import { TabPanel, SlotFillProvider } from '@wordpress/components'; + +import '../../scss/settings.scss'; +import './provider'; // TODO: This is for testing purposes only, please remove this line + +import { Header, SettingsWrapper } from './components'; +import { updateUrl } from './utils/utils'; +const { classifAISettings } = window; +const { services } = classifAISettings; + +const Content = () => { + const serviceKeys = Object.keys( services ); + const serviceOptions = serviceKeys.map( ( slug ) => { + return { + name: slug, + title: services[ slug ], + className: slug, + }; + } ); + + // Switch the default settings tab based on the URL tab query + const urlParams = new URLSearchParams( window.location.search ); + const requestedTab = urlParams.get( 'tab' ); + const initialTab = serviceKeys.includes( requestedTab ) + ? requestedTab + : 'language_processing'; + + return ( + updateUrl( 'tab', tabName ) } + > + { ( tab ) => { + return ( + <> + { serviceOptions.map( ( key ) => { + if ( key.name !== tab.name ) { + return null; + } + + return ( + + ); + } ) } + + ); + } } + + ); +}; + +const Settings = () => { + return ( + +
+ + + ); +}; + +domReady( () => { + const root = createRoot( document.getElementById( 'classifai-settings' ) ); + + root.render( ); +} ); diff --git a/src/js/settings/provider.js b/src/js/settings/provider.js new file mode 100644 index 000000000..a03a7ae22 --- /dev/null +++ b/src/js/settings/provider.js @@ -0,0 +1,34 @@ +import { addFilter } from '@wordpress/hooks'; +import { Fill, TextareaControl } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; + +function providerSettings() { + return ( { featureSettings, setSettings } ) => { + const { provider, message } = featureSettings; + if ( provider !== 'a' ) { + return null; + } + + return ( + <> + + + setSettings( { + message: value, + } ) + } + /> + + + ); + }; +} + +addFilter( + 'classifai.PluginSettings', + 'classifai/provider-settings', + providerSettings +); diff --git a/src/js/settings/utils/icons.js b/src/js/settings/utils/icons.js new file mode 100644 index 000000000..75fcf1c4d --- /dev/null +++ b/src/js/settings/utils/icons.js @@ -0,0 +1,52 @@ +/** + * WordPress dependencies + */ +import { SVG, Path } from '@wordpress/components'; + +export const ClassifAILogo = ( + + + + + + + + + + + + + +); diff --git a/src/js/settings/utils/utils.js b/src/js/settings/utils/utils.js new file mode 100644 index 000000000..5f2c3fb31 --- /dev/null +++ b/src/js/settings/utils/utils.js @@ -0,0 +1,20 @@ +// Update URL based on the current tab and feature selected +export const updateUrl = ( key, value ) => { + const urlParams = new URLSearchParams( window.location.search ); + urlParams.set( key, value ); + + if ( window.history.pushState ) { + const newUrl = + window.location.protocol + + '//' + + window.location.host + + window.location.pathname + + '?' + + urlParams.toString() + + window.location.hash; + + window.history.replaceState( { path: newUrl }, '', newUrl ); + } else { + window.location.search = urlParams.toString(); + } +}; diff --git a/src/scss/settings.scss b/src/scss/settings.scss new file mode 100644 index 000000000..19f5f7fee --- /dev/null +++ b/src/scss/settings.scss @@ -0,0 +1,132 @@ +#classifai-settings { + .settings-panel { + background: #fff; + border: 1px solid #e0e0e0; + padding: 16px; + + .settings-label { + font-size: 11px; + font-weight: 500; + line-height: 1.4; + text-transform: uppercase; + display: inline-block; + margin-top: 30px; + margin-bottom: 15px; + padding: 0px; + } + } + + .setting-tabs { + margin-left: -20px; + .components-tab-panel__tabs { + justify-content: center; + border-bottom: 1px solid #ccd0d4; + padding-bottom: 0; + + .components-tab-panel__tabs-item { + font-size: 15px; + } + + .active-tab, + .components-tab-panel__tabs-item:focus:not(:disabled) { + box-shadow: inset 0 -2px var(--wp-admin-theme-color); + } + } + + @media screen and (max-width: 782px) { + margin-left: -10px; + } + } + + .classifai-settings-wrapper { + max-width: 1280px; + margin: 0 auto; + padding-top: 24px; + + .feature-tabs { + display: flex; + gap: 24px; + + .components-tab-panel__tabs { + flex-direction: column; + border: 0px; + justify-content: start; + flex-basis: 0; + flex-grow: 1; + + .components-tab-panel__tabs-item{ + background: #fafafa; + border: 1px solid #e0e0e0; + + &.active-tab { + background: #fff; + border-bottom: 1px solid var(--wp-admin-theme-color); + box-shadow: inset 0 -2px var(--wp-admin-theme-color); + } + + &:hover, + &:focus { + background: #fff; + } + } + } + + .components-tab-panel__tab-content{ + flex-basis: 0; + flex-grow: 3; + } + + @media screen and (max-width: 782px) { + display: block; + gap: 0; + } + } + } + + #classifai-header { + background-color: #fff; + box-sizing: border-box; + display: block; + left: 0; + right: 0; + top: 0; + position: sticky; + z-index: 1001; + margin-left: -20px; + padding: 0 10px; + box-shadow: 0px 10px 10px 0px #f0f0f1; + + @media screen and (max-width: 782px) { + margin-left: -10px; + padding: 0 5px; + } + + body.admin-bar & { + @media (min-width: 601px) { + top: 46px; + } + + @media (min-width: 783px) { + top: 32px; + } + } + + .classifai-header-layout { + justify-content: space-between; + padding: 18px; + display: flex; + align-items: center; + } + + #classifai-logo { + line-height: 0; + width: 180px; + } + + #classifai-header-controls { + display: flex; + flex-direction: row; + align-items: center; + } + } +} diff --git a/webpack.config.js b/webpack.config.js index 6acba9b7c..066fcdcc0 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -43,6 +43,7 @@ module.exports = { './src/js/media-modal/views/generate-image-media-upload.js', ], 'extend-image-blocks': './src/js/extend-image-block-generate-image.js', + settings: './src/js/settings/index.js', }, module: { rules: [ From 3d9608d8d9cf05d3dbd26a45bef93029bf43a8df Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Thu, 16 May 2024 19:04:19 +0530 Subject: [PATCH 003/138] Created user access component. --- src/js/components/user-selector.js | 5 +- src/js/settings/components/user-access.js | 87 +++++++++++++++++++++++ 2 files changed, 90 insertions(+), 2 deletions(-) create mode 100644 src/js/settings/components/user-access.js diff --git a/src/js/components/user-selector.js b/src/js/components/user-selector.js index 80be5a9b6..e0e842dbc 100644 --- a/src/js/components/user-selector.js +++ b/src/js/components/user-selector.js @@ -16,8 +16,9 @@ import { addQueryArgs } from '@wordpress/url'; * @param {Object} props The block props. * @param {string} props.value The selected user ids. * @param {string} props.onChange The change handler. + * @param {string} props.label The label for the field. */ -export const UserSelector = ( { value, onChange } ) => { +export const UserSelector = ( { value, onChange, label = null } ) => { const [ usersByName, setUsersByName ] = useState( {} ); const [ values, setValues ] = useState( [] ); const [ search, setSearch ] = useState( '' ); @@ -116,7 +117,7 @@ export const UserSelector = ( { value, onChange } ) => { suggestions={ suggestions } onChange={ handleChange } onInputChange={ debouncedSearch } - label={ null } + label={ label } placeholder={ __( 'Search for users', 'classifai' ) } __experimentalShowHowTo={ false } messages={ { diff --git a/src/js/settings/components/user-access.js b/src/js/settings/components/user-access.js new file mode 100644 index 000000000..fda477eeb --- /dev/null +++ b/src/js/settings/components/user-access.js @@ -0,0 +1,87 @@ +/** + * External dependencies + */ +import { + CheckboxControl, + PanelBody, + PanelRow, + ToggleControl, +} from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import { getFeature } from '../utils/utils'; +import { UserSelector } from '../../components'; + +export const UserPermissions = ( { + featureName, + featureSettings, + setSettings, +} ) => { + const feature = getFeature( featureName ); + const roles = feature.roles || {}; + return ( + + +
+
+ { __( 'Allowed roles', 'classifai' ) } +
+ { Object.keys( roles ).map( ( role ) => { + return ( + { + setSettings( { + ...featureSettings, + roles: { + ...featureSettings.roles, + [ role ]: value ? role : '0', + }, + } ); + } } + /> + ); + } ) } +
+
+ +
+ { + setSettings( { + ...featureSettings, + users, + } ); + } } + label={ __( 'Allowed users', 'classifai' ) } + /> +
+
+ +
+ { + setSettings( { + ...featureSettings, + user_based_opt_out: value ? '1' : 'no', + } ); + } } + /> +
+
+
+ ); +}; From 9852d84e945627297590f961f123dc4e0476d910 Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Thu, 16 May 2024 19:06:21 +0530 Subject: [PATCH 004/138] Improved folder structure and some other improvements. --- .../settings/components/feature-settings.js | 100 ++++++++---------- .../settings/components/settings-wrapper.js | 9 +- src/js/settings/components/settings.js | 65 ++++++++++++ src/js/settings/index.js | 71 ++----------- src/js/settings/providers/index.js | 1 + src/js/settings/providers/openai-whisper.js | 40 +++++++ src/js/settings/utils/utils.js | 12 +++ src/scss/settings.scss | 43 +++++--- 8 files changed, 205 insertions(+), 136 deletions(-) create mode 100644 src/js/settings/components/settings.js create mode 100644 src/js/settings/providers/index.js create mode 100644 src/js/settings/providers/openai-whisper.js diff --git a/src/js/settings/components/feature-settings.js b/src/js/settings/components/feature-settings.js index b23503842..205a5a2e0 100644 --- a/src/js/settings/components/feature-settings.js +++ b/src/js/settings/components/feature-settings.js @@ -8,12 +8,16 @@ import { ToggleControl, SelectControl, Button, + Panel, + PanelBody, } from '@wordpress/components'; import { useState } from '@wordpress/element'; +import { UserPermissions } from './user-access'; /** * Internal dependencies */ +import { getFeature } from '../utils/utils'; // Provides an entry point to slot in additional settings. Must be placed // outside of function to avoid unnecessary rerenders. @@ -29,12 +33,21 @@ const AdditionalSettings = withFilters( 'classifai.PluginSettings' )( * @param {Object} props All the props passed to this function */ export const FeatureSettings = ( props ) => { - const { feature, featureSettings, setFeatureSettings, saveSettings } = + const { featureName, featureSettings, setFeatureSettings, saveSettings } = props; - const featureName = feature?.name; - const featureTitle = feature?.title || __( 'Feature', 'classifai' ); + const feature = getFeature( featureName ); + const featureTitle = feature?.label || __( 'Feature', 'classifai' ); const [ hasUpdates, setHasUpdates ] = useState( false ); + const providers = Object.keys( feature?.providers || {} ).map( + ( value ) => { + return { + value, + label: feature.providers[ value ] || '', + }; + } + ); + function setSettings( newSettings ) { setFeatureSettings( { ...featureSettings, @@ -50,62 +63,43 @@ export const FeatureSettings = ( props ) => { return ( <> -

- { + -
-
-
-
- { __( 'Enable feature', 'classifai' ) } -
-
- - setSettings( { - status: status ? '1' : '0', - } ) - } - /> -
+ > + + + setSettings( { + status: status ? '1' : '0', + } ) + } + /> -
- - { __( 'Select a provider', 'classifai' ) } - -
-
- - setSettings( { provider } ) - } - value={ featureSettings.provider } - options={ [ - { - label: 'Option A', - value: 'a', - }, - { - label: 'Option B', - value: 'b', - }, - { - label: 'Option C', - value: 'c', - }, - ] } - /> -
- - -
+ setSettings( { provider } ) } + value={ featureSettings.provider } + options={ providers } + /> + + + + + +
+
{ isSetupPage && ( - ) } { ! isSetupPage && (
- + { > { ( feature ) => { return ( - <> - { featureOptions.map( ( key ) => { - if ( key.name !== feature.name ) { - return null; - } - - return ( - - ); - } ) } - + ); } } diff --git a/src/js/settings/components/settings.js b/src/js/settings/components/settings.js index a541c3dd9..398082c00 100644 --- a/src/js/settings/components/settings.js +++ b/src/js/settings/components/settings.js @@ -44,22 +44,7 @@ const Content = () => { } } > { ( tab ) => { - return ( - <> - { serviceOptions.map( ( key ) => { - if ( key.name !== tab.name ) { - return null; - } - - return ( - - ); - } ) } - - ); + return ; } } ); diff --git a/src/js/settings/features/index.js b/src/js/settings/features/index.js new file mode 100644 index 000000000..6d7d1790b --- /dev/null +++ b/src/js/settings/features/index.js @@ -0,0 +1 @@ +import './title-generation'; diff --git a/src/js/settings/features/title-generation.js b/src/js/settings/features/title-generation.js new file mode 100644 index 000000000..f600521f4 --- /dev/null +++ b/src/js/settings/features/title-generation.js @@ -0,0 +1,38 @@ +import { addFilter } from '@wordpress/hooks'; +import { Fill, TextControl } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; + +function titleGenerationSettings( FeatureSettings ) { + return ( props ) => { + const { featureName, featureSettings, setSettings } = props; + if ( featureName !== 'feature_title_generation' ) { + return ; + } + + const provider = featureSettings?.provider || ''; + const numberOfSuggestions = + featureSettings?.[ provider ]?.number_of_suggestions || '1'; + return ( + <> + + + + setSettings( { + number_of_suggestions: value, + } ) + } + /> + + + ); + }; +} + +addFilter( + 'classifai.FeatureSettings', + 'classifai-feature-settings/title-generation', + titleGenerationSettings +); diff --git a/src/js/settings/hooks/use-settings.js b/src/js/settings/hooks/use-settings.js index 9dd061574..c6f993445 100644 --- a/src/js/settings/hooks/use-settings.js +++ b/src/js/settings/hooks/use-settings.js @@ -1,28 +1,91 @@ +/** + * WordPress dependencies + */ +import { useSelect, useDispatch } from '@wordpress/data'; import apiFetch from '@wordpress/api-fetch'; -import { useEffect, useState } from '@wordpress/element'; +import { useEffect } from '@wordpress/element'; -export const useSettings = () => { - const [ settings, setSettings ] = useState( {} ); // TODO: Set default settings here +/** + * Internal dependencies + */ +import { STORE_NAME } from '../data/store'; +export const useSettings = ( loadSettings = false ) => { + const dispatch = useDispatch(); + + const settings = useSelect( ( select ) => + select( STORE_NAME ).getSettings() + ); + const isLoaded = useSelect( ( select ) => + select( STORE_NAME ).getIsLoaded() + ); + const isSaving = useSelect( ( select ) => + select( STORE_NAME ).getIsSaving() + ); + const currentService = useSelect( ( select ) => + select( STORE_NAME ).getCurrentService() + ); + const currentFeature = useSelect( ( select ) => + select( STORE_NAME ).getCurrentFeature() + ); + + const getFeatureSettings = ( feature ) => settings[ feature ] || {}; + + const setSettings = ( data ) => dispatch( STORE_NAME ).setSettings( data ); + const setFeatureSettings = ( feature, data ) => + dispatch( STORE_NAME ).setFeatureSettings( feature, data ); + const setIsSaving = ( saving ) => + dispatch( STORE_NAME ).setIsSaving( saving ); + const setIsLoaded = ( loaded ) => + dispatch( STORE_NAME ).setIsLoaded( loaded ); + + // Load settings when the hook is called useEffect( () => { - apiFetch( { path: '/classifai/v1/settings' } ).then( ( res ) => { - setSettings( res ); - } ); - }, [] ); + if ( ! loadSettings ) { + return; + } + + ( async () => { + const classifAISettings = await apiFetch( { + path: '/classifai/v1/settings', + } ); // TODO: handle error + + setSettings( classifAISettings ); + setIsLoaded( true ); + } )(); + }, [ loadSettings ] ); - const saveSettings = ( featureSettings ) => { + /** + * Save settings for a feature. + * + * @param {string} featureName Feature name + */ + const saveSettings = ( featureName ) => { + setIsSaving( true ); apiFetch( { - path: '/classifai/v1/settings', + path: '/classifai/v1/settings/', method: 'POST', - data: featureSettings, - } ).then( ( res ) => { - setSettings( res ); - } ); + data: { [ featureName ]: settings[ featureName ] }, + } ) + .then( ( res ) => { + setSettings( res ); + setIsSaving( false ); + } ) + .catch( ( error ) => { + // eslint-disable-next-line no-console + console.error( error ); // TODO: handle error and show a notice + setIsSaving( false ); + } ); }; return { settings, - setSettings, + isLoaded, + isSaving, + currentFeature, + currentService, + getFeatureSettings, + setFeatureSettings, saveSettings, }; }; diff --git a/src/js/settings/index.js b/src/js/settings/index.js index 586fc467c..ae9d54451 100644 --- a/src/js/settings/index.js +++ b/src/js/settings/index.js @@ -8,6 +8,10 @@ import { createRoot } from '@wordpress/element'; * Internal dependencies */ import { Settings } from './components/settings'; +import './data/store'; +import '../../scss/settings.scss'; +import './features'; // TODO: This is for testing purposes only and still in experimental phase. +import './providers'; // TODO: This is for testing purposes only and still in experimental phase. domReady( () => { const root = createRoot( document.getElementById( 'classifai-settings' ) ); diff --git a/src/js/settings/provider.js b/src/js/settings/provider.js deleted file mode 100644 index a03a7ae22..000000000 --- a/src/js/settings/provider.js +++ /dev/null @@ -1,34 +0,0 @@ -import { addFilter } from '@wordpress/hooks'; -import { Fill, TextareaControl } from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; - -function providerSettings() { - return ( { featureSettings, setSettings } ) => { - const { provider, message } = featureSettings; - if ( provider !== 'a' ) { - return null; - } - - return ( - <> - - - setSettings( { - message: value, - } ) - } - /> - - - ); - }; -} - -addFilter( - 'classifai.PluginSettings', - 'classifai/provider-settings', - providerSettings -); diff --git a/src/js/settings/providers/openai-whisper.js b/src/js/settings/providers/openai-whisper.js index df2dc36bf..edb6943b0 100644 --- a/src/js/settings/providers/openai-whisper.js +++ b/src/js/settings/providers/openai-whisper.js @@ -2,18 +2,18 @@ import { addFilter } from '@wordpress/hooks'; import { Fill, TextControl } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; -function providerSettings() { +function openAIWhisperSettings( ProviderSettings ) { return ( props ) => { - const { featureSettings, setSettings } = props; - const { provider } = featureSettings; - if ( provider !== 'openai_whisper' ) { - return null; + const { featureSettings = {}, setSettings = {} } = props; + if ( featureSettings.provider !== 'openai_whisper' ) { + return ; } const apiKey = featureSettings.openai_whisper?.api_key || ''; return ( <> + Date: Sat, 18 May 2024 12:46:33 +0530 Subject: [PATCH 008/138] Improvements in root component. --- .../{settings.js => classifai-settings.js} | 21 +++++++------------ src/js/settings/index.js | 4 ++-- 2 files changed, 9 insertions(+), 16 deletions(-) rename src/js/settings/components/{settings.js => classifai-settings.js} (61%) diff --git a/src/js/settings/components/settings.js b/src/js/settings/components/classifai-settings.js similarity index 61% rename from src/js/settings/components/settings.js rename to src/js/settings/components/classifai-settings.js index 398082c00..e91c5d423 100644 --- a/src/js/settings/components/settings.js +++ b/src/js/settings/components/classifai-settings.js @@ -6,23 +6,15 @@ import { TabPanel, SlotFillProvider } from '@wordpress/components'; /** * Internal dependencies */ -import { Header, SettingsWrapper } from '../components'; -import { updateUrl } from '../utils/utils'; -import { useSettings } from '../hooks/use-settings'; +import { Header, SettingsWrapper } from '.'; +import { getInitialService, updateUrl } from '../utils/utils'; +import { useSettings } from '../hooks'; const { services } = window.classifAISettings; const Content = () => { - useSettings( true ); // Load settings. - - // Switch the default settings tab based on the URL tab query - const urlParams = new URLSearchParams( window.location.search ); - const requestedTab = urlParams.get( 'tab' ); - const initialService = Object.keys( services || {} ).includes( - requestedTab - ) - ? requestedTab - : 'language_processing'; + const { setCurrentService } = useSettings( true ); // Load settings. + const initialService = getInitialService(); const serviceKeys = Object.keys( services || {} ); const serviceOptions = serviceKeys.map( ( slug ) => { @@ -40,6 +32,7 @@ const Content = () => { initialTabName={ initialService } tabs={ serviceOptions } onSelect={ ( tabName ) => { + setCurrentService( tabName ); return updateUrl( 'tab', tabName ); } } > @@ -50,7 +43,7 @@ const Content = () => { ); }; -export const Settings = () => { +export const ClassifAISettings = () => { return (
diff --git a/src/js/settings/index.js b/src/js/settings/index.js index ae9d54451..6f890ee9f 100644 --- a/src/js/settings/index.js +++ b/src/js/settings/index.js @@ -7,7 +7,7 @@ import { createRoot } from '@wordpress/element'; /** * Internal dependencies */ -import { Settings } from './components/settings'; +import { ClassifAISettings } from './components/classifai-settings'; import './data/store'; import '../../scss/settings.scss'; import './features'; // TODO: This is for testing purposes only and still in experimental phase. @@ -16,5 +16,5 @@ import './providers'; // TODO: This is for testing purposes only and still in ex domReady( () => { const root = createRoot( document.getElementById( 'classifai-settings' ) ); - root.render( ); + root.render( ); } ); From e93c8fba67095a7b350e1d16e64d2dd6465fefa2 Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Sat, 18 May 2024 12:47:29 +0530 Subject: [PATCH 009/138] Add current service and feature releated functions in useSettings hook. --- .../hooks/{use-settings.js => index.js} | 6 ++++ src/js/settings/utils/utils.js | 36 +++++++++++++++++++ 2 files changed, 42 insertions(+) rename src/js/settings/hooks/{use-settings.js => index.js} (90%) diff --git a/src/js/settings/hooks/use-settings.js b/src/js/settings/hooks/index.js similarity index 90% rename from src/js/settings/hooks/use-settings.js rename to src/js/settings/hooks/index.js index c6f993445..1b11e6b0b 100644 --- a/src/js/settings/hooks/use-settings.js +++ b/src/js/settings/hooks/index.js @@ -38,6 +38,10 @@ export const useSettings = ( loadSettings = false ) => { dispatch( STORE_NAME ).setIsSaving( saving ); const setIsLoaded = ( loaded ) => dispatch( STORE_NAME ).setIsLoaded( loaded ); + const setCurrentService = ( service ) => + dispatch( STORE_NAME ).setCurrentService( service ); + const setCurrentFeature = ( feature ) => + dispatch( STORE_NAME ).setCurrentFeature( feature ); // Load settings when the hook is called useEffect( () => { @@ -87,5 +91,7 @@ export const useSettings = ( loadSettings = false ) => { getFeatureSettings, setFeatureSettings, saveSettings, + setCurrentService, + setCurrentFeature, }; }; diff --git a/src/js/settings/utils/utils.js b/src/js/settings/utils/utils.js index eb89fba39..9d4b946c0 100644 --- a/src/js/settings/utils/utils.js +++ b/src/js/settings/utils/utils.js @@ -35,3 +35,39 @@ export const getFeatures = () => { return features; }; + +/** + * Get the initial service based on the URL query. + * + * @return {string} The initial service based on the URL query. + */ +export const getInitialService = () => { + const { services } = window.classifAISettings; + const urlParams = new URLSearchParams( window.location.search ); + const requestedTab = urlParams.get( 'tab' ); + const initialService = Object.keys( services || {} ).includes( + requestedTab + ) + ? requestedTab + : 'language_processing'; + return initialService; +}; + +/** + * Get the initial feature based on the URL query. + * + * @param {string} service The current service. + * @return {string} The initial feature based on the URL query. + */ +export const getInitialFeature = ( service ) => { + const { features } = window.classifAISettings; + const urlParams = new URLSearchParams( window.location.search ); + const requestedFeature = urlParams.get( 'feature' ); + const serviceFeatures = features[ service ] || {}; + const initialFeature = Object.keys( serviceFeatures ).includes( + requestedFeature + ) + ? requestedFeature + : Object.keys( serviceFeatures )[ 0 ] || 'feature_classification'; + return initialFeature; +}; From 6d0c0835ed7c025f856109c7526c49ff74ae175b Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Sat, 18 May 2024 12:48:30 +0530 Subject: [PATCH 010/138] Update in settings-wrapper component. --- src/js/settings/components/settings-wrapper.js | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/js/settings/components/settings-wrapper.js b/src/js/settings/components/settings-wrapper.js index 9bf7c8e95..d1784c971 100644 --- a/src/js/settings/components/settings-wrapper.js +++ b/src/js/settings/components/settings-wrapper.js @@ -4,7 +4,8 @@ import { TabPanel } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { FeatureSettings } from './feature-settings'; -import { updateUrl } from '../utils/utils'; +import { updateUrl, getInitialFeature } from '../utils/utils'; +import { useSettings } from '../hooks'; /** * Internal dependencies @@ -20,14 +21,9 @@ const { features } = window.classifAISettings; */ export const SettingsWrapper = ( { tab } ) => { // Switch the default feature tab based on the URL feature query - const urlParams = new URLSearchParams( window.location.search ); - const requestedFeature = urlParams.get( 'feature' ); + const initialFeature = getInitialFeature( tab ); const serviceFeatures = features[ tab ] || {}; - const initialFeature = Object.keys( serviceFeatures ).includes( - requestedFeature - ) - ? requestedFeature - : Object.keys( serviceFeatures )[ 0 ] || 'classification'; + const { setCurrentFeature } = useSettings(); // Get the features for the selected service. const featureOptions = Object.keys( serviceFeatures ).map( ( feature ) => { @@ -48,6 +44,7 @@ export const SettingsWrapper = ( { tab } ) => { initialTabName={ initialFeature } tabs={ featureOptions } onSelect={ ( featureName ) => { + setCurrentFeature( featureName ); return updateUrl( 'feature', featureName ); } } > From 8b92370467414531c3d5a17ca16f949a221618c0 Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Sat, 18 May 2024 12:49:06 +0530 Subject: [PATCH 011/138] Set default service and feature based on tab. --- src/js/settings/data/reducer.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/js/settings/data/reducer.js b/src/js/settings/data/reducer.js index be54af3b8..eb3555f8b 100644 --- a/src/js/settings/data/reducer.js +++ b/src/js/settings/data/reducer.js @@ -1,8 +1,12 @@ +import { getInitialFeature, getInitialService } from '../utils/utils'; + const { classifAISettings } = window; +const initialService = getInitialService(); +const initialFeature = getInitialFeature( initialService ); const DEFAULT_STATE = { - currentService: 'language_processing', - currentFeature: 'classification', - settings: classifAISettings || {}, + currentService: initialService, + currentFeature: initialFeature, + settings: classifAISettings.settings || {}, isLoaded: false, isSaving: false, }; From c59868821b11ebd6a6e21e3a116fed24fe63c5fa Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Sat, 18 May 2024 13:03:08 +0530 Subject: [PATCH 012/138] Re-structure folders in components folder. --- .../index.js} | 15 +++++++++----- .../components/{header.js => header/index.js} | 2 +- src/js/settings/components/index.js | 6 +++++- .../index.js} | 20 +++++++++---------- .../index.js} | 4 ++-- src/js/settings/index.js | 2 +- 6 files changed, 29 insertions(+), 20 deletions(-) rename src/js/settings/components/{classifai-settings.js => classifai-settings/index.js} (76%) rename src/js/settings/components/{header.js => header/index.js} (97%) rename src/js/settings/components/{settings-wrapper.js => service-settings/index.js} (66%) rename src/js/settings/components/{user-access.js => user-permissions/index.js} (94%) diff --git a/src/js/settings/components/classifai-settings.js b/src/js/settings/components/classifai-settings/index.js similarity index 76% rename from src/js/settings/components/classifai-settings.js rename to src/js/settings/components/classifai-settings/index.js index e91c5d423..1da8da86f 100644 --- a/src/js/settings/components/classifai-settings.js +++ b/src/js/settings/components/classifai-settings/index.js @@ -6,9 +6,9 @@ import { TabPanel, SlotFillProvider } from '@wordpress/components'; /** * Internal dependencies */ -import { Header, SettingsWrapper } from '.'; -import { getInitialService, updateUrl } from '../utils/utils'; -import { useSettings } from '../hooks'; +import { Header, ServiceSettings } from '..'; +import { getInitialService, updateUrl } from '../../utils/utils'; +import { useSettings } from '../../hooks'; const { services } = window.classifAISettings; @@ -36,8 +36,13 @@ const Content = () => { return updateUrl( 'tab', tabName ); } } > - { ( tab ) => { - return ; + { ( service ) => { + return ( + + ); } } ); diff --git a/src/js/settings/components/header.js b/src/js/settings/components/header/index.js similarity index 97% rename from src/js/settings/components/header.js rename to src/js/settings/components/header/index.js index 7dad8f801..4203d5433 100644 --- a/src/js/settings/components/header.js +++ b/src/js/settings/components/header/index.js @@ -14,7 +14,7 @@ import { __ } from '@wordpress/i18n'; /** * Internal dependencies */ -import { ClassifAILogo } from '../utils/icons'; +import { ClassifAILogo } from '../../utils/icons'; export const Header = ( props ) => { const { isSetupPage } = props; diff --git a/src/js/settings/components/index.js b/src/js/settings/components/index.js index b899466a0..57147daaa 100644 --- a/src/js/settings/components/index.js +++ b/src/js/settings/components/index.js @@ -1,3 +1,7 @@ export * from './header'; -export * from './settings-wrapper'; +export * from './classifai-settings'; +export * from './service-settings'; export * from './feature-settings'; +export * from './additional-feature-settings'; +export * from './provider-settings'; +export * from './user-permissions'; diff --git a/src/js/settings/components/settings-wrapper.js b/src/js/settings/components/service-settings/index.js similarity index 66% rename from src/js/settings/components/settings-wrapper.js rename to src/js/settings/components/service-settings/index.js index d1784c971..d7d4b7d36 100644 --- a/src/js/settings/components/settings-wrapper.js +++ b/src/js/settings/components/service-settings/index.js @@ -3,9 +3,9 @@ */ import { TabPanel } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; -import { FeatureSettings } from './feature-settings'; -import { updateUrl, getInitialFeature } from '../utils/utils'; -import { useSettings } from '../hooks'; +import { FeatureSettings } from '../feature-settings'; +import { updateUrl, getInitialFeature } from '../../utils/utils'; +import { useSettings } from '../../hooks'; /** * Internal dependencies @@ -13,16 +13,16 @@ import { useSettings } from '../hooks'; const { features } = window.classifAISettings; /** - * SettingsWrapper component to render the feature navigation tabs and the feature settings. + * ServiceSettings component to render the feature navigation tabs and the feature settings. * - * @param {Object} props All the props passed to this function - * @param {string} props.tab The name of the tab. - * @return {Object} The SettingsWrapper component. + * @param {Object} props All the props passed to this function + * @param {string} props.service The name of the service. + * @return {Object} The ServiceSettings component. */ -export const SettingsWrapper = ( { tab } ) => { +export const ServiceSettings = ( { service } ) => { // Switch the default feature tab based on the URL feature query - const initialFeature = getInitialFeature( tab ); - const serviceFeatures = features[ tab ] || {}; + const initialFeature = getInitialFeature( service ); + const serviceFeatures = features[ service ] || {}; const { setCurrentFeature } = useSettings(); // Get the features for the selected service. diff --git a/src/js/settings/components/user-access.js b/src/js/settings/components/user-permissions/index.js similarity index 94% rename from src/js/settings/components/user-access.js rename to src/js/settings/components/user-permissions/index.js index fda477eeb..716d8eb8b 100644 --- a/src/js/settings/components/user-access.js +++ b/src/js/settings/components/user-permissions/index.js @@ -12,8 +12,8 @@ import { __ } from '@wordpress/i18n'; /** * Internal dependencies */ -import { getFeature } from '../utils/utils'; -import { UserSelector } from '../../components'; +import { getFeature } from '../../utils/utils'; +import { UserSelector } from '../../../components'; export const UserPermissions = ( { featureName, diff --git a/src/js/settings/index.js b/src/js/settings/index.js index 6f890ee9f..0e757f16f 100644 --- a/src/js/settings/index.js +++ b/src/js/settings/index.js @@ -7,7 +7,7 @@ import { createRoot } from '@wordpress/element'; /** * Internal dependencies */ -import { ClassifAISettings } from './components/classifai-settings'; +import { ClassifAISettings } from './components'; import './data/store'; import '../../scss/settings.scss'; import './features'; // TODO: This is for testing purposes only and still in experimental phase. From 27f65da0a8e2c718cb601c2aa210d0402083e7d1 Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Sat, 18 May 2024 15:01:18 +0530 Subject: [PATCH 013/138] Make use of to extend provider and additional feature settings. --- .../index.js} | 57 ++++++------------- src/js/settings/utils/utils.js | 10 ++++ 2 files changed, 28 insertions(+), 39 deletions(-) rename src/js/settings/components/{feature-settings.js => feature-settings/index.js} (62%) diff --git a/src/js/settings/components/feature-settings.js b/src/js/settings/components/feature-settings/index.js similarity index 62% rename from src/js/settings/components/feature-settings.js rename to src/js/settings/components/feature-settings/index.js index 913906166..6f032989a 100644 --- a/src/js/settings/components/feature-settings.js +++ b/src/js/settings/components/feature-settings/index.js @@ -3,47 +3,36 @@ */ import { __, sprintf } from '@wordpress/i18n'; import { - withFilters, - Slot, ToggleControl, SelectControl, Button, Panel, PanelBody, Spinner, + Slot, } from '@wordpress/components'; import { useState } from '@wordpress/element'; -import { UserPermissions } from './user-access'; +import { PluginArea } from '@wordpress/plugins'; /** * Internal dependencies */ -import { getFeature } from '../utils/utils'; -import { useSettings } from '../hooks/use-settings'; - -// Provides an entry point to slot in additional settings. -const ProviderSettingsComponent = () => <>; -const FeatureSettingsComponent = () => <>; -const ProviderSettings = withFilters( 'classifai.ProviderSettings' )( - ProviderSettingsComponent -); -const AdditionalFeatureSettings = withFilters( 'classifai.FeatureSettings' )( - FeatureSettingsComponent -); +import { getFeature, getScope } from '../../utils/utils'; +import { useSettings } from '../../hooks'; +import { UserPermissions } from '../user-permissions'; /** * Feature Settings component. * - * @param {Object} props All the props passed to this function + * @param {Object} props All the props passed to this function. + * @param {string} props.featureName The name of the feature. */ -export const FeatureSettings = ( props ) => { - const { featureName } = props; +export const FeatureSettings = ( { featureName } ) => { const { setFeatureSettings, saveSettings, settings, isLoaded } = useSettings(); const feature = getFeature( featureName ); const featureSettings = settings[ featureName ] || {}; const featureTitle = feature?.label || __( 'Feature', 'classifai' ); - const [ hasUpdates, setHasUpdates ] = useState( false ); const providers = Object.keys( feature?.providers || {} ).map( ( value ) => { @@ -55,20 +44,15 @@ export const FeatureSettings = ( props ) => { ); function setSettings( newSettings ) { - setFeatureSettings( featureName, { - ...featureSettings, - ...newSettings, - } ); - setHasUpdates( true ); + setFeatureSettings( newSettings ); } function saveFeatureSettings() { saveSettings( featureName ); - setHasUpdates( false ); } if ( ! isLoaded ) { - return ; + return ; // TODO: Add proper styling for the spinner. } return ( @@ -96,8 +80,12 @@ export const FeatureSettings = ( props ) => { options={ providers } /> - - + + { ( fills ) => <> { fills } } + + + { ( fills ) => <> { fills } } + {
- - + + ); }; diff --git a/src/js/settings/utils/utils.js b/src/js/settings/utils/utils.js index 9d4b946c0..7dc89c928 100644 --- a/src/js/settings/utils/utils.js +++ b/src/js/settings/utils/utils.js @@ -71,3 +71,13 @@ export const getInitialFeature = ( service ) => { : Object.keys( serviceFeatures )[ 0 ] || 'feature_classification'; return initialFeature; }; + +/** + * Get the scope name for the given string. + * + * @param {string} name The name to convert to a valid scope name. + * @return {string} returns the scope name + */ +export const getScope = ( name ) => { + return ( name || '' ).replace( /_/g, '-' ); +}; From f8af6aa113cb081eb220a0d5a3bda4c8fa7d5553 Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Sat, 18 May 2024 15:02:54 +0530 Subject: [PATCH 014/138] Extend feature settings for comment-moderation feature. --- src/js/settings/components/index.js | 2 - src/js/settings/data/actions.js | 21 +++++-- src/js/settings/data/selectors.js | 11 +++- .../settings/features/comment-moderation.js | 62 +++++++++++++++++++ src/js/settings/features/index.js | 2 +- src/js/settings/features/title-generation.js | 38 ------------ src/js/settings/hooks/index.js | 4 +- src/scss/settings.scss | 1 + 8 files changed, 90 insertions(+), 51 deletions(-) create mode 100644 src/js/settings/features/comment-moderation.js delete mode 100644 src/js/settings/features/title-generation.js diff --git a/src/js/settings/components/index.js b/src/js/settings/components/index.js index 57147daaa..1eeb72244 100644 --- a/src/js/settings/components/index.js +++ b/src/js/settings/components/index.js @@ -2,6 +2,4 @@ export * from './header'; export * from './classifai-settings'; export * from './service-settings'; export * from './feature-settings'; -export * from './additional-feature-settings'; -export * from './provider-settings'; export * from './user-permissions'; diff --git a/src/js/settings/data/actions.js b/src/js/settings/data/actions.js index e106e4a62..cb5ace5e9 100644 --- a/src/js/settings/data/actions.js +++ b/src/js/settings/data/actions.js @@ -1,14 +1,23 @@ +export const setFeatureSettings = + ( settings ) => + ( { select, dispatch } ) => { + const currentFeature = select.getCurrentFeature(); + const featureSettings = select.getFeatureSettings(); + dispatch( { + type: 'SET_FEATURE_SETTINGS', + feature: currentFeature, + payload: { + ...featureSettings, + ...settings, + }, + } ); + }; + export const setSettings = ( settings ) => ( { type: 'SET_SETTINGS', payload: settings, } ); -export const setFeatureSettings = ( feature, settings ) => ( { - type: 'SET_FEATURE_SETTINGS', - feature, - payload: settings, -} ); - export const setCurrentService = ( service ) => ( { type: 'SET_CURRENT_SERVICE', payload: service, diff --git a/src/js/settings/data/selectors.js b/src/js/settings/data/selectors.js index 4a465c988..54e35b965 100644 --- a/src/js/settings/data/selectors.js +++ b/src/js/settings/data/selectors.js @@ -1,5 +1,12 @@ -export const getSettings = ( state, feature ) => - feature ? state.setting?.[ feature ] || state.settings : state.settings; +export const getSettings = ( state, feature ) => { + if ( feature ) { + return state.settings?.[ feature ] || state.settings; + } + return state.settings; +}; + +export const getFeatureSettings = ( state ) => + state.settings?.[ state.currentFeature ] || {}; export const getCurrentService = ( state ) => state.currentService; diff --git a/src/js/settings/features/comment-moderation.js b/src/js/settings/features/comment-moderation.js new file mode 100644 index 000000000..6ad1930f5 --- /dev/null +++ b/src/js/settings/features/comment-moderation.js @@ -0,0 +1,62 @@ +/** + * WordPress dependencies + */ +import { CheckboxControl, Fill } from '@wordpress/components'; +import { registerPlugin } from '@wordpress/plugins'; +import { __ } from '@wordpress/i18n'; +import { useSelect, useDispatch } from '@wordpress/data'; + +const ModerationSettings = () => { + const featureSettings = useSelect( ( select ) => + select( 'classifai-settings' ).getFeatureSettings() + ); + const { setFeatureSettings } = useDispatch( 'classifai-settings' ); + const contentTypes = { + comments: __( 'Comments', 'classifai' ), + }; + return ( + <> + +
+
+ { __( 'Content to moderate', 'classifai' ) } +
+ { Object.keys( contentTypes ).map( ( contentType ) => { + return ( + { + setFeatureSettings( { + content_types: { + ...featureSettings.content_types, + [ contentType ]: value + ? contentType + : '0', + }, + } ); + } } + /> + ); + } ) } + + { __( + 'Choose what type of content to moderate.', + 'classifai' + ) } + +
+
+ + ); +}; + +registerPlugin( 'classifai-feature-title-generation', { + scope: 'feature-moderation', + render: ModerationSettings, +} ); diff --git a/src/js/settings/features/index.js b/src/js/settings/features/index.js index 6d7d1790b..c10962afd 100644 --- a/src/js/settings/features/index.js +++ b/src/js/settings/features/index.js @@ -1 +1 @@ -import './title-generation'; +import './comment-moderation'; diff --git a/src/js/settings/features/title-generation.js b/src/js/settings/features/title-generation.js deleted file mode 100644 index f600521f4..000000000 --- a/src/js/settings/features/title-generation.js +++ /dev/null @@ -1,38 +0,0 @@ -import { addFilter } from '@wordpress/hooks'; -import { Fill, TextControl } from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; - -function titleGenerationSettings( FeatureSettings ) { - return ( props ) => { - const { featureName, featureSettings, setSettings } = props; - if ( featureName !== 'feature_title_generation' ) { - return ; - } - - const provider = featureSettings?.provider || ''; - const numberOfSuggestions = - featureSettings?.[ provider ]?.number_of_suggestions || '1'; - return ( - <> - - - - setSettings( { - number_of_suggestions: value, - } ) - } - /> - - - ); - }; -} - -addFilter( - 'classifai.FeatureSettings', - 'classifai-feature-settings/title-generation', - titleGenerationSettings -); diff --git a/src/js/settings/hooks/index.js b/src/js/settings/hooks/index.js index 1b11e6b0b..41a7a2bf0 100644 --- a/src/js/settings/hooks/index.js +++ b/src/js/settings/hooks/index.js @@ -32,8 +32,8 @@ export const useSettings = ( loadSettings = false ) => { const getFeatureSettings = ( feature ) => settings[ feature ] || {}; const setSettings = ( data ) => dispatch( STORE_NAME ).setSettings( data ); - const setFeatureSettings = ( feature, data ) => - dispatch( STORE_NAME ).setFeatureSettings( feature, data ); + const setFeatureSettings = ( data ) => + dispatch( STORE_NAME ).setFeatureSettings( data ); const setIsSaving = ( saving ) => dispatch( STORE_NAME ).setIsSaving( saving ); const setIsLoaded = ( loaded ) => diff --git a/src/scss/settings.scss b/src/scss/settings.scss index 968d8bbcc..0a5d08c61 100644 --- a/src/scss/settings.scss +++ b/src/scss/settings.scss @@ -10,6 +10,7 @@ margin-top: 16px; } + label.components-base-control__label, label.components-input-control__label, label.components-form-token-field__label { margin-top: 16px; From de4663731f44f821c9284d82078f49fd9261d0f3 Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Sat, 18 May 2024 15:03:32 +0530 Subject: [PATCH 015/138] Extend open-moderation provider fields. --- src/js/settings/providers/index.js | 2 +- .../settings/providers/openai-moderation.js | 47 +++++++++++++++++++ src/js/settings/providers/openai-whisper.js | 40 ---------------- 3 files changed, 48 insertions(+), 41 deletions(-) create mode 100644 src/js/settings/providers/openai-moderation.js delete mode 100644 src/js/settings/providers/openai-whisper.js diff --git a/src/js/settings/providers/index.js b/src/js/settings/providers/index.js index 664a9176d..21f08a6f9 100644 --- a/src/js/settings/providers/index.js +++ b/src/js/settings/providers/index.js @@ -1 +1 @@ -import './openai-whisper'; +import './openai-moderation'; diff --git a/src/js/settings/providers/openai-moderation.js b/src/js/settings/providers/openai-moderation.js new file mode 100644 index 000000000..18f968a3a --- /dev/null +++ b/src/js/settings/providers/openai-moderation.js @@ -0,0 +1,47 @@ +import { registerPlugin } from '@wordpress/plugins'; +import { useSelect, useDispatch } from '@wordpress/data'; +import { Fill, TextControl } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; + +const OpenAIModerationSettings = () => { + const providerName = 'openai_moderation'; + const featureSettings = useSelect( ( select ) => + select( 'classifai-settings' ).getFeatureSettings() + ); + const { setFeatureSettings } = useDispatch( 'classifai-settings' ); + const providerSettings = featureSettings[ providerName ] || {}; + const setProviderSettings = ( data ) => + setFeatureSettings( { + [ providerName ]: { + ...providerSettings, + ...data, + }, + } ); + + return ( + + + setProviderSettings( { api_key: value } ) + } + /> + + { __( "Don't have an OpenAI account yet? ", 'classifai' ) } + + { __( 'Sign up for one', 'classifai' ) } + { ' ' } + { __( 'in order to get your API key.', 'classifai' ) } + + + ); +}; + +registerPlugin( 'classifai-provider-moderation-chatgpt', { + scope: 'openai-moderation', + render: OpenAIModerationSettings, +} ); diff --git a/src/js/settings/providers/openai-whisper.js b/src/js/settings/providers/openai-whisper.js deleted file mode 100644 index edb6943b0..000000000 --- a/src/js/settings/providers/openai-whisper.js +++ /dev/null @@ -1,40 +0,0 @@ -import { addFilter } from '@wordpress/hooks'; -import { Fill, TextControl } from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; - -function openAIWhisperSettings( ProviderSettings ) { - return ( props ) => { - const { featureSettings = {}, setSettings = {} } = props; - if ( featureSettings.provider !== 'openai_whisper' ) { - return ; - } - - const apiKey = featureSettings.openai_whisper?.api_key || ''; - - return ( - <> - - - - setSettings( { - openai_whisper: { - ...featureSettings.openai_whisper, - api_key: value, - }, - } ) - } - /> - - - ); - }; -} - -addFilter( - 'classifai.ProviderSettings', - 'classifai-provider-settings/openai-whisper', - openAIWhisperSettings -); From 0bf80548415311d28a3ace95bdf534d33491042e Mon Sep 17 00:00:00 2001 From: faisal-alvi Date: Fri, 24 May 2024 17:41:40 +0530 Subject: [PATCH 016/138] narrow down the container --- src/scss/settings.scss | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/scss/settings.scss b/src/scss/settings.scss index 0a5d08c61..820eeb070 100644 --- a/src/scss/settings.scss +++ b/src/scss/settings.scss @@ -27,7 +27,8 @@ } .setting-tabs { - margin-left: -20px; + max-width: 850px; + margin: auto; .components-tab-panel__tabs { justify-content: start; border-bottom: 1px solid #ccd0d4; @@ -37,6 +38,7 @@ .components-tab-panel__tabs-item { font-size: 15px; + height: auto; } .active-tab, From 9130c00eb96b7f47e22e7265c9ad840d1c121f64 Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Fri, 24 May 2024 22:43:00 +0530 Subject: [PATCH 017/138] Fix header links. --- src/js/settings/components/header/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/js/settings/components/header/index.js b/src/js/settings/components/header/index.js index 4203d5433..b7c3932a8 100644 --- a/src/js/settings/components/header/index.js +++ b/src/js/settings/components/header/index.js @@ -27,13 +27,13 @@ export const Header = ( props ) => {

{ isSetupPage && ( - ) } { ! isSetupPage && ( -
+
+
diff --git a/src/js/settings/components/service-settings/index.js b/src/js/settings/components/service-settings/index.js index f3e420e24..e9dbe64cb 100644 --- a/src/js/settings/components/service-settings/index.js +++ b/src/js/settings/components/service-settings/index.js @@ -1,63 +1,62 @@ /** * External dependencies */ -import { TabPanel } from '@wordpress/components'; +import { NavLink, Navigate, Outlet, useParams } from 'react-router-dom'; + +/** + * WordPress dependencies + */ import { __ } from '@wordpress/i18n'; -import { FeatureSettings } from '../feature-settings'; -import { updateUrl, getInitialFeature } from '../../utils/utils'; import { useDispatch } from '@wordpress/data'; +import { useEffect } from '@wordpress/element'; /** * Internal dependencies */ import { STORE_NAME } from '../../data/store'; -const { features } = window.classifAISettings; +const { features, services } = window.classifAISettings; /** * ServiceSettings component to render the feature navigation tabs and the feature settings. * - * @param {Object} props All the props passed to this function - * @param {string} props.service The name of the service. * @return {Object} The ServiceSettings component. */ -export const ServiceSettings = ( { service } ) => { - // Switch the default feature tab based on the URL feature query - const initialFeature = getInitialFeature( service ); - const serviceFeatures = features[ service ] || {}; - const { setCurrentFeature } = useDispatch( STORE_NAME ); +export const ServiceSettings = () => { + const { setCurrentService } = useDispatch( STORE_NAME ); + const { service } = useParams(); + useEffect( () => { + if ( service && services[ service ] ) { + setCurrentService( service ); + } + }, [ service, setCurrentService ] ); - // Get the features for the selected service. - const featureOptions = Object.keys( serviceFeatures ).map( ( feature ) => { - return { - name: feature, - title: - serviceFeatures[ feature ]?.label || - __( 'Feature', 'classifai' ), - className: feature, - }; - } ); + // If the service is not available, redirect to the language processing page. + if ( ! services[ service ] ) { + return ; + } + const serviceFeatures = features[ service ] || {}; return ( -
- { - setCurrentFeature( featureName ); - return updateUrl( 'feature', featureName ); - } } - > - { ( feature ) => { - return ( - - ); - } } - +
+
+ { Object.keys( serviceFeatures ).map( ( feature ) => ( + + isActive + ? 'active-tab classifai-tabs-item' + : 'classifai-tabs-item' + } + > + { serviceFeatures[ feature ]?.label || + __( 'Feature', 'classifai' ) } + + ) ) } +
+
+ +
); }; diff --git a/src/scss/settings.scss b/src/scss/settings.scss index 9c0057178..88e006f42 100644 --- a/src/scss/settings.scss +++ b/src/scss/settings.scss @@ -1,5 +1,6 @@ #classifai-settings { font-size: 14px; + box-sizing: border-box; #classifai-header { background-color: #fff; @@ -103,58 +104,33 @@ font-size: 15px; } - .setting-tabs { + .classifai-settings-wrapper { width: 100%; max-width: 1280px; margin: 0 auto; - .components-tab-panel__tabs { + + .classifai-tabs { + display: flex; + align-items: stretch; + flex-direction: row; justify-content: start; border-bottom: 1px solid #ccd0d4; padding-bottom: 0; margin-right: 20px; - .components-tab-panel__tabs-item { - font-size: 15px; - height: auto; - min-height: 45px; - } - - .active-tab, - .components-tab-panel__tabs-item:focus:not(:disabled) { - box-shadow: inset 0 -2px var(--wp-admin-theme-color); - } - - @media screen and (max-width: 782px) { - margin-right: 10px; - margin-top: 12px; - } - } - } - - .classifai-settings-wrapper { - margin-right: 20px; - padding-top: 24px; - - @media screen and (max-width: 782px) { - margin-right: 10px; - } - - .feature-tabs { - display: flex; - gap: 24px; - - .components-tab-panel__tabs { + &[aria-orientation="vertical"] { flex-direction: column; border: 0px; justify-content: start; flex-basis: 0; flex-grow: 1; + margin-right: 0px; - .components-tab-panel__tabs-item{ + .classifai-tabs-item { background: #fafafa; border: 1px solid #e0e0e0; text-align: left; - padding: 10px 16px; + padding: 14px 16px; &.active-tab { background: #fff; @@ -166,23 +142,132 @@ &:focus { background: #fff; } + + @media screen and (max-width: 782px) { + display: block; + } } @media screen and (max-width: 782px) { margin-right: 0px; margin-top: 0px; } + + @media screen and (max-width: 782px) { + display: block; + gap: 0; + } } - .components-tab-panel__tab-content{ - flex-basis: 0; - flex-grow: 3; + .active-tab, + .classifai-tabs-item:focus:not(:disabled) { + box-shadow: inset 0 -2px var(--wp-admin-theme-color); } @media screen and (max-width: 782px) { - display: block; - gap: 0; + margin-right: 10px; + margin-top: 12px; } + + .classifai-tabs-item { + position: relative; + border-radius: 0; + background: transparent; + border: none; + box-shadow: none; + cursor: pointer; + padding: 14px 16px; + margin-left: 0; + font-weight: 500; + text-decoration: none; + font-size: 15px; + height: auto; + color: var(--wp-components-color-foreground, #1e1e1e); + + &:hover, + &:focus { + color:var(--wp-components-color-accent, var(--wp-admin-theme-color, #3858e9)); + } + + &:focus:not(:disabled) { + position: relative; + box-shadow: none; + outline: none; + } + + // Tab indicator + &::after { + content: ""; + position: absolute; + right: 0; + bottom: 0; + left: 0; + pointer-events: none; + + // Draw the indicator. + background: var(--wp-admin-theme-color, #3858e9); + height: calc(0 * var(--wp-admin-border-width-focus)); + border-radius: 0; + + // Animation + transition: all 0.1s linear; + } + + // Active. + &.active-tab::after { + height: calc(1 * var(--wp-admin-border-width-focus)); + + // Windows high contrast mode. + outline: 2px solid transparent; + outline-offset: -1px; + } + + // Focus. + &::before { + content: ""; + position: absolute; + top: 12px; + right: 12px; + bottom: 12px; + left: 12px; + pointer-events: none; + + // Draw the indicator. + box-shadow: 0 0 0 0 transparent; + border-radius: 2px; + + // Animation + transition: all 0.1s linear; + } + + &:focus-visible::before { + box-shadow: 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color, #3858e9); + + // Windows high contrast mode. + outline: 2px solid transparent; + } + } + } + } + + .service-settings-wrapper { + margin-right: 20px; + padding-top: 24px; + display: flex; + gap: 24px; + + @media screen and (max-width: 782px) { + margin-right: 10px; + } + + .feature-settings-wrapper{ + flex-basis: 0; + flex-grow: 3; + } + + @media screen and (max-width: 782px) { + display: block; + gap: 0; } } } From 2d6ed38d814e3d8663763a6a77a6c5fcbb63273c Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Thu, 6 Jun 2024 11:50:21 +0530 Subject: [PATCH 025/138] Code Improvements --- .../components/allowed-roles/index.js | 4 +- .../components/classifai-settings/index.js | 37 ++++++++++++++++++- .../components/feature-settings/index.js | 23 ++++-------- .../components/service-settings/index.js | 13 ++----- .../components/user-permissions/index.js | 16 ++++---- 5 files changed, 57 insertions(+), 36 deletions(-) diff --git a/src/js/settings/components/allowed-roles/index.js b/src/js/settings/components/allowed-roles/index.js index 8c393e0ee..21fb8ebfb 100644 --- a/src/js/settings/components/allowed-roles/index.js +++ b/src/js/settings/components/allowed-roles/index.js @@ -14,8 +14,8 @@ import { SettingsRow } from '../settings-row'; export const AllowedRoles = ( { featureName } ) => { const { setFeatureSettings } = useDispatch( STORE_NAME ); - const featureSettings = useSelect( ( select ) => - select( STORE_NAME ).getFeatureSettings() + const featureSettings = useSelect( + ( select ) => select( STORE_NAME ).getSettings( featureName ) || {} ); const feature = getFeature( featureName ); const roles = feature.roles || {}; diff --git a/src/js/settings/components/classifai-settings/index.js b/src/js/settings/components/classifai-settings/index.js index 5508535da..71645573f 100644 --- a/src/js/settings/components/classifai-settings/index.js +++ b/src/js/settings/components/classifai-settings/index.js @@ -26,12 +26,42 @@ import { STORE_NAME } from '../../data/store'; const { services, features } = window.classifAISettings; +/** + * DefaultFeatureSettings component to navigate to the default feature settings. + * If no feature is selected, it will redirect to the first feature. + */ const DefaultFeatureSettings = () => { const { service } = useParams(); const feature = Object.keys( features[ service ] || {} )[ 0 ]; return ; }; +/** + * FeatureSettingsWrapper component to render the feature settings. + * If the feature is not available, it will redirect to the first feature. + */ +const FeatureSettingsWrapper = () => { + const { service, feature } = useParams(); + const serviceFeatures = Object.keys( features[ service ] || {} ); + + if ( ! serviceFeatures.includes( feature ) ) { + return ; + } + + return ; +}; + +const ServiceSettingsWrapper = () => { + const { service } = useParams(); + + // If the service is not available, redirect to the language processing page. + if ( ! services[ service ] ) { + return ; + } + + return ; +}; + /** * ServiceNavigation component to render the service navigation tabs. * @@ -79,14 +109,17 @@ export const ClassifAISettings = () => {
- }> + } + > } /> } + element={ } /> { /* When no routes match, it will redirect to this route path. Note that it should be registered above. */ } diff --git a/src/js/settings/components/feature-settings/index.js b/src/js/settings/components/feature-settings/index.js index 3be69433f..a449b23bb 100644 --- a/src/js/settings/components/feature-settings/index.js +++ b/src/js/settings/components/feature-settings/index.js @@ -23,37 +23,30 @@ import { useSettings } from '../../hooks'; import { UserPermissions } from '../user-permissions'; import { SettingsRow } from '../settings-row'; import { STORE_NAME } from '../../data/store'; -import { Navigate, useParams } from 'react-router-dom'; -const { features } = window.classifAISettings; /** * Feature Settings component. * + * @param {Object} props Component props. + * @param {string} props.featureName Feature name. */ -export const FeatureSettings = () => { - const { feature: featureName, service } = useParams(); - const serviceFeatures = Object.keys( features[ service ] || {} ); - const { setCurrentFeature } = useDispatch( STORE_NAME ); - const { setFeatureSettings, saveSettings } = useSettings(); +export const FeatureSettings = ( { featureName } ) => { + const { setCurrentFeature, setFeatureSettings } = useDispatch( STORE_NAME ); + const { saveSettings } = useSettings(); useEffect( () => { setCurrentFeature( featureName ); }, [ featureName, setCurrentFeature ] ); - const { settings, isLoaded } = useSelect( ( select ) => { + const { featureSettings, isLoaded } = useSelect( ( select ) => { return { - settings: select( STORE_NAME ).getSettings(), + featureSettings: + select( STORE_NAME ).getSettings( featureName ) || {}, isLoaded: select( STORE_NAME ).getIsLoaded(), }; } ); - // If the feature is not available, redirect to the first feature. - if ( ! serviceFeatures.includes( featureName ) ) { - return ; - } - const feature = getFeature( featureName ); - const featureSettings = settings[ featureName ] || {}; const featureTitle = feature?.label || __( 'Feature', 'classifai' ); const providers = Object.keys( feature?.providers || {} ).map( diff --git a/src/js/settings/components/service-settings/index.js b/src/js/settings/components/service-settings/index.js index e9dbe64cb..1a321592d 100644 --- a/src/js/settings/components/service-settings/index.js +++ b/src/js/settings/components/service-settings/index.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { NavLink, Navigate, Outlet, useParams } from 'react-router-dom'; +import { NavLink, Outlet, useParams } from 'react-router-dom'; /** * WordPress dependencies @@ -14,7 +14,7 @@ import { useEffect } from '@wordpress/element'; * Internal dependencies */ import { STORE_NAME } from '../../data/store'; -const { features, services } = window.classifAISettings; +const { features } = window.classifAISettings; /** * ServiceSettings component to render the feature navigation tabs and the feature settings. @@ -25,16 +25,9 @@ export const ServiceSettings = () => { const { setCurrentService } = useDispatch( STORE_NAME ); const { service } = useParams(); useEffect( () => { - if ( service && services[ service ] ) { - setCurrentService( service ); - } + setCurrentService( service ); }, [ service, setCurrentService ] ); - // If the service is not available, redirect to the language processing page. - if ( ! services[ service ] ) { - return ; - } - const serviceFeatures = features[ service ] || {}; return (
diff --git a/src/js/settings/components/user-permissions/index.js b/src/js/settings/components/user-permissions/index.js index a701d7d1e..e2cef3a85 100644 --- a/src/js/settings/components/user-permissions/index.js +++ b/src/js/settings/components/user-permissions/index.js @@ -3,6 +3,7 @@ */ import { PanelBody, ToggleControl } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; +import { useDispatch, useSelect } from '@wordpress/data'; /** * Internal dependencies @@ -10,12 +11,13 @@ import { __ } from '@wordpress/i18n'; import { UserSelector } from '../../../components'; import { AllowedRoles } from '../allowed-roles'; import { SettingsRow } from '../settings-row'; +import { STORE_NAME } from '../../data/store'; -export const UserPermissions = ( { - featureName, - featureSettings, - setSettings, -} ) => { +export const UserPermissions = ( { featureName } ) => { + const { setFeatureSettings } = useDispatch( STORE_NAME ); + const featureSettings = useSelect( ( select ) => { + return select( STORE_NAME ).getSettings( featureName ) || {}; + } ); return ( { - setSettings( { + setFeatureSettings( { ...featureSettings, users, } ); @@ -52,7 +54,7 @@ export const UserPermissions = ( { { - setSettings( { + setFeatureSettings( { ...featureSettings, user_based_opt_out: value ? '1' : 'no', } ); From a1252b69124e3d9ab37d2b934b916e85a5419b2a Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Thu, 6 Jun 2024 12:11:07 +0530 Subject: [PATCH 026/138] Simplified extended code. --- .../settings/features/comment-moderation.js | 20 +++++----- .../settings/providers/openai-moderation.js | 40 +++++++++++-------- 2 files changed, 33 insertions(+), 27 deletions(-) diff --git a/src/js/settings/features/comment-moderation.js b/src/js/settings/features/comment-moderation.js index 6ad1930f5..c0709184e 100644 --- a/src/js/settings/features/comment-moderation.js +++ b/src/js/settings/features/comment-moderation.js @@ -5,6 +5,7 @@ import { CheckboxControl, Fill } from '@wordpress/components'; import { registerPlugin } from '@wordpress/plugins'; import { __ } from '@wordpress/i18n'; import { useSelect, useDispatch } from '@wordpress/data'; +import { SettingsRow } from '../components'; const ModerationSettings = () => { const featureSettings = useSelect( ( select ) => @@ -17,10 +18,13 @@ const ModerationSettings = () => { return ( <> -
-
- { __( 'Content to moderate', 'classifai' ) } -
+ { Object.keys( contentTypes ).map( ( contentType ) => { return ( { /> ); } ) } - - { __( - 'Choose what type of content to moderate.', - 'classifai' - ) } - -
+
); diff --git a/src/js/settings/providers/openai-moderation.js b/src/js/settings/providers/openai-moderation.js index 18f968a3a..f09ec115b 100644 --- a/src/js/settings/providers/openai-moderation.js +++ b/src/js/settings/providers/openai-moderation.js @@ -2,6 +2,7 @@ import { registerPlugin } from '@wordpress/plugins'; import { useSelect, useDispatch } from '@wordpress/data'; import { Fill, TextControl } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; +import { SettingsRow } from '../components'; const OpenAIModerationSettings = () => { const providerName = 'openai_moderation'; @@ -18,25 +19,32 @@ const OpenAIModerationSettings = () => { }, } ); + const Description = () => ( + <> + { __( "Don't have an OpenAI account yet?", 'classifai' ) } + + { __( 'Sign up for one', 'classifai' ) } + { ' ' } + { __( 'in order to get your API key.', 'classifai' ) } + + ); + return ( - - setProviderSettings( { api_key: value } ) - } - /> - - { __( "Don't have an OpenAI account yet? ", 'classifai' ) } - - { __( 'Sign up for one', 'classifai' ) } - { ' ' } - { __( 'in order to get your API key.', 'classifai' ) } - + description={ } + > + + setProviderSettings( { api_key: value } ) + } + /> + ); }; From b87868365af5c67773f72834b4f4c2555480e760 Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Fri, 7 Jun 2024 16:16:10 +0530 Subject: [PATCH 027/138] Improved component re-rendering --- .../components/allowed-roles/index.js | 14 +-- .../feature-settings/enable-feature.js | 48 ++++++++++ .../components/feature-settings/index.js | 88 +++---------------- .../feature-settings/save-settings-button.js | 58 ++++++++++++ src/js/settings/data/selectors.js | 8 +- 5 files changed, 131 insertions(+), 85 deletions(-) create mode 100644 src/js/settings/components/feature-settings/enable-feature.js create mode 100644 src/js/settings/components/feature-settings/save-settings-button.js diff --git a/src/js/settings/components/allowed-roles/index.js b/src/js/settings/components/allowed-roles/index.js index 21fb8ebfb..ed5c90a8e 100644 --- a/src/js/settings/components/allowed-roles/index.js +++ b/src/js/settings/components/allowed-roles/index.js @@ -14,11 +14,11 @@ import { SettingsRow } from '../settings-row'; export const AllowedRoles = ( { featureName } ) => { const { setFeatureSettings } = useDispatch( STORE_NAME ); - const featureSettings = useSelect( - ( select ) => select( STORE_NAME ).getSettings( featureName ) || {} + const roles = useSelect( + ( select ) => select( STORE_NAME ).getFeatureSettings( 'roles' ) || {} ); const feature = getFeature( featureName ); - const roles = feature.roles || {}; + const featureRoles = feature.roles || {}; return ( { 'classifai' ) } > - { Object.keys( roles ).map( ( role ) => { + { Object.keys( featureRoles ).map( ( role ) => { return ( { setFeatureSettings( { roles: { - ...featureSettings.roles, + ...roles, [ role ]: value ? role : '0', }, } ); diff --git a/src/js/settings/components/feature-settings/enable-feature.js b/src/js/settings/components/feature-settings/enable-feature.js new file mode 100644 index 000000000..0e0f2444b --- /dev/null +++ b/src/js/settings/components/feature-settings/enable-feature.js @@ -0,0 +1,48 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { ToggleControl } from '@wordpress/components'; +import { useDispatch, useSelect } from '@wordpress/data'; +import { decodeEntities } from '@wordpress/html-entities'; + +/** + * Internal dependencies + */ +import { getFeature } from '../../utils/utils'; +import { SettingsRow } from '../settings-row'; +import { STORE_NAME } from '../../data/store'; + +/** + * Enable Feature Toggle component. + * + * @param {Object} props Component props. + * @param {string} props.featureName Feature name. + */ +export const EnableToggleControl = ( { featureName } ) => { + const { setFeatureSettings } = useDispatch( STORE_NAME ); + const status = useSelect( + ( select ) => select( STORE_NAME ).getFeatureSettings( 'status' ) || '0' + ); + + const feature = getFeature( featureName ); + const enableDescription = decodeEntities( + feature?.enable_description || __( 'Enable feature', 'classifai' ) + ); + + return ( + + + setFeatureSettings( { + status: value ? '1' : '0', // TODO: Use boolean, currently using string for compatibility. + } ) + } + /> + + ); +}; diff --git a/src/js/settings/components/feature-settings/index.js b/src/js/settings/components/feature-settings/index.js index a449b23bb..a9351cc8d 100644 --- a/src/js/settings/components/feature-settings/index.js +++ b/src/js/settings/components/feature-settings/index.js @@ -2,15 +2,7 @@ * WordPress dependencies */ import { __, sprintf } from '@wordpress/i18n'; -import { - ToggleControl, - SelectControl, - Button, - Panel, - PanelBody, - Spinner, - Slot, -} from '@wordpress/components'; +import { Panel, PanelBody, Spinner, Slot } from '@wordpress/components'; import { PluginArea } from '@wordpress/plugins'; import { useDispatch, useSelect } from '@wordpress/data'; import { useEffect } from '@wordpress/element'; @@ -19,10 +11,11 @@ import { useEffect } from '@wordpress/element'; * Internal dependencies */ import { getFeature, getScope } from '../../utils/utils'; -import { useSettings } from '../../hooks'; import { UserPermissions } from '../user-permissions'; -import { SettingsRow } from '../settings-row'; import { STORE_NAME } from '../../data/store'; +import { ProviderSettings } from '../provider-settings'; +import { EnableToggleControl } from './enable-feature'; +import { SaveSettingsButton } from './save-settings-button'; /** * Feature Settings component. @@ -31,41 +24,19 @@ import { STORE_NAME } from '../../data/store'; * @param {string} props.featureName Feature name. */ export const FeatureSettings = ( { featureName } ) => { - const { setCurrentFeature, setFeatureSettings } = useDispatch( STORE_NAME ); - const { saveSettings } = useSettings(); + const { setCurrentFeature } = useDispatch( STORE_NAME ); useEffect( () => { setCurrentFeature( featureName ); }, [ featureName, setCurrentFeature ] ); - const { featureSettings, isLoaded } = useSelect( ( select ) => { - return { - featureSettings: - select( STORE_NAME ).getSettings( featureName ) || {}, - isLoaded: select( STORE_NAME ).getIsLoaded(), - }; - } ); + const isLoaded = useSelect( ( select ) => + select( STORE_NAME ).getIsLoaded() + ); const feature = getFeature( featureName ); const featureTitle = feature?.label || __( 'Feature', 'classifai' ); - const providers = Object.keys( feature?.providers || {} ).map( - ( value ) => { - return { - value, - label: feature.providers[ value ] || '', - }; - } - ); - - function setSettings( newSettings ) { - setFeatureSettings( newSettings ); - } - - function saveFeatureSettings() { - saveSettings( featureName ); - } - if ( ! isLoaded ) { return ; // TODO: Add proper styling for the spinner. } @@ -80,53 +51,18 @@ export const FeatureSettings = ( { featureName } ) => { className="settings-panel" > - - - setSettings( { - status: status ? '1' : '0', // TODO: Use boolean, currently using string for compatibility. - } ) - } - /> - - - - setSettings( { provider } ) - } - value={ featureSettings.provider } - options={ providers } - /> - - - - { ( fills ) => <> { fills } } - + + { ( fills ) => <> { fills } } - +
- +
- ); }; diff --git a/src/js/settings/components/feature-settings/save-settings-button.js b/src/js/settings/components/feature-settings/save-settings-button.js new file mode 100644 index 000000000..daac31ece --- /dev/null +++ b/src/js/settings/components/feature-settings/save-settings-button.js @@ -0,0 +1,58 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { Button } from '@wordpress/components'; +import { useSelect, useDispatch } from '@wordpress/data'; +import apiFetch from '@wordpress/api-fetch'; + +/** + * Internal dependencies + */ +import { STORE_NAME } from '../../data/store'; + +/** + * Save Settings Button component. + * + * @param {Object} props Component props. + * @param {string} props.featureName Feature name. + */ +export const SaveSettingsButton = ( { featureName } ) => { + const { setIsSaving, setSettings } = useDispatch( STORE_NAME ); + const settings = useSelect( ( select ) => + select( STORE_NAME ).getSettings() + ); + + /** + * Save settings for a feature. + * + * @param {string} featureName Feature name + */ + const saveSettings = () => { + setIsSaving( true ); + apiFetch( { + path: '/classifai/v1/settings/', + method: 'POST', + data: { [ featureName ]: settings[ featureName ] }, + } ) + .then( ( res ) => { + setSettings( res ); + setIsSaving( false ); + } ) + .catch( ( error ) => { + // eslint-disable-next-line no-console + console.error( error ); // TODO: handle error and show a notice + setIsSaving( false ); + } ); + }; + + return ( + + ); +}; diff --git a/src/js/settings/data/selectors.js b/src/js/settings/data/selectors.js index 54e35b965..c10547335 100644 --- a/src/js/settings/data/selectors.js +++ b/src/js/settings/data/selectors.js @@ -5,8 +5,12 @@ export const getSettings = ( state, feature ) => { return state.settings; }; -export const getFeatureSettings = ( state ) => - state.settings?.[ state.currentFeature ] || {}; +export const getFeatureSettings = ( state, key ) => { + if ( key ) { + return state.settings?.[ state.currentFeature ]?.[ key ]; + } + return state.settings?.[ state.currentFeature ] || {}; +}; export const getCurrentService = ( state ) => state.currentService; From 13e7a17d6617d2c8337c30d7a6097d17a7d63b87 Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Fri, 7 Jun 2024 16:18:10 +0530 Subject: [PATCH 028/138] Added provider fields for the chatGPT, Gemini API and Azure OpenAI --- includes/Classifai/Admin/Settings.php | 7 +- .../provider-settings/azure-openai.js | 85 +++++++++++++++++++ .../provider-settings/google-gemini-api.js | 44 ++++++++++ .../components/provider-settings/index.js | 77 +++++++++++++++++ .../provider-settings/openai-chatgpt.js | 64 ++++++++++++++ .../components/user-permissions/index.js | 20 +++-- src/js/settings/data/actions.js | 18 ++++ .../settings/providers/openai-moderation.js | 2 +- 8 files changed, 305 insertions(+), 12 deletions(-) create mode 100644 src/js/settings/components/provider-settings/azure-openai.js create mode 100644 src/js/settings/components/provider-settings/google-gemini-api.js create mode 100644 src/js/settings/components/provider-settings/index.js create mode 100644 src/js/settings/components/provider-settings/openai-chatgpt.js diff --git a/includes/Classifai/Admin/Settings.php b/includes/Classifai/Admin/Settings.php index dfeeacd5a..5c46d1118 100644 --- a/includes/Classifai/Admin/Settings.php +++ b/includes/Classifai/Admin/Settings.php @@ -129,9 +129,10 @@ public function get_features( $with_instance = false ) { foreach ( $service->feature_classes as $feature ) { $services[ $service->get_menu_slug() ][ $feature::ID ] = array( - 'label' => $feature->get_label(), - 'providers' => $feature->get_providers(), - 'roles' => $feature->get_roles(), + 'label' => $feature->get_label(), + 'providers' => $feature->get_providers(), + 'roles' => $feature->get_roles(), + 'enable_description' => $feature->get_enable_description(), ); } } diff --git a/src/js/settings/components/provider-settings/azure-openai.js b/src/js/settings/components/provider-settings/azure-openai.js new file mode 100644 index 000000000..094a06f91 --- /dev/null +++ b/src/js/settings/components/provider-settings/azure-openai.js @@ -0,0 +1,85 @@ +import { useSelect, useDispatch } from '@wordpress/data'; +// eslint-disable-next-line @wordpress/no-unsafe-wp-apis +import { __experimentalInputControl as InputControl } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +import { SettingsRow } from '../settings-row'; +import { STORE_NAME } from '../../data/store'; + +export const AzureOpenAISettings = ( { featureName } ) => { + const providerName = 'azure_openai'; + const providerSettings = useSelect( + ( select ) => + select( STORE_NAME ).getFeatureSettings( providerName ) || {} + ); + const { setProviderSettings } = useDispatch( STORE_NAME ); + const onChange = ( data ) => setProviderSettings( providerName, data ); + + const Description = () => ( + <> + { __( + 'Supported protocol and hostname endpoints, e.g.,', + 'classifai' + ) } + + { __( 'https://EXAMPLE.openai.azure.com', 'classifai' ) } + + + ); + + return ( + <> + } + > + + onChange( { endpoint_url: value } ) + } + /> + + + onChange( { api_key: value } ) } + /> + + + onChange( { deployment: value } ) } + /> + + { [ + 'feature_content_resizing', + 'feature_title_generation', + ].includes( featureName ) && ( + + + onChange( { number_of_suggestions: value } ) + } + /> + + ) } + + ); +}; diff --git a/src/js/settings/components/provider-settings/google-gemini-api.js b/src/js/settings/components/provider-settings/google-gemini-api.js new file mode 100644 index 000000000..d82135dd3 --- /dev/null +++ b/src/js/settings/components/provider-settings/google-gemini-api.js @@ -0,0 +1,44 @@ +import { useSelect, useDispatch } from '@wordpress/data'; +// eslint-disable-next-line @wordpress/no-unsafe-wp-apis +import { __experimentalInputControl as InputControl } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +import { SettingsRow } from '../settings-row'; +import { STORE_NAME } from '../../data/store'; + +export const GoogleAIGeminiAPISettings = ( { featureName } ) => { + const providerName = 'googleai_gemini_api'; + const providerSettings = useSelect( + ( select ) => + select( STORE_NAME ).getFeatureSettings( providerName ) || {} + ); + const { setProviderSettings } = useDispatch( STORE_NAME ); + const onChange = ( data ) => setProviderSettings( providerName, data ); + + const Description = () => ( + <> + { __( "Don't have an Google AI (Gemini API) key?", 'classifai' ) }{ ' ' } + + { __( 'Get an API key', 'classifai' ) } + { ' ' } + { __( 'now.', 'classifai' ) } + + ); + + return ( + <> + } + > + onChange( { api_key: value } ) } + /> + + + ); +}; diff --git a/src/js/settings/components/provider-settings/index.js b/src/js/settings/components/provider-settings/index.js new file mode 100644 index 000000000..3f485b2d6 --- /dev/null +++ b/src/js/settings/components/provider-settings/index.js @@ -0,0 +1,77 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { SelectControl, Slot } from '@wordpress/components'; +import { useDispatch, useSelect } from '@wordpress/data'; +import { PluginArea } from '@wordpress/plugins'; + +/** + * Internal dependencies + */ +import { getFeature, getScope } from '../../utils/utils'; +import { SettingsRow } from '../settings-row'; +import { STORE_NAME } from '../../data/store'; +import { OpenAIChatGPTSettings } from './openai-chatgpt'; +import { GoogleAIGeminiAPISettings } from './google-gemini-api'; +import { AzureOpenAISettings } from './azure-openai'; + +const ProviderFields = ( { provider, featureName } ) => { + switch ( provider ) { + case 'openai_chatgpt': + return ; + + case 'googleai_gemini_api': + return ; + + case 'azure_openai': + return ; + + default: + return null; + } +}; + +/** + * Feature Settings component. + * + * @param {Object} props Component props. + * @param {string} props.featureName Feature name. + */ +export const ProviderSettings = ( { featureName } ) => { + const { setFeatureSettings } = useDispatch( STORE_NAME ); + const provider = useSelect( + ( select ) => + select( STORE_NAME ).getFeatureSettings( 'provider' ) || + Object.keys( feature?.providers || {} )[ 0 ] + ); + + const feature = getFeature( featureName ); + const providers = Object.keys( feature?.providers || {} ).map( + ( value ) => { + return { + value, + label: feature.providers[ value ] || '', + }; + } + ); + + return ( + <> + + + setFeatureSettings( { provider: value } ) + } + value={ provider } + options={ providers } + /> + + + + { ( fills ) => <> { fills } } + + + + ); +}; diff --git a/src/js/settings/components/provider-settings/openai-chatgpt.js b/src/js/settings/components/provider-settings/openai-chatgpt.js new file mode 100644 index 000000000..70b485b46 --- /dev/null +++ b/src/js/settings/components/provider-settings/openai-chatgpt.js @@ -0,0 +1,64 @@ +import { useSelect, useDispatch } from '@wordpress/data'; +// eslint-disable-next-line @wordpress/no-unsafe-wp-apis +import { __experimentalInputControl as InputControl } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +import { SettingsRow } from '../settings-row'; +import { STORE_NAME } from '../../data/store'; + +export const OpenAIChatGPTSettings = ( { featureName } ) => { + const providerName = 'openai_chatgpt'; + const providerSettings = useSelect( + ( select ) => + select( STORE_NAME ).getFeatureSettings( providerName ) || {} + ); + const { setProviderSettings } = useDispatch( STORE_NAME ); + const onChange = ( data ) => setProviderSettings( providerName, data ); + + const Description = () => ( + <> + { __( "Don't have an OpenAI account yet? ", 'classifai' ) } + + { __( 'Sign up for one', 'classifai' ) } + { ' ' } + { __( 'in order to get your API key.', 'classifai' ) } + + ); + + return ( + <> + } + > + onChange( { api_key: value } ) } + /> + + { [ + 'feature_content_resizing', + 'feature_title_generation', + ].includes( featureName ) && ( + + + onChange( { number_of_suggestions: value } ) + } + /> + + ) } + + ); +}; diff --git a/src/js/settings/components/user-permissions/index.js b/src/js/settings/components/user-permissions/index.js index e2cef3a85..413f428f1 100644 --- a/src/js/settings/components/user-permissions/index.js +++ b/src/js/settings/components/user-permissions/index.js @@ -15,8 +15,13 @@ import { STORE_NAME } from '../../data/store'; export const UserPermissions = ( { featureName } ) => { const { setFeatureSettings } = useDispatch( STORE_NAME ); - const featureSettings = useSelect( ( select ) => { - return select( STORE_NAME ).getSettings( featureName ) || {}; + // eslint-disable-next-line camelcase + const { users, user_based_opt_out } = useSelect( ( select ) => { + return { + users: select( STORE_NAME ).getFeatureSettings( 'users' ), + user_based_opt_out: + select( STORE_NAME ).getFeatureSettings( 'user_based_opt_out' ), + }; } ); return ( { ) } > { + value={ users || [] } + onChange={ ( value ) => { setFeatureSettings( { - ...featureSettings, - users, + users: value, } ); } } /> @@ -52,10 +56,10 @@ export const UserPermissions = ( { featureName } ) => { ) } > { setFeatureSettings( { - ...featureSettings, user_based_opt_out: value ? '1' : 'no', } ); } } diff --git a/src/js/settings/data/actions.js b/src/js/settings/data/actions.js index cb5ace5e9..1839a0f97 100644 --- a/src/js/settings/data/actions.js +++ b/src/js/settings/data/actions.js @@ -13,6 +13,24 @@ export const setFeatureSettings = } ); }; +export const setProviderSettings = + ( provider, settings ) => + ( { select, dispatch } ) => { + const currentFeature = select.getCurrentFeature(); + const featureSettings = select.getFeatureSettings(); + dispatch( { + type: 'SET_FEATURE_SETTINGS', + feature: currentFeature, + payload: { + ...featureSettings, + [ provider ]: { + ...( featureSettings[ provider ] || {} ), + ...settings, + }, + }, + } ); + }; + export const setSettings = ( settings ) => ( { type: 'SET_SETTINGS', payload: settings, diff --git a/src/js/settings/providers/openai-moderation.js b/src/js/settings/providers/openai-moderation.js index f09ec115b..1f5effa94 100644 --- a/src/js/settings/providers/openai-moderation.js +++ b/src/js/settings/providers/openai-moderation.js @@ -21,7 +21,7 @@ const OpenAIModerationSettings = () => { const Description = () => ( <> - { __( "Don't have an OpenAI account yet?", 'classifai' ) } + { __( "Don't have an OpenAI account yet? ", 'classifai' ) } Date: Fri, 7 Jun 2024 16:18:57 +0530 Subject: [PATCH 029/138] Temporary stop E2E tests. --- .github/workflows/cypress.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/cypress.yml b/.github/workflows/cypress.yml index 59c096836..ba665134f 100644 --- a/.github/workflows/cypress.yml +++ b/.github/workflows/cypress.yml @@ -5,9 +5,9 @@ on: branches: - develop - trunk - pull_request: - branches: - - develop + # pull_request: + # branches: + # - develop jobs: cypress: From c9617d1c5261992daa823b65d18b281e849486d6 Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Tue, 11 Jun 2024 10:42:26 +0530 Subject: [PATCH 030/138] Updated update settings API --- includes/Classifai/Admin/Settings.php | 34 ++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/includes/Classifai/Admin/Settings.php b/includes/Classifai/Admin/Settings.php index 5c46d1118..5bb172d41 100644 --- a/includes/Classifai/Admin/Settings.php +++ b/includes/Classifai/Admin/Settings.php @@ -215,13 +215,45 @@ public function update_settings_callback( $request ) { return new \WP_Error( 'invalid_feature', __( 'Invalid feature.', 'classifai' ), [ 'status' => 400 ] ); } + // Load settings error functions. + if ( ! function_exists( 'add_settings_error' ) ) { + require_once ABSPATH . 'wp-admin/includes/template.php'; + } + $new_settings = $feature->sanitize_settings( $settings[ $feature_key ] ); if ( is_wp_error( $new_settings ) ) { return $new_settings; } + // Update settings. $feature->update_settings( $new_settings ); - return $this->get_settings_callback(); + + $setting_errors = get_settings_errors(); + $errors = array(); + if ( ! empty( $setting_errors ) ) { + foreach ( $setting_errors as $setting_error ) { + if ( empty( $setting_error['message'] ) ) { + continue; + } + + $errors[] = array( + 'code' => $setting_error['code'], + 'message' => wp_strip_all_tags( $setting_error['message'] ), + ); + } + } + + $response = array( + 'success' => true, + 'settings' => $this->get_settings(), + ); + + if ( ! empty( $errors ) ) { + $response['success'] = false; + $response['errors'] = $errors; + } + + return rest_ensure_response( $response ); } /** From 3f7e5863ddf52a1803239135322447cfce5cfa52 Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Tue, 11 Jun 2024 10:43:14 +0530 Subject: [PATCH 031/138] Added error notice in settings. --- .../components/feature-settings/index.js | 2 ++ .../components/feature-settings/notices.js | 16 ++++++++++++ .../feature-settings/save-settings-button.js | 26 ++++++++++++++++--- .../components/provider-settings/index.js | 2 +- src/scss/settings.scss | 13 ++++++++++ 5 files changed, 55 insertions(+), 4 deletions(-) create mode 100644 src/js/settings/components/feature-settings/notices.js diff --git a/src/js/settings/components/feature-settings/index.js b/src/js/settings/components/feature-settings/index.js index a9351cc8d..12f942f64 100644 --- a/src/js/settings/components/feature-settings/index.js +++ b/src/js/settings/components/feature-settings/index.js @@ -16,6 +16,7 @@ import { STORE_NAME } from '../../data/store'; import { ProviderSettings } from '../provider-settings'; import { EnableToggleControl } from './enable-feature'; import { SaveSettingsButton } from './save-settings-button'; +import { Notices } from './notices'; /** * Feature Settings component. @@ -43,6 +44,7 @@ export const FeatureSettings = ( { featureName } ) => { return ( <> + { + const { removeNotice } = useDispatch( noticesStore ); + const notices = useSelect( ( select ) => + select( noticesStore ).getNotices() + ); + + if ( notices.length === 0 ) { + return null; + } + + return ; +}; diff --git a/src/js/settings/components/feature-settings/save-settings-button.js b/src/js/settings/components/feature-settings/save-settings-button.js index daac31ece..8e5c464b2 100644 --- a/src/js/settings/components/feature-settings/save-settings-button.js +++ b/src/js/settings/components/feature-settings/save-settings-button.js @@ -4,6 +4,7 @@ import { __ } from '@wordpress/i18n'; import { Button } from '@wordpress/components'; import { useSelect, useDispatch } from '@wordpress/data'; +import { store as noticesStore } from '@wordpress/notices'; import apiFetch from '@wordpress/api-fetch'; /** @@ -18,6 +19,10 @@ import { STORE_NAME } from '../../data/store'; * @param {string} props.featureName Feature name. */ export const SaveSettingsButton = ( { featureName } ) => { + const { createErrorNotice, removeNotices } = useDispatch( noticesStore ); + const notices = useSelect( ( select ) => + select( noticesStore ).getNotices() + ); const { setIsSaving, setSettings } = useDispatch( STORE_NAME ); const settings = useSelect( ( select ) => select( STORE_NAME ).getSettings() @@ -29,6 +34,7 @@ export const SaveSettingsButton = ( { featureName } ) => { * @param {string} featureName Feature name */ const saveSettings = () => { + removeNotices( notices.map( ( { id } ) => id ) ); setIsSaving( true ); apiFetch( { path: '/classifai/v1/settings/', @@ -36,12 +42,26 @@ export const SaveSettingsButton = ( { featureName } ) => { data: { [ featureName ]: settings[ featureName ] }, } ) .then( ( res ) => { - setSettings( res ); + if ( res.errors && res.errors.length ) { + res.errors.forEach( ( error ) => + createErrorNotice( error.message ) + ); + setSettings( res.settings ); + setIsSaving( false ); + return; + } + + setSettings( res.settings ); setIsSaving( false ); } ) .catch( ( error ) => { - // eslint-disable-next-line no-console - console.error( error ); // TODO: handle error and show a notice + createErrorNotice( + error.message || + __( + 'An error occurred while saving settings.', + 'classifai' + ) + ); setIsSaving( false ); } ); }; diff --git a/src/js/settings/components/provider-settings/index.js b/src/js/settings/components/provider-settings/index.js index 3f485b2d6..88df73eae 100644 --- a/src/js/settings/components/provider-settings/index.js +++ b/src/js/settings/components/provider-settings/index.js @@ -40,13 +40,13 @@ const ProviderFields = ( { provider, featureName } ) => { */ export const ProviderSettings = ( { featureName } ) => { const { setFeatureSettings } = useDispatch( STORE_NAME ); + const feature = getFeature( featureName ); const provider = useSelect( ( select ) => select( STORE_NAME ).getFeatureSettings( 'provider' ) || Object.keys( feature?.providers || {} )[ 0 ] ); - const feature = getFeature( featureName ); const providers = Object.keys( feature?.providers || {} ).map( ( value ) => { return { diff --git a/src/scss/settings.scss b/src/scss/settings.scss index 88e006f42..acd7fd9c7 100644 --- a/src/scss/settings.scss +++ b/src/scss/settings.scss @@ -50,6 +50,19 @@ } } + .components-notice { + &, &__content { + margin: 0; + } + } + + .components-notice-list { + display: flex; + flex-direction: column; + gap: 8px; + margin-block-end: 16px; + } + .settings-panel { .settings-row { display: flex; From 68230072d34f6ed1a863621e81488e89388922b0 Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Tue, 11 Jun 2024 10:43:58 +0530 Subject: [PATCH 032/138] Removed unwanted hook. --- src/js/settings/hooks/index.js | 78 ---------------------------------- 1 file changed, 78 deletions(-) delete mode 100644 src/js/settings/hooks/index.js diff --git a/src/js/settings/hooks/index.js b/src/js/settings/hooks/index.js deleted file mode 100644 index 864daec4b..000000000 --- a/src/js/settings/hooks/index.js +++ /dev/null @@ -1,78 +0,0 @@ -/** - * WordPress dependencies - */ -import { useSelect, useDispatch } from '@wordpress/data'; -import apiFetch from '@wordpress/api-fetch'; - -/** - * Internal dependencies - */ -import { STORE_NAME } from '../data/store'; - -export const useSettings = () => { - const dispatch = useDispatch(); - - const settings = useSelect( ( select ) => - select( STORE_NAME ).getSettings() - ); - const isLoaded = useSelect( ( select ) => - select( STORE_NAME ).getIsLoaded() - ); - const isSaving = useSelect( ( select ) => - select( STORE_NAME ).getIsSaving() - ); - const currentService = useSelect( ( select ) => - select( STORE_NAME ).getCurrentService() - ); - const currentFeature = useSelect( ( select ) => - select( STORE_NAME ).getCurrentFeature() - ); - - const getFeatureSettings = ( feature ) => settings[ feature ] || {}; - - const setSettings = ( data ) => dispatch( STORE_NAME ).setSettings( data ); - const setFeatureSettings = ( data ) => - dispatch( STORE_NAME ).setFeatureSettings( data ); - const setIsSaving = ( saving ) => - dispatch( STORE_NAME ).setIsSaving( saving ); - const setCurrentService = ( service ) => - dispatch( STORE_NAME ).setCurrentService( service ); - const setCurrentFeature = ( feature ) => - dispatch( STORE_NAME ).setCurrentFeature( feature ); - - /** - * Save settings for a feature. - * - * @param {string} featureName Feature name - */ - const saveSettings = ( featureName ) => { - setIsSaving( true ); - apiFetch( { - path: '/classifai/v1/settings/', - method: 'POST', - data: { [ featureName ]: settings[ featureName ] }, - } ) - .then( ( res ) => { - setSettings( res ); - setIsSaving( false ); - } ) - .catch( ( error ) => { - // eslint-disable-next-line no-console - console.error( error ); // TODO: handle error and show a notice - setIsSaving( false ); - } ); - }; - - return { - settings, - isLoaded, - isSaving, - currentFeature, - currentService, - getFeatureSettings, - setFeatureSettings, - saveSettings, - setCurrentService, - setCurrentFeature, - }; -}; From f371339c33916f0aa6c6943c3dd0bc5287b1bbf1 Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Fri, 28 Jun 2024 12:22:51 +0530 Subject: [PATCH 033/138] Use context to avoid props-drilling --- .../settings/components/allowed-roles/index.js | 4 +++- .../components/classifai-settings/index.js | 7 ++++++- .../components/feature-settings/context.js | 12 ++++++++++++ .../feature-settings/enable-feature.js | 6 +++--- .../components/feature-settings/index.js | 12 +++++++----- .../feature-settings/save-settings-button.js | 6 +++--- .../provider-settings/azure-openai.js | 4 +++- .../provider-settings/google-gemini-api.js | 2 +- .../components/provider-settings/index.js | 17 ++++++++--------- .../provider-settings/openai-chatgpt.js | 4 +++- .../components/user-permissions/index.js | 4 ++-- 11 files changed, 51 insertions(+), 27 deletions(-) create mode 100644 src/js/settings/components/feature-settings/context.js diff --git a/src/js/settings/components/allowed-roles/index.js b/src/js/settings/components/allowed-roles/index.js index ed5c90a8e..ba8549656 100644 --- a/src/js/settings/components/allowed-roles/index.js +++ b/src/js/settings/components/allowed-roles/index.js @@ -11,8 +11,10 @@ import { __ } from '@wordpress/i18n'; import { getFeature } from '../../utils/utils'; import { STORE_NAME } from '../../data/store'; import { SettingsRow } from '../settings-row'; +import { useFeatureContext } from '../feature-settings/context'; -export const AllowedRoles = ( { featureName } ) => { +export const AllowedRoles = () => { + const { featureName } = useFeatureContext(); const { setFeatureSettings } = useDispatch( STORE_NAME ); const roles = useSelect( ( select ) => select( STORE_NAME ).getFeatureSettings( 'roles' ) || {} diff --git a/src/js/settings/components/classifai-settings/index.js b/src/js/settings/components/classifai-settings/index.js index 71645573f..6f063d622 100644 --- a/src/js/settings/components/classifai-settings/index.js +++ b/src/js/settings/components/classifai-settings/index.js @@ -23,6 +23,7 @@ import apiFetch from '@wordpress/api-fetch'; */ import { FeatureSettings, Header, ServiceSettings } from '..'; import { STORE_NAME } from '../../data/store'; +import { FeatureContext } from '../feature-settings/context'; const { services, features } = window.classifAISettings; @@ -48,7 +49,11 @@ const FeatureSettingsWrapper = () => { return ; } - return ; + return ( + + + + ); }; const ServiceSettingsWrapper = () => { diff --git a/src/js/settings/components/feature-settings/context.js b/src/js/settings/components/feature-settings/context.js new file mode 100644 index 000000000..4a0d0362e --- /dev/null +++ b/src/js/settings/components/feature-settings/context.js @@ -0,0 +1,12 @@ +/** + * External dependencies + */ +import { createContext, useContext } from '@wordpress/element'; + +export const FeatureContext = createContext( { + featureName: '', +} ); + +export const useFeatureContext = () => { + return useContext( FeatureContext ); +}; diff --git a/src/js/settings/components/feature-settings/enable-feature.js b/src/js/settings/components/feature-settings/enable-feature.js index 0e0f2444b..4cb6e46cb 100644 --- a/src/js/settings/components/feature-settings/enable-feature.js +++ b/src/js/settings/components/feature-settings/enable-feature.js @@ -12,14 +12,14 @@ import { decodeEntities } from '@wordpress/html-entities'; import { getFeature } from '../../utils/utils'; import { SettingsRow } from '../settings-row'; import { STORE_NAME } from '../../data/store'; +import { useFeatureContext } from './context'; /** * Enable Feature Toggle component. * - * @param {Object} props Component props. - * @param {string} props.featureName Feature name. */ -export const EnableToggleControl = ( { featureName } ) => { +export const EnableToggleControl = () => { + const { featureName } = useFeatureContext(); const { setFeatureSettings } = useDispatch( STORE_NAME ); const status = useSelect( ( select ) => select( STORE_NAME ).getFeatureSettings( 'status' ) || '0' diff --git a/src/js/settings/components/feature-settings/index.js b/src/js/settings/components/feature-settings/index.js index 12f942f64..4b3bd8824 100644 --- a/src/js/settings/components/feature-settings/index.js +++ b/src/js/settings/components/feature-settings/index.js @@ -17,6 +17,7 @@ import { ProviderSettings } from '../provider-settings'; import { EnableToggleControl } from './enable-feature'; import { SaveSettingsButton } from './save-settings-button'; import { Notices } from './notices'; +import { useFeatureContext } from './context'; /** * Feature Settings component. @@ -24,7 +25,8 @@ import { Notices } from './notices'; * @param {Object} props Component props. * @param {string} props.featureName Feature name. */ -export const FeatureSettings = ( { featureName } ) => { +export const FeatureSettings = () => { + const { featureName } = useFeatureContext(); const { setCurrentFeature } = useDispatch( STORE_NAME ); useEffect( () => { @@ -53,16 +55,16 @@ export const FeatureSettings = ( { featureName } ) => { className="settings-panel" > - - + + { ( fills ) => <> { fills } } - +
- +
diff --git a/src/js/settings/components/feature-settings/save-settings-button.js b/src/js/settings/components/feature-settings/save-settings-button.js index 8e5c464b2..5b993505f 100644 --- a/src/js/settings/components/feature-settings/save-settings-button.js +++ b/src/js/settings/components/feature-settings/save-settings-button.js @@ -11,6 +11,7 @@ import apiFetch from '@wordpress/api-fetch'; * Internal dependencies */ import { STORE_NAME } from '../../data/store'; +import { useFeatureContext } from './context'; /** * Save Settings Button component. @@ -18,7 +19,8 @@ import { STORE_NAME } from '../../data/store'; * @param {Object} props Component props. * @param {string} props.featureName Feature name. */ -export const SaveSettingsButton = ( { featureName } ) => { +export const SaveSettingsButton = () => { + const { featureName } = useFeatureContext(); const { createErrorNotice, removeNotices } = useDispatch( noticesStore ); const notices = useSelect( ( select ) => select( noticesStore ).getNotices() @@ -30,8 +32,6 @@ export const SaveSettingsButton = ( { featureName } ) => { /** * Save settings for a feature. - * - * @param {string} featureName Feature name */ const saveSettings = () => { removeNotices( notices.map( ( { id } ) => id ) ); diff --git a/src/js/settings/components/provider-settings/azure-openai.js b/src/js/settings/components/provider-settings/azure-openai.js index 094a06f91..50df13a1a 100644 --- a/src/js/settings/components/provider-settings/azure-openai.js +++ b/src/js/settings/components/provider-settings/azure-openai.js @@ -4,8 +4,10 @@ import { __experimentalInputControl as InputControl } from '@wordpress/component import { __ } from '@wordpress/i18n'; import { SettingsRow } from '../settings-row'; import { STORE_NAME } from '../../data/store'; +import { useFeatureContext } from '../feature-settings/context'; -export const AzureOpenAISettings = ( { featureName } ) => { +export const AzureOpenAISettings = () => { + const { featureName } = useFeatureContext(); const providerName = 'azure_openai'; const providerSettings = useSelect( ( select ) => diff --git a/src/js/settings/components/provider-settings/google-gemini-api.js b/src/js/settings/components/provider-settings/google-gemini-api.js index d82135dd3..94ce2d4cc 100644 --- a/src/js/settings/components/provider-settings/google-gemini-api.js +++ b/src/js/settings/components/provider-settings/google-gemini-api.js @@ -5,7 +5,7 @@ import { __ } from '@wordpress/i18n'; import { SettingsRow } from '../settings-row'; import { STORE_NAME } from '../../data/store'; -export const GoogleAIGeminiAPISettings = ( { featureName } ) => { +export const GoogleAIGeminiAPISettings = () => { const providerName = 'googleai_gemini_api'; const providerSettings = useSelect( ( select ) => diff --git a/src/js/settings/components/provider-settings/index.js b/src/js/settings/components/provider-settings/index.js index 88df73eae..ed90ed47b 100644 --- a/src/js/settings/components/provider-settings/index.js +++ b/src/js/settings/components/provider-settings/index.js @@ -15,17 +15,18 @@ import { STORE_NAME } from '../../data/store'; import { OpenAIChatGPTSettings } from './openai-chatgpt'; import { GoogleAIGeminiAPISettings } from './google-gemini-api'; import { AzureOpenAISettings } from './azure-openai'; +import { useFeatureContext } from '../feature-settings/context'; -const ProviderFields = ( { provider, featureName } ) => { +const ProviderFields = ( { provider } ) => { switch ( provider ) { case 'openai_chatgpt': - return ; + return ; case 'googleai_gemini_api': - return ; + return ; case 'azure_openai': - return ; + return ; default: return null; @@ -34,11 +35,9 @@ const ProviderFields = ( { provider, featureName } ) => { /** * Feature Settings component. - * - * @param {Object} props Component props. - * @param {string} props.featureName Feature name. */ -export const ProviderSettings = ( { featureName } ) => { +export const ProviderSettings = () => { + const { featureName } = useFeatureContext(); const { setFeatureSettings } = useDispatch( STORE_NAME ); const feature = getFeature( featureName ); const provider = useSelect( @@ -67,7 +66,7 @@ export const ProviderSettings = ( { featureName } ) => { options={ providers } />
- + { ( fills ) => <> { fills } } diff --git a/src/js/settings/components/provider-settings/openai-chatgpt.js b/src/js/settings/components/provider-settings/openai-chatgpt.js index 70b485b46..b9e3f729a 100644 --- a/src/js/settings/components/provider-settings/openai-chatgpt.js +++ b/src/js/settings/components/provider-settings/openai-chatgpt.js @@ -4,8 +4,10 @@ import { __experimentalInputControl as InputControl } from '@wordpress/component import { __ } from '@wordpress/i18n'; import { SettingsRow } from '../settings-row'; import { STORE_NAME } from '../../data/store'; +import { useFeatureContext } from '../feature-settings/context'; -export const OpenAIChatGPTSettings = ( { featureName } ) => { +export const OpenAIChatGPTSettings = () => { + const { featureName } = useFeatureContext(); const providerName = 'openai_chatgpt'; const providerSettings = useSelect( ( select ) => diff --git a/src/js/settings/components/user-permissions/index.js b/src/js/settings/components/user-permissions/index.js index 413f428f1..745b00b13 100644 --- a/src/js/settings/components/user-permissions/index.js +++ b/src/js/settings/components/user-permissions/index.js @@ -13,7 +13,7 @@ import { AllowedRoles } from '../allowed-roles'; import { SettingsRow } from '../settings-row'; import { STORE_NAME } from '../../data/store'; -export const UserPermissions = ( { featureName } ) => { +export const UserPermissions = () => { const { setFeatureSettings } = useDispatch( STORE_NAME ); // eslint-disable-next-line camelcase const { users, user_based_opt_out } = useSelect( ( select ) => { @@ -28,7 +28,7 @@ export const UserPermissions = ( { featureName } ) => { title={ __( 'User permissions', 'classifai' ) } initialOpen={ true } > - + Date: Fri, 28 Jun 2024 12:29:22 +0530 Subject: [PATCH 034/138] Remove unwanted doc comments. --- src/js/settings/components/feature-settings/index.js | 3 --- .../components/feature-settings/save-settings-button.js | 3 --- 2 files changed, 6 deletions(-) diff --git a/src/js/settings/components/feature-settings/index.js b/src/js/settings/components/feature-settings/index.js index 4b3bd8824..b35e313f0 100644 --- a/src/js/settings/components/feature-settings/index.js +++ b/src/js/settings/components/feature-settings/index.js @@ -21,9 +21,6 @@ import { useFeatureContext } from './context'; /** * Feature Settings component. - * - * @param {Object} props Component props. - * @param {string} props.featureName Feature name. */ export const FeatureSettings = () => { const { featureName } = useFeatureContext(); diff --git a/src/js/settings/components/feature-settings/save-settings-button.js b/src/js/settings/components/feature-settings/save-settings-button.js index 5b993505f..dd00931e9 100644 --- a/src/js/settings/components/feature-settings/save-settings-button.js +++ b/src/js/settings/components/feature-settings/save-settings-button.js @@ -15,9 +15,6 @@ import { useFeatureContext } from './context'; /** * Save Settings Button component. - * - * @param {Object} props Component props. - * @param {string} props.featureName Feature name. */ export const SaveSettingsButton = () => { const { featureName } = useFeatureContext(); From acda4c0994cfd5623a025c65920db2f40a0d32ab Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Fri, 28 Jun 2024 15:03:35 +0530 Subject: [PATCH 035/138] Added provider settings for IBM watson NLU. --- .../provider-settings/ibm-watson-nlu.js | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 src/js/settings/components/provider-settings/ibm-watson-nlu.js diff --git a/src/js/settings/components/provider-settings/ibm-watson-nlu.js b/src/js/settings/components/provider-settings/ibm-watson-nlu.js new file mode 100644 index 000000000..209797702 --- /dev/null +++ b/src/js/settings/components/provider-settings/ibm-watson-nlu.js @@ -0,0 +1,90 @@ +import { useSelect, useDispatch } from '@wordpress/data'; +import { useState } from '@wordpress/element'; +import { + __experimentalInputControl as InputControl, // eslint-disable-line @wordpress/no-unsafe-wp-apis + Button, +} from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +import { SettingsRow } from '../settings-row'; +import { STORE_NAME } from '../../data/store'; + +export const IBMWatsonNLUSettings = () => { + const providerName = 'ibm_watson_nlu'; + const providerSettings = useSelect( + ( select ) => + select( STORE_NAME ).getFeatureSettings( providerName ) || {} + ); + const [ useAPIkey, setUseAPIkey ] = useState( + ! providerSettings.username || 'apikey' === providerSettings.username + ); + const { setProviderSettings } = useDispatch( STORE_NAME ); + const onChange = ( data ) => setProviderSettings( providerName, data ); + + const Description = () => ( + <> + { __( "Don't have an IBM Cloud account yet?", 'classifai' ) }{ ' ' } + + { __( 'Register for one', 'classifai' ) } + + { __( ' and set up a ', 'classifai' ) } + + { __( 'Natural Language Understanding', 'classifai' ) } + + { __( ' Resource to get your API key.', 'classifai' ) } + + ); + + return ( + <> + + + onChange( { endpoint_url: value } ) + } + /> + + { ! useAPIkey && ( + + + onChange( { username: value } ) + } + /> + + ) } + } + > + onChange( { password: value } ) } + /> + + + + + + ); +}; From eee66985408d7b1311fd90931ad7ed1526480b69 Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Fri, 28 Jun 2024 15:04:04 +0530 Subject: [PATCH 036/138] Updated UI for configured providers. --- .../components/provider-settings/index.js | 74 +++++++++++++++---- src/js/settings/utils/utils.js | 15 ++++ src/scss/settings.scss | 12 +++ 3 files changed, 85 insertions(+), 16 deletions(-) diff --git a/src/js/settings/components/provider-settings/index.js b/src/js/settings/components/provider-settings/index.js index ed90ed47b..d468f30f7 100644 --- a/src/js/settings/components/provider-settings/index.js +++ b/src/js/settings/components/provider-settings/index.js @@ -2,20 +2,22 @@ * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { SelectControl, Slot } from '@wordpress/components'; +import { SelectControl, Slot, Icon, Tooltip } from '@wordpress/components'; import { useDispatch, useSelect } from '@wordpress/data'; +import { useState } from '@wordpress/element'; import { PluginArea } from '@wordpress/plugins'; /** * Internal dependencies */ -import { getFeature, getScope } from '../../utils/utils'; +import { getFeature, getScope, isProviderConfigured } from '../../utils/utils'; import { SettingsRow } from '../settings-row'; import { STORE_NAME } from '../../data/store'; import { OpenAIChatGPTSettings } from './openai-chatgpt'; import { GoogleAIGeminiAPISettings } from './google-gemini-api'; import { AzureOpenAISettings } from './azure-openai'; import { useFeatureContext } from '../feature-settings/context'; +import { IBMWatsonNLUSettings } from './ibm-watson-nlu'; const ProviderFields = ( { provider } ) => { switch ( provider ) { @@ -28,6 +30,9 @@ const ProviderFields = ( { provider } ) => { case 'azure_openai': return ; + case 'ibm_watson_nlu': + return ; + default: return null; } @@ -37,6 +42,7 @@ const ProviderFields = ( { provider } ) => { * Feature Settings component. */ export const ProviderSettings = () => { + const [ editProvider, setEditProvider ] = useState( false ); const { featureName } = useFeatureContext(); const { setFeatureSettings } = useDispatch( STORE_NAME ); const feature = getFeature( featureName ); @@ -45,6 +51,12 @@ export const ProviderSettings = () => { select( STORE_NAME ).getFeatureSettings( 'provider' ) || Object.keys( feature?.providers || {} )[ 0 ] ); + const featureSettings = useSelect( ( select ) => + select( STORE_NAME ).getFeatureSettings() + ); + + const configured = isProviderConfigured( featureSettings ); + const providerLabel = feature.providers[ provider ] || ''; const providers = Object.keys( feature?.providers || {} ).map( ( value ) => { @@ -57,20 +69,50 @@ export const ProviderSettings = () => { return ( <> - - - setFeatureSettings( { provider: value } ) - } - value={ provider } - options={ providers } - /> - - - - { ( fills ) => <> { fills } } - - + { configured && ! editProvider && ( + <> + + <> + + <> + { providerLabel } + + { ' ' } + + setEditProvider( true ) } + /> + + + + + ) } + { ( ! configured || editProvider ) && ( + <> + + + setFeatureSettings( { provider: value } ) + } + value={ provider } + options={ providers } + /> + + + + { ( fills ) => <> { fills } } + + + + ) } ); }; diff --git a/src/js/settings/utils/utils.js b/src/js/settings/utils/utils.js index 7dc89c928..d758bd1b3 100644 --- a/src/js/settings/utils/utils.js +++ b/src/js/settings/utils/utils.js @@ -81,3 +81,18 @@ export const getInitialFeature = ( service ) => { export const getScope = ( name ) => { return ( name || '' ).replace( /_/g, '-' ); }; + +/** + * Check if the provider is configured. + * + * @param {Object} featureSettings The feature settings. + * @return {boolean} True if the provider is configured, false otherwise. + */ +export const isProviderConfigured = ( featureSettings ) => { + const selectedProvider = featureSettings?.provider; + if ( ! selectedProvider ) { + return false; + } + + return featureSettings[ selectedProvider ]?.authenticated || false; +}; diff --git a/src/scss/settings.scss b/src/scss/settings.scss index acd7fd9c7..2630d807e 100644 --- a/src/scss/settings.scss +++ b/src/scss/settings.scss @@ -261,6 +261,18 @@ } } } + + span.dashicons.dashicons-yes-alt { + color: #46b450; + } + + .classifai-settings-edit-provider { + cursor: pointer; + + &:hover { + color: #3858e9; + } + } } .service-settings-wrapper { From 8b17b65c0dc429b3d7d4227a8a40fb5ea82242a9 Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Fri, 28 Jun 2024 15:18:21 +0530 Subject: [PATCH 037/138] Added additional openai providers. --- .../components/provider-settings/index.js | 12 +++++++ .../provider-settings/openai-embeddings.js | 20 +++++++++++ .../provider-settings/openai-moderation.js | 20 +++++++++++ .../provider-settings/openai-whisper.js | 20 +++++++++++ .../components/provider-settings/openai.js | 34 +++++++++++++++++++ src/scss/settings.scss | 1 + 6 files changed, 107 insertions(+) create mode 100644 src/js/settings/components/provider-settings/openai-embeddings.js create mode 100644 src/js/settings/components/provider-settings/openai-moderation.js create mode 100644 src/js/settings/components/provider-settings/openai-whisper.js create mode 100644 src/js/settings/components/provider-settings/openai.js diff --git a/src/js/settings/components/provider-settings/index.js b/src/js/settings/components/provider-settings/index.js index d468f30f7..b9ac370f4 100644 --- a/src/js/settings/components/provider-settings/index.js +++ b/src/js/settings/components/provider-settings/index.js @@ -18,6 +18,9 @@ import { GoogleAIGeminiAPISettings } from './google-gemini-api'; import { AzureOpenAISettings } from './azure-openai'; import { useFeatureContext } from '../feature-settings/context'; import { IBMWatsonNLUSettings } from './ibm-watson-nlu'; +import { OpenAIModerationSettings } from './openai-moderation'; +import { OpenAIEmbeddingsSettings } from './openai-embeddings'; +import { OpenAIWhisperSettings } from './openai-whisper'; const ProviderFields = ( { provider } ) => { switch ( provider ) { @@ -33,6 +36,15 @@ const ProviderFields = ( { provider } ) => { case 'ibm_watson_nlu': return ; + case 'openai_embeddings': + return ; + + case 'openai_whisper': + return ; + + case 'openai_moderation': + return ; + default: return null; } diff --git a/src/js/settings/components/provider-settings/openai-embeddings.js b/src/js/settings/components/provider-settings/openai-embeddings.js new file mode 100644 index 000000000..d7e4424b9 --- /dev/null +++ b/src/js/settings/components/provider-settings/openai-embeddings.js @@ -0,0 +1,20 @@ +import { useSelect, useDispatch } from '@wordpress/data'; +import { STORE_NAME } from '../../data/store'; +import { OpenAISettings } from './openai'; + +export const OpenAIEmbeddingsSettings = () => { + const providerName = 'openai_embeddings'; + const providerSettings = useSelect( + ( select ) => + select( STORE_NAME ).getFeatureSettings( providerName ) || {} + ); + const { setProviderSettings } = useDispatch( STORE_NAME ); + const onChange = ( data ) => setProviderSettings( providerName, data ); + + return ( + + ); +}; diff --git a/src/js/settings/components/provider-settings/openai-moderation.js b/src/js/settings/components/provider-settings/openai-moderation.js new file mode 100644 index 000000000..2a3a56897 --- /dev/null +++ b/src/js/settings/components/provider-settings/openai-moderation.js @@ -0,0 +1,20 @@ +import { useSelect, useDispatch } from '@wordpress/data'; +import { STORE_NAME } from '../../data/store'; +import { OpenAISettings } from './openai'; + +export const OpenAIModerationSettings = () => { + const providerName = 'openai_moderation'; + const providerSettings = useSelect( + ( select ) => + select( STORE_NAME ).getFeatureSettings( providerName ) || {} + ); + const { setProviderSettings } = useDispatch( STORE_NAME ); + const onChange = ( data ) => setProviderSettings( providerName, data ); + + return ( + + ); +}; diff --git a/src/js/settings/components/provider-settings/openai-whisper.js b/src/js/settings/components/provider-settings/openai-whisper.js new file mode 100644 index 000000000..1baea5760 --- /dev/null +++ b/src/js/settings/components/provider-settings/openai-whisper.js @@ -0,0 +1,20 @@ +import { useSelect, useDispatch } from '@wordpress/data'; +import { STORE_NAME } from '../../data/store'; +import { OpenAISettings } from './openai'; + +export const OpenAIWhisperSettings = () => { + const providerName = 'openai_whisper'; + const providerSettings = useSelect( + ( select ) => + select( STORE_NAME ).getFeatureSettings( providerName ) || {} + ); + const { setProviderSettings } = useDispatch( STORE_NAME ); + const onChange = ( data ) => setProviderSettings( providerName, data ); + + return ( + + ); +}; diff --git a/src/js/settings/components/provider-settings/openai.js b/src/js/settings/components/provider-settings/openai.js new file mode 100644 index 000000000..3b845679e --- /dev/null +++ b/src/js/settings/components/provider-settings/openai.js @@ -0,0 +1,34 @@ +// eslint-disable-next-line @wordpress/no-unsafe-wp-apis +import { __experimentalInputControl as InputControl } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +import { SettingsRow } from '../settings-row'; + +export const OpenAISettings = ( { providerSettings, onChange } ) => { + const Description = () => ( + <> + { __( "Don't have an OpenAI account yet? ", 'classifai' ) } + + { __( 'Sign up for one', 'classifai' ) } + { ' ' } + { __( 'in order to get your API key.', 'classifai' ) } + + ); + + return ( + <> + } + > + onChange( { api_key: value } ) } + /> + + + ); +}; diff --git a/src/scss/settings.scss b/src/scss/settings.scss index 2630d807e..270f300ea 100644 --- a/src/scss/settings.scss +++ b/src/scss/settings.scss @@ -92,6 +92,7 @@ } .settings-description { + margin-top: 8px; font-size: 13px; } From d6d0297a4d45fcbe7e5e2a5b0f9dfb5e778428ec Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Fri, 28 Jun 2024 16:27:32 +0530 Subject: [PATCH 038/138] Added some more providers --- .../provider-settings/azure-ai-vision.js | 97 +++++++++++++++++++ .../provider-settings/azure-openai.js | 3 +- .../provider-settings/azure-personlizer.js | 37 +++++++ 3 files changed, 135 insertions(+), 2 deletions(-) create mode 100644 src/js/settings/components/provider-settings/azure-ai-vision.js create mode 100644 src/js/settings/components/provider-settings/azure-personlizer.js diff --git a/src/js/settings/components/provider-settings/azure-ai-vision.js b/src/js/settings/components/provider-settings/azure-ai-vision.js new file mode 100644 index 000000000..bede98834 --- /dev/null +++ b/src/js/settings/components/provider-settings/azure-ai-vision.js @@ -0,0 +1,97 @@ +import { useSelect, useDispatch } from '@wordpress/data'; +// eslint-disable-next-line @wordpress/no-unsafe-wp-apis +import { __experimentalInputControl as InputControl } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +import { SettingsRow } from '../settings-row'; +import { STORE_NAME } from '../../data/store'; +import { useFeatureContext } from '../feature-settings/context'; + +export const AzureAIVisionSettings = () => { + const providerName = 'ms_computer_vision'; + const { featureName } = useFeatureContext(); + const providerSettings = useSelect( + ( select ) => + select( STORE_NAME ).getFeatureSettings( providerName ) || {} + ); + const { setProviderSettings } = useDispatch( STORE_NAME ); + const onChange = ( data ) => setProviderSettings( providerName, data ); + + const Description = () => ( + <> + { __( + 'Supported protocol and hostname endpoints, e.g.,', + 'classifai' + ) } + + { __( 'https://EXAMPLE.openai.azure.com', 'classifai' ) } + + + ); + + return ( + <> + } + > + + onChange( { endpoint_url: value } ) + } + /> + + + onChange( { api_key: value } ) } + /> + + { 'feature_descriptive_text_generator' === featureName && ( + + + onChange( { + descriptive_confidence_threshold: value, + } ) + } + /> + + ) } + { 'feature_image_tags_generator' === featureName && ( + + + onChange( { + tag_confidence_threshold: value, + } ) + } + /> + + ) } + + ); +}; diff --git a/src/js/settings/components/provider-settings/azure-openai.js b/src/js/settings/components/provider-settings/azure-openai.js index 50df13a1a..6b27c10df 100644 --- a/src/js/settings/components/provider-settings/azure-openai.js +++ b/src/js/settings/components/provider-settings/azure-openai.js @@ -6,9 +6,8 @@ import { SettingsRow } from '../settings-row'; import { STORE_NAME } from '../../data/store'; import { useFeatureContext } from '../feature-settings/context'; -export const AzureOpenAISettings = () => { +export const AzureOpenAISettings = ( { providerName = 'azure_openai' } ) => { const { featureName } = useFeatureContext(); - const providerName = 'azure_openai'; const providerSettings = useSelect( ( select ) => select( STORE_NAME ).getFeatureSettings( providerName ) || {} diff --git a/src/js/settings/components/provider-settings/azure-personlizer.js b/src/js/settings/components/provider-settings/azure-personlizer.js new file mode 100644 index 000000000..2bab84b6e --- /dev/null +++ b/src/js/settings/components/provider-settings/azure-personlizer.js @@ -0,0 +1,37 @@ +import { useSelect, useDispatch } from '@wordpress/data'; +// eslint-disable-next-line @wordpress/no-unsafe-wp-apis +import { __experimentalInputControl as InputControl } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +import { SettingsRow } from '../settings-row'; +import { STORE_NAME } from '../../data/store'; + +export const AzurePersonalizerSettings = () => { + const providerName = 'ms_azure_personalizer'; + const providerSettings = useSelect( + ( select ) => + select( STORE_NAME ).getFeatureSettings( providerName ) || {} + ); + const { setProviderSettings } = useDispatch( STORE_NAME ); + const onChange = ( data ) => setProviderSettings( providerName, data ); + + return ( + <> + + + onChange( { endpoint_url: value } ) + } + /> + + + onChange( { api_key: value } ) } + /> + + + ); +}; From 2f349d6637de36fa63aae452e3de7d1ff5c5e90f Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Fri, 28 Jun 2024 20:04:35 +0530 Subject: [PATCH 039/138] Match component style with admin color. --- includes/Classifai/Admin/Settings.php | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/includes/Classifai/Admin/Settings.php b/includes/Classifai/Admin/Settings.php index 5bb172d41..b2cc1a207 100644 --- a/includes/Classifai/Admin/Settings.php +++ b/includes/Classifai/Admin/Settings.php @@ -85,12 +85,7 @@ public function admin_enqueue_scripts( $hook_suffix ) { wp_enqueue_style( 'classifai-settings', CLASSIFAI_PLUGIN_URL . 'dist/settings.css', - array_filter( - get_asset_info( 'settings', 'dependencies' ), - function ( $style ) { - return wp_style_is( $style, 'registered' ); - } - ), + array( 'wp-edit-blocks' ), get_asset_info( 'settings', 'version' ), 'all' ); From ef5f224f8d38290d18babd3b8c29539d0087f673 Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Fri, 28 Jun 2024 20:24:37 +0530 Subject: [PATCH 040/138] Started Adding additional feature setting fields. --- .../descriptive-text-generator.js | 50 +++++++++++++++++++ .../feature-additional-settings/index.js | 41 +++++++++++++++ .../components/feature-settings/index.js | 11 ++-- .../components/provider-settings/index.js | 23 ++++++--- 4 files changed, 112 insertions(+), 13 deletions(-) create mode 100644 src/js/settings/components/feature-additional-settings/descriptive-text-generator.js create mode 100644 src/js/settings/components/feature-additional-settings/index.js diff --git a/src/js/settings/components/feature-additional-settings/descriptive-text-generator.js b/src/js/settings/components/feature-additional-settings/descriptive-text-generator.js new file mode 100644 index 000000000..d9f213d02 --- /dev/null +++ b/src/js/settings/components/feature-additional-settings/descriptive-text-generator.js @@ -0,0 +1,50 @@ +import { useSelect, useDispatch } from '@wordpress/data'; +// eslint-disable-next-line @wordpress/no-unsafe-wp-apis +import { CheckboxControl } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +import { SettingsRow } from '../settings-row'; +import { STORE_NAME } from '../../data/store'; + +export const DescriptiveTextGeneratorSettings = () => { + const featureSettings = useSelect( ( select ) => + select( STORE_NAME ).getFeatureSettings() + ); + const { setFeatureSettings } = useDispatch( STORE_NAME ); + + const options = { + alt: __( 'Alt text', 'classifai' ), + caption: __( 'Image caption', 'classifai' ), + description: __( 'Image description', 'classifai' ), + }; + return ( + + { Object.keys( options ).map( ( option ) => { + return ( + { + setFeatureSettings( { + descriptive_text_fields: { + ...featureSettings.descriptive_text_fields, + [ option ]: value ? option : '0', + }, + } ); + } } + /> + ); + } ) } + + ); +}; diff --git a/src/js/settings/components/feature-additional-settings/index.js b/src/js/settings/components/feature-additional-settings/index.js new file mode 100644 index 000000000..e3e18600b --- /dev/null +++ b/src/js/settings/components/feature-additional-settings/index.js @@ -0,0 +1,41 @@ +/** + * WordPress dependencies + */ +import { Slot } from '@wordpress/components'; +import { PluginArea } from '@wordpress/plugins'; + +/** + * Internal dependencies + */ +import { getScope } from '../../utils/utils'; +import { useFeatureContext } from '../feature-settings/context'; +import { DescriptiveTextGeneratorSettings } from './descriptive-text-generator'; + +const AdditionalSettingsFields = () => { + const { featureName } = useFeatureContext(); + + switch ( featureName ) { + case 'feature_descriptive_text_generator': + return ; + + default: + return null; + } +}; + +/** + * Feature Additional Settings component. + */ +export const FeatureAdditionalSettings = () => { + const { featureName } = useFeatureContext(); + + return ( + <> + + + { ( fills ) => <> { fills } } + + + + ); +}; diff --git a/src/js/settings/components/feature-settings/index.js b/src/js/settings/components/feature-settings/index.js index b35e313f0..774d033c9 100644 --- a/src/js/settings/components/feature-settings/index.js +++ b/src/js/settings/components/feature-settings/index.js @@ -2,15 +2,14 @@ * WordPress dependencies */ import { __, sprintf } from '@wordpress/i18n'; -import { Panel, PanelBody, Spinner, Slot } from '@wordpress/components'; -import { PluginArea } from '@wordpress/plugins'; +import { Panel, PanelBody, Spinner } from '@wordpress/components'; import { useDispatch, useSelect } from '@wordpress/data'; import { useEffect } from '@wordpress/element'; /** * Internal dependencies */ -import { getFeature, getScope } from '../../utils/utils'; +import { getFeature } from '../../utils/utils'; import { UserPermissions } from '../user-permissions'; import { STORE_NAME } from '../../data/store'; import { ProviderSettings } from '../provider-settings'; @@ -18,6 +17,7 @@ import { EnableToggleControl } from './enable-feature'; import { SaveSettingsButton } from './save-settings-button'; import { Notices } from './notices'; import { useFeatureContext } from './context'; +import { FeatureAdditionalSettings } from '../feature-additional-settings'; /** * Feature Settings component. @@ -54,16 +54,13 @@ export const FeatureSettings = () => { - - { ( fills ) => <> { fills } } - +
- ); }; diff --git a/src/js/settings/components/provider-settings/index.js b/src/js/settings/components/provider-settings/index.js index b9ac370f4..66668e575 100644 --- a/src/js/settings/components/provider-settings/index.js +++ b/src/js/settings/components/provider-settings/index.js @@ -21,6 +21,8 @@ import { IBMWatsonNLUSettings } from './ibm-watson-nlu'; import { OpenAIModerationSettings } from './openai-moderation'; import { OpenAIEmbeddingsSettings } from './openai-embeddings'; import { OpenAIWhisperSettings } from './openai-whisper'; +import { AzureAIVisionSettings } from './azure-ai-vision'; +import { AzurePersonalizerSettings } from './azure-personlizer'; const ProviderFields = ( { provider } ) => { switch ( provider ) { @@ -31,7 +33,8 @@ const ProviderFields = ( { provider } ) => { return ; case 'azure_openai': - return ; + case 'azure_openai_embeddings': + return ; case 'ibm_watson_nlu': return ; @@ -45,6 +48,12 @@ const ProviderFields = ( { provider } ) => { case 'openai_moderation': return ; + case 'ms_computer_vision': + return ; + + case 'ms_azure_personalizer': + return ; + default: return null; } @@ -67,9 +76,7 @@ export const ProviderSettings = () => { select( STORE_NAME ).getFeatureSettings() ); - const configured = isProviderConfigured( featureSettings ); const providerLabel = feature.providers[ provider ] || ''; - const providers = Object.keys( feature?.providers || {} ).map( ( value ) => { return { @@ -79,9 +86,14 @@ export const ProviderSettings = () => { } ); + const configured = + isProviderConfigured( featureSettings ) && + ! editProvider && + providerLabel; + return ( <> - { configured && ! editProvider && ( + { configured && ( <> <> @@ -96,7 +108,6 @@ export const ProviderSettings = () => { className="classifai-settings-edit-provider" style={ { cursor: 'pointer', - } } onClick={ () => setEditProvider( true ) } /> @@ -105,7 +116,7 @@ export const ProviderSettings = () => { ) } - { ( ! configured || editProvider ) && ( + { ! configured && ( <> Date: Fri, 28 Jun 2024 20:35:12 +0530 Subject: [PATCH 041/138] Indicate saving settings. --- .../components/feature-settings/save-settings-button.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/js/settings/components/feature-settings/save-settings-button.js b/src/js/settings/components/feature-settings/save-settings-button.js index dd00931e9..fc54d5ced 100644 --- a/src/js/settings/components/feature-settings/save-settings-button.js +++ b/src/js/settings/components/feature-settings/save-settings-button.js @@ -23,6 +23,9 @@ export const SaveSettingsButton = () => { select( noticesStore ).getNotices() ); const { setIsSaving, setSettings } = useDispatch( STORE_NAME ); + const isSaving = useSelect( ( select ) => + select( STORE_NAME ).getIsSaving() + ); const settings = useSelect( ( select ) => select( STORE_NAME ).getSettings() ); @@ -68,8 +71,11 @@ export const SaveSettingsButton = () => { className="save-settings-button" variant="primary" onClick={ saveSettings } + isBusy={ isSaving } > - { __( 'Save Settings', 'classifai' ) } + { isSaving + ? __( 'Saving…', 'classifai' ) + : __( 'Save Settings', 'classifai' ) } ); }; From 9b66776543382d41366f65528c76521607036637 Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Mon, 15 Jul 2024 17:30:25 +0530 Subject: [PATCH 042/138] Added OpenAI dalle provider settings. --- .../components/provider-settings/index.js | 22 +-- .../provider-settings/openai-dalle.js | 134 ++++++++++++++++++ 2 files changed, 147 insertions(+), 9 deletions(-) create mode 100644 src/js/settings/components/provider-settings/openai-dalle.js diff --git a/src/js/settings/components/provider-settings/index.js b/src/js/settings/components/provider-settings/index.js index 66668e575..0dbb2727c 100644 --- a/src/js/settings/components/provider-settings/index.js +++ b/src/js/settings/components/provider-settings/index.js @@ -23,6 +23,7 @@ import { OpenAIEmbeddingsSettings } from './openai-embeddings'; import { OpenAIWhisperSettings } from './openai-whisper'; import { AzureAIVisionSettings } from './azure-ai-vision'; import { AzurePersonalizerSettings } from './azure-personlizer'; +import { OpenAIDallESettings } from './openai-dalle'; const ProviderFields = ( { provider } ) => { switch ( provider ) { @@ -48,6 +49,9 @@ const ProviderFields = ( { provider } ) => { case 'openai_moderation': return ; + case 'openai_dalle': + return ; + case 'ms_computer_vision': return ; @@ -116,8 +120,8 @@ export const ProviderSettings = () => { ) } - { ! configured && ( - <> + <> + { ! configured && ( @@ -129,13 +133,13 @@ export const ProviderSettings = () => { options={ providers } /> - - - { ( fills ) => <> { fills } } - - - - ) } + ) } + + + { ( fills ) => <> { fills } } + + + ); }; diff --git a/src/js/settings/components/provider-settings/openai-dalle.js b/src/js/settings/components/provider-settings/openai-dalle.js new file mode 100644 index 000000000..4a453e642 --- /dev/null +++ b/src/js/settings/components/provider-settings/openai-dalle.js @@ -0,0 +1,134 @@ +import { + __experimentalInputControl as InputControl, // eslint-disable-line @wordpress/no-unsafe-wp-apis + SelectControl, +} from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +import { SettingsRow } from '../settings-row'; +import { useSelect, useDispatch } from '@wordpress/data'; +import { STORE_NAME } from '../../data/store'; + +export const OpenAIDallESettings = () => { + const providerName = 'openai_dalle'; + const providerSettings = useSelect( + ( select ) => + select( STORE_NAME ).getFeatureSettings( providerName ) || {} + ); + const { setProviderSettings } = useDispatch( STORE_NAME ); + const onChange = ( data ) => setProviderSettings( providerName, data ); + + const Description = () => ( + <> + { __( "Don't have an OpenAI account yet? ", 'classifai' ) } + + { __( 'Sign up for one', 'classifai' ) } + { ' ' } + { __( 'in order to get your API key.', 'classifai' ) } + + ); + + return ( + <> + } + > + onChange( { api_key: value } ) } + /> + + + + onChange( { number_of_images: value } ) + } + value={ providerSettings.number_of_images || '1' } + options={ Array.from( { length: 10 }, ( v, i ) => ( { + label: i + 1, + value: i + 1, + } ) ) } + /> + + + onChange( { quality: value } ) } + value={ providerSettings.quality || 'standard' } + options={ [ + { + label: __( 'Standard', 'classifai' ), + value: 'standard', + }, + { + label: __( 'High Definition', 'classifai' ), + value: 'hd', + }, + ] } + /> + + + onChange( { image_size: value } ) } + value={ providerSettings.image_size || '1024x1024' } + options={ [ + { + label: __( '1024x1024 (square)', 'classifai' ), + value: '1024x1024', + }, + { + label: __( '1792x1024 (landscape)', 'classifai' ), + value: '1792x1024', + }, + { + label: __( '1024x1792 (portrait)', 'classifai' ), + value: '1024x1792', + }, + ] } + /> + + + onChange( { style: value } ) } + value={ providerSettings.style || 'vivid' } + options={ [ + { + label: __( 'Vivid', 'classifai' ), + value: 'vivid', + }, + { + label: __( 'Natural', 'classifai' ), + value: 'natural', + }, + ] } + /> + + + ); +}; From 7f20d041727cb5689c3d7b04c95b7b090ec3477f Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Mon, 15 Jul 2024 18:22:02 +0530 Subject: [PATCH 043/138] Added additional settings for image tags generator feature. --- .../image-tag-generator.js | 38 +++++++++++++++++++ .../feature-additional-settings/index.js | 4 ++ 2 files changed, 42 insertions(+) create mode 100644 src/js/settings/components/feature-additional-settings/image-tag-generator.js diff --git a/src/js/settings/components/feature-additional-settings/image-tag-generator.js b/src/js/settings/components/feature-additional-settings/image-tag-generator.js new file mode 100644 index 000000000..2ce95dee5 --- /dev/null +++ b/src/js/settings/components/feature-additional-settings/image-tag-generator.js @@ -0,0 +1,38 @@ +import { useSelect, useDispatch } from '@wordpress/data'; +// eslint-disable-next-line @wordpress/no-unsafe-wp-apis +import { SelectControl } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +import { SettingsRow } from '../settings-row'; +import { STORE_NAME } from '../../data/store'; + +export const ImageTagGeneratorSettings = () => { + const featureSettings = useSelect( ( select ) => + select( STORE_NAME ).getFeatureSettings() + ); + const { setFeatureSettings } = useDispatch( STORE_NAME ); + + const attachmentTaxonomies = useSelect( ( select ) => { + const { getTaxonomies } = select( 'core' ); + return getTaxonomies( { type: 'attachment' } ) || []; + }, [] ); + + const options = attachmentTaxonomies.map( ( taxonomy ) => { + return { + value: taxonomy.slug, + label: taxonomy.labels.name, + }; + } ); + return ( + + { + setFeatureSettings( { + tag_taxonomy: value, + } ); + } } + value={ featureSettings.tag_taxonomy || 'classifai-image-tags' } + options={ options } + /> + + ); +}; diff --git a/src/js/settings/components/feature-additional-settings/index.js b/src/js/settings/components/feature-additional-settings/index.js index e3e18600b..eb41739e1 100644 --- a/src/js/settings/components/feature-additional-settings/index.js +++ b/src/js/settings/components/feature-additional-settings/index.js @@ -10,6 +10,7 @@ import { PluginArea } from '@wordpress/plugins'; import { getScope } from '../../utils/utils'; import { useFeatureContext } from '../feature-settings/context'; import { DescriptiveTextGeneratorSettings } from './descriptive-text-generator'; +import { ImageTagGeneratorSettings } from './image-tag-generator'; const AdditionalSettingsFields = () => { const { featureName } = useFeatureContext(); @@ -18,6 +19,9 @@ const AdditionalSettingsFields = () => { case 'feature_descriptive_text_generator': return ; + case 'feature_image_tags_generator': + return ; + default: return null; } From 10e7b2468718a48d05b8bd9d34d340a5915a2b93 Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Mon, 15 Jul 2024 19:44:06 +0530 Subject: [PATCH 044/138] Add AWS polly providers. --- .../provider-settings/amazon-polly.js | 128 ++++++++++++++++++ .../components/provider-settings/index.js | 4 + 2 files changed, 132 insertions(+) create mode 100644 src/js/settings/components/provider-settings/amazon-polly.js diff --git a/src/js/settings/components/provider-settings/amazon-polly.js b/src/js/settings/components/provider-settings/amazon-polly.js new file mode 100644 index 000000000..e0e1a76d6 --- /dev/null +++ b/src/js/settings/components/provider-settings/amazon-polly.js @@ -0,0 +1,128 @@ +import { useSelect, useDispatch } from '@wordpress/data'; +import { + __experimentalInputControl as InputControl, // eslint-disable-line @wordpress/no-unsafe-wp-apis + SelectControl, +} from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +import { SettingsRow } from '../settings-row'; +import { STORE_NAME } from '../../data/store'; + +export const AmazonPollySettings = () => { + const providerName = 'aws_polly'; + const providerSettings = useSelect( + ( select ) => + select( STORE_NAME ).getFeatureSettings( providerName ) || {} + ); + const { setProviderSettings } = useDispatch( STORE_NAME ); + const onChange = ( data ) => setProviderSettings( providerName, data ); + + return ( + <> + + + onChange( { access_key_id: value } ) + } + /> + + + + onChange( { secret_access_key: value } ) + } + /> + + us-east-1.', + 'classifai' + ) } + > + onChange( { aws_region: value } ) } + /> + + + { __( 'Amazon Polly offers ', 'classifai' ) } + + { __( 'Long-Form', 'classifai' ) } + + ,{ ' ' } + + { __( 'Neural', 'classifai' ) }{ ' ' } + + { __( + ' and Standard text-to-speech voices. Please check the ', + 'classifai' + ) } + + { __( 'documentation', 'classifai' ) } + { ' ' } + { __( + 'to review pricing for Long-Form, Neural and Standard usage.', + 'classifai' + ) } + + } + > + + onChange( { voice_engine: value } ) + } + value={ providerSettings.voice_engine || 'standard' } + options={ [ + { + label: __( 'Standard', 'classifai' ), + value: 'standard', + }, + { + label: __( 'Neural', 'classifai' ), + value: 'neural', + }, + { + label: __( 'Long Form', 'classifai' ), + value: 'long-form', + }, + ] } + /> + + + onChange( { voice: value } ) } + value={ providerSettings.voice || '' } + options={ ( providerSettings.voices || [] ) + .filter( ( voice ) => + voice.SupportedEngines?.includes( + providerSettings.voice_engine + ) + ) + .map( ( voice ) => { + return { + value: voice.Id, + label: `${ voice?.LanguageName } - ${ voice?.Name } (${ voice?.Gender })`, + }; + } ) } + /> + + + ); +}; diff --git a/src/js/settings/components/provider-settings/index.js b/src/js/settings/components/provider-settings/index.js index 0dbb2727c..8da8b2904 100644 --- a/src/js/settings/components/provider-settings/index.js +++ b/src/js/settings/components/provider-settings/index.js @@ -24,6 +24,7 @@ import { OpenAIWhisperSettings } from './openai-whisper'; import { AzureAIVisionSettings } from './azure-ai-vision'; import { AzurePersonalizerSettings } from './azure-personlizer'; import { OpenAIDallESettings } from './openai-dalle'; +import { AmazonPollySettings } from './amazon-polly'; const ProviderFields = ( { provider } ) => { switch ( provider ) { @@ -58,6 +59,9 @@ const ProviderFields = ( { provider } ) => { case 'ms_azure_personalizer': return ; + case 'aws_polly': + return ; + default: return null; } From 293b63780c4af3874be152bad4ba88d6ad72f15e Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Mon, 15 Jul 2024 22:19:05 +0530 Subject: [PATCH 045/138] Added Azure Text to Speech provider settings. --- .../provider-settings/amazon-polly.js | 11 ++- .../provider-settings/azure-text-to-speech.js | 75 +++++++++++++++++++ .../components/provider-settings/index.js | 4 + 3 files changed, 86 insertions(+), 4 deletions(-) create mode 100644 src/js/settings/components/provider-settings/azure-text-to-speech.js diff --git a/src/js/settings/components/provider-settings/amazon-polly.js b/src/js/settings/components/provider-settings/amazon-polly.js index e0e1a76d6..df79f8e59 100644 --- a/src/js/settings/components/provider-settings/amazon-polly.js +++ b/src/js/settings/components/provider-settings/amazon-polly.js @@ -44,10 +44,13 @@ export const AmazonPollySettings = () => { us-east-1.', - 'classifai' - ) } + description={ + <> + { ' ' } + { __( 'Enter the AWS Region. eg: ', 'classifai' ) } + { __( 'us-east-1', 'classifai' ) } . + + } > { + const providerName = 'ms_azure_text_to_speech'; + const providerSettings = useSelect( + ( select ) => + select( STORE_NAME ).getFeatureSettings( providerName ) || {} + ); + const { setProviderSettings } = useDispatch( STORE_NAME ); + const onChange = ( data ) => setProviderSettings( providerName, data ); + + const Description = () => ( + <> + { __( 'Text to Speech region endpoint, e.g. ', 'classifai' ) } + + { __( + 'https://LOCATION.tts.speech.microsoft.com/', + 'classifai' + ) } + + { '. ' } + { __( ' Replace ', 'classifai' ) } + { __( 'LOCATION', 'classifai' ) } + { __( + ' with the Location/Region you selected for the resource in Azure.', + 'classifai' + ) } + + ); + + return ( + <> + } + > + + onChange( { endpoint_url: value } ) + } + /> + + + onChange( { api_key: value } ) } + /> + + { !! providerSettings.voices?.length && ( + + onChange( { voice: value } ) } + value={ providerSettings.voice || '' } + options={ ( providerSettings.voices || [] ).map( + ( ele ) => ( { + label: `${ ele.LocaleName } (${ ele.DisplayName }/${ ele.Gender })`, + value: `${ ele.ShortName }|${ ele.Gender }`, + } ) + ) } + /> + + ) } + + ); +}; diff --git a/src/js/settings/components/provider-settings/index.js b/src/js/settings/components/provider-settings/index.js index 8da8b2904..3d0f572e9 100644 --- a/src/js/settings/components/provider-settings/index.js +++ b/src/js/settings/components/provider-settings/index.js @@ -25,6 +25,7 @@ import { AzureAIVisionSettings } from './azure-ai-vision'; import { AzurePersonalizerSettings } from './azure-personlizer'; import { OpenAIDallESettings } from './openai-dalle'; import { AmazonPollySettings } from './amazon-polly'; +import { AzureTextToSpeechSettings } from './azure-text-to-speech'; const ProviderFields = ( { provider } ) => { switch ( provider ) { @@ -62,6 +63,9 @@ const ProviderFields = ( { provider } ) => { case 'aws_polly': return ; + case 'ms_azure_text_to_speech': + return ; + default: return null; } From 56bae2a8e0364991c94039b2babe628f60c68e8c Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Mon, 15 Jul 2024 22:38:15 +0530 Subject: [PATCH 046/138] Added provider settings for the OpenAI TTS --- .../components/provider-settings/index.js | 4 + .../openai-text-to-speech.js | 172 ++++++++++++++++++ 2 files changed, 176 insertions(+) create mode 100644 src/js/settings/components/provider-settings/openai-text-to-speech.js diff --git a/src/js/settings/components/provider-settings/index.js b/src/js/settings/components/provider-settings/index.js index 3d0f572e9..5b9d7b7aa 100644 --- a/src/js/settings/components/provider-settings/index.js +++ b/src/js/settings/components/provider-settings/index.js @@ -26,6 +26,7 @@ import { AzurePersonalizerSettings } from './azure-personlizer'; import { OpenAIDallESettings } from './openai-dalle'; import { AmazonPollySettings } from './amazon-polly'; import { AzureTextToSpeechSettings } from './azure-text-to-speech'; +import { OpenAITextToSpeachSettings } from './openai-text-to-speech'; const ProviderFields = ( { provider } ) => { switch ( provider ) { @@ -66,6 +67,9 @@ const ProviderFields = ( { provider } ) => { case 'ms_azure_text_to_speech': return ; + case 'openai_text_to_speech': + return ; + default: return null; } diff --git a/src/js/settings/components/provider-settings/openai-text-to-speech.js b/src/js/settings/components/provider-settings/openai-text-to-speech.js new file mode 100644 index 000000000..21568b8fe --- /dev/null +++ b/src/js/settings/components/provider-settings/openai-text-to-speech.js @@ -0,0 +1,172 @@ +import { + __experimentalInputControl as InputControl, // eslint-disable-line @wordpress/no-unsafe-wp-apis + SelectControl, +} from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +import { SettingsRow } from '../settings-row'; +import { useSelect, useDispatch } from '@wordpress/data'; +import { STORE_NAME } from '../../data/store'; + +export const OpenAITextToSpeachSettings = () => { + const providerName = 'openai_text_to_speech'; + const providerSettings = useSelect( + ( select ) => + select( STORE_NAME ).getFeatureSettings( providerName ) || {} + ); + const { setProviderSettings } = useDispatch( STORE_NAME ); + const onChange = ( data ) => setProviderSettings( providerName, data ); + + const Description = () => ( + <> + { __( "Don't have an OpenAI account yet? ", 'classifai' ) } + + { __( 'Sign up for one', 'classifai' ) } + { ' ' } + { __( 'in order to get your API key.', 'classifai' ) } + + ); + + return ( + <> + } + > + onChange( { api_key: value } ) } + /> + + + { __( 'Select a ', 'classifai' ) } + + { __( 'model', 'classifai' ) } + + { __( ' depending on your requirement.', 'classifai' ) } + + } + > + onChange( { tts_model: value } ) } + value={ providerSettings.tts_model || 'tts-1' } + options={ [ + { + label: __( + 'Text-to-speech 1 (Optimized for speed)', + 'classifai' + ), + value: 'tts-1', + }, + { + label: __( + 'Text-to-speech 1 HD (Optimized for quality)', + 'classifai' + ), + value: 'tts-1-hd', + }, + ] } + /> + + + { __( 'Select the speech ', 'classifai' ) } + + { __( 'voice', 'classifai' ) } + + . + + } + > + onChange( { voice: value } ) } + value={ providerSettings.voice || 'alloy' } + options={ [ + { + label: __( 'Alloy (male)', 'classifai' ), + value: 'alloy', + }, + { + label: __( 'Echo (male)', 'classifai' ), + value: 'echo', + }, + { + label: __( 'Fable (male)', 'classifai' ), + value: 'fable', + }, + { + label: __( 'Onyx (male)', 'classifai' ), + value: 'onyx', + }, + { + label: __( 'Nova (female)', 'classifai' ), + value: 'nova', + }, + { + label: __( 'Shimmer (female)', 'classifai' ), + value: 'shimmer', + }, + ] } + /> + + + onChange( { format: value } ) } + value={ providerSettings.format || '.mp3' } + options={ [ + { + label: __( '.mp3', 'classifai' ), + value: 'mp3', + }, + { + label: __( '.wav', 'classifai' ), + value: 'wav', + }, + ] } + /> + + + onChange( { speed: value } ) } + value={ providerSettings.speed || 1 } + type="number" + step="0.25" + min="0.25" + max="4" + /> + + + ); +}; From 3b0a6840852b6bdf5df530e830953a3c20a79046 Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Tue, 16 Jul 2024 12:33:51 +0530 Subject: [PATCH 047/138] Added TTS feature settings. --- .../feature-additional-settings/index.js | 4 ++ .../text-to-speech.js | 44 +++++++++++++++++++ src/js/settings/utils/utils.js | 32 ++++++++++++++ 3 files changed, 80 insertions(+) create mode 100644 src/js/settings/components/feature-additional-settings/text-to-speech.js diff --git a/src/js/settings/components/feature-additional-settings/index.js b/src/js/settings/components/feature-additional-settings/index.js index eb41739e1..b8a6bb658 100644 --- a/src/js/settings/components/feature-additional-settings/index.js +++ b/src/js/settings/components/feature-additional-settings/index.js @@ -11,6 +11,7 @@ import { getScope } from '../../utils/utils'; import { useFeatureContext } from '../feature-settings/context'; import { DescriptiveTextGeneratorSettings } from './descriptive-text-generator'; import { ImageTagGeneratorSettings } from './image-tag-generator'; +import { TextToSpeechSettings } from './text-to-speech'; const AdditionalSettingsFields = () => { const { featureName } = useFeatureContext(); @@ -22,6 +23,9 @@ const AdditionalSettingsFields = () => { case 'feature_image_tags_generator': return ; + case 'feature_text_to_speech_generation': + return ; + default: return null; } diff --git a/src/js/settings/components/feature-additional-settings/text-to-speech.js b/src/js/settings/components/feature-additional-settings/text-to-speech.js new file mode 100644 index 000000000..c733190b2 --- /dev/null +++ b/src/js/settings/components/feature-additional-settings/text-to-speech.js @@ -0,0 +1,44 @@ +import { useSelect, useDispatch } from '@wordpress/data'; +// eslint-disable-next-line @wordpress/no-unsafe-wp-apis +import { CheckboxControl } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +import { SettingsRow } from '../settings-row'; +import { STORE_NAME } from '../../data/store'; +import { usePostTypes } from '../../utils/utils'; + +export const TextToSpeechSettings = () => { + const featureSettings = useSelect( ( select ) => + select( STORE_NAME ).getFeatureSettings() + ); + const { setFeatureSettings } = useDispatch( STORE_NAME ); + const { postTypesSelectOptions } = usePostTypes(); + + return ( + + { postTypesSelectOptions.map( ( option ) => { + const { value: key, label } = option; + return ( + { + setFeatureSettings( { + post_types: { + ...featureSettings.post_types, + [ key ]: value ? key : '0', + }, + } ); + } } + /> + ); + } ) } + + ); +}; diff --git a/src/js/settings/utils/utils.js b/src/js/settings/utils/utils.js index d758bd1b3..4f98cfd92 100644 --- a/src/js/settings/utils/utils.js +++ b/src/js/settings/utils/utils.js @@ -1,3 +1,7 @@ +import { useSelect } from '@wordpress/data'; +import { useMemo } from '@wordpress/element'; +import { store as coreStore } from '@wordpress/core-data'; + // Update URL based on the current tab and feature selected export const updateUrl = ( key, value ) => { const urlParams = new URLSearchParams( window.location.search ); @@ -96,3 +100,31 @@ export const isProviderConfigured = ( featureSettings ) => { return featureSettings[ selectedProvider ]?.authenticated || false; }; + +/** + * Returns a helper object that contains: + * An `options` object from the available post types, to be passed to a `SelectControl`. + * + * @return {Object} The helper object related to post types. + */ +export const usePostTypes = () => { + const postTypes = useSelect( ( select ) => { + const { getPostTypes } = select( coreStore ); + const excludedPostTypes = [ 'attachment' ]; + const filteredPostTypes = getPostTypes( { per_page: -1 } )?.filter( + ( { viewable, slug } ) => + viewable && ! excludedPostTypes.includes( slug ) + ); + return filteredPostTypes; + }, [] ); + + const postTypesSelectOptions = useMemo( + () => + ( postTypes || [] ).map( ( { labels, slug } ) => ( { + label: labels.singular_name, + value: slug, + } ) ), + [ postTypes ] + ); + return { postTypesSelectOptions, postTypes }; +}; From 9fa97c119ed41bde1b00efa0642bd587a59bcbb0 Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Wed, 17 Jul 2024 23:28:42 +0530 Subject: [PATCH 048/138] Added Title generation settings. --- .../feature-additional-settings/index.js | 4 + .../prompt-repeater.js | 166 ++++++++++++++++++ .../title-generation.js | 32 ++++ src/scss/settings.scss | 69 ++++++++ 4 files changed, 271 insertions(+) create mode 100644 src/js/settings/components/feature-additional-settings/prompt-repeater.js create mode 100644 src/js/settings/components/feature-additional-settings/title-generation.js diff --git a/src/js/settings/components/feature-additional-settings/index.js b/src/js/settings/components/feature-additional-settings/index.js index b8a6bb658..cf15d98b1 100644 --- a/src/js/settings/components/feature-additional-settings/index.js +++ b/src/js/settings/components/feature-additional-settings/index.js @@ -12,11 +12,15 @@ import { useFeatureContext } from '../feature-settings/context'; import { DescriptiveTextGeneratorSettings } from './descriptive-text-generator'; import { ImageTagGeneratorSettings } from './image-tag-generator'; import { TextToSpeechSettings } from './text-to-speech'; +import { TitleGenerationSettings } from './title-generation'; const AdditionalSettingsFields = () => { const { featureName } = useFeatureContext(); switch ( featureName ) { + case 'feature_title_generation': + return ; + case 'feature_descriptive_text_generator': return ; diff --git a/src/js/settings/components/feature-additional-settings/prompt-repeater.js b/src/js/settings/components/feature-additional-settings/prompt-repeater.js new file mode 100644 index 000000000..ddbd7ea18 --- /dev/null +++ b/src/js/settings/components/feature-additional-settings/prompt-repeater.js @@ -0,0 +1,166 @@ +import { + Button, + __experimentalInputControl as InputControl, // eslint-disable-line @wordpress/no-unsafe-wp-apis + TextareaControl, + __experimentalConfirmDialog as ConfirmDialog, // eslint-disable-line @wordpress/no-unsafe-wp-apis +} from '@wordpress/components'; +import { useState } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; + +export const PromptRepeater = ( props ) => { + const [ showConfirmDialog, setShowConfirmDialog ] = useState( false ); + const [ activeIndex, setActiveIndex ] = useState( null ); + const { prompts = [], setPromts } = props; + + const placeholder = + prompts?.filter( ( prompt ) => prompt.original )[ 0 ]?.prompt || ''; + + // Add a new prompt. + const addPrompt = () => { + setPromts( [ + ...prompts, + { default: 0, original: 0, prompt: '', title: '' }, + ] ); + }; + + // Remove a prompt. + const removePrompt = ( index ) => { + const prompt = prompts.splice( index, 1 ); + // Make the first prompt default if default prompt is removed. + if ( prompt[ 0 ]?.default ) { + prompts[ 0 ].default = 1; + } + setPromts( [ ...prompts ] ); + }; + + // Update prompt. + const onChange = ( index, changes ) => { + // Remove default from all other prompts + if ( changes.default ) { + prompts.forEach( ( prompt, i ) => { + if ( i !== index ) { + prompt.default = 0; + } + } ); + } + + prompts[ index ] = { + ...prompts[ index ], + ...changes, + }; + setPromts( [ ...prompts ] ); + }; + + // Confirm dialog to remove prompt. + const handleConfirm = () => { + setShowConfirmDialog( false ); + removePrompt( activeIndex ); + }; + + return ( +
+ { prompts.map( ( prompt, index ) => ( +
+ { !! prompt.original && ( + <> +

+ + { __( + 'ClassifAI default prompt: ', + 'classifai' + ) } + + { prompt.prompt } +

+ + ) } + { ! prompt.original && ( + <> + { + onChange( index, { + title: value, + } ); + } } + help={ __( + 'Short description of prompt to use for identification.', + 'classifai' + ) } + /> + { + onChange( index, { + prompt: value, + } ); + } } + /> + + ) } +
+ + { ! prompt.original && ( + <> + { '|' } + + + ) } +
+
+ ) ) } + setShowConfirmDialog( false ) } + confirmButtonText={ __( 'Remove' ) } + size="medium" + > + { __( + 'Are you sure you want to remove the prompt?', + 'classifai' + ) } + + +
+ ); +}; diff --git a/src/js/settings/components/feature-additional-settings/title-generation.js b/src/js/settings/components/feature-additional-settings/title-generation.js new file mode 100644 index 000000000..5cdb51e30 --- /dev/null +++ b/src/js/settings/components/feature-additional-settings/title-generation.js @@ -0,0 +1,32 @@ +import { useSelect, useDispatch } from '@wordpress/data'; +import { __ } from '@wordpress/i18n'; +import { SettingsRow } from '../settings-row'; +import { STORE_NAME } from '../../data/store'; +import { PromptRepeater } from './prompt-repeater'; + +export const TitleGenerationSettings = () => { + const featureSettings = useSelect( ( select ) => + select( STORE_NAME ).getFeatureSettings() + ); + const { setFeatureSettings } = useDispatch( STORE_NAME ); + const setPromts = ( prompts ) => { + setFeatureSettings( { + generate_title_prompt: prompts, + } ); + }; + + return ( + + + + ); +}; diff --git a/src/scss/settings.scss b/src/scss/settings.scss index 270f300ea..a38030035 100644 --- a/src/scss/settings.scss +++ b/src/scss/settings.scss @@ -274,6 +274,75 @@ color: #3858e9; } } + + .classifai-field-type-prompt-setting { + padding: 5px 20px; + border: 1px solid #f0f0f1; + position: relative; + display: flex; + flex-direction:column; + max-width: 600px; + + &:first-child { + > input, + > label { + display: none; + } + } + + &:nth-child(2n + 1) { + background-color: #f0f0f1; + } + + label { + display: block; + } + + input, textarea { + margin-right: 0; + display: block; + } + + input[type="text"] { + width: 90%; + } + + .components-base-control { + margin-top: 8px; + } + + /* Style for prompt action items container */ + .actions-rows { + display: flex; + margin: 0.35em 0 .5em!important; + + /* Style for prompt actions */ + button { + cursor: pointer; + text-decoration: none; + + &.action__remove_prompt { + color: #b32d2e; + } + } + + span.separator{ + margin-left: 8px; + margin-right: 8px; + color: #a7aaad; + } + + /* Styling for default prompt first action */ + button:disabled{ + pointer-events: none; + } + + } + + + button { + margin-top: 10px; + } + } } .service-settings-wrapper { From 2ea55eefd64c46382eb588dbeaed996ee3ad8508 Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Wed, 17 Jul 2024 23:37:30 +0530 Subject: [PATCH 049/138] Content resizing settings. --- .../content-resizing.js | 43 +++++++++++++++++++ .../feature-additional-settings/index.js | 4 ++ 2 files changed, 47 insertions(+) create mode 100644 src/js/settings/components/feature-additional-settings/content-resizing.js diff --git a/src/js/settings/components/feature-additional-settings/content-resizing.js b/src/js/settings/components/feature-additional-settings/content-resizing.js new file mode 100644 index 000000000..ef4160f11 --- /dev/null +++ b/src/js/settings/components/feature-additional-settings/content-resizing.js @@ -0,0 +1,43 @@ +import { useSelect, useDispatch } from '@wordpress/data'; +import { __ } from '@wordpress/i18n'; +import { SettingsRow } from '../settings-row'; +import { STORE_NAME } from '../../data/store'; +import { PromptRepeater } from './prompt-repeater'; + +export const ContentResizingSettings = () => { + const featureSettings = useSelect( ( select ) => + select( STORE_NAME ).getFeatureSettings() + ); + const { setFeatureSettings } = useDispatch( STORE_NAME ); + + return ( + <> + + { + setFeatureSettings( { + condense_text_prompt: prompts, + } ); + } } + /> + + + { + setFeatureSettings( { + expand_text_prompt: prompts, + } ); + } } + /> + + + ); +}; diff --git a/src/js/settings/components/feature-additional-settings/index.js b/src/js/settings/components/feature-additional-settings/index.js index cf15d98b1..764e61c8f 100644 --- a/src/js/settings/components/feature-additional-settings/index.js +++ b/src/js/settings/components/feature-additional-settings/index.js @@ -13,6 +13,7 @@ import { DescriptiveTextGeneratorSettings } from './descriptive-text-generator'; import { ImageTagGeneratorSettings } from './image-tag-generator'; import { TextToSpeechSettings } from './text-to-speech'; import { TitleGenerationSettings } from './title-generation'; +import { ContentResizingSettings } from './content-resizing'; const AdditionalSettingsFields = () => { const { featureName } = useFeatureContext(); @@ -21,6 +22,9 @@ const AdditionalSettingsFields = () => { case 'feature_title_generation': return ; + case 'feature_content_resizing': + return ; + case 'feature_descriptive_text_generator': return ; From 42910c291e885b6debee325861a6dd90ccd212ae Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Thu, 18 Jul 2024 00:14:13 +0530 Subject: [PATCH 050/138] Excerpt generation settings. --- .../excerpt-generation.js | 65 +++++++++++++++++++ .../feature-additional-settings/index.js | 4 ++ src/js/settings/utils/utils.js | 18 ++++- 3 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 src/js/settings/components/feature-additional-settings/excerpt-generation.js diff --git a/src/js/settings/components/feature-additional-settings/excerpt-generation.js b/src/js/settings/components/feature-additional-settings/excerpt-generation.js new file mode 100644 index 000000000..2e23d6cd0 --- /dev/null +++ b/src/js/settings/components/feature-additional-settings/excerpt-generation.js @@ -0,0 +1,65 @@ +import { useSelect, useDispatch } from '@wordpress/data'; +import { __ } from '@wordpress/i18n'; +import { CheckboxControl } from '@wordpress/components'; +import { SettingsRow } from '../settings-row'; +import { STORE_NAME } from '../../data/store'; +import { PromptRepeater } from './prompt-repeater'; +import { usePostTypes } from '../../utils/utils'; + +export const ExcerptGenerationSettings = () => { + const featureSettings = useSelect( ( select ) => + select( STORE_NAME ).getFeatureSettings() + ); + const { excerptPostTypesOptions } = usePostTypes(); + const { setFeatureSettings } = useDispatch( STORE_NAME ); + const setPromts = ( prompts ) => { + setFeatureSettings( { + generate_excerpt_prompt: prompts, + } ); + }; + + return ( + <> + + + + + { ( excerptPostTypesOptions || [] ).map( ( option ) => { + const { value: key, label } = option; + return ( + { + setFeatureSettings( { + post_types: { + ...featureSettings.post_types, + [ key ]: value ? key : '0', + }, + } ); + } } + /> + ); + } ) } + + + ); +}; diff --git a/src/js/settings/components/feature-additional-settings/index.js b/src/js/settings/components/feature-additional-settings/index.js index 764e61c8f..968a9e33c 100644 --- a/src/js/settings/components/feature-additional-settings/index.js +++ b/src/js/settings/components/feature-additional-settings/index.js @@ -14,6 +14,7 @@ import { ImageTagGeneratorSettings } from './image-tag-generator'; import { TextToSpeechSettings } from './text-to-speech'; import { TitleGenerationSettings } from './title-generation'; import { ContentResizingSettings } from './content-resizing'; +import { ExcerptGenerationSettings } from './excerpt-generation'; const AdditionalSettingsFields = () => { const { featureName } = useFeatureContext(); @@ -22,6 +23,9 @@ const AdditionalSettingsFields = () => { case 'feature_title_generation': return ; + case 'feature_excerpt_generation': + return ; + case 'feature_content_resizing': return ; diff --git a/src/js/settings/utils/utils.js b/src/js/settings/utils/utils.js index 4f98cfd92..cc1062288 100644 --- a/src/js/settings/utils/utils.js +++ b/src/js/settings/utils/utils.js @@ -126,5 +126,21 @@ export const usePostTypes = () => { } ) ), [ postTypes ] ); - return { postTypesSelectOptions, postTypes }; + + const excerptPostTypes = useSelect( ( select ) => { + const { getPostTypes } = select( coreStore ); + const filteredPostTypes = getPostTypes( { per_page: -1 } )?.filter( + ( { viewable, supports } ) => viewable && supports?.excerpt + ); + return filteredPostTypes; + }, [] ); + + const excerptPostTypesOptions = useMemo( () => { + return ( excerptPostTypes || [] ).map( ( { labels, slug } ) => ( { + label: labels.singular_name, + value: slug, + } ) ); + }, [ excerptPostTypes ] ); + + return { postTypesSelectOptions, postTypes, excerptPostTypesOptions }; }; From 1c93d654450efd478b11a07c3f0df1c9d856d357 Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Thu, 18 Jul 2024 15:18:11 +0530 Subject: [PATCH 051/138] Initial classification settings. --- .../classification.js | 152 ++++++++++++++++++ .../feature-additional-settings/index.js | 4 + src/js/settings/utils/utils.js | 36 +++++ 3 files changed, 192 insertions(+) create mode 100644 src/js/settings/components/feature-additional-settings/classification.js diff --git a/src/js/settings/components/feature-additional-settings/classification.js b/src/js/settings/components/feature-additional-settings/classification.js new file mode 100644 index 000000000..f3274d79f --- /dev/null +++ b/src/js/settings/components/feature-additional-settings/classification.js @@ -0,0 +1,152 @@ +import { useSelect, useDispatch } from '@wordpress/data'; +// eslint-disable-next-line @wordpress/no-unsafe-wp-apis +import { RadioControl, CheckboxControl } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +import { SettingsRow } from '../settings-row'; +import { STORE_NAME } from '../../data/store'; +import { usePostTypes, usePostStatuses } from '../../utils/utils'; + +const ClassificationMethodSettings = () => { + const featureSettings = useSelect( ( select ) => + select( STORE_NAME ).getFeatureSettings() + ); + const { setFeatureSettings } = useDispatch( STORE_NAME ); + + const classifaicationMethodOptions = [ + { + label: __( + 'Recommend terms even if they do not exist on the site', + 'classifai' + ), + value: 'recommended_terms', + }, + { + label: __( + 'Only recommend terms that already exist on the site', + 'classifai' + ), + value: 'existing_terms', + }, + ]; + + if ( + [ 'openai_embeddings', 'azure_openai_embeddings' ].includes( + featureSettings.provider + ) + ) { + delete classifaicationMethodOptions[ 0 ]; + if ( featureSettings.classification_method === 'recommended_terms' ) { + setFeatureSettings( { + classification_method: 'existing_terms', + } ); + } + } + + return ( + + { + setFeatureSettings( { + classification_method: value, + } ); + } } + options={ classifaicationMethodOptions } + selected={ featureSettings.classification_method } + /> + + ); +}; + +export const ClassificationSettings = () => { + const featureSettings = useSelect( ( select ) => + select( STORE_NAME ).getFeatureSettings() + ); + const { setFeatureSettings } = useDispatch( STORE_NAME ); + const { postTypesSelectOptions } = usePostTypes(); + const { postStatusOptions } = usePostStatuses(); + + return ( + <> + + { + setFeatureSettings( { + classification_mode: value, + } ); + } } + options={ [ + { + label: __( 'Manual review', 'classifai' ), + value: 'manual_review', + }, + { + label: __( + 'Automatic classification', + 'classifai' + ), + value: 'automatic_classification', + }, + ] } + selected={ featureSettings.classification_mode } + /> + + + + { postStatusOptions.map( ( option ) => { + const { value: key, label } = option; + return ( + { + setFeatureSettings( { + post_statuses: { + ...featureSettings.post_statuses, + [ key ]: value ? key : '0', + }, + } ); + } } + /> + ); + } ) } + + + { postTypesSelectOptions.map( ( option ) => { + const { value: key, label } = option; + return ( + { + setFeatureSettings( { + post_types: { + ...featureSettings.post_types, + [ key ]: value ? key : '0', + }, + } ); + } } + /> + ); + } ) } + + + ); +}; diff --git a/src/js/settings/components/feature-additional-settings/index.js b/src/js/settings/components/feature-additional-settings/index.js index 968a9e33c..4aea2c4a6 100644 --- a/src/js/settings/components/feature-additional-settings/index.js +++ b/src/js/settings/components/feature-additional-settings/index.js @@ -15,11 +15,15 @@ import { TextToSpeechSettings } from './text-to-speech'; import { TitleGenerationSettings } from './title-generation'; import { ContentResizingSettings } from './content-resizing'; import { ExcerptGenerationSettings } from './excerpt-generation'; +import { ClassificationSettings } from './classification'; const AdditionalSettingsFields = () => { const { featureName } = useFeatureContext(); switch ( featureName ) { + case 'feature_classification': + return ; + case 'feature_title_generation': return ; diff --git a/src/js/settings/utils/utils.js b/src/js/settings/utils/utils.js index cc1062288..befc07e92 100644 --- a/src/js/settings/utils/utils.js +++ b/src/js/settings/utils/utils.js @@ -144,3 +144,39 @@ export const usePostTypes = () => { return { postTypesSelectOptions, postTypes, excerptPostTypesOptions }; }; + +/** + * Post Statuses Hook. + * Returns a helper object that contains: + * An `options` object from the available post statuses. + * + * @return {Object} The helper object related to post statuses. + */ +export const usePostStatuses = () => { + const postStatuses = useSelect( ( select ) => { + const excludeStatutes = [ + 'auto-draft', + 'inherit', + 'trash', + 'future', + 'request-pending', + 'request-confirmed', + 'request-failed', + 'request-completed', + ]; + return select( 'core' ) + .getStatuses() + ?.filter( ( { slug } ) => ! excludeStatutes.includes( slug ) ); + }, [] ); + + const postStatusOptions = useMemo( + () => + ( postStatuses || [] ).map( ( { name, slug } ) => ( { + label: name, + value: slug, + } ) ), + [ postStatuses ] + ); + + return { postStatusOptions }; +}; From 2b91a7c271da9c8c595abca215c9b9140edc3172 Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Thu, 18 Jul 2024 19:07:53 +0530 Subject: [PATCH 052/138] Added classification settings. --- .../classification.js | 3 +- .../nlu-feature.js | 146 ++++++++++++++++++ src/js/settings/utils/utils.js | 26 ++++ src/scss/settings.scss | 11 ++ 4 files changed, 185 insertions(+), 1 deletion(-) create mode 100644 src/js/settings/components/feature-additional-settings/nlu-feature.js diff --git a/src/js/settings/components/feature-additional-settings/classification.js b/src/js/settings/components/feature-additional-settings/classification.js index f3274d79f..b65d0daaf 100644 --- a/src/js/settings/components/feature-additional-settings/classification.js +++ b/src/js/settings/components/feature-additional-settings/classification.js @@ -1,10 +1,10 @@ import { useSelect, useDispatch } from '@wordpress/data'; -// eslint-disable-next-line @wordpress/no-unsafe-wp-apis import { RadioControl, CheckboxControl } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { SettingsRow } from '../settings-row'; import { STORE_NAME } from '../../data/store'; import { usePostTypes, usePostStatuses } from '../../utils/utils'; +import { NLUFeatureSettings } from './nlu-feature'; const ClassificationMethodSettings = () => { const featureSettings = useSelect( ( select ) => @@ -91,6 +91,7 @@ export const ClassificationSettings = () => { />
+ { + const featureSettings = useSelect( ( select ) => + select( STORE_NAME ).getFeatureSettings() + ); + const { setFeatureSettings } = useDispatch( STORE_NAME ); + const { taxonomies = [] } = useTaxonomies(); + + const nluFeatures = { + category: { + label: __( 'Category', 'classifai' ), + defaultThreshold: 70, + }, + keyword: { + label: __( 'Keyword', 'classifai' ), + defaultThreshold: 70, + }, + entity: { + label: __( 'Entity', 'classifai' ), + defaultThreshold: 70, + }, + concept: { + label: __( 'Concept', 'classifai' ), + defaultThreshold: 70, + }, + }; + + const options = + taxonomies + ?.filter( ( taxonomy ) => { + const intersection = ( taxonomy.types || [] ).filter( + ( type ) => featureSettings.post_types?.[ type ] === type + ); + return intersection.length > 0; + } ) + ?.map( ( taxonomy ) => ( { + label: taxonomy.name, + value: taxonomy.slug, + } ) ) || []; + + let features = {}; + if ( 'ibm_watson_nlu' === featureSettings.provider ) { + features = nluFeatures; + if ( options ) { + options.push( + { + label: __( 'Watson Category', 'classifai' ), + value: 'watson-category', + }, + { + label: __( 'Watson Keyword', 'classifai' ), + value: 'watson-keyword', + }, + { + label: __( 'Watson Entity', 'classifai' ), + value: 'watson-entity', + }, + { + label: __( 'Watson Concept', 'classifai' ), + value: 'watson-concept', + } + ); + } + } else { + options?.forEach( ( taxonomy ) => { + features[ taxonomy.value ] = { + label: taxonomy.label, + defaultThreshold: 75, + }; + } ); + } + + return ( + <> + { Object.keys( features ).map( ( feature ) => { + const { defaultThreshold, label } = features[ feature ]; + return ( + + { + setFeatureSettings( { + [ feature ]: value ? 1 : 0, + } ); + } } + /> + { + setFeatureSettings( { + [ `${ feature }_threshold` ]: value, + } ); + } } + /> + { 'ibm_watson_nlu' === featureSettings.provider && ( + ( { + label: taxonomy.label, + value: taxonomy.value, + } ) + ) } + onChange={ ( value ) => { + setFeatureSettings( { + [ `${ feature }_taxonomy` ]: value, + } ); + } } + /> + ) } + + ); + } ) } + + ); +}; diff --git a/src/js/settings/utils/utils.js b/src/js/settings/utils/utils.js index befc07e92..1f2f8c8ec 100644 --- a/src/js/settings/utils/utils.js +++ b/src/js/settings/utils/utils.js @@ -180,3 +180,29 @@ export const usePostStatuses = () => { return { postStatusOptions }; }; + +/** + * Post Statuses Hook. + * Returns a helper object that contains: + * An `options` object from the available post statuses. + * + * @return {Object} The helper object related to post statuses. + */ +export const useTaxonomies = () => { + const taxonomyOptions = useSelect( ( select ) => { + // Remove the NLUs taxonomies from the list of taxonomies + const excludedTaxonomies = [ + 'watson-category', + 'watson-keyword', + 'watson-concept', + 'watson-entity', + ]; + return select( 'core' ) + .getTaxonomies() + ?.filter( ( { slug } ) => ! excludedTaxonomies.includes( slug ) ); + }, [] ); + + const taxonomies = useMemo( () => taxonomyOptions, [ taxonomyOptions ] ); + + return { taxonomies }; +}; diff --git a/src/scss/settings.scss b/src/scss/settings.scss index a38030035..d5e8aba90 100644 --- a/src/scss/settings.scss +++ b/src/scss/settings.scss @@ -343,6 +343,17 @@ margin-top: 10px; } } + + .settings-row.nlu-features { + .components-base-control { + max-width: 180px; + margin-bottom: 8px; + } + } + + .components-radio-control__option { + margin-bottom: 4px; + } } .service-settings-wrapper { From f58286cd0f1411f3148d3684b49201b4710c0038 Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Thu, 18 Jul 2024 19:52:23 +0530 Subject: [PATCH 053/138] Added moderation settings. --- .../feature-additional-settings/index.js | 4 ++ .../feature-additional-settings/moderation.js | 47 +++++++++++++++++++ src/js/settings/index.js | 2 - 3 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 src/js/settings/components/feature-additional-settings/moderation.js diff --git a/src/js/settings/components/feature-additional-settings/index.js b/src/js/settings/components/feature-additional-settings/index.js index 4aea2c4a6..2063c28f1 100644 --- a/src/js/settings/components/feature-additional-settings/index.js +++ b/src/js/settings/components/feature-additional-settings/index.js @@ -16,6 +16,7 @@ import { TitleGenerationSettings } from './title-generation'; import { ContentResizingSettings } from './content-resizing'; import { ExcerptGenerationSettings } from './excerpt-generation'; import { ClassificationSettings } from './classification'; +import { ModerationSettings } from './moderation'; const AdditionalSettingsFields = () => { const { featureName } = useFeatureContext(); @@ -42,6 +43,9 @@ const AdditionalSettingsFields = () => { case 'feature_text_to_speech_generation': return ; + case 'feature_moderation': + return ; + default: return null; } diff --git a/src/js/settings/components/feature-additional-settings/moderation.js b/src/js/settings/components/feature-additional-settings/moderation.js new file mode 100644 index 000000000..cd7cf5b4a --- /dev/null +++ b/src/js/settings/components/feature-additional-settings/moderation.js @@ -0,0 +1,47 @@ +import { useSelect, useDispatch } from '@wordpress/data'; +// eslint-disable-next-line @wordpress/no-unsafe-wp-apis +import { CheckboxControl } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +import { SettingsRow } from '../settings-row'; +import { STORE_NAME } from '../../data/store'; + +export const ModerationSettings = () => { + const featureSettings = useSelect( ( select ) => + select( STORE_NAME ).getFeatureSettings() + ); + const { setFeatureSettings } = useDispatch( STORE_NAME ); + const contentTypes = { + comments: __( 'Comments', 'classifai' ), + }; + + return ( + + { Object.keys( contentTypes ).map( ( contentType ) => { + return ( + { + setFeatureSettings( { + content_types: { + ...featureSettings.content_types, + [ contentType ]: value ? contentType : '0', + }, + } ); + } } + /> + ); + } ) } + + ); +}; diff --git a/src/js/settings/index.js b/src/js/settings/index.js index 0e757f16f..fe2cd56a4 100644 --- a/src/js/settings/index.js +++ b/src/js/settings/index.js @@ -10,8 +10,6 @@ import { createRoot } from '@wordpress/element'; import { ClassifAISettings } from './components'; import './data/store'; import '../../scss/settings.scss'; -import './features'; // TODO: This is for testing purposes only and still in experimental phase. -import './providers'; // TODO: This is for testing purposes only and still in experimental phase. domReady( () => { const root = createRoot( document.getElementById( 'classifai-settings' ) ); From 2d0cc10a8f7a09b3d82da68fc609ed020904040e Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Thu, 18 Jul 2024 23:14:22 +0530 Subject: [PATCH 054/138] Make User Permissions panel toggle persistence. --- .../components/user-permissions/index.js | 7 ++- src/js/settings/utils/utils.js | 54 ++++++++++++++++++- 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/src/js/settings/components/user-permissions/index.js b/src/js/settings/components/user-permissions/index.js index 745b00b13..10b72ba75 100644 --- a/src/js/settings/components/user-permissions/index.js +++ b/src/js/settings/components/user-permissions/index.js @@ -12,8 +12,10 @@ import { UserSelector } from '../../../components'; import { AllowedRoles } from '../allowed-roles'; import { SettingsRow } from '../settings-row'; import { STORE_NAME } from '../../data/store'; +import { useUserPermissionsPreferences } from '../../utils/utils'; export const UserPermissions = () => { + const { isOpen, setIsOpen } = useUserPermissionsPreferences(); const { setFeatureSettings } = useDispatch( STORE_NAME ); // eslint-disable-next-line camelcase const { users, user_based_opt_out } = useSelect( ( select ) => { @@ -26,7 +28,10 @@ export const UserPermissions = () => { return ( { + setIsOpen( opened ); + } } > diff --git a/src/js/settings/utils/utils.js b/src/js/settings/utils/utils.js index 1f2f8c8ec..30027b8d3 100644 --- a/src/js/settings/utils/utils.js +++ b/src/js/settings/utils/utils.js @@ -1,4 +1,4 @@ -import { useSelect } from '@wordpress/data'; +import { useSelect, useDispatch } from '@wordpress/data'; import { useMemo } from '@wordpress/element'; import { store as coreStore } from '@wordpress/core-data'; @@ -206,3 +206,55 @@ export const useTaxonomies = () => { return { taxonomies }; }; + +/** + * User Permissions Preferences Hook. + * + * Exports a hook that returns the user permissions preferences. + * It uses the `core/preferences` store to manage the user permissions panel state. + * @return {Object} The user permissions preferences. + */ +export const useUserPermissionsPreferences = () => { + let cache; + const { set, setPersistenceLayer } = useDispatch( 'core/preferences' ); + setPersistenceLayer( { + async get() { + if ( cache ) { + return cache; + } + + const preferences = JSON.parse( + window.localStorage.getItem( 'CLASSIFAI_SETTINGS_PREFERENCES' ) + ); + if ( preferences ) { + cache = preferences; + } else { + cache = {}; + } + return cache; + }, + set( preferences ) { + cache = preferences; + window.localStorage.setItem( + 'CLASSIFAI_SETTINGS_PREFERENCES', + JSON.stringify( preferences ) + ); + }, + } ); + + const isOpen = useSelect( ( select ) => { + const { get } = select( 'core/preferences' ); + + const open = get( 'classifai/settings', 'user-permissions-panel-open' ); + if ( open === undefined ) { + return true; + } + return open; + }, [] ); + + const setIsOpen = ( value ) => { + set( 'classifai/settings', 'user-permissions-panel-open', value ); + }; + + return { isOpen, setIsOpen }; +}; From f25cf1c8c4dd1eef2867ec919e589f452e879a91 Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Sat, 3 Aug 2024 22:39:29 +0530 Subject: [PATCH 055/138] Add ClassifAI registration --- includes/Classifai/Admin/Settings.php | 89 +++++++++ .../classifai-registration/index.js | 184 ++++++++++++++++++ .../components/classifai-settings/index.js | 18 ++ 3 files changed, 291 insertions(+) create mode 100644 src/js/settings/components/classifai-registration/index.js diff --git a/includes/Classifai/Admin/Settings.php b/includes/Classifai/Admin/Settings.php index b2cc1a207..6b96c9b71 100644 --- a/includes/Classifai/Admin/Settings.php +++ b/includes/Classifai/Admin/Settings.php @@ -172,6 +172,23 @@ public function register_routes() { ], ] ); + + register_rest_route( + 'classifai/v1', + 'registration', + [ + [ + 'methods' => \WP_REST_Server::READABLE, + 'callback' => [ $this, 'get_registration_settings_callback' ], + 'permission_callback' => [ $this, 'registration_settings_permissions_check' ], + ], + [ + 'methods' => \WP_REST_Server::EDITABLE, + 'callback' => [ $this, 'update_registration_settings_callback' ], + 'permission_callback' => [ $this, 'registration_settings_permissions_check' ], + ], + ] + ); } /** @@ -259,4 +276,76 @@ public function update_settings_callback( $request ) { public function update_settings_permissions_check() { return current_user_can( 'manage_options' ); } + + /** + * Callback for getting the registration settings. + * + * @return \WP_REST_Response + */ + public function get_registration_settings_callback() { + $service_manager = new ServicesManager(); + $settings = $service_manager->get_settings(); + return rest_ensure_response( $settings ); + } + + /** + * Update the registration settings. + * + * @param \WP_REST_Request $request Full data about the request. + * @return \WP_REST_Response|\WP_Error + */ + public function update_registration_settings_callback( $request ) { + // Load settings error functions. + if ( ! function_exists( 'add_settings_error' ) ) { + require_once ABSPATH . 'wp-admin/includes/template.php'; + } + + $service_manager = new ServicesManager(); + $settings = $service_manager->get_settings(); + $new_settings = $service_manager->sanitize_settings( $request->get_json_params() ); + + if ( is_wp_error( $new_settings ) ) { + return $new_settings; + } + + // Update the settings with the new values. + $new_settings = array_merge( $settings, $new_settings ); + update_option( 'classifai_settings', $new_settings ); + + $setting_errors = get_settings_errors(); + $errors = array(); + if ( ! empty( $setting_errors ) ) { + foreach ( $setting_errors as $setting_error ) { + if ( empty( $setting_error['message'] ) ) { + continue; + } + + $errors[] = array( + 'code' => $setting_error['code'], + 'message' => wp_strip_all_tags( $setting_error['message'] ), + ); + } + } + + $response = array( + 'success' => true, + 'settings' => $new_settings, + ); + + if ( ! empty( $errors ) ) { + $response['success'] = false; + $response['errors'] = $errors; + } + + return rest_ensure_response( $response ); + } + + /** + * Check if a given request has access to get/update registration settings. + * + * @return bool|\WP_Error + */ + public function registration_settings_permissions_check() { + return current_user_can( 'manage_options' ); + } } diff --git a/src/js/settings/components/classifai-registration/index.js b/src/js/settings/components/classifai-registration/index.js new file mode 100644 index 000000000..4e3b164cd --- /dev/null +++ b/src/js/settings/components/classifai-registration/index.js @@ -0,0 +1,184 @@ +/** + * External dependencies + */ +import { NavLink } from 'react-router-dom'; +import { + Panel, + PanelBody, + Spinner, + Button, + __experimentalInputControl as InputControl, // eslint-disable-line @wordpress/no-unsafe-wp-apis +} from '@wordpress/components'; +import { Notices } from '../feature-settings/notices'; +import { __ } from '@wordpress/i18n'; +import { SettingsRow } from '../settings-row'; +import apiFetch from '@wordpress/api-fetch'; +import { useState, useEffect } from '@wordpress/element'; +import { useDispatch, useSelect } from '@wordpress/data'; +import { store as noticesStore } from '@wordpress/notices'; + +const ClassifAIRegistrationForm = () => { + const [ settings, setSettings ] = useState( {} ); + const [ isLoaded, setIsLoaded ] = useState( false ); + + // Load the settings. + useEffect( () => { + ( async () => { + const registrationSettings = await apiFetch( { + path: '/classifai/v1/registration', + } ); // TODO: handle error + + setSettings( registrationSettings ); + setIsLoaded( true ); + } )(); + }, [ setSettings, setIsLoaded ] ); + + if ( ! isLoaded ) { + return ; + } + + return ( + <> + + + + + { + setSettings( { ...settings, email: value } ); + } } + /> + + + { + // eslint-disable-next-line @wordpress/i18n-translator-comments + __( + 'Registration is 100% free and provides update notifications and upgrades inside the dashboard.', + 'classifai' + ) + }{ ' ' } + + { __( + 'Register for your key', + 'classifai' + ) } + + + } + > + { + setSettings( { + ...settings, + license_key: value, + } ); + } } + /> + + + +
+ +
+ + ); +}; + +/** + * Save Settings Button component. + * + * @param {Object} props Component props. + * @param {Object} props.settings Settings object. + * @param {Function} props.setSettings Set settings function. + * @return {Object} SaveSettingsButton Component. + */ +export const SaveSettingsButton = ( { settings, setSettings }) => { + const { createErrorNotice, removeNotices } = useDispatch( noticesStore ); + const notices = useSelect( ( select ) => + select( noticesStore ).getNotices() + ); + const [ isSaving, setIsSaving ] = useState( false ); + + /** + * Save settings for a feature. + */ + const saveSettings = () => { + removeNotices( notices.map( ( { id } ) => id ) ); + setIsSaving( true ); + apiFetch( { + path: '/classifai/v1/registration/', + method: 'POST', + data: settings, + } ) + .then( ( res ) => { + if ( res.errors && res.errors.length ) { + res.errors.forEach( ( error ) => + createErrorNotice( error.message ) + ); + setSettings( res.settings ); + setIsSaving( false ); + return; + } + + setSettings( res.settings ); + setIsSaving( false ); + } ) + .catch( ( error ) => { + createErrorNotice( + error.message || + __( + 'An error occurred while saving settings.', + 'classifai' + ) + ); + setIsSaving( false ); + } ); + }; + + return ( + + ); +}; + +export const ClassifAIRegistration = () => { + return ( +
+
+ + { __( 'ClassifAI Registration', 'classifai' ) } + +
+
+ +
+
+ ); +}; diff --git a/src/js/settings/components/classifai-settings/index.js b/src/js/settings/components/classifai-settings/index.js index 6f063d622..9e38136c3 100644 --- a/src/js/settings/components/classifai-settings/index.js +++ b/src/js/settings/components/classifai-settings/index.js @@ -15,6 +15,7 @@ import { */ import { useDispatch } from '@wordpress/data'; import { SlotFillProvider } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; import { useEffect } from '@wordpress/element'; import apiFetch from '@wordpress/api-fetch'; @@ -24,6 +25,7 @@ import apiFetch from '@wordpress/api-fetch'; import { FeatureSettings, Header, ServiceSettings } from '..'; import { STORE_NAME } from '../../data/store'; import { FeatureContext } from '../feature-settings/context'; +import { ClassifAIRegistration } from '../classifai-registration'; const { services, features } = window.classifAISettings; @@ -89,9 +91,21 @@ export const ServiceNavigation = () => { { services[ service ] } ) ) } + + isActive + ? 'active-tab classifai-tabs-item' + : 'classifai-tabs-item' + } + > + { __( 'ClassifAI Registration', 'classifai') } +
); }; + export const ClassifAISettings = () => { const { setSettings, setIsLoaded } = useDispatch( STORE_NAME ); @@ -127,6 +141,10 @@ export const ClassifAISettings = () => { element={ } />
+ } + /> { /* When no routes match, it will redirect to this route path. Note that it should be registered above. */ } Date: Tue, 20 Aug 2024 17:12:50 +0530 Subject: [PATCH 056/138] add fragment between Tooltip and Icon --- .../components/provider-settings/index.js | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/js/settings/components/provider-settings/index.js b/src/js/settings/components/provider-settings/index.js index 5b9d7b7aa..1cfd725d7 100644 --- a/src/js/settings/components/provider-settings/index.js +++ b/src/js/settings/components/provider-settings/index.js @@ -119,14 +119,18 @@ export const ProviderSettings = () => { { ' ' } - setEditProvider( true ) } - /> + {/* The fragment is necessary here. `Tooltip` tries to pass `refs` to `Icon` which isn't + wrapped inside forwardRef(), without which it throws an error. DO NOT REMOVE THE FRAGMENTS. */} + <> + setEditProvider( true ) } + /> + From bbf2e4dff17acb801bae53cf0212c7bf5291b4a4 Mon Sep 17 00:00:00 2001 From: Siddharth Thevaril Date: Tue, 20 Aug 2024 20:25:22 +0530 Subject: [PATCH 057/138] create the onboarding page --- includes/Classifai/Admin/Settings.php | 2 +- .../Admin/templates/onboarding-header.php | 1 + .../components/classifai-onboarding/index.js | 73 +++++++++++++++++++ .../feature-settings/enable-feature.js | 6 +- src/js/settings/components/index.js | 1 + src/js/settings/index.js | 16 +++- 6 files changed, 94 insertions(+), 5 deletions(-) create mode 100644 src/js/settings/components/classifai-onboarding/index.js diff --git a/includes/Classifai/Admin/Settings.php b/includes/Classifai/Admin/Settings.php index b2cc1a207..58676d155 100644 --- a/includes/Classifai/Admin/Settings.php +++ b/includes/Classifai/Admin/Settings.php @@ -53,7 +53,7 @@ public function render_settings_page() { * @param string $hook_suffix The current admin page. */ public function admin_enqueue_scripts( $hook_suffix ) { - if ( 'tools_page_classifai' !== $hook_suffix ) { + if ( ! in_array( $hook_suffix, array( 'tools_page_classifai_setup', 'tools_page_classifai' ), true ) ) { return; } diff --git a/includes/Classifai/Admin/templates/onboarding-header.php b/includes/Classifai/Admin/templates/onboarding-header.php index a3932d061..75247e245 100644 --- a/includes/Classifai/Admin/templates/onboarding-header.php +++ b/includes/Classifai/Admin/templates/onboarding-header.php @@ -28,4 +28,5 @@ ?>
+
diff --git a/src/js/settings/components/classifai-onboarding/index.js b/src/js/settings/components/classifai-onboarding/index.js new file mode 100644 index 000000000..557be189a --- /dev/null +++ b/src/js/settings/components/classifai-onboarding/index.js @@ -0,0 +1,73 @@ +import { useDispatch } from '@wordpress/data'; +import { useEffect } from '@wordpress/element'; +import { ToggleControl, Flex, FlexItem } from '@wordpress/components'; +import apiFetch from '@wordpress/api-fetch'; + +import { STORE_NAME } from '../../data/store'; +import { FeatureContext } from '../feature-settings/context'; +import { EnableToggleControl } from '../feature-settings/enable-feature'; + +export const ClassifAIOnboarding = () => { + const { setSettings, setIsLoaded } = useDispatch( STORE_NAME ); + + // Load the settings. + useEffect( () => { + ( async () => { + const classifAISettings = await apiFetch( { + path: '/classifai/v1/settings', + } ); // TODO: handle error + + setSettings( classifAISettings ); + setIsLoaded( true ); + } )(); + }, [ setSettings, setIsLoaded ] ); + + const { features, services } = classifAISettings; + + return Object.keys( services ).map( service => ( + <> +
+ { services[ service ] } +
+
+
    + { + Object.keys( features[ service ] ).map( featureSlug => ( +
  • + + + { + ( { feature, status, setFeatureSettings } ) => { + return ( + + + { feature.label } + + + + // setFeatureSettings( + // { + // status: value ? '1' : '0', // TODO: Use boolean, currently using string for compatibility. + // }, + // featureSlug + // ) + } + /> + + + ) + } + } + + +
  • + ) ) + } +
+
+ + ) ); + +}; diff --git a/src/js/settings/components/feature-settings/enable-feature.js b/src/js/settings/components/feature-settings/enable-feature.js index 4cb6e46cb..57fd62ae4 100644 --- a/src/js/settings/components/feature-settings/enable-feature.js +++ b/src/js/settings/components/feature-settings/enable-feature.js @@ -18,7 +18,7 @@ import { useFeatureContext } from './context'; * Enable Feature Toggle component. * */ -export const EnableToggleControl = () => { +export const EnableToggleControl = ( { children } ) => { const { featureName } = useFeatureContext(); const { setFeatureSettings } = useDispatch( STORE_NAME ); const status = useSelect( @@ -30,6 +30,10 @@ export const EnableToggleControl = () => { feature?.enable_description || __( 'Enable feature', 'classifai' ) ); + if ( children && 'function' === typeof children ) { + return children( { feature, status, setFeatureSettings } ); + } + return ( { - const root = createRoot( document.getElementById( 'classifai-settings' ) ); + const onboardingEl = document.getElementById( 'classifai-onboarding' ); - root.render( ); + if ( onboardingEl ) { + const onboardingRoot = createRoot( onboardingEl ); + onboardingRoot.render( ); + } + + const settingsEl = document.getElementById( 'classifai-settings' ); + + if ( settingsEl ) { + const settingsRoot = createRoot( settingsEl ); + settingsRoot.render( ); + } } ); From 78fdcff0b16920aaee971a892abeafb7bf19cb0b Mon Sep 17 00:00:00 2001 From: Siddharth Thevaril Date: Wed, 21 Aug 2024 00:28:43 +0530 Subject: [PATCH 058/138] use hook instead --- .../components/classifai-onboarding/index.js | 9 +++----- .../feature-settings/enable-feature.js | 12 +++-------- src/js/settings/data/actions.js | 7 ++++--- src/js/settings/data/hooks.js | 21 +++++++++++++++++++ src/js/settings/data/selectors.js | 6 +++--- 5 files changed, 34 insertions(+), 21 deletions(-) create mode 100644 src/js/settings/data/hooks.js diff --git a/src/js/settings/components/classifai-onboarding/index.js b/src/js/settings/components/classifai-onboarding/index.js index 557be189a..604643a43 100644 --- a/src/js/settings/components/classifai-onboarding/index.js +++ b/src/js/settings/components/classifai-onboarding/index.js @@ -47,12 +47,9 @@ export const ClassifAIOnboarding = () => { - // setFeatureSettings( - // { - // status: value ? '1' : '0', // TODO: Use boolean, currently using string for compatibility. - // }, - // featureSlug - // ) + setFeatureSettings( { + status: value ? '1' : '0', // TODO: Use boolean, currently using string for compatibility. + } ) } /> diff --git a/src/js/settings/components/feature-settings/enable-feature.js b/src/js/settings/components/feature-settings/enable-feature.js index 57fd62ae4..01e6368ce 100644 --- a/src/js/settings/components/feature-settings/enable-feature.js +++ b/src/js/settings/components/feature-settings/enable-feature.js @@ -3,7 +3,6 @@ */ import { __ } from '@wordpress/i18n'; import { ToggleControl } from '@wordpress/components'; -import { useDispatch, useSelect } from '@wordpress/data'; import { decodeEntities } from '@wordpress/html-entities'; /** @@ -11,20 +10,15 @@ import { decodeEntities } from '@wordpress/html-entities'; */ import { getFeature } from '../../utils/utils'; import { SettingsRow } from '../settings-row'; -import { STORE_NAME } from '../../data/store'; -import { useFeatureContext } from './context'; +import { useFeatureSettings } from '../../data/hooks'; /** * Enable Feature Toggle component. * */ export const EnableToggleControl = ( { children } ) => { - const { featureName } = useFeatureContext(); - const { setFeatureSettings } = useDispatch( STORE_NAME ); - const status = useSelect( - ( select ) => select( STORE_NAME ).getFeatureSettings( 'status' ) || '0' - ); - + const { featureName, getFeatureSettings, setFeatureSettings } = useFeatureSettings(); + const status = getFeatureSettings( 'status' ) || '0'; const feature = getFeature( featureName ); const enableDescription = decodeEntities( feature?.enable_description || __( 'Enable feature', 'classifai' ) diff --git a/src/js/settings/data/actions.js b/src/js/settings/data/actions.js index 1839a0f97..d5a493c26 100644 --- a/src/js/settings/data/actions.js +++ b/src/js/settings/data/actions.js @@ -1,8 +1,9 @@ export const setFeatureSettings = - ( settings ) => + ( settings, feature = null ) => ( { select, dispatch } ) => { - const currentFeature = select.getCurrentFeature(); - const featureSettings = select.getFeatureSettings(); + const currentFeature = feature || select.getCurrentFeature(); + const featureSettings = select.getFeatureSettings( null, feature ); + dispatch( { type: 'SET_FEATURE_SETTINGS', feature: currentFeature, diff --git a/src/js/settings/data/hooks.js b/src/js/settings/data/hooks.js new file mode 100644 index 000000000..2adcb6c9b --- /dev/null +++ b/src/js/settings/data/hooks.js @@ -0,0 +1,21 @@ +import { useContext } from '@wordpress/element'; +import { useDispatch, useSelect } from '@wordpress/data'; +import { STORE_NAME } from '../data/store'; + +import { FeatureContext } from '../components/feature-settings/context'; + +export const useFeatureSettings = () => { + let { featureName } = useContext( FeatureContext ); + + if ( ! featureName ) { + featureName = null; + } + + const { setFeatureSettings } = useDispatch( STORE_NAME ); + + return { + featureName, + getFeatureSettings: ( key ) => useSelect( select => select( STORE_NAME ).getFeatureSettings( key, featureName ) ), + setFeatureSettings: ( settings ) => setFeatureSettings( settings, featureName ), + } +}; diff --git a/src/js/settings/data/selectors.js b/src/js/settings/data/selectors.js index c10547335..b31e67de2 100644 --- a/src/js/settings/data/selectors.js +++ b/src/js/settings/data/selectors.js @@ -5,11 +5,11 @@ export const getSettings = ( state, feature ) => { return state.settings; }; -export const getFeatureSettings = ( state, key ) => { +export const getFeatureSettings = ( state, key, feature ) => { if ( key ) { - return state.settings?.[ state.currentFeature ]?.[ key ]; + return state.settings?.[ feature || state.currentFeature ]?.[ key ]; } - return state.settings?.[ state.currentFeature ] || {}; + return state.settings?.[ feature || state.currentFeature ] || {}; }; export const getCurrentService = ( state ) => state.currentService; From 4638e83f679a8f2bed22b615a88d7bb0822ec82e Mon Sep 17 00:00:00 2001 From: Siddharth Thevaril Date: Fri, 23 Aug 2024 22:05:44 +0530 Subject: [PATCH 059/138] add onboarding page --- includes/Classifai/Admin/Settings.php | 59 +++++++------ includes/Classifai/Plugin.php | 1 + src/js/settings/classifai-admin.js | 15 ++++ .../settings/components/admin-header/index.js | 11 +++ .../configuration-status.js | 80 +++++++++++++++++ .../configure-features.js | 87 +++++++++++++++++++ .../classifai-onboarding/enable-features.js | 78 +++++++++++++++++ .../components/classifai-onboarding/index.js | 61 +++---------- .../components/classifai-onboarding/utils.js | 0 .../components/feature-settings/index.js | 6 +- .../feature-settings/save-settings-button.js | 40 +++++++-- src/js/settings/components/header/index.js | 13 ++- src/js/settings/components/index.js | 2 + src/js/settings/components/layout/index.js | 7 ++ src/js/settings/data/actions.js | 10 +++ src/js/settings/data/hooks.js | 16 +++- src/js/settings/data/reducer.js | 14 +++ src/js/settings/data/selectors.js | 4 + src/js/settings/index.js | 17 ++-- src/js/settings/utils/utils.js | 27 ++++++ src/scss/admin.scss | 7 -- src/scss/settings.scss | 43 +++++++++ 22 files changed, 491 insertions(+), 107 deletions(-) create mode 100644 src/js/settings/classifai-admin.js create mode 100644 src/js/settings/components/admin-header/index.js create mode 100644 src/js/settings/components/classifai-onboarding/configuration-status.js create mode 100644 src/js/settings/components/classifai-onboarding/configure-features.js create mode 100644 src/js/settings/components/classifai-onboarding/enable-features.js create mode 100644 src/js/settings/components/classifai-onboarding/utils.js create mode 100644 src/js/settings/components/layout/index.js diff --git a/includes/Classifai/Admin/Settings.php b/includes/Classifai/Admin/Settings.php index 58676d155..58089f08b 100644 --- a/includes/Classifai/Admin/Settings.php +++ b/includes/Classifai/Admin/Settings.php @@ -53,7 +53,7 @@ public function render_settings_page() { * @param string $hook_suffix The current admin page. */ public function admin_enqueue_scripts( $hook_suffix ) { - if ( ! in_array( $hook_suffix, array( 'tools_page_classifai_setup', 'tools_page_classifai' ), true ) ) { + if ( ! in_array( $hook_suffix, array( 'admin_page_classifai_setup', 'tools_page_classifai' ), true ) ) { return; } @@ -201,43 +201,46 @@ public function get_settings_permissions_check() { */ public function update_settings_callback( $request ) { $settings = $request->get_json_params(); + $features = $this->get_features( true ); - $feature_key = key( $settings ); - $features = $this->get_features( true ); - $feature = $features[ $feature_key ]; + foreach ( $settings as $feature_key => $feature_setting ) { + $feature = $features[ $feature_key ]; - if ( ! $feature ) { - return new \WP_Error( 'invalid_feature', __( 'Invalid feature.', 'classifai' ), [ 'status' => 400 ] ); - } + if ( ! $feature ) { + return new \WP_Error( 'invalid_feature', __( 'Invalid feature.', 'classifai' ), [ 'status' => 400 ] ); + } - // Load settings error functions. - if ( ! function_exists( 'add_settings_error' ) ) { - require_once ABSPATH . 'wp-admin/includes/template.php'; - } + // Load settings error functions. + if ( ! function_exists( 'add_settings_error' ) ) { + require_once ABSPATH . 'wp-admin/includes/template.php'; + } - $new_settings = $feature->sanitize_settings( $settings[ $feature_key ] ); - if ( is_wp_error( $new_settings ) ) { - return $new_settings; - } + $new_settings = $feature->sanitize_settings( $settings[ $feature_key ] ); - // Update settings. - $feature->update_settings( $new_settings ); + if ( is_wp_error( $new_settings ) ) { + continue; + } - $setting_errors = get_settings_errors(); - $errors = array(); - if ( ! empty( $setting_errors ) ) { - foreach ( $setting_errors as $setting_error ) { - if ( empty( $setting_error['message'] ) ) { - continue; + // Update settings. + $feature->update_settings( $new_settings ); + + $setting_errors = get_settings_errors(); + $errors = array(); + if ( ! empty( $setting_errors ) ) { + foreach ( $setting_errors as $setting_error ) { + if ( empty( $setting_error['message'] ) ) { + continue; + } + + $errors[] = array( + 'code' => $setting_error['code'], + 'message' => wp_strip_all_tags( $setting_error['message'] ), + ); } - - $errors[] = array( - 'code' => $setting_error['code'], - 'message' => wp_strip_all_tags( $setting_error['message'] ), - ); } } + $response = array( 'success' => true, 'settings' => $this->get_settings(), diff --git a/includes/Classifai/Plugin.php b/includes/Classifai/Plugin.php index 3e1e37110..7b0ec6f74 100644 --- a/includes/Classifai/Plugin.php +++ b/includes/Classifai/Plugin.php @@ -192,6 +192,7 @@ public function enqueue_admin_assets() { 'ajax_nonce' => wp_create_nonce( 'classifai' ), 'opt_out_enabled_features' => array_keys( $allowed_features ), 'profile_url' => esc_url( get_edit_profile_url( get_current_user_id() ) . '#classifai-profile-features-section' ), + 'plugin_url' => CLASSIFAI_PLUGIN_URL, ]; wp_localize_script( diff --git a/src/js/settings/classifai-admin.js b/src/js/settings/classifai-admin.js new file mode 100644 index 000000000..a1ba5adb1 --- /dev/null +++ b/src/js/settings/classifai-admin.js @@ -0,0 +1,15 @@ +import { useSelect } from '@wordpress/data'; + +import { STORE_NAME } from './data/store'; +import { ClassifAISettings, ClassifAIOnboarding } from './components'; + +export const ClassifAIAdmin = () => { + const settingsScreen = useSelect( select => select( STORE_NAME ).getSettingsScreen() ); + + return ( + <> + { 'settings' === settingsScreen && } + { 'onboarding' === settingsScreen && } + + ); +}; diff --git a/src/js/settings/components/admin-header/index.js b/src/js/settings/components/admin-header/index.js new file mode 100644 index 000000000..a5ef6178c --- /dev/null +++ b/src/js/settings/components/admin-header/index.js @@ -0,0 +1,11 @@ +export const AdminHeader = () => { + return ( +
+
Logo
+
+
Switch mode
+
Help
+
+
+ ); +}; diff --git a/src/js/settings/components/classifai-onboarding/configuration-status.js b/src/js/settings/components/classifai-onboarding/configuration-status.js new file mode 100644 index 000000000..285056b8a --- /dev/null +++ b/src/js/settings/components/classifai-onboarding/configuration-status.js @@ -0,0 +1,80 @@ +import { Icon, BaseControl } from '@wordpress/components'; +import { useSelect } from '@wordpress/data'; +import { __ } from '@wordpress/i18n'; + +import { STORE_NAME } from '../../data/store'; +import { isFeatureActive, getFeature, getEnabledFeaturesSlugs } from '../../utils/utils'; + +export const ConfigurationStatus = ( { setStep } ) => { + const { features: __settings, services } = classifAISettings; + const settingsState = useSelect( select => select( STORE_NAME ).getSettings() ); + const enabledFeatureSlugs = getEnabledFeaturesSlugs(); + const settings = Object.keys( __settings ).reduce( ( a, c ) => { + const res = Object.keys( __settings[ c ] ).reduce( ( __a, __c ) => { + return { + ...__a, + [ __c ]: settingsState[ __c ] + } + }, {} ); + + return { + ...a, + [ c ]: res + } + }, {} ); + + return ( + <> +

{ __( 'Welcome to ClassifAI', 'classifai' ) }

+
+
+ +
+
+ { + Object.keys( services ).map( ( service ) => { + const enabledFeatures = Object + .keys( settings[ service ] ) + .map( featureSlug => { + const feature = getFeature( featureSlug ); + const label = feature.label; + + if ( ! enabledFeatureSlugs.includes( featureSlug ) ) { + return null; + } + + return ( + + { isFeatureActive( settings[ service ][ featureSlug ] ) + ? + : + } + { label } + + ) + } ) + .filter( Boolean ); + + if ( ! enabledFeatures.length ) { + return []; + } + + return ( + +
+

+ { services[ service ] } +

+
+ { enabledFeatures } +
+
+
+ ) + } ) + } +
+
+ + ); +}; diff --git a/src/js/settings/components/classifai-onboarding/configure-features.js b/src/js/settings/components/classifai-onboarding/configure-features.js new file mode 100644 index 000000000..67e199bf2 --- /dev/null +++ b/src/js/settings/components/classifai-onboarding/configure-features.js @@ -0,0 +1,87 @@ +import { useState, useEffect } from '@wordpress/element'; +import { useSelect, useDispatch } from '@wordpress/data'; +import { Fill, SlotFillProvider, Button } from '@wordpress/components'; +import { store as noticesStore } from '@wordpress/notices'; +import { __ } from '@wordpress/i18n'; + +import { FeatureSettings } from '..'; +import { FeatureContext } from '../feature-settings/context'; +import { useFeatureSettings } from '../../data/hooks'; +import { getFeature } from '../../utils/utils'; +import { STORE_NAME } from '../../data/store'; +import { getEnabledFeaturesSlugs } from '../../utils/utils'; + +export const ConfigureFeatures = ( { step, setStep } ) => { + const { isSaving } = useFeatureSettings(); + const enabledFeatures = getEnabledFeaturesSlugs(); + const [ currentFeature, setCurrentFeature ] = useState( enabledFeatures[0] ); + const errors = useSelect( select => select( STORE_NAME ).getSaveErrors() ); + const { setSaveErrors } = useDispatch( STORE_NAME ); + const { removeNotices } = useDispatch( noticesStore ); + let featureIndex = enabledFeatures.findIndex( ef => ef === currentFeature ); + const notices = useSelect( ( select ) => + select( noticesStore ).getNotices() + ); + + useEffect( () => { + setSaveErrors( [] ); + }, [] ); + + useEffect( () => { + if ( false !== isSaving ) { + return; + } + + if ( featureIndex + 1 !== enabledFeatures.length && ! errors.length ) { + setCurrentFeature( enabledFeatures[ ++featureIndex ] ); + } else if ( featureIndex + 1 === enabledFeatures.length ) { + setStep( 'configuration_status' ); + } + }, [ isSaving ] ); + + return ( + <> +

{ __( 'Set up AI Providers', 'classifai' ) }

+
+
+ { + enabledFeatures.map( feature => ( +
{ + removeNotices( notices.map( ( { id } ) => id ) ); + setCurrentFeature( feature ); + } } + className={ `classifai-tabs-item ${ feature === currentFeature && 'active-tab' }` } + > + { getFeature( feature ).label } +
+ ) ) + } +
+
+ + + + + + + + +
+
+ + ); +}; diff --git a/src/js/settings/components/classifai-onboarding/enable-features.js b/src/js/settings/components/classifai-onboarding/enable-features.js new file mode 100644 index 000000000..f88b5929d --- /dev/null +++ b/src/js/settings/components/classifai-onboarding/enable-features.js @@ -0,0 +1,78 @@ +import { ToggleControl, Flex, FlexItem, BaseControl } from '@wordpress/components'; +import { useEffect } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; + +import { FeatureContext } from '../feature-settings/context'; +import { EnableToggleControl } from '../feature-settings/enable-feature'; +import { SaveSettingsButton } from '../../components/feature-settings/save-settings-button'; +import { useFeatureSettings } from '../../data/hooks'; + +export const EnableFeatures = ( { step, setStep } ) => { + const { features, services } = classifAISettings; + const { isSaving } = useFeatureSettings(); + + useEffect( () => { + if ( 'enable_features' === step && false === isSaving ) { + setStep( 'configure_features' ); + } + }, [ isSaving ] ); + + const featureToggles = Object.keys( services ).map( ( service, serviceIndex ) => ( + +
+

+ { services[ service ] } +

+
+ { + Object.keys( features[ service ] ).map( featureSlug => ( + + + + { + ( { feature, status, setFeatureSettings } ) => { + return ( + + + { feature.label } + + + + setFeatureSettings( { + status: value ? '1' : '0', // TODO: Use boolean, currently using string for compatibility. + } ) + } + /> + + + ) + } + } + + + + ) ) + } +
+
+ { Object.keys( services ).length !== serviceIndex + 1 &&
} +
+ ) ); + + return ( + <> +

{ __( 'Set up ClassifAI to meet your needs', 'classifai' ) }

+
+
+ +
+
+ { featureToggles } + +
+
+ + ) +}; diff --git a/src/js/settings/components/classifai-onboarding/index.js b/src/js/settings/components/classifai-onboarding/index.js index 604643a43..e990595ef 100644 --- a/src/js/settings/components/classifai-onboarding/index.js +++ b/src/js/settings/components/classifai-onboarding/index.js @@ -1,14 +1,16 @@ import { useDispatch } from '@wordpress/data'; -import { useEffect } from '@wordpress/element'; -import { ToggleControl, Flex, FlexItem } from '@wordpress/components'; +import { useEffect, useState } from '@wordpress/element'; import apiFetch from '@wordpress/api-fetch'; import { STORE_NAME } from '../../data/store'; -import { FeatureContext } from '../feature-settings/context'; -import { EnableToggleControl } from '../feature-settings/enable-feature'; +import { EnableFeatures } from './enable-features'; +import { ConfigureFeatures } from './configure-features'; +import { ConfigurationStatus } from './configuration-status'; +import { Header, Layout } from '../../components'; export const ClassifAIOnboarding = () => { const { setSettings, setIsLoaded } = useDispatch( STORE_NAME ); + const [ step, setStep ] = useState( 'enable_features' ); // Load the settings. useEffect( () => { @@ -22,49 +24,14 @@ export const ClassifAIOnboarding = () => { } )(); }, [ setSettings, setIsLoaded ] ); - const { features, services } = classifAISettings; - - return Object.keys( services ).map( service => ( + return ( <> -
- { services[ service ] } -
-
-
    - { - Object.keys( features[ service ] ).map( featureSlug => ( -
  • - - - { - ( { feature, status, setFeatureSettings } ) => { - return ( - - - { feature.label } - - - - setFeatureSettings( { - status: value ? '1' : '0', // TODO: Use boolean, currently using string for compatibility. - } ) - } - /> - - - ) - } - } - - -
  • - ) ) - } -
-
+
+ + { 'enable_features' === step && } + { 'configure_features' === step && } + { 'configuration_status' === step && } + - ) ); - + ) }; diff --git a/src/js/settings/components/classifai-onboarding/utils.js b/src/js/settings/components/classifai-onboarding/utils.js new file mode 100644 index 000000000..e69de29bb diff --git a/src/js/settings/components/feature-settings/index.js b/src/js/settings/components/feature-settings/index.js index 774d033c9..015f8bfcf 100644 --- a/src/js/settings/components/feature-settings/index.js +++ b/src/js/settings/components/feature-settings/index.js @@ -14,7 +14,7 @@ import { UserPermissions } from '../user-permissions'; import { STORE_NAME } from '../../data/store'; import { ProviderSettings } from '../provider-settings'; import { EnableToggleControl } from './enable-feature'; -import { SaveSettingsButton } from './save-settings-button'; +import { SaveSettingsButton, SaveButtonSlot } from './save-settings-button'; import { Notices } from './notices'; import { useFeatureContext } from './context'; import { FeatureAdditionalSettings } from '../feature-additional-settings'; @@ -59,7 +59,9 @@ export const FeatureSettings = () => {
- + + +
); diff --git a/src/js/settings/components/feature-settings/save-settings-button.js b/src/js/settings/components/feature-settings/save-settings-button.js index fc54d5ced..10c50189b 100644 --- a/src/js/settings/components/feature-settings/save-settings-button.js +++ b/src/js/settings/components/feature-settings/save-settings-button.js @@ -2,7 +2,7 @@ * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { Button } from '@wordpress/components'; +import { Button, Slot } from '@wordpress/components'; import { useSelect, useDispatch } from '@wordpress/data'; import { store as noticesStore } from '@wordpress/notices'; import apiFetch from '@wordpress/api-fetch'; @@ -11,18 +11,18 @@ import apiFetch from '@wordpress/api-fetch'; * Internal dependencies */ import { STORE_NAME } from '../../data/store'; -import { useFeatureContext } from './context'; +import { useFeatureSettings } from '../../data/hooks'; /** * Save Settings Button component. */ -export const SaveSettingsButton = () => { - const { featureName } = useFeatureContext(); +export const SaveSettingsButton = ( { disableErrorReporting = false } ) => { + const { featureName } = useFeatureSettings(); const { createErrorNotice, removeNotices } = useDispatch( noticesStore ); const notices = useSelect( ( select ) => select( noticesStore ).getNotices() ); - const { setIsSaving, setSettings } = useDispatch( STORE_NAME ); + const { setIsSaving, setSettings, setSaveErrors } = useDispatch( STORE_NAME ); const isSaving = useSelect( ( select ) => select( STORE_NAME ).getIsSaving() ); @@ -36,19 +36,27 @@ export const SaveSettingsButton = () => { const saveSettings = () => { removeNotices( notices.map( ( { id } ) => id ) ); setIsSaving( true ); + + const data = featureName ? { [ featureName ]: settings[ featureName ] } : settings; + apiFetch( { path: '/classifai/v1/settings/', method: 'POST', - data: { [ featureName ]: settings[ featureName ] }, + data, } ) .then( ( res ) => { if ( res.errors && res.errors.length ) { - res.errors.forEach( ( error ) => - createErrorNotice( error.message ) - ); + if ( ! disableErrorReporting ) { + res.errors.forEach( ( error ) => { + createErrorNotice( error.message ) + } ); + } setSettings( res.settings ); setIsSaving( false ); + setSaveErrors( res.errors ); return; + } else { + setSaveErrors( [] ); } setSettings( res.settings ); @@ -79,3 +87,17 @@ export const SaveSettingsButton = () => { ); }; + +export const SaveButtonSlot = ( { children } ) => { + return ( + <> + + { ( fills ) => <>{ fills } } + + { children } + + { ( fills ) => <>{ fills } } + + + ) +}; diff --git a/src/js/settings/components/header/index.js b/src/js/settings/components/header/index.js index b7c3932a8..cc567267c 100644 --- a/src/js/settings/components/header/index.js +++ b/src/js/settings/components/header/index.js @@ -8,17 +8,22 @@ import { VisuallyHidden, Button, } from '@wordpress/components'; +import { useSelect, useDispatch } from '@wordpress/data'; import { external, help, cog, tool } from '@wordpress/icons'; import { __ } from '@wordpress/i18n'; /** * Internal dependencies */ +import { STORE_NAME } from '../..//data/store'; import { ClassifAILogo } from '../../utils/icons'; export const Header = ( props ) => { const { isSetupPage } = props; + const settingsScreen = useSelect( select => select( STORE_NAME ).getSettingsScreen() ); + const { setSettingsScreen } = useDispatch( STORE_NAME ); + return (
@@ -33,8 +38,14 @@ export const Header = ( props ) => { ) } { ! isSetupPage && ( diff --git a/src/js/settings/components/index.js b/src/js/settings/components/index.js index a6aa466e8..407e17689 100644 --- a/src/js/settings/components/index.js +++ b/src/js/settings/components/index.js @@ -1,4 +1,6 @@ export * from './header'; +export * from './layout'; +export * from './admin-header'; export * from './classifai-onboarding'; export * from './classifai-settings'; export * from './service-settings'; diff --git a/src/js/settings/components/layout/index.js b/src/js/settings/components/layout/index.js new file mode 100644 index 000000000..1c63d1cab --- /dev/null +++ b/src/js/settings/components/layout/index.js @@ -0,0 +1,7 @@ +export const Layout = ( { children } ) => { + return ( +
+ { children } +
+ ) +}; \ No newline at end of file diff --git a/src/js/settings/data/actions.js b/src/js/settings/data/actions.js index d5a493c26..c7d471eb7 100644 --- a/src/js/settings/data/actions.js +++ b/src/js/settings/data/actions.js @@ -56,3 +56,13 @@ export const setIsSaving = ( isSaving ) => ( { type: 'SET_IS_SAVING', payload: isSaving, } ); + +export const setSettingsScreen = ( screen ) => ( { + type: 'SET_SETTINGS_SCREEN', + payload: screen, +} ); + +export const setSaveErrors = ( data ) => ( { + type: 'SET_SAVE_ERRORS', + payload: data, +} ); diff --git a/src/js/settings/data/hooks.js b/src/js/settings/data/hooks.js index 2adcb6c9b..ecf0ce428 100644 --- a/src/js/settings/data/hooks.js +++ b/src/js/settings/data/hooks.js @@ -1,10 +1,11 @@ -import { useContext } from '@wordpress/element'; +import { useContext, useState, useEffect } from '@wordpress/element'; import { useDispatch, useSelect } from '@wordpress/data'; import { STORE_NAME } from '../data/store'; import { FeatureContext } from '../components/feature-settings/context'; export const useFeatureSettings = () => { + const [ isSaving, setIsSaving ] = useState( null ); let { featureName } = useContext( FeatureContext ); if ( ! featureName ) { @@ -12,10 +13,23 @@ export const useFeatureSettings = () => { } const { setFeatureSettings } = useDispatch( STORE_NAME ); + const __isSaving = useSelect( select => select( STORE_NAME ).getIsSaving() ); + + useEffect( () => { + if ( __isSaving ) { + setIsSaving( __isSaving ); + } else if ( false === __isSaving && null !== isSaving ) { + setIsSaving( false ); + setTimeout( () => setIsSaving( null ), 0 ); + } + }, [ __isSaving ] ); return { + isSaving, featureName, getFeatureSettings: ( key ) => useSelect( select => select( STORE_NAME ).getFeatureSettings( key, featureName ) ), + getSettings: ( key, featureName ) => useSelect( select => select( STORE_NAME ).getSettings( key, featureName ) ), setFeatureSettings: ( settings ) => setFeatureSettings( settings, featureName ), + getIsSaving: () => useSelect( select => select( STORE_NAME ).getIsSaving() ), } }; diff --git a/src/js/settings/data/reducer.js b/src/js/settings/data/reducer.js index eb3555f8b..e93945539 100644 --- a/src/js/settings/data/reducer.js +++ b/src/js/settings/data/reducer.js @@ -9,6 +9,8 @@ const DEFAULT_STATE = { settings: classifAISettings.settings || {}, isLoaded: false, isSaving: false, + settingsScreen: 'settings', + saveErrors: [], }; /** @@ -60,6 +62,18 @@ export const reducer = ( state = DEFAULT_STATE, action ) => { isSaving: action.payload, }; + case 'SET_SETTINGS_SCREEN': + return { + ...state, + settingsScreen: action.payload + } + + case 'SET_SAVE_ERRORS': + return { + ...state, + saveErrors: action.payload + } + default: return state; } diff --git a/src/js/settings/data/selectors.js b/src/js/settings/data/selectors.js index b31e67de2..681f81068 100644 --- a/src/js/settings/data/selectors.js +++ b/src/js/settings/data/selectors.js @@ -19,3 +19,7 @@ export const getCurrentFeature = ( state ) => state.currentFeature; export const getIsLoaded = ( state ) => state.isLoaded; export const getIsSaving = ( state ) => state.isSaving; + +export const getSettingsScreen = ( state ) => state.settingsScreen; + +export const getSaveErrors = ( state ) => state.saveErrors; diff --git a/src/js/settings/index.js b/src/js/settings/index.js index e22e90b19..f565cc2e9 100644 --- a/src/js/settings/index.js +++ b/src/js/settings/index.js @@ -7,22 +7,15 @@ import { createRoot } from '@wordpress/element'; /** * Internal dependencies */ -import { ClassifAISettings, ClassifAIOnboarding } from './components'; +import { ClassifAIAdmin } from './classifai-admin'; import './data/store'; import '../../scss/settings.scss'; domReady( () => { - const onboardingEl = document.getElementById( 'classifai-onboarding' ); + const adminEl = document.getElementById( 'classifai-settings' ); - if ( onboardingEl ) { - const onboardingRoot = createRoot( onboardingEl ); - onboardingRoot.render( ); - } - - const settingsEl = document.getElementById( 'classifai-settings' ); - - if ( settingsEl ) { - const settingsRoot = createRoot( settingsEl ); - settingsRoot.render( ); + if ( adminEl ) { + const settingsRoot = createRoot( adminEl ); + settingsRoot.render( ); } } ); diff --git a/src/js/settings/utils/utils.js b/src/js/settings/utils/utils.js index 30027b8d3..3fe58bd9e 100644 --- a/src/js/settings/utils/utils.js +++ b/src/js/settings/utils/utils.js @@ -258,3 +258,30 @@ export const useUserPermissionsPreferences = () => { return { isOpen, setIsOpen }; }; + +/** + * Returns array of feature slugs for features that are enabled, and not + * necessarily authenticated. + * + * @returns {Array} Array of feature slugs, for example + * ['feature_excerpt_generation', 'feature_content_resizing'] + */ +export function getEnabledFeaturesSlugs() { + const { settings: features } = classifAISettings; + + return Object.keys( features ).filter( feature => '1' === features[ feature ].status ) +}; + +/** + * Returns true if a feature is enabled and authenticated. + * + * @param {Object} feature The feature object. + * @returns {Boolean} True if the feature is enabled and authenticated, false otherwise. + */ +export const isFeatureActive = ( feature ) => { + const isEnabled = '1' === feature.status; + const provider = feature?.provider; + const authenticated = feature[ provider ].authenticated; + + return isEnabled && authenticated; +}; diff --git a/src/scss/admin.scss b/src/scss/admin.scss index 62ef171e2..b9ad1e95f 100644 --- a/src/scss/admin.scss +++ b/src/scss/admin.scss @@ -576,13 +576,6 @@ input.classifai-button { margin-top: 20px; } - h2.classifai-setup-title { - font-size: 22px; - padding: 0px; - font-weight: 400; - margin: 0px; - } - .classifai-step1-content, .classifai-step4-content { max-width: 400px; diff --git a/src/scss/settings.scss b/src/scss/settings.scss index d5e8aba90..a440bf0ab 100644 --- a/src/scss/settings.scss +++ b/src/scss/settings.scss @@ -377,3 +377,46 @@ } } } + +.classifai-setup-title { + font-size: 22px; + padding: 0px; + font-weight: 400; + text-align: center; + margin: 3rem 0; +} + +.classifai-onboarding { + &__configure { + display: flex; + gap: 2rem; + padding-top: 0 !important; + + .classifai-settings-footer { + display: flex; + + & > button { + margin-top: 1rem; + flex-grow: 1; + flex-basis: 0; + } + } + + &--status, + &--enable-features { + & > div { + flex-grow: 1; + flex-basis: 0; + max-width: 50%; + + img { + max-width: 100%; + } + } + } + } + + &__welcome-to-classifai { + text-align: center; + } +} \ No newline at end of file From 72ae5e44008b3d8f2350ea6b66c88e4cc830239e Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Mon, 26 Aug 2024 08:39:38 +0530 Subject: [PATCH 060/138] Display notice at feature level. --- .../classifai-registration/index.js | 13 ++++++--- .../components/feature-settings/index.js | 2 +- .../components/feature-settings/notices.js | 10 +++++-- .../feature-settings/save-settings-button.js | 29 ++++++++++--------- 4 files changed, 33 insertions(+), 21 deletions(-) diff --git a/src/js/settings/components/classifai-registration/index.js b/src/js/settings/components/classifai-registration/index.js index 4e3b164cd..78a19e950 100644 --- a/src/js/settings/components/classifai-registration/index.js +++ b/src/js/settings/components/classifai-registration/index.js @@ -39,7 +39,7 @@ const ClassifAIRegistrationForm = () => { return ( <> - + { * @param {Function} props.setSettings Set settings function. * @return {Object} SaveSettingsButton Component. */ -export const SaveSettingsButton = ( { settings, setSettings }) => { +export const SaveSettingsButton = ( { settings, setSettings } ) => { const { createErrorNotice, removeNotices } = useDispatch( noticesStore ); const notices = useSelect( ( select ) => select( noticesStore ).getNotices() @@ -132,7 +132,9 @@ export const SaveSettingsButton = ( { settings, setSettings }) => { .then( ( res ) => { if ( res.errors && res.errors.length ) { res.errors.forEach( ( error ) => - createErrorNotice( error.message ) + createErrorNotice( error.message, { + id: 'error-registration', + } ) ); setSettings( res.settings ); setIsSaving( false ); @@ -148,7 +150,10 @@ export const SaveSettingsButton = ( { settings, setSettings }) => { __( 'An error occurred while saving settings.', 'classifai' - ) + ), + { + id: 'error-registration', + } ); setIsSaving( false ); } ); diff --git a/src/js/settings/components/feature-settings/index.js b/src/js/settings/components/feature-settings/index.js index 015f8bfcf..aa799476e 100644 --- a/src/js/settings/components/feature-settings/index.js +++ b/src/js/settings/components/feature-settings/index.js @@ -43,7 +43,7 @@ export const FeatureSettings = () => { return ( <> - + { +export const Notices = ( { feature } ) => { const { removeNotice } = useDispatch( noticesStore ); const notices = useSelect( ( select ) => select( noticesStore ).getNotices() ); - if ( notices.length === 0 ) { + const featureNotices = notices.filter( + ( notice ) => notice.id === `error-${ feature }` + ); + + if ( featureNotices.length === 0 ) { return null; } - return ; + return ; }; diff --git a/src/js/settings/components/feature-settings/save-settings-button.js b/src/js/settings/components/feature-settings/save-settings-button.js index 10c50189b..64df08e7a 100644 --- a/src/js/settings/components/feature-settings/save-settings-button.js +++ b/src/js/settings/components/feature-settings/save-settings-button.js @@ -22,7 +22,8 @@ export const SaveSettingsButton = ( { disableErrorReporting = false } ) => { const notices = useSelect( ( select ) => select( noticesStore ).getNotices() ); - const { setIsSaving, setSettings, setSaveErrors } = useDispatch( STORE_NAME ); + const { setIsSaving, setSettings, setSaveErrors } = + useDispatch( STORE_NAME ); const isSaving = useSelect( ( select ) => select( STORE_NAME ).getIsSaving() ); @@ -37,7 +38,9 @@ export const SaveSettingsButton = ( { disableErrorReporting = false } ) => { removeNotices( notices.map( ( { id } ) => id ) ); setIsSaving( true ); - const data = featureName ? { [ featureName ]: settings[ featureName ] } : settings; + const data = featureName + ? { [ featureName ]: settings[ featureName ] } + : settings; apiFetch( { path: '/classifai/v1/settings/', @@ -48,16 +51,17 @@ export const SaveSettingsButton = ( { disableErrorReporting = false } ) => { if ( res.errors && res.errors.length ) { if ( ! disableErrorReporting ) { res.errors.forEach( ( error ) => { - createErrorNotice( error.message ) + createErrorNotice( error.message, { + id: `error-${ featureName }`, + } ); } ); } setSettings( res.settings ); setIsSaving( false ); setSaveErrors( res.errors ); return; - } else { - setSaveErrors( [] ); } + setSaveErrors( [] ); setSettings( res.settings ); setIsSaving( false ); @@ -68,7 +72,10 @@ export const SaveSettingsButton = ( { disableErrorReporting = false } ) => { __( 'An error occurred while saving settings.', 'classifai' - ) + ), + { + id: `error-${ featureName }`, + } ); setIsSaving( false ); } ); @@ -91,13 +98,9 @@ export const SaveSettingsButton = ( { disableErrorReporting = false } ) => { export const SaveButtonSlot = ( { children } ) => { return ( <> - - { ( fills ) => <>{ fills } } - + { ( fills ) => <>{ fills } } { children } - - { ( fills ) => <>{ fills } } - + { ( fills ) => <>{ fills } } - ) + ); }; From 9afdbc969306ff6837d6b380e9cee9080708214a Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Mon, 26 Aug 2024 10:27:43 +0530 Subject: [PATCH 061/138] Add personalizer deprecation notice. --- .../components/feature-settings/index.js | 37 ++++++++++++++++++- src/scss/settings.scss | 6 ++- 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/src/js/settings/components/feature-settings/index.js b/src/js/settings/components/feature-settings/index.js index aa799476e..b72f3c58d 100644 --- a/src/js/settings/components/feature-settings/index.js +++ b/src/js/settings/components/feature-settings/index.js @@ -2,7 +2,7 @@ * WordPress dependencies */ import { __, sprintf } from '@wordpress/i18n'; -import { Panel, PanelBody, Spinner } from '@wordpress/components'; +import { Panel, PanelBody, Spinner, Notice } from '@wordpress/components'; import { useDispatch, useSelect } from '@wordpress/data'; import { useEffect } from '@wordpress/element'; @@ -19,6 +19,38 @@ import { Notices } from './notices'; import { useFeatureContext } from './context'; import { FeatureAdditionalSettings } from '../feature-additional-settings'; +const PersonalizerDeprecationNotice = () => ( + +

+ + { __( 'As of September 2023', 'classifai' ) } + + { ', ' } + { __( + 'new Personalizer resources can no longer be created in Azure. This is currently the only provider available for the Recommended Content feature and as such, this feature will not work unless you had previously created a Personalizer resource. The Azure AI Personalizer provider is deprecated and will be removed in a future release. We hope to replace this provider with another one in a coming release to continue to support this feature', + 'classifai' + ) } + { __( '(see ', 'classifai' ) } + + { __( 'issue#392', 'classifai' ) } + + { ').' } +

+
+); + /** * Feature Settings component. */ @@ -43,6 +75,9 @@ export const FeatureSettings = () => { return ( <> + { 'feature_recommended_content' === featureName && ( + + ) } Date: Mon, 26 Aug 2024 11:37:23 +0530 Subject: [PATCH 062/138] Hide credentials fields if provider is configured. --- .../provider-settings/amazon-polly.js | 95 +++++++++++-------- .../provider-settings/azure-ai-vision.js | 46 +++++---- .../provider-settings/azure-openai.js | 77 ++++++++------- .../provider-settings/azure-personlizer.js | 6 +- .../provider-settings/azure-text-to-speech.js | 46 +++++---- .../provider-settings/google-gemini-api.js | 6 +- .../provider-settings/ibm-watson-nlu.js | 6 +- .../components/provider-settings/index.js | 48 ++++++---- .../provider-settings/openai-chatgpt.js | 24 ++--- .../provider-settings/openai-dalle.js | 24 ++--- .../provider-settings/openai-embeddings.js | 6 +- .../provider-settings/openai-moderation.js | 6 +- .../openai-text-to-speech.js | 24 ++--- .../provider-settings/openai-whisper.js | 6 +- 14 files changed, 248 insertions(+), 172 deletions(-) diff --git a/src/js/settings/components/provider-settings/amazon-polly.js b/src/js/settings/components/provider-settings/amazon-polly.js index df79f8e59..6ac58e052 100644 --- a/src/js/settings/components/provider-settings/amazon-polly.js +++ b/src/js/settings/components/provider-settings/amazon-polly.js @@ -7,7 +7,7 @@ import { __ } from '@wordpress/i18n'; import { SettingsRow } from '../settings-row'; import { STORE_NAME } from '../../data/store'; -export const AmazonPollySettings = () => { +export const AmazonPollySettings = ( { isConfigured = false } ) => { const providerName = 'aws_polly'; const providerSettings = useSelect( ( select ) => @@ -18,46 +18,59 @@ export const AmazonPollySettings = () => { return ( <> - - - onChange( { access_key_id: value } ) - } - /> - - - - onChange( { secret_access_key: value } ) - } - /> - - - { ' ' } - { __( 'Enter the AWS Region. eg: ', 'classifai' ) } - { __( 'us-east-1', 'classifai' ) } . - - } - > - onChange( { aws_region: value } ) } - /> - + { ! isConfigured && ( + <> + + + onChange( { access_key_id: value } ) + } + /> + + + + onChange( { secret_access_key: value } ) + } + /> + + + { ' ' } + { __( + 'Enter the AWS Region. eg: ', + 'classifai' + ) } + + { ' ' } + { __( 'us-east-1', 'classifai' ) }{ ' ' } + + . + + } + > + + onChange( { aws_region: value } ) + } + /> + + + ) } { +export const AzureAIVisionSettings = ( { isConfigured = false } ) => { const providerName = 'ms_computer_vision'; const { featureName } = useFeatureContext(); const providerSettings = useSelect( @@ -30,25 +30,31 @@ export const AzureAIVisionSettings = () => { return ( <> - } - > - - onChange( { endpoint_url: value } ) - } - /> - - - onChange( { api_key: value } ) } - /> - + { ! isConfigured && ( + <> + } + > + + onChange( { endpoint_url: value } ) + } + /> + + + + onChange( { api_key: value } ) + } + /> + + + ) } { 'feature_descriptive_text_generator' === featureName && ( { +export const AzureOpenAISettings = ( { + providerName = 'azure_openai', + isConfigured = false, +} ) => { const { featureName } = useFeatureContext(); const providerSettings = useSelect( ( select ) => @@ -29,38 +32,46 @@ export const AzureOpenAISettings = ( { providerName = 'azure_openai' } ) => { return ( <> - } - > - - onChange( { endpoint_url: value } ) - } - /> - - - onChange( { api_key: value } ) } - /> - - - onChange( { deployment: value } ) } - /> - + { ! isConfigured && ( + <> + } + > + + onChange( { endpoint_url: value } ) + } + /> + + + + onChange( { api_key: value } ) + } + /> + + + + onChange( { deployment: value } ) + } + /> + + + ) } { [ 'feature_content_resizing', 'feature_title_generation', diff --git a/src/js/settings/components/provider-settings/azure-personlizer.js b/src/js/settings/components/provider-settings/azure-personlizer.js index 2bab84b6e..10e8dea7d 100644 --- a/src/js/settings/components/provider-settings/azure-personlizer.js +++ b/src/js/settings/components/provider-settings/azure-personlizer.js @@ -5,7 +5,7 @@ import { __ } from '@wordpress/i18n'; import { SettingsRow } from '../settings-row'; import { STORE_NAME } from '../../data/store'; -export const AzurePersonalizerSettings = () => { +export const AzurePersonalizerSettings = ( { isConfigured = false } ) => { const providerName = 'ms_azure_personalizer'; const providerSettings = useSelect( ( select ) => @@ -14,6 +14,10 @@ export const AzurePersonalizerSettings = () => { const { setProviderSettings } = useDispatch( STORE_NAME ); const onChange = ( data ) => setProviderSettings( providerName, data ); + if ( isConfigured ) { + return null; + } + return ( <> diff --git a/src/js/settings/components/provider-settings/azure-text-to-speech.js b/src/js/settings/components/provider-settings/azure-text-to-speech.js index 58152ed77..47eee610d 100644 --- a/src/js/settings/components/provider-settings/azure-text-to-speech.js +++ b/src/js/settings/components/provider-settings/azure-text-to-speech.js @@ -7,7 +7,7 @@ import { __ } from '@wordpress/i18n'; import { SettingsRow } from '../settings-row'; import { STORE_NAME } from '../../data/store'; -export const AzureTextToSpeechSettings = () => { +export const AzureTextToSpeechSettings = ( { isConfigured = false } ) => { const providerName = 'ms_azure_text_to_speech'; const providerSettings = useSelect( ( select ) => @@ -37,25 +37,31 @@ export const AzureTextToSpeechSettings = () => { return ( <> - } - > - - onChange( { endpoint_url: value } ) - } - /> - - - onChange( { api_key: value } ) } - /> - + { ! isConfigured && ( + <> + } + > + + onChange( { endpoint_url: value } ) + } + /> + + + + onChange( { api_key: value } ) + } + /> + + + ) } { !! providerSettings.voices?.length && ( { +export const GoogleAIGeminiAPISettings = ( { isConfigured = false } ) => { const providerName = 'googleai_gemini_api'; const providerSettings = useSelect( ( select ) => @@ -14,6 +14,10 @@ export const GoogleAIGeminiAPISettings = () => { const { setProviderSettings } = useDispatch( STORE_NAME ); const onChange = ( data ) => setProviderSettings( providerName, data ); + if ( isConfigured ) { + return null; + } + const Description = () => ( <> { __( "Don't have an Google AI (Gemini API) key?", 'classifai' ) }{ ' ' } diff --git a/src/js/settings/components/provider-settings/ibm-watson-nlu.js b/src/js/settings/components/provider-settings/ibm-watson-nlu.js index 209797702..a8095deed 100644 --- a/src/js/settings/components/provider-settings/ibm-watson-nlu.js +++ b/src/js/settings/components/provider-settings/ibm-watson-nlu.js @@ -8,7 +8,7 @@ import { __ } from '@wordpress/i18n'; import { SettingsRow } from '../settings-row'; import { STORE_NAME } from '../../data/store'; -export const IBMWatsonNLUSettings = () => { +export const IBMWatsonNLUSettings = ( { isConfigured = false } ) => { const providerName = 'ibm_watson_nlu'; const providerSettings = useSelect( ( select ) => @@ -20,6 +20,10 @@ export const IBMWatsonNLUSettings = () => { const { setProviderSettings } = useDispatch( STORE_NAME ); const onChange = ( data ) => setProviderSettings( providerName, data ); + if ( isConfigured ) { + return null; + } + const Description = () => ( <> { __( "Don't have an IBM Cloud account yet?", 'classifai' ) }{ ' ' } diff --git a/src/js/settings/components/provider-settings/index.js b/src/js/settings/components/provider-settings/index.js index 1cfd725d7..8ed8a57a3 100644 --- a/src/js/settings/components/provider-settings/index.js +++ b/src/js/settings/components/provider-settings/index.js @@ -28,47 +28,52 @@ import { AmazonPollySettings } from './amazon-polly'; import { AzureTextToSpeechSettings } from './azure-text-to-speech'; import { OpenAITextToSpeachSettings } from './openai-text-to-speech'; -const ProviderFields = ( { provider } ) => { +const ProviderFields = ( { provider, isConfigured } ) => { switch ( provider ) { case 'openai_chatgpt': - return ; + return ; case 'googleai_gemini_api': - return ; + return ; case 'azure_openai': case 'azure_openai_embeddings': - return ; + return ( + + ); case 'ibm_watson_nlu': - return ; + return ; case 'openai_embeddings': - return ; + return ; case 'openai_whisper': - return ; + return ; case 'openai_moderation': - return ; + return ; case 'openai_dalle': - return ; + return ; case 'ms_computer_vision': - return ; + return ; case 'ms_azure_personalizer': - return ; + return ; case 'aws_polly': - return ; + return ; case 'ms_azure_text_to_speech': - return ; + return ; case 'openai_text_to_speech': - return ; + return ; default: return null; @@ -104,7 +109,7 @@ export const ProviderSettings = () => { const configured = isProviderConfigured( featureSettings ) && - ! editProvider && + featureName !== editProvider && providerLabel; return ( @@ -119,8 +124,8 @@ export const ProviderSettings = () => { { ' ' } - {/* The fragment is necessary here. `Tooltip` tries to pass `refs` to `Icon` which isn't - wrapped inside forwardRef(), without which it throws an error. DO NOT REMOVE THE FRAGMENTS. */} + { /* The fragment is necessary here. `Tooltip` tries to pass `refs` to `Icon` which isn't + wrapped inside forwardRef(), without which it throws an error. DO NOT REMOVE THE FRAGMENTS. */ } <> { style={ { cursor: 'pointer', } } - onClick={ () => setEditProvider( true ) } + onClick={ () => + setEditProvider( featureName ) + } /> @@ -150,7 +157,10 @@ export const ProviderSettings = () => { /> ) } - + { ( fills ) => <> { fills } } diff --git a/src/js/settings/components/provider-settings/openai-chatgpt.js b/src/js/settings/components/provider-settings/openai-chatgpt.js index b9e3f729a..d0d048217 100644 --- a/src/js/settings/components/provider-settings/openai-chatgpt.js +++ b/src/js/settings/components/provider-settings/openai-chatgpt.js @@ -6,7 +6,7 @@ import { SettingsRow } from '../settings-row'; import { STORE_NAME } from '../../data/store'; import { useFeatureContext } from '../feature-settings/context'; -export const OpenAIChatGPTSettings = () => { +export const OpenAIChatGPTSettings = ( { isConfigured = false } ) => { const { featureName } = useFeatureContext(); const providerName = 'openai_chatgpt'; const providerSettings = useSelect( @@ -31,16 +31,18 @@ export const OpenAIChatGPTSettings = () => { return ( <> - } - > - onChange( { api_key: value } ) } - /> - + { ! isConfigured && ( + } + > + onChange( { api_key: value } ) } + /> + + ) } { [ 'feature_content_resizing', 'feature_title_generation', diff --git a/src/js/settings/components/provider-settings/openai-dalle.js b/src/js/settings/components/provider-settings/openai-dalle.js index 4a453e642..de85c2c47 100644 --- a/src/js/settings/components/provider-settings/openai-dalle.js +++ b/src/js/settings/components/provider-settings/openai-dalle.js @@ -7,7 +7,7 @@ import { SettingsRow } from '../settings-row'; import { useSelect, useDispatch } from '@wordpress/data'; import { STORE_NAME } from '../../data/store'; -export const OpenAIDallESettings = () => { +export const OpenAIDallESettings = ( { isConfigured = false } ) => { const providerName = 'openai_dalle'; const providerSettings = useSelect( ( select ) => @@ -31,16 +31,18 @@ export const OpenAIDallESettings = () => { return ( <> - } - > - onChange( { api_key: value } ) } - /> - + { ! isConfigured && ( + } + > + onChange( { api_key: value } ) } + /> + + ) } { +export const OpenAIEmbeddingsSettings = ( { isConfigured = false } ) => { const providerName = 'openai_embeddings'; const providerSettings = useSelect( ( select ) => @@ -11,6 +11,10 @@ export const OpenAIEmbeddingsSettings = () => { const { setProviderSettings } = useDispatch( STORE_NAME ); const onChange = ( data ) => setProviderSettings( providerName, data ); + if ( isConfigured ) { + return null; + } + return ( { +export const OpenAIModerationSettings = ( { isConfigured = false } ) => { const providerName = 'openai_moderation'; const providerSettings = useSelect( ( select ) => @@ -11,6 +11,10 @@ export const OpenAIModerationSettings = () => { const { setProviderSettings } = useDispatch( STORE_NAME ); const onChange = ( data ) => setProviderSettings( providerName, data ); + if ( isConfigured ) { + return null; + } + return ( { +export const OpenAITextToSpeachSettings = ( { isConfigured = false } ) => { const providerName = 'openai_text_to_speech'; const providerSettings = useSelect( ( select ) => @@ -31,16 +31,18 @@ export const OpenAITextToSpeachSettings = () => { return ( <> - } - > - onChange( { api_key: value } ) } - /> - + { ! isConfigured && ( + } + > + onChange( { api_key: value } ) } + /> + + ) } { +export const OpenAIWhisperSettings = ( { isConfigured = false } ) => { const providerName = 'openai_whisper'; const providerSettings = useSelect( ( select ) => @@ -11,6 +11,10 @@ export const OpenAIWhisperSettings = () => { const { setProviderSettings } = useDispatch( STORE_NAME ); const onChange = ( data ) => setProviderSettings( providerName, data ); + if ( isConfigured ) { + return null; + } + return ( Date: Mon, 26 Aug 2024 11:59:43 +0530 Subject: [PATCH 063/138] Fix PHPCS errors. --- includes/Classifai/Admin/Settings.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/includes/Classifai/Admin/Settings.php b/includes/Classifai/Admin/Settings.php index 9d654c16e..b30f2a2f4 100644 --- a/includes/Classifai/Admin/Settings.php +++ b/includes/Classifai/Admin/Settings.php @@ -221,7 +221,7 @@ public function update_settings_callback( $request ) { $features = $this->get_features( true ); foreach ( $settings as $feature_key => $feature_setting ) { - $feature = $features[ $feature_key ]; + $feature = $features[ $feature_key ]; if ( ! $feature ) { return new \WP_Error( 'invalid_feature', __( 'Invalid feature.', 'classifai' ), [ 'status' => 400 ] ); @@ -257,7 +257,6 @@ public function update_settings_callback( $request ) { } } - $response = array( 'success' => true, 'settings' => $this->get_settings(), From 50c50f37a72a8ce3973815960d7b141bc2cbb3ee Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Mon, 26 Aug 2024 16:15:00 +0530 Subject: [PATCH 064/138] Remove unwanted files. --- src/js/settings/components/admin-header/index.js | 11 ----------- .../settings/components/classifai-onboarding/utils.js | 0 2 files changed, 11 deletions(-) delete mode 100644 src/js/settings/components/admin-header/index.js delete mode 100644 src/js/settings/components/classifai-onboarding/utils.js diff --git a/src/js/settings/components/admin-header/index.js b/src/js/settings/components/admin-header/index.js deleted file mode 100644 index a5ef6178c..000000000 --- a/src/js/settings/components/admin-header/index.js +++ /dev/null @@ -1,11 +0,0 @@ -export const AdminHeader = () => { - return ( -
-
Logo
-
-
Switch mode
-
Help
-
-
- ); -}; diff --git a/src/js/settings/components/classifai-onboarding/utils.js b/src/js/settings/components/classifai-onboarding/utils.js deleted file mode 100644 index e69de29bb..000000000 From 79133966e03103a28c10a0a9e65f2076c177abaf Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Tue, 27 Aug 2024 14:14:34 +0530 Subject: [PATCH 065/138] Remove admin component. --- src/js/settings/classifai-admin.js | 15 --------------- src/js/settings/index.js | 4 ++-- 2 files changed, 2 insertions(+), 17 deletions(-) delete mode 100644 src/js/settings/classifai-admin.js diff --git a/src/js/settings/classifai-admin.js b/src/js/settings/classifai-admin.js deleted file mode 100644 index a1ba5adb1..000000000 --- a/src/js/settings/classifai-admin.js +++ /dev/null @@ -1,15 +0,0 @@ -import { useSelect } from '@wordpress/data'; - -import { STORE_NAME } from './data/store'; -import { ClassifAISettings, ClassifAIOnboarding } from './components'; - -export const ClassifAIAdmin = () => { - const settingsScreen = useSelect( select => select( STORE_NAME ).getSettingsScreen() ); - - return ( - <> - { 'settings' === settingsScreen && } - { 'onboarding' === settingsScreen && } - - ); -}; diff --git a/src/js/settings/index.js b/src/js/settings/index.js index f565cc2e9..f4deb8c01 100644 --- a/src/js/settings/index.js +++ b/src/js/settings/index.js @@ -7,15 +7,15 @@ import { createRoot } from '@wordpress/element'; /** * Internal dependencies */ -import { ClassifAIAdmin } from './classifai-admin'; import './data/store'; import '../../scss/settings.scss'; +import { ClassifAISettings } from './components'; domReady( () => { const adminEl = document.getElementById( 'classifai-settings' ); if ( adminEl ) { const settingsRoot = createRoot( adminEl ); - settingsRoot.render( ); + settingsRoot.render( ); } } ); From 6e012f6dd6cb9b03a8fbaf6c1bada6bb1e83c850 Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Tue, 27 Aug 2024 17:22:47 +0530 Subject: [PATCH 066/138] Refactor onboarding steps. --- includes/Classifai/Admin/Settings.php | 31 ++-- .../classifai-onboarding/enable-features.js | 144 ++++++++++++------ .../components/classifai-onboarding/hooks.js | 25 +++ .../components/classifai-onboarding/index.js | 42 ++--- .../components/classifai-settings/index.js | 39 ++++- .../feature-settings/enable-feature.js | 12 +- .../feature-settings/save-settings-button.js | 25 ++- src/js/settings/components/header/index.js | 100 +++++++++--- src/js/settings/components/index.js | 1 - src/js/settings/data/actions.js | 4 - src/js/settings/data/hooks.js | 18 ++- src/js/settings/data/reducer.js | 11 +- src/js/settings/data/selectors.js | 2 - src/js/settings/utils/utils.js | 26 ++++ src/scss/settings.scss | 22 +-- 15 files changed, 339 insertions(+), 163 deletions(-) create mode 100644 src/js/settings/components/classifai-onboarding/hooks.js diff --git a/includes/Classifai/Admin/Settings.php b/includes/Classifai/Admin/Settings.php index b30f2a2f4..b9712cba5 100644 --- a/includes/Classifai/Admin/Settings.php +++ b/includes/Classifai/Admin/Settings.php @@ -217,25 +217,36 @@ public function get_settings_permissions_check() { * @return \WP_REST_Response|\WP_Error */ public function update_settings_callback( $request ) { - $settings = $request->get_json_params(); + $params = $request->get_json_params(); + $settings = $params['settings'] ?? []; + $is_setup = $params['is_setup'] ?? false; + $step = $params['step'] ?? ''; $features = $this->get_features( true ); - foreach ( $settings as $feature_key => $feature_setting ) { + // Load settings error functions. + if ( ! function_exists( 'add_settings_error' ) ) { + require_once ABSPATH . 'wp-admin/includes/template.php'; + } + + foreach ( $settings as $feature_key => $feature_settings ) { $feature = $features[ $feature_key ]; if ( ! $feature ) { return new \WP_Error( 'invalid_feature', __( 'Invalid feature.', 'classifai' ), [ 'status' => 400 ] ); } - // Load settings error functions. - if ( ! function_exists( 'add_settings_error' ) ) { - require_once ABSPATH . 'wp-admin/includes/template.php'; - } - - $new_settings = $feature->sanitize_settings( $settings[ $feature_key ] ); + // Skip sanitizing settings for setup step 1. + if ( true === $is_setup && 'enable_features' === $step ) { + $current_settings = $feature->get_settings(); - if ( is_wp_error( $new_settings ) ) { - continue; + // Update only status of the feature. + $current_settings['status'] = $feature_settings['status'] ?? $current_settings['status']; + $new_settings = $current_settings; + } else { + $new_settings = $feature->sanitize_settings( $settings[ $feature_key ] ); + if ( is_wp_error( $new_settings ) ) { + continue; + } } // Update settings. diff --git a/src/js/settings/components/classifai-onboarding/enable-features.js b/src/js/settings/components/classifai-onboarding/enable-features.js index f88b5929d..9046f9e61 100644 --- a/src/js/settings/components/classifai-onboarding/enable-features.js +++ b/src/js/settings/components/classifai-onboarding/enable-features.js @@ -1,4 +1,10 @@ -import { ToggleControl, Flex, FlexItem, BaseControl } from '@wordpress/components'; +import { + ToggleControl, + Flex, + FlexItem, + BaseControl, + Button, +} from '@wordpress/components'; import { useEffect } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; @@ -6,73 +12,121 @@ import { FeatureContext } from '../feature-settings/context'; import { EnableToggleControl } from '../feature-settings/enable-feature'; import { SaveSettingsButton } from '../../components/feature-settings/save-settings-button'; import { useFeatureSettings } from '../../data/hooks'; +import { useSetupPage } from './hooks'; +import { useNavigate } from 'react-router-dom'; -export const EnableFeatures = ( { step, setStep } ) => { - const { features, services } = classifAISettings; +export const EnableFeatures = () => { + const { features, services } = window.classifAISettings; const { isSaving } = useFeatureSettings(); + const { step, nextStepPath } = useSetupPage(); + const navigate = useNavigate(); useEffect( () => { if ( 'enable_features' === step && false === isSaving ) { - setStep( 'configure_features' ); + navigate( nextStepPath ); } - }, [ isSaving ] ); + }, [ isSaving, nextStepPath, step ] ); - const featureToggles = Object.keys( services ).map( ( service, serviceIndex ) => ( - -
-

- { services[ service ] } -

-
- { - Object.keys( features[ service ] ).map( featureSlug => ( - - - - { - ( { feature, status, setFeatureSettings } ) => { + const featureToggles = Object.keys( services ).map( + ( service, serviceIndex ) => ( + +
+

+ { services[ service ] } +

+
+ { Object.keys( features[ service ] ).map( + ( featureSlug ) => ( + + + + { ( { + feature, + status, + setFeatureSettings, + } ) => { return ( - { feature.label } + + { + feature.label + } + - setFeatureSettings( { - status: value ? '1' : '0', // TODO: Use boolean, currently using string for compatibility. - } ) + checked={ + status === + '1' + } + onChange={ ( + value + ) => + setFeatureSettings( + { + status: value + ? '1' + : '0', // TODO: Use boolean, currently using string for compatibility. + } + ) } /> - ) - } - } - - - - ) ) - } + ); + } } + + + + ) + ) } +
-
- { Object.keys( services ).length !== serviceIndex + 1 &&
} - - ) ); + { Object.keys( services ).length !== serviceIndex + 1 && ( +
+ ) } + + ) + ); return ( <> -

{ __( 'Set up ClassifAI to meet your needs', 'classifai' ) }

-
-
- +

+ { __( 'Welcome to ClassifAI', 'classifai' ) } +

+
+
+
+ { +
-
- { featureToggles } - +
+
+

+ { __( + 'Set up ClassifAI to meet your needs', + 'classifai' + ) } +

+ { featureToggles } +
+ + +
+
- ) + ); }; diff --git a/src/js/settings/components/classifai-onboarding/hooks.js b/src/js/settings/components/classifai-onboarding/hooks.js new file mode 100644 index 000000000..12bd79ff0 --- /dev/null +++ b/src/js/settings/components/classifai-onboarding/hooks.js @@ -0,0 +1,25 @@ +/** + * External dependencies + */ +import { useLocation } from 'react-router-dom'; + +/** + * Internal dependencies + */ +import { getNextOnboardingStep } from '../../utils/utils'; + +export const useSetupPage = () => { + const location = useLocation(); + const isSetupPage = + location?.pathname?.startsWith( '/classifai_setup' ) || false; + const step = isSetupPage ? location?.pathname?.split( '/' )[ 2 ] || '' : ''; + const nextStep = step ? getNextOnboardingStep( step ) : ''; + const nextStepPath = nextStep ? `/classifai_setup/${ nextStep }` : ''; + + return { + isSetupPage, + step, + nextStep, + nextStepPath, + }; +}; diff --git a/src/js/settings/components/classifai-onboarding/index.js b/src/js/settings/components/classifai-onboarding/index.js index e990595ef..b909747ce 100644 --- a/src/js/settings/components/classifai-onboarding/index.js +++ b/src/js/settings/components/classifai-onboarding/index.js @@ -1,37 +1,15 @@ -import { useDispatch } from '@wordpress/data'; -import { useEffect, useState } from '@wordpress/element'; -import apiFetch from '@wordpress/api-fetch'; +import { useState } from '@wordpress/element'; +import { Outlet } from 'react-router-dom'; -import { STORE_NAME } from '../../data/store'; -import { EnableFeatures } from './enable-features'; -import { ConfigureFeatures } from './configure-features'; -import { ConfigurationStatus } from './configuration-status'; -import { Header, Layout } from '../../components'; +// Export the steps of the onboarding process. +export { EnableFeatures } from './enable-features'; +export { ConfigureFeatures } from './configure-features'; +export { ConfigurationStatus } from './configuration-status'; export const ClassifAIOnboarding = () => { - const { setSettings, setIsLoaded } = useDispatch( STORE_NAME ); - const [ step, setStep ] = useState( 'enable_features' ); - - // Load the settings. - useEffect( () => { - ( async () => { - const classifAISettings = await apiFetch( { - path: '/classifai/v1/settings', - } ); // TODO: handle error - - setSettings( classifAISettings ); - setIsLoaded( true ); - } )(); - }, [ setSettings, setIsLoaded ] ); - return ( - <> -
- - { 'enable_features' === step && } - { 'configure_features' === step && } - { 'configuration_status' === step && } - - - ) +
+ +
+ ); }; diff --git a/src/js/settings/components/classifai-settings/index.js b/src/js/settings/components/classifai-settings/index.js index 9e38136c3..1c301acf4 100644 --- a/src/js/settings/components/classifai-settings/index.js +++ b/src/js/settings/components/classifai-settings/index.js @@ -22,10 +22,17 @@ import apiFetch from '@wordpress/api-fetch'; /** * Internal dependencies */ -import { FeatureSettings, Header, ServiceSettings } from '..'; +import { + ClassifAIOnboarding, + FeatureSettings, + Header, + ServiceSettings, +} from '..'; import { STORE_NAME } from '../../data/store'; import { FeatureContext } from '../feature-settings/context'; import { ClassifAIRegistration } from '../classifai-registration'; +import { ConfigureFeatures, EnableFeatures } from '../classifai-onboarding'; +import { useSetupPage } from '../classifai-onboarding/hooks'; const { services, features } = window.classifAISettings; @@ -75,6 +82,11 @@ const ServiceSettingsWrapper = () => { * @return {Object} The ServiceNavigation component. */ export const ServiceNavigation = () => { + const { isSetupPage } = useSetupPage(); + if ( isSetupPage ) { + return null; + } + const serviceKeys = Object.keys( services || {} ); return (
@@ -123,8 +135,8 @@ export const ClassifAISettings = () => { return ( -
+
@@ -141,6 +153,29 @@ export const ClassifAISettings = () => { element={ } /> + } + > + + } + /> + } + /> + {/* } + /> */} + } + /> + } diff --git a/src/js/settings/components/feature-settings/enable-feature.js b/src/js/settings/components/feature-settings/enable-feature.js index 01e6368ce..4d49372e5 100644 --- a/src/js/settings/components/feature-settings/enable-feature.js +++ b/src/js/settings/components/feature-settings/enable-feature.js @@ -15,19 +15,23 @@ import { useFeatureSettings } from '../../data/hooks'; /** * Enable Feature Toggle component. * + * @param {Object} props Component props. + * @param {Object} props.children Component children. */ export const EnableToggleControl = ( { children } ) => { - const { featureName, getFeatureSettings, setFeatureSettings } = useFeatureSettings(); + const { featureName, getFeatureSettings, setFeatureSettings } = + useFeatureSettings(); const status = getFeatureSettings( 'status' ) || '0'; const feature = getFeature( featureName ); - const enableDescription = decodeEntities( - feature?.enable_description || __( 'Enable feature', 'classifai' ) - ); if ( children && 'function' === typeof children ) { return children( { feature, status, setFeatureSettings } ); } + const enableDescription = decodeEntities( + feature?.enable_description || __( 'Enable feature', 'classifai' ) + ); + return ( { +export const SaveSettingsButton = ( { + disableErrorReporting = false, + label = __( 'Save Settings', 'classifai' ), +} ) => { const { featureName } = useFeatureSettings(); + const { isSetupPage, step } = useSetupPage(); const { createErrorNotice, removeNotices } = useDispatch( noticesStore ); const notices = useSelect( ( select ) => select( noticesStore ).getNotices() @@ -38,9 +43,16 @@ export const SaveSettingsButton = ( { disableErrorReporting = false } ) => { removeNotices( notices.map( ( { id } ) => id ) ); setIsSaving( true ); - const data = featureName - ? { [ featureName ]: settings[ featureName ] } - : settings; + const data = { + settings: featureName + ? { [ featureName ]: settings[ featureName ] } + : settings, + }; + + if ( isSetupPage ) { + data.is_setup = true; + data.step = step; + } apiFetch( { path: '/classifai/v1/settings/', @@ -62,7 +74,6 @@ export const SaveSettingsButton = ( { disableErrorReporting = false } ) => { return; } setSaveErrors( [] ); - setSettings( res.settings ); setIsSaving( false ); } ) @@ -88,9 +99,7 @@ export const SaveSettingsButton = ( { disableErrorReporting = false } ) => { onClick={ saveSettings } isBusy={ isSaving } > - { isSaving - ? __( 'Saving…', 'classifai' ) - : __( 'Save Settings', 'classifai' ) } + { isSaving ? __( 'Saving…', 'classifai' ) : label } ); }; diff --git a/src/js/settings/components/header/index.js b/src/js/settings/components/header/index.js index cc567267c..53e407247 100644 --- a/src/js/settings/components/header/index.js +++ b/src/js/settings/components/header/index.js @@ -1,28 +1,40 @@ /** * External dependencies */ +import { NavLink } from 'react-router-dom'; + import { DropdownMenu, MenuGroup, MenuItem, VisuallyHidden, - Button, + Icon, } from '@wordpress/components'; -import { useSelect, useDispatch } from '@wordpress/data'; import { external, help, cog, tool } from '@wordpress/icons'; import { __ } from '@wordpress/i18n'; /** * Internal dependencies */ -import { STORE_NAME } from '../..//data/store'; import { ClassifAILogo } from '../../utils/icons'; +import { useSetupPage } from '../classifai-onboarding/hooks'; -export const Header = ( props ) => { - const { isSetupPage } = props; - - const settingsScreen = useSelect( select => select( STORE_NAME ).getSettingsScreen() ); - const { setSettingsScreen } = useDispatch( STORE_NAME ); +export const Header = () => { + const { isSetupPage, step } = useSetupPage(); + const onBoardingSteps = { + enable_features: { + step: __( '1', 'classifai' ), + title: __( 'Enable Features', 'classifai' ), + }, + classifai_registration: { + step: __( '2', 'classifai' ), + title: __( 'Register ClassifAI', 'classifai' ), + }, + configure_features: { + step: __( '3', 'classifai' ), + title: __( 'Access AI', 'classifai' ), + }, + }; return (
@@ -32,23 +44,24 @@ export const Header = ( props ) => {
{ isSetupPage && ( - + ) } { ! isSetupPage && ( - + ) } {
+ { isSetupPage && ( +
+
+
+ { Object.keys( onBoardingSteps ).map( + ( stepKey, stepIndex ) => { + return ( + + + { Object.keys( onBoardingSteps ) + .length !== + stepIndex + 1 && ( +
+ ) } +
+ ); + } + ) } +
+
+
+ ) }
); }; diff --git a/src/js/settings/components/index.js b/src/js/settings/components/index.js index 407e17689..566174739 100644 --- a/src/js/settings/components/index.js +++ b/src/js/settings/components/index.js @@ -1,6 +1,5 @@ export * from './header'; export * from './layout'; -export * from './admin-header'; export * from './classifai-onboarding'; export * from './classifai-settings'; export * from './service-settings'; diff --git a/src/js/settings/data/actions.js b/src/js/settings/data/actions.js index c7d471eb7..f43342e45 100644 --- a/src/js/settings/data/actions.js +++ b/src/js/settings/data/actions.js @@ -57,10 +57,6 @@ export const setIsSaving = ( isSaving ) => ( { payload: isSaving, } ); -export const setSettingsScreen = ( screen ) => ( { - type: 'SET_SETTINGS_SCREEN', - payload: screen, -} ); export const setSaveErrors = ( data ) => ( { type: 'SET_SAVE_ERRORS', diff --git a/src/js/settings/data/hooks.js b/src/js/settings/data/hooks.js index ecf0ce428..754304695 100644 --- a/src/js/settings/data/hooks.js +++ b/src/js/settings/data/hooks.js @@ -13,7 +13,9 @@ export const useFeatureSettings = () => { } const { setFeatureSettings } = useDispatch( STORE_NAME ); - const __isSaving = useSelect( select => select( STORE_NAME ).getIsSaving() ); + const __isSaving = useSelect( ( select ) => + select( STORE_NAME ).getIsSaving() + ); useEffect( () => { if ( __isSaving ) { @@ -27,9 +29,13 @@ export const useFeatureSettings = () => { return { isSaving, featureName, - getFeatureSettings: ( key ) => useSelect( select => select( STORE_NAME ).getFeatureSettings( key, featureName ) ), - getSettings: ( key, featureName ) => useSelect( select => select( STORE_NAME ).getSettings( key, featureName ) ), - setFeatureSettings: ( settings ) => setFeatureSettings( settings, featureName ), - getIsSaving: () => useSelect( select => select( STORE_NAME ).getIsSaving() ), - } + getFeatureSettings: ( key ) => + useSelect( ( select ) => + select( STORE_NAME ).getFeatureSettings( key, featureName ) + ), + setFeatureSettings: ( settings ) => + setFeatureSettings( settings, featureName ), + getIsSaving: () => + useSelect( ( select ) => select( STORE_NAME ).getIsSaving() ), + }; }; diff --git a/src/js/settings/data/reducer.js b/src/js/settings/data/reducer.js index e93945539..7ff54108d 100644 --- a/src/js/settings/data/reducer.js +++ b/src/js/settings/data/reducer.js @@ -9,7 +9,6 @@ const DEFAULT_STATE = { settings: classifAISettings.settings || {}, isLoaded: false, isSaving: false, - settingsScreen: 'settings', saveErrors: [], }; @@ -62,17 +61,11 @@ export const reducer = ( state = DEFAULT_STATE, action ) => { isSaving: action.payload, }; - case 'SET_SETTINGS_SCREEN': - return { - ...state, - settingsScreen: action.payload - } - case 'SET_SAVE_ERRORS': return { ...state, - saveErrors: action.payload - } + saveErrors: action.payload, + }; default: return state; diff --git a/src/js/settings/data/selectors.js b/src/js/settings/data/selectors.js index 681f81068..36fd78bda 100644 --- a/src/js/settings/data/selectors.js +++ b/src/js/settings/data/selectors.js @@ -20,6 +20,4 @@ export const getIsLoaded = ( state ) => state.isLoaded; export const getIsSaving = ( state ) => state.isSaving; -export const getSettingsScreen = ( state ) => state.settingsScreen; - export const getSaveErrors = ( state ) => state.saveErrors; diff --git a/src/js/settings/utils/utils.js b/src/js/settings/utils/utils.js index 3fe58bd9e..f16418acc 100644 --- a/src/js/settings/utils/utils.js +++ b/src/js/settings/utils/utils.js @@ -285,3 +285,29 @@ export const isFeatureActive = ( feature ) => { return isEnabled && authenticated; }; + +/** + * Returns the onboarding steps. + * + * @return {Array} Array of onboarding steps. + */ +export const getOnboardingSteps = () => { + return [ + 'enable_features', + // 'classifai_registration', // TODO: Uncomment when registration is implemented + 'configure_features', + 'finish', + ]; +}; + +/** + * Get the next step in the onboarding process. + * + * @param {string} currentStep + * @return {string} The next step in the onboarding process. + */ +export const getNextOnboardingStep = ( currentStep ) => { + const steps = getOnboardingSteps(); + const currentIndex = steps.indexOf( currentStep ); + return steps[ currentIndex + 1 ]; +}; diff --git a/src/scss/settings.scss b/src/scss/settings.scss index e500d42e7..25e337ba8 100644 --- a/src/scss/settings.scss +++ b/src/scss/settings.scss @@ -382,20 +382,8 @@ } } -.classifai-setup-title { - font-size: 22px; - padding: 0px; - font-weight: 400; - text-align: center; - margin: 3rem 0; -} - .classifai-onboarding { &__configure { - display: flex; - gap: 2rem; - padding-top: 0 !important; - .classifai-settings-footer { display: flex; @@ -408,14 +396,8 @@ &--status, &--enable-features { - & > div { - flex-grow: 1; - flex-basis: 0; - max-width: 50%; - - img { - max-width: 100%; - } + & img { + max-width: 100%; } } } From cd0fa2a628bf48192af8b6e232bf5d4c0780f78d Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Tue, 27 Aug 2024 17:47:06 +0530 Subject: [PATCH 067/138] Added onSaveSuccess callback support. --- .../classifai-onboarding/enable-features.js | 16 +++++----------- .../components/classifai-onboarding/index.js | 1 - .../components/feature-settings/index.js | 4 ++-- .../feature-settings/save-settings-button.js | 13 ++++++------- 4 files changed, 13 insertions(+), 21 deletions(-) diff --git a/src/js/settings/components/classifai-onboarding/enable-features.js b/src/js/settings/components/classifai-onboarding/enable-features.js index 9046f9e61..3dd329286 100644 --- a/src/js/settings/components/classifai-onboarding/enable-features.js +++ b/src/js/settings/components/classifai-onboarding/enable-features.js @@ -5,27 +5,21 @@ import { BaseControl, Button, } from '@wordpress/components'; -import { useEffect } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import { FeatureContext } from '../feature-settings/context'; import { EnableToggleControl } from '../feature-settings/enable-feature'; import { SaveSettingsButton } from '../../components/feature-settings/save-settings-button'; -import { useFeatureSettings } from '../../data/hooks'; import { useSetupPage } from './hooks'; import { useNavigate } from 'react-router-dom'; export const EnableFeatures = () => { const { features, services } = window.classifAISettings; - const { isSaving } = useFeatureSettings(); - const { step, nextStepPath } = useSetupPage(); + const { nextStepPath } = useSetupPage(); const navigate = useNavigate(); - - useEffect( () => { - if ( 'enable_features' === step && false === isSaving ) { - navigate( nextStepPath ); - } - }, [ isSaving, nextStepPath, step ] ); + const onSaveSuccess = () => { + navigate( nextStepPath ); + }; const featureToggles = Object.keys( services ).map( ( service, serviceIndex ) => ( @@ -121,7 +115,7 @@ export const EnableFeatures = () => {
diff --git a/src/js/settings/components/classifai-onboarding/index.js b/src/js/settings/components/classifai-onboarding/index.js index b909747ce..9bd839eb2 100644 --- a/src/js/settings/components/classifai-onboarding/index.js +++ b/src/js/settings/components/classifai-onboarding/index.js @@ -1,4 +1,3 @@ -import { useState } from '@wordpress/element'; import { Outlet } from 'react-router-dom'; // Export the steps of the onboarding process. diff --git a/src/js/settings/components/feature-settings/index.js b/src/js/settings/components/feature-settings/index.js index b72f3c58d..10fa818cb 100644 --- a/src/js/settings/components/feature-settings/index.js +++ b/src/js/settings/components/feature-settings/index.js @@ -54,7 +54,7 @@ const PersonalizerDeprecationNotice = () => ( /** * Feature Settings component. */ -export const FeatureSettings = () => { +export const FeatureSettings = ( { onSaveSuccess = () => {} } ) => { const { featureName } = useFeatureContext(); const { setCurrentFeature } = useDispatch( STORE_NAME ); @@ -95,7 +95,7 @@ export const FeatureSettings = () => {
- +
diff --git a/src/js/settings/components/feature-settings/save-settings-button.js b/src/js/settings/components/feature-settings/save-settings-button.js index bb6913700..19ea4a0d0 100644 --- a/src/js/settings/components/feature-settings/save-settings-button.js +++ b/src/js/settings/components/feature-settings/save-settings-button.js @@ -18,7 +18,7 @@ import { useSetupPage } from '../classifai-onboarding/hooks'; * Save Settings Button component. */ export const SaveSettingsButton = ( { - disableErrorReporting = false, + onSaveSuccess = () => {}, label = __( 'Save Settings', 'classifai' ), } ) => { const { featureName } = useFeatureSettings(); @@ -61,19 +61,18 @@ export const SaveSettingsButton = ( { } ) .then( ( res ) => { if ( res.errors && res.errors.length ) { - if ( ! disableErrorReporting ) { - res.errors.forEach( ( error ) => { - createErrorNotice( error.message, { - id: `error-${ featureName }`, - } ); + res.errors.forEach( ( error ) => { + createErrorNotice( error.message, { + id: `error-${ featureName }`, } ); - } + } ); setSettings( res.settings ); setIsSaving( false ); setSaveErrors( res.errors ); return; } setSaveErrors( [] ); + onSaveSuccess(); setSettings( res.settings ); setIsSaving( false ); } ) From d7c29c863c9db9f683fc8390de799ab28c0629ca Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Wed, 28 Aug 2024 17:13:26 +0530 Subject: [PATCH 068/138] Remove layout component. --- src/js/settings/components/index.js | 1 - src/js/settings/components/layout/index.js | 7 ------- 2 files changed, 8 deletions(-) delete mode 100644 src/js/settings/components/layout/index.js diff --git a/src/js/settings/components/index.js b/src/js/settings/components/index.js index 566174739..a6aa466e8 100644 --- a/src/js/settings/components/index.js +++ b/src/js/settings/components/index.js @@ -1,5 +1,4 @@ export * from './header'; -export * from './layout'; export * from './classifai-onboarding'; export * from './classifai-settings'; export * from './service-settings'; diff --git a/src/js/settings/components/layout/index.js b/src/js/settings/components/layout/index.js deleted file mode 100644 index 1c63d1cab..000000000 --- a/src/js/settings/components/layout/index.js +++ /dev/null @@ -1,7 +0,0 @@ -export const Layout = ( { children } ) => { - return ( -
- { children } -
- ) -}; \ No newline at end of file From c4b63e74b57fe602c4326644291fd7a48612a683 Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Wed, 28 Aug 2024 17:13:53 +0530 Subject: [PATCH 069/138] Update finish step designs. --- .../configuration-status.js | 80 ------------- .../classifai-onboarding/finish-step.js | 111 ++++++++++++++++++ 2 files changed, 111 insertions(+), 80 deletions(-) delete mode 100644 src/js/settings/components/classifai-onboarding/configuration-status.js create mode 100644 src/js/settings/components/classifai-onboarding/finish-step.js diff --git a/src/js/settings/components/classifai-onboarding/configuration-status.js b/src/js/settings/components/classifai-onboarding/configuration-status.js deleted file mode 100644 index 285056b8a..000000000 --- a/src/js/settings/components/classifai-onboarding/configuration-status.js +++ /dev/null @@ -1,80 +0,0 @@ -import { Icon, BaseControl } from '@wordpress/components'; -import { useSelect } from '@wordpress/data'; -import { __ } from '@wordpress/i18n'; - -import { STORE_NAME } from '../../data/store'; -import { isFeatureActive, getFeature, getEnabledFeaturesSlugs } from '../../utils/utils'; - -export const ConfigurationStatus = ( { setStep } ) => { - const { features: __settings, services } = classifAISettings; - const settingsState = useSelect( select => select( STORE_NAME ).getSettings() ); - const enabledFeatureSlugs = getEnabledFeaturesSlugs(); - const settings = Object.keys( __settings ).reduce( ( a, c ) => { - const res = Object.keys( __settings[ c ] ).reduce( ( __a, __c ) => { - return { - ...__a, - [ __c ]: settingsState[ __c ] - } - }, {} ); - - return { - ...a, - [ c ]: res - } - }, {} ); - - return ( - <> -

{ __( 'Welcome to ClassifAI', 'classifai' ) }

-
-
- -
-
- { - Object.keys( services ).map( ( service ) => { - const enabledFeatures = Object - .keys( settings[ service ] ) - .map( featureSlug => { - const feature = getFeature( featureSlug ); - const label = feature.label; - - if ( ! enabledFeatureSlugs.includes( featureSlug ) ) { - return null; - } - - return ( - - { isFeatureActive( settings[ service ][ featureSlug ] ) - ? - : - } - { label } - - ) - } ) - .filter( Boolean ); - - if ( ! enabledFeatures.length ) { - return []; - } - - return ( - -
-

- { services[ service ] } -

-
- { enabledFeatures } -
-
-
- ) - } ) - } -
-
- - ); -}; diff --git a/src/js/settings/components/classifai-onboarding/finish-step.js b/src/js/settings/components/classifai-onboarding/finish-step.js new file mode 100644 index 000000000..87503af58 --- /dev/null +++ b/src/js/settings/components/classifai-onboarding/finish-step.js @@ -0,0 +1,111 @@ +import { Icon } from '@wordpress/components'; +import { useSelect } from '@wordpress/data'; +import { __ } from '@wordpress/i18n'; + +import { STORE_NAME } from '../../data/store'; +import { isFeatureActive, getFeature } from '../../utils/utils'; + +export const FinishStep = () => { + const { features: __settings, services } = window.classifAISettings; + const settingsState = useSelect( ( select ) => + select( STORE_NAME ).getSettings() + ); + const enabledFeatureSlugs = Object.keys( settingsState ).filter( + ( feature ) => settingsState[ feature ].status === '1' + ); + const settings = Object.keys( __settings ).reduce( ( a, c ) => { + const res = Object.keys( __settings[ c ] ).reduce( ( __a, __c ) => { + return { + ...__a, + [ __c ]: settingsState[ __c ], + }; + }, {} ); + + return { + ...a, + [ c ]: res, + }; + }, {} ); + + return ( + <> +

+ { __( 'Welcome to ClassifAI', 'classifai' ) } +

+
+
+
+ { +
+
+
+
+

+ { __( + 'ClassifAI configured successfully!', + 'classifai' + ) } +

+ { Object.keys( services ).map( ( service ) => { + const enabledFeatures = Object.keys( + settings[ service ] + ) + .map( ( featureSlug ) => { + const feature = getFeature( featureSlug ); + const label = feature.label; + + if ( + ! enabledFeatureSlugs.includes( + featureSlug + ) + ) { + return null; + } + + return ( +
  • + { isFeatureActive( + settings[ service ][ + featureSlug + ] + ) ? ( + + ) : ( + + ) }{ ' ' } + { label } +
  • + ); + } ) + .filter( Boolean ); + + if ( ! enabledFeatures.length ) { + return []; + } + + return ( +
    +

    + { services[ service ] } +

    +
    +
      { enabledFeatures }
    +
    +
    + ); + } ) } +
    +
    +
    + + ); +}; From 85f9eafdef1ab21f26fdc4fb35fabd08a114bf92 Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Wed, 28 Aug 2024 17:15:26 +0530 Subject: [PATCH 070/138] Refactor configure feature step. --- .../configure-features.js | 107 ++++++++---------- .../classifai-onboarding/enable-features.js | 4 +- .../components/classifai-onboarding/index.js | 2 +- .../components/classifai-settings/index.js | 7 +- src/js/settings/utils/utils.js | 15 +-- 5 files changed, 57 insertions(+), 78 deletions(-) diff --git a/src/js/settings/components/classifai-onboarding/configure-features.js b/src/js/settings/components/classifai-onboarding/configure-features.js index 67e199bf2..5c199c8cc 100644 --- a/src/js/settings/components/classifai-onboarding/configure-features.js +++ b/src/js/settings/components/classifai-onboarding/configure-features.js @@ -1,81 +1,68 @@ -import { useState, useEffect } from '@wordpress/element'; -import { useSelect, useDispatch } from '@wordpress/data'; +import { useState } from '@wordpress/element'; +import { useSelect } from '@wordpress/data'; import { Fill, SlotFillProvider, Button } from '@wordpress/components'; -import { store as noticesStore } from '@wordpress/notices'; import { __ } from '@wordpress/i18n'; import { FeatureSettings } from '..'; import { FeatureContext } from '../feature-settings/context'; -import { useFeatureSettings } from '../../data/hooks'; import { getFeature } from '../../utils/utils'; import { STORE_NAME } from '../../data/store'; -import { getEnabledFeaturesSlugs } from '../../utils/utils'; +import { useSetupPage } from './hooks'; +import { useNavigate } from 'react-router-dom'; -export const ConfigureFeatures = ( { step, setStep } ) => { - const { isSaving } = useFeatureSettings(); - const enabledFeatures = getEnabledFeaturesSlugs(); - const [ currentFeature, setCurrentFeature ] = useState( enabledFeatures[0] ); - const errors = useSelect( select => select( STORE_NAME ).getSaveErrors() ); - const { setSaveErrors } = useDispatch( STORE_NAME ); - const { removeNotices } = useDispatch( noticesStore ); - let featureIndex = enabledFeatures.findIndex( ef => ef === currentFeature ); - const notices = useSelect( ( select ) => - select( noticesStore ).getNotices() +export const ConfigureFeatures = () => { + const enabledFeatures = useSelect( ( select ) => { + const settings = select( STORE_NAME ).getSettings(); + return Object.keys( settings ).filter( + ( feature ) => settings[ feature ].status === '1' + ); + } ); + const { nextStepPath } = useSetupPage(); + const navigate = useNavigate(); + const [ currentFeature, setCurrentFeature ] = useState( + enabledFeatures[ 0 ] + ); + let featureIndex = enabledFeatures.findIndex( + ( ef ) => ef === currentFeature ); - useEffect( () => { - setSaveErrors( [] ); - }, [] ); - - useEffect( () => { - if ( false !== isSaving ) { - return; - } - - if ( featureIndex + 1 !== enabledFeatures.length && ! errors.length ) { + const onSaveSuccess = () => { + if ( featureIndex + 1 !== enabledFeatures.length ) { setCurrentFeature( enabledFeatures[ ++featureIndex ] ); - } else if ( featureIndex + 1 === enabledFeatures.length ) { - setStep( 'configuration_status' ); + } else { + // Navigate to the next step. + navigate( nextStepPath ); } - }, [ isSaving ] ); + }; return ( <> -

    { __( 'Set up AI Providers', 'classifai' ) }

    -
    -
    - { - enabledFeatures.map( feature => ( -
    { - removeNotices( notices.map( ( { id } ) => id ) ); - setCurrentFeature( feature ); - } } - className={ `classifai-tabs-item ${ feature === currentFeature && 'active-tab' }` } - > - { getFeature( feature ).label } -
    - ) ) - } +

    + { __( 'Set up AI Providers', 'classifai' ) } +

    +
    +
    + { enabledFeatures.map( ( feature ) => ( + + ) ) }
    -
    - +
    + - + - diff --git a/src/js/settings/components/classifai-onboarding/enable-features.js b/src/js/settings/components/classifai-onboarding/enable-features.js index 3dd329286..87d69753f 100644 --- a/src/js/settings/components/classifai-onboarding/enable-features.js +++ b/src/js/settings/components/classifai-onboarding/enable-features.js @@ -25,9 +25,9 @@ export const EnableFeatures = () => { ( service, serviceIndex ) => (
    -

    +

    { services[ service ] } -

    +
    { Object.keys( features[ service ] ).map( ( featureSlug ) => ( diff --git a/src/js/settings/components/classifai-onboarding/index.js b/src/js/settings/components/classifai-onboarding/index.js index 9bd839eb2..106b17a25 100644 --- a/src/js/settings/components/classifai-onboarding/index.js +++ b/src/js/settings/components/classifai-onboarding/index.js @@ -3,7 +3,7 @@ import { Outlet } from 'react-router-dom'; // Export the steps of the onboarding process. export { EnableFeatures } from './enable-features'; export { ConfigureFeatures } from './configure-features'; -export { ConfigurationStatus } from './configuration-status'; +export { FinishStep } from './finish-step'; export const ClassifAIOnboarding = () => { return ( diff --git a/src/js/settings/components/classifai-settings/index.js b/src/js/settings/components/classifai-settings/index.js index 1c301acf4..1c7ec1a1c 100644 --- a/src/js/settings/components/classifai-settings/index.js +++ b/src/js/settings/components/classifai-settings/index.js @@ -31,7 +31,11 @@ import { import { STORE_NAME } from '../../data/store'; import { FeatureContext } from '../feature-settings/context'; import { ClassifAIRegistration } from '../classifai-registration'; -import { ConfigureFeatures, EnableFeatures } from '../classifai-onboarding'; +import { + ConfigureFeatures, + EnableFeatures, + FinishStep, +} from '../classifai-onboarding'; import { useSetupPage } from '../classifai-onboarding/hooks'; const { services, features } = window.classifAISettings; @@ -175,6 +179,7 @@ export const ClassifAISettings = () => { path="configure_features" element={ } /> + } /> { return { isOpen, setIsOpen }; }; -/** - * Returns array of feature slugs for features that are enabled, and not - * necessarily authenticated. - * - * @returns {Array} Array of feature slugs, for example - * ['feature_excerpt_generation', 'feature_content_resizing'] - */ -export function getEnabledFeaturesSlugs() { - const { settings: features } = classifAISettings; - - return Object.keys( features ).filter( feature => '1' === features[ feature ].status ) -}; - /** * Returns true if a feature is enabled and authenticated. * * @param {Object} feature The feature object. - * @returns {Boolean} True if the feature is enabled and authenticated, false otherwise. + * @return {boolean} True if the feature is enabled and authenticated, false otherwise. */ export const isFeatureActive = ( feature ) => { const isEnabled = '1' === feature.status; From 9766aac104e8de573ef18707731cde12600e3520 Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Wed, 28 Aug 2024 17:25:51 +0530 Subject: [PATCH 071/138] Removed unwanted state from the onboarding process. --- .../components/classifai-settings/index.js | 6 +----- .../components/feature-settings/index.js | 3 +++ .../feature-settings/save-settings-button.js | 9 +++++---- src/js/settings/components/header/index.js | 3 ++- src/js/settings/data/actions.js | 6 ------ src/js/settings/data/hooks.js | 16 +--------------- src/js/settings/data/reducer.js | 7 ------- src/js/settings/data/selectors.js | 2 -- 8 files changed, 12 insertions(+), 40 deletions(-) diff --git a/src/js/settings/components/classifai-settings/index.js b/src/js/settings/components/classifai-settings/index.js index 1c7ec1a1c..820766794 100644 --- a/src/js/settings/components/classifai-settings/index.js +++ b/src/js/settings/components/classifai-settings/index.js @@ -116,7 +116,7 @@ export const ServiceNavigation = () => { : 'classifai-tabs-item' } > - { __( 'ClassifAI Registration', 'classifai') } + { __( 'ClassifAI Registration', 'classifai' ) }
    ); @@ -171,10 +171,6 @@ export const ClassifAISettings = () => { path="enable_features" element={ } /> - {/* } - /> */} } diff --git a/src/js/settings/components/feature-settings/index.js b/src/js/settings/components/feature-settings/index.js index 10fa818cb..d7a32f373 100644 --- a/src/js/settings/components/feature-settings/index.js +++ b/src/js/settings/components/feature-settings/index.js @@ -53,6 +53,9 @@ const PersonalizerDeprecationNotice = () => ( /** * Feature Settings component. + * + * @param {Object} props Component props. + * @param {Function} props.onSaveSuccess Callback function to be executed after saving settings. */ export const FeatureSettings = ( { onSaveSuccess = () => {} } ) => { const { featureName } = useFeatureContext(); diff --git a/src/js/settings/components/feature-settings/save-settings-button.js b/src/js/settings/components/feature-settings/save-settings-button.js index 19ea4a0d0..9da8b4ffa 100644 --- a/src/js/settings/components/feature-settings/save-settings-button.js +++ b/src/js/settings/components/feature-settings/save-settings-button.js @@ -16,6 +16,10 @@ import { useSetupPage } from '../classifai-onboarding/hooks'; /** * Save Settings Button component. + * + * @param {Object} props Component props. + * @param {Function} props.onSaveSuccess Callback function to be executed after saving settings. + * @param {string} props.label Button label. */ export const SaveSettingsButton = ( { onSaveSuccess = () => {}, @@ -27,8 +31,7 @@ export const SaveSettingsButton = ( { const notices = useSelect( ( select ) => select( noticesStore ).getNotices() ); - const { setIsSaving, setSettings, setSaveErrors } = - useDispatch( STORE_NAME ); + const { setIsSaving, setSettings } = useDispatch( STORE_NAME ); const isSaving = useSelect( ( select ) => select( STORE_NAME ).getIsSaving() ); @@ -68,10 +71,8 @@ export const SaveSettingsButton = ( { } ); setSettings( res.settings ); setIsSaving( false ); - setSaveErrors( res.errors ); return; } - setSaveErrors( [] ); onSaveSuccess(); setSettings( res.settings ); setIsSaving( false ); diff --git a/src/js/settings/components/header/index.js b/src/js/settings/components/header/index.js index 53e407247..3f74bb4b2 100644 --- a/src/js/settings/components/header/index.js +++ b/src/js/settings/components/header/index.js @@ -136,7 +136,8 @@ export const Header = () => { }` } >
    - {/* TODO: Update this with action router navlinks */} + + { /* TODO: Update this with action router navlinks */ } { onBoardingSteps[ diff --git a/src/js/settings/data/actions.js b/src/js/settings/data/actions.js index f43342e45..d5a493c26 100644 --- a/src/js/settings/data/actions.js +++ b/src/js/settings/data/actions.js @@ -56,9 +56,3 @@ export const setIsSaving = ( isSaving ) => ( { type: 'SET_IS_SAVING', payload: isSaving, } ); - - -export const setSaveErrors = ( data ) => ( { - type: 'SET_SAVE_ERRORS', - payload: data, -} ); diff --git a/src/js/settings/data/hooks.js b/src/js/settings/data/hooks.js index 754304695..7a2429970 100644 --- a/src/js/settings/data/hooks.js +++ b/src/js/settings/data/hooks.js @@ -1,11 +1,10 @@ -import { useContext, useState, useEffect } from '@wordpress/element'; +import { useContext } from '@wordpress/element'; import { useDispatch, useSelect } from '@wordpress/data'; import { STORE_NAME } from '../data/store'; import { FeatureContext } from '../components/feature-settings/context'; export const useFeatureSettings = () => { - const [ isSaving, setIsSaving ] = useState( null ); let { featureName } = useContext( FeatureContext ); if ( ! featureName ) { @@ -13,21 +12,8 @@ export const useFeatureSettings = () => { } const { setFeatureSettings } = useDispatch( STORE_NAME ); - const __isSaving = useSelect( ( select ) => - select( STORE_NAME ).getIsSaving() - ); - - useEffect( () => { - if ( __isSaving ) { - setIsSaving( __isSaving ); - } else if ( false === __isSaving && null !== isSaving ) { - setIsSaving( false ); - setTimeout( () => setIsSaving( null ), 0 ); - } - }, [ __isSaving ] ); return { - isSaving, featureName, getFeatureSettings: ( key ) => useSelect( ( select ) => diff --git a/src/js/settings/data/reducer.js b/src/js/settings/data/reducer.js index 7ff54108d..eb3555f8b 100644 --- a/src/js/settings/data/reducer.js +++ b/src/js/settings/data/reducer.js @@ -9,7 +9,6 @@ const DEFAULT_STATE = { settings: classifAISettings.settings || {}, isLoaded: false, isSaving: false, - saveErrors: [], }; /** @@ -61,12 +60,6 @@ export const reducer = ( state = DEFAULT_STATE, action ) => { isSaving: action.payload, }; - case 'SET_SAVE_ERRORS': - return { - ...state, - saveErrors: action.payload, - }; - default: return state; } diff --git a/src/js/settings/data/selectors.js b/src/js/settings/data/selectors.js index 36fd78bda..b31e67de2 100644 --- a/src/js/settings/data/selectors.js +++ b/src/js/settings/data/selectors.js @@ -19,5 +19,3 @@ export const getCurrentFeature = ( state ) => state.currentFeature; export const getIsLoaded = ( state ) => state.isLoaded; export const getIsSaving = ( state ) => state.isSaving; - -export const getSaveErrors = ( state ) => state.saveErrors; From 6b16f634bd143a17aa8e7fa2038146cc39e21335 Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Wed, 28 Aug 2024 18:56:22 +0530 Subject: [PATCH 072/138] Update onboarding header navigation. --- .../components/classifai-settings/index.js | 6 ++ src/js/settings/components/header/index.js | 85 ++++++++++++++----- src/js/settings/data/hooks.js | 11 ++- 3 files changed, 73 insertions(+), 29 deletions(-) diff --git a/src/js/settings/components/classifai-settings/index.js b/src/js/settings/components/classifai-settings/index.js index 820766794..40d9b47a4 100644 --- a/src/js/settings/components/classifai-settings/index.js +++ b/src/js/settings/components/classifai-settings/index.js @@ -176,6 +176,12 @@ export const ClassifAISettings = () => { element={ } /> } /> + + } + /> { step: __( '3', 'classifai' ), title: __( 'Access AI', 'classifai' ), }, + finish: { + step: __( '4', 'classifai' ), + title: __( 'Finish', 'classifai' ), + }, }; return ( @@ -126,38 +130,73 @@ export const Header = () => {
    { Object.keys( onBoardingSteps ).map( ( stepKey, stepIndex ) => { + if ( stepKey === 'finish' ) { + return null; + } + + const isCompleted = + stepIndex < + Object.keys( onBoardingSteps ).indexOf( + step + ); + const isCurrent = step === stepKey; + const shouldShowLink = + isCompleted || isCurrent; + const classes = []; + if ( isCompleted ) { + classes.push( 'is-complete' ); + } + if ( isCurrent ) { + classes.push( 'is-active' ); + } + + const stepLabel = ( + <> + + { isCompleted ? ( + + ) : ( + <> + { + onBoardingSteps[ + stepKey + ].step + } + + ) } + + + { + onBoardingSteps[ stepKey ] + .title + } + + + ); + return ( { Object.keys( onBoardingSteps ) .length !== - stepIndex + 1 && ( + stepIndex + 2 && (
    ) } diff --git a/src/js/settings/data/hooks.js b/src/js/settings/data/hooks.js index 7a2429970..2ea1de038 100644 --- a/src/js/settings/data/hooks.js +++ b/src/js/settings/data/hooks.js @@ -12,16 +12,15 @@ export const useFeatureSettings = () => { } const { setFeatureSettings } = useDispatch( STORE_NAME ); + const getFeatureSettings = ( key ) => + useSelect( ( select ) => + select( STORE_NAME ).getFeatureSettings( key, featureName ) + ); return { featureName, - getFeatureSettings: ( key ) => - useSelect( ( select ) => - select( STORE_NAME ).getFeatureSettings( key, featureName ) - ), + getFeatureSettings, setFeatureSettings: ( settings ) => setFeatureSettings( settings, featureName ), - getIsSaving: () => - useSelect( ( select ) => select( STORE_NAME ).getIsSaving() ), }; }; From 6f1b8508f74aa798f09cf0440bf8f8259db67553 Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Wed, 28 Aug 2024 19:09:50 +0530 Subject: [PATCH 073/138] Added button links to last step. --- includes/Classifai/Admin/Settings.php | 1 + .../classifai-onboarding/enable-features.js | 8 ++++-- .../classifai-onboarding/finish-step.js | 28 +++++++++++++++++-- 3 files changed, 32 insertions(+), 5 deletions(-) diff --git a/includes/Classifai/Admin/Settings.php b/includes/Classifai/Admin/Settings.php index b9712cba5..bd5146e9c 100644 --- a/includes/Classifai/Admin/Settings.php +++ b/includes/Classifai/Admin/Settings.php @@ -71,6 +71,7 @@ public function admin_enqueue_scripts( $hook_suffix ) { 'features' => $this->get_features(), 'services' => get_services_menu(), 'settings' => $this->get_settings(), + 'dashboardUrl' => admin_url( '/' ), ); wp_add_inline_script( diff --git a/src/js/settings/components/classifai-onboarding/enable-features.js b/src/js/settings/components/classifai-onboarding/enable-features.js index 87d69753f..e8af2b722 100644 --- a/src/js/settings/components/classifai-onboarding/enable-features.js +++ b/src/js/settings/components/classifai-onboarding/enable-features.js @@ -14,7 +14,7 @@ import { useSetupPage } from './hooks'; import { useNavigate } from 'react-router-dom'; export const EnableFeatures = () => { - const { features, services } = window.classifAISettings; + const { features, services, dashboardUrl } = window.classifAISettings; const { nextStepPath } = useSetupPage(); const navigate = useNavigate(); const onSaveSuccess = () => { @@ -110,7 +110,11 @@ export const EnableFeatures = () => { { featureToggles }
    - { - const { features: __settings, services } = window.classifAISettings; + const { + features: __settings, + services, + dashboardUrl, + } = window.classifAISettings; + const navigate = useNavigate(); const settingsState = useSelect( ( select ) => select( STORE_NAME ).getSettings() ); @@ -32,7 +38,7 @@ export const FinishStep = () => {

    { __( 'Welcome to ClassifAI', 'classifai' ) }

    -
    +
    { ); } ) }
    +
    + + +
    From 050e68b267d994d21dd5577d8433689e5ab223c7 Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Wed, 28 Aug 2024 19:39:41 +0530 Subject: [PATCH 074/138] Add registration step. --- .../classifai-registration.js | 30 +++++++++++++++++++ .../components/classifai-onboarding/index.js | 1 + .../classifai-registration/index.js | 21 +++++++++---- .../components/classifai-settings/index.js | 5 ++++ src/js/settings/utils/utils.js | 2 +- 5 files changed, 53 insertions(+), 6 deletions(-) create mode 100644 src/js/settings/components/classifai-onboarding/classifai-registration.js diff --git a/src/js/settings/components/classifai-onboarding/classifai-registration.js b/src/js/settings/components/classifai-onboarding/classifai-registration.js new file mode 100644 index 000000000..e206561c2 --- /dev/null +++ b/src/js/settings/components/classifai-onboarding/classifai-registration.js @@ -0,0 +1,30 @@ +import { Button, Fill } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; + +import { useNavigate } from 'react-router-dom'; +import { ClassifAIRegistrationForm } from '../classifai-registration'; +import { useSetupPage } from './hooks'; + +export const ClassifAIRegistrationStep = () => { + const navigate = useNavigate(); + const { nextStepPath } = useSetupPage(); + return ( + <> +

    + { __( 'Register ClassifAI', 'classifai' ) } +

    +
    +
    + navigate( nextStepPath ) } + /> + + + +
    +
    + + ); +}; diff --git a/src/js/settings/components/classifai-onboarding/index.js b/src/js/settings/components/classifai-onboarding/index.js index 106b17a25..9534f384f 100644 --- a/src/js/settings/components/classifai-onboarding/index.js +++ b/src/js/settings/components/classifai-onboarding/index.js @@ -4,6 +4,7 @@ import { Outlet } from 'react-router-dom'; export { EnableFeatures } from './enable-features'; export { ConfigureFeatures } from './configure-features'; export { FinishStep } from './finish-step'; +export { ClassifAIRegistrationStep } from './classifai-registration'; export const ClassifAIOnboarding = () => { return ( diff --git a/src/js/settings/components/classifai-registration/index.js b/src/js/settings/components/classifai-registration/index.js index 78a19e950..ac9ac48b4 100644 --- a/src/js/settings/components/classifai-registration/index.js +++ b/src/js/settings/components/classifai-registration/index.js @@ -7,6 +7,7 @@ import { PanelBody, Spinner, Button, + Slot, __experimentalInputControl as InputControl, // eslint-disable-line @wordpress/no-unsafe-wp-apis } from '@wordpress/components'; import { Notices } from '../feature-settings/notices'; @@ -17,7 +18,7 @@ import { useState, useEffect } from '@wordpress/element'; import { useDispatch, useSelect } from '@wordpress/data'; import { store as noticesStore } from '@wordpress/notices'; -const ClassifAIRegistrationForm = () => { +export const ClassifAIRegistrationForm = ( { onSaveSuccess = () => {} } ) => { const [ settings, setSettings ] = useState( {} ); const [ isLoaded, setIsLoaded ] = useState( false ); @@ -94,9 +95,13 @@ const ClassifAIRegistrationForm = () => {
    + + { ( fills ) => <>{ fills } } +
    @@ -106,12 +111,17 @@ const ClassifAIRegistrationForm = () => { /** * Save Settings Button component. * - * @param {Object} props Component props. - * @param {Object} props.settings Settings object. - * @param {Function} props.setSettings Set settings function. + * @param {Object} props Component props. + * @param {Object} props.settings Settings object. + * @param {Function} props.setSettings Set settings function. + * @param {Function} props.onSaveSuccess Callback function to be executed after saving settings. * @return {Object} SaveSettingsButton Component. */ -export const SaveSettingsButton = ( { settings, setSettings } ) => { +export const SaveSettingsButton = ( { + settings, + setSettings, + onSaveSuccess = () => {}, +} ) => { const { createErrorNotice, removeNotices } = useDispatch( noticesStore ); const notices = useSelect( ( select ) => select( noticesStore ).getNotices() @@ -142,6 +152,7 @@ export const SaveSettingsButton = ( { settings, setSettings } ) => { } setSettings( res.settings ); + onSaveSuccess(); setIsSaving( false ); } ) .catch( ( error ) => { diff --git a/src/js/settings/components/classifai-settings/index.js b/src/js/settings/components/classifai-settings/index.js index 40d9b47a4..2fea7ac9d 100644 --- a/src/js/settings/components/classifai-settings/index.js +++ b/src/js/settings/components/classifai-settings/index.js @@ -35,6 +35,7 @@ import { ConfigureFeatures, EnableFeatures, FinishStep, + ClassifAIRegistrationStep, } from '../classifai-onboarding'; import { useSetupPage } from '../classifai-onboarding/hooks'; @@ -171,6 +172,10 @@ export const ClassifAISettings = () => { path="enable_features" element={ } /> + } + /> } diff --git a/src/js/settings/utils/utils.js b/src/js/settings/utils/utils.js index 4b640e82e..7cf2beaf0 100644 --- a/src/js/settings/utils/utils.js +++ b/src/js/settings/utils/utils.js @@ -281,7 +281,7 @@ export const isFeatureActive = ( feature ) => { export const getOnboardingSteps = () => { return [ 'enable_features', - // 'classifai_registration', // TODO: Uncomment when registration is implemented + 'classifai_registration', 'configure_features', 'finish', ]; From 5923c70b4e7c662a441bd2dde3dea73f32f5cd75 Mon Sep 17 00:00:00 2001 From: Siddharth Thevaril Date: Thu, 29 Aug 2024 16:13:34 +0530 Subject: [PATCH 075/138] add previewer search control --- .../classification.js | 99 ++++++++++++++++++- src/scss/settings.scss | 50 ++++++++++ 2 files changed, 148 insertions(+), 1 deletion(-) diff --git a/src/js/settings/components/feature-additional-settings/classification.js b/src/js/settings/components/feature-additional-settings/classification.js index b65d0daaf..5bb278d3e 100644 --- a/src/js/settings/components/feature-additional-settings/classification.js +++ b/src/js/settings/components/feature-additional-settings/classification.js @@ -1,5 +1,7 @@ import { useSelect, useDispatch } from '@wordpress/data'; -import { RadioControl, CheckboxControl } from '@wordpress/components'; +import { RadioControl, CheckboxControl, Button, SearchControl, TextHighlight } from '@wordpress/components'; +import { useState, useEffect } from '@wordpress/element'; +import { useDebounce } from '@wordpress/compose'; import { __ } from '@wordpress/i18n'; import { SettingsRow } from '../settings-row'; import { STORE_NAME } from '../../data/store'; @@ -67,6 +69,12 @@ export const ClassificationSettings = () => { return ( <> + + + + { @@ -151,3 +159,92 @@ export const ClassificationSettings = () => { ); }; + +function Previewer() { + return ( +
    + +
    + ); +} + +function PostSelector() { + const [ searchText, setSearchText ] = useState( '' ); + const [ searchResults, setSearchResults ] = useState( [] ); + const [ selectedPostId, setSelectedPostId ] = useState( 0 ); + const [ shouldSearch, setShoudlSearch ] = useState( true ); + const debouncedSearch = useDebounce( setSearchText, 1000 ); + + function selectPost( post ) { + setShoudlSearch( false ); + setSelectedPostId( post.id ); + setSearchText( post.title ); + setSearchResults( [] ); + } + + useEffect( () => { + if ( ! searchText ) { + setSearchResults( [] ); + return; + } + + if ( ! shouldSearch ) { + return; + } + + ( async () => { + const response = await wp.apiRequest({ + path: '/wp/v2/posts', + data: { + search: searchText + } + } ); + + if ( Array.isArray( response ) ) { + setSearchResults( + response.map( post => ( { id: post.id, title: post.title.rendered } ) ) + ); + } + } )() + }, [ searchText, shouldSearch ] ); + + const searchResultsHtml = searchResults.length ? searchResults.map( ( post ) => ( +
    selectPost( post ) } + className='classifai__classification-previewer-search-item' + > + +
    + ) ) : []; + + return ( +
    +
    + { + setShoudlSearch( true ); + debouncedSearch( text ); + } } + onClose={ () => { + setSearchText( '' ); + setSearchResults( [] ); + setShoudlSearch( true ); + } } + /> + { + searchResults.length ? ( +
    + { searchResultsHtml } +
    + ) : null + } +
    + +
    + ); +} diff --git a/src/scss/settings.scss b/src/scss/settings.scss index 25e337ba8..9bfacf1b8 100644 --- a/src/scss/settings.scss +++ b/src/scss/settings.scss @@ -406,3 +406,53 @@ text-align: center; } } + +// Previewer. +.classifai__classification-previewer { + position: fixed; + top: 0; + right: 0; + width: 100%; + max-width: 500px; + height: 100%; + box-shadow: 0 19px 28px rgba(0,0,0,0.30), 0 15px 12px rgba(0,0,0,0.22); + backdrop-filter: blur(15px); + background-color: rgba(0, 0, 0, 0.2); + z-index: 99999; + padding: 4rem 1rem; +} + +.classifai__classification-previewer-search-control { + display: flex; + gap: 1rem; + width: 100%; + + & > div { + flex-grow: 1; + } +} + +.classifai__classification-previewer-search-and-results { + position: relative; + background-color: #fff; +} + +.classifai__classification-previewer-search-results { + position: absolute; + top: 32px; + left: 0; + width: 100%; + padding: 0.25rem 0; + height: auto; + background-color: #fff; +} + +.classifai__classification-previewer-search-item { + padding: 0.25rem 0.5rem; + + &:hover { + background-color: #777; + color: #fff; + cursor: pointer; + } +} From 0c320fd4fd26e7df0928d6e2b5f55b1b50e82463 Mon Sep 17 00:00:00 2001 From: Siddharth Thevaril Date: Thu, 29 Aug 2024 19:15:33 +0530 Subject: [PATCH 076/138] fetch results --- .../classification.js | 110 +++++++++++++++++- src/scss/settings.scss | 30 +++++ 2 files changed, 136 insertions(+), 4 deletions(-) diff --git a/src/js/settings/components/feature-additional-settings/classification.js b/src/js/settings/components/feature-additional-settings/classification.js index 5bb278d3e..3d266aed0 100644 --- a/src/js/settings/components/feature-additional-settings/classification.js +++ b/src/js/settings/components/feature-additional-settings/classification.js @@ -1,7 +1,18 @@ import { useSelect, useDispatch } from '@wordpress/data'; -import { RadioControl, CheckboxControl, Button, SearchControl, TextHighlight } from '@wordpress/components'; +import { + RadioControl, + CheckboxControl, + Button, + SearchControl, + TextHighlight, + Card, + CardHeader, + CardBody, + __experimentalHeading as Heading +} from '@wordpress/components'; import { useState, useEffect } from '@wordpress/element'; import { useDebounce } from '@wordpress/compose'; +import apiFetch from '@wordpress/api-fetch'; import { __ } from '@wordpress/i18n'; import { SettingsRow } from '../settings-row'; import { STORE_NAME } from '../../data/store'; @@ -161,17 +172,19 @@ export const ClassificationSettings = () => { }; function Previewer() { + const [ selectedPostId, setSelectedPostId ] = useState( 0 ); + return (
    - + +
    ); } -function PostSelector() { +function PostSelector( { setSelectedPostId } ) { const [ searchText, setSearchText ] = useState( '' ); const [ searchResults, setSearchResults ] = useState( [] ); - const [ selectedPostId, setSelectedPostId ] = useState( 0 ); const [ shouldSearch, setShoudlSearch ] = useState( true ); const debouncedSearch = useDebounce( setSearchText, 1000 ); @@ -232,6 +245,7 @@ function PostSelector() { } } onClose={ () => { setSearchText( '' ); + setSelectedPostId( 0 ); setSearchResults( [] ); setShoudlSearch( true ); } } @@ -248,3 +262,91 @@ function PostSelector() {
    ); } + +function PreviewerResults( { selectedPostId } ) { + const activeProvider = useSelect( ( select ) => select( STORE_NAME ).getFeatureSettings().provider ); + + if ( ! selectedPostId ) { + return null; + } + + if ( ! activeProvider ) { + return null; + } + + return ( +
    + { 'azure_openai_embeddings' === activeProvider && } +
    + ); +} + +function AzureOpenAIEmbeddingsResults( { postId } ) { + const [ responseData, setResponseData ] = useState( [] ); + + useEffect( () => { + if ( ! postId ) { + return; + } + + const formData = new FormData(); + + formData.append( 'post_id', postId ); + formData.append( + 'action', + 'get_post_classifier_embeddings_preview_data' + ); + // formData.append( 'nonce', previewerNonce ); + + ( async () => { + const response = await fetch( ajaxurl, { + method: 'POST', + body: formData, + } ); + + if ( ! response.ok ) { + return; + } + + const responseJSON = await response.json(); + + if ( responseJSON.success ) { + const flattenedResponse = responseJSON.data.reduce((acc, obj) => { + const [ key, value ] = Object.entries( obj )[0]; + acc[ key ] = value; + return acc; + }, {} ); + + setResponseData( flattenedResponse ); + } + } )() + }, [ postId ] ); + + const card = Object.keys( responseData ).map( ( taxLabel, index ) => { + const tags = responseData[ taxLabel ].map( ( tag, _index ) => ( +
    + { formatScore( tag.score ) } + { tag.label } +
    + ) ); + + return ( + + + + { taxLabel } + + + + { tags.length ? tags : __( `No classification data found for ${ taxLabel }`, 'classifai' ) } + + + ) + } ); + + return card.length ? card : null +} + +function formatScore( score ) { + return ( score * 100 ).toFixed( 2 ); +} \ No newline at end of file diff --git a/src/scss/settings.scss b/src/scss/settings.scss index 9bfacf1b8..1f46b10b0 100644 --- a/src/scss/settings.scss +++ b/src/scss/settings.scss @@ -447,6 +447,10 @@ background-color: #fff; } +.classifai__classification-previewer-search-result-container { + margin-top: 4rem; +} + .classifai__classification-previewer-search-item { padding: 0.25rem 0.5rem; @@ -456,3 +460,29 @@ cursor: pointer; } } + +.classifai__classification-previewer-result-card { + margin-bottom: 2rem; +} + +.classifai__classification-previewer-result-card-heading { + font-size: 1.15rem !important; +} + +.classifai__classification-previewer-result-tag { + display: inline-block; + box-shadow: 0 2px 3px rgba(0, 0, 0, 0.2); + margin-right: 0.5rem; + margin-bottom: 0.5rem; + border: 1px solid #eaeaea; +} + +.classifai__classification-previewer-result-tag-score { + display: inline-block; + padding: 0.25rem 0.5rem; +} + +.classifai__classification-previewer-result-tag-label { + display: inline-block; + padding: 0 0.5rem; +} \ No newline at end of file From 5da48606cc390d8f1f3dcc3eefedb0874cd949f2 Mon Sep 17 00:00:00 2001 From: Siddharth Thevaril Date: Thu, 29 Aug 2024 20:32:29 +0530 Subject: [PATCH 077/138] color code results --- .../classification.js | 48 +++++++-------- src/scss/settings.scss | 59 +++++++++++++------ 2 files changed, 67 insertions(+), 40 deletions(-) diff --git a/src/js/settings/components/feature-additional-settings/classification.js b/src/js/settings/components/feature-additional-settings/classification.js index 3d266aed0..1e90d2dd4 100644 --- a/src/js/settings/components/feature-additional-settings/classification.js +++ b/src/js/settings/components/feature-additional-settings/classification.js @@ -12,7 +12,6 @@ import { } from '@wordpress/components'; import { useState, useEffect } from '@wordpress/element'; import { useDebounce } from '@wordpress/compose'; -import apiFetch from '@wordpress/api-fetch'; import { __ } from '@wordpress/i18n'; import { SettingsRow } from '../settings-row'; import { STORE_NAME } from '../../data/store'; @@ -71,6 +70,7 @@ const ClassificationMethodSettings = () => { }; export const ClassificationSettings = () => { + const [ isPreviewerOpen, setIsPreviewerOpen ] = useState( false ); const featureSettings = useSelect( ( select ) => select( STORE_NAME ).getFeatureSettings() ); @@ -81,10 +81,10 @@ export const ClassificationSettings = () => { return ( <> - - + { ); }; -function Previewer() { +function Previewer( { isPreviewerOpen = false } ) { const [ selectedPostId, setSelectedPostId ] = useState( 0 ); return ( -
    +
    @@ -283,6 +283,7 @@ function PreviewerResults( { selectedPostId } ) { function AzureOpenAIEmbeddingsResults( { postId } ) { const [ responseData, setResponseData ] = useState( [] ); + const settings = useSelect( ( select ) => select( STORE_NAME ).getFeatureSettings() ); useEffect( () => { if ( ! postId ) { @@ -311,30 +312,31 @@ function AzureOpenAIEmbeddingsResults( { postId } ) { const responseJSON = await response.json(); if ( responseJSON.success ) { - const flattenedResponse = responseJSON.data.reduce((acc, obj) => { - const [ key, value ] = Object.entries( obj )[0]; - acc[ key ] = value; - return acc; - }, {} ); - - setResponseData( flattenedResponse ); + setResponseData( responseJSON.data ); } } )() }, [ postId ] ); - const card = Object.keys( responseData ).map( ( taxLabel, index ) => { - const tags = responseData[ taxLabel ].map( ( tag, _index ) => ( -
    - { formatScore( tag.score ) } - { tag.label } -
    - ) ); + const card = Object.keys( responseData ).map( ( taxSlug ) => { + const tags = responseData[ taxSlug ].data.map( ( tag, _index ) => { + const threshold = settings[ `${ taxSlug}_threshold` ]; + const score = normalizeScore( tag.score ); + + const scoreClass = score >= threshold ? 'classifai__classification-previewer-result-tag--exceeds-threshold' : ''; + + return ( +
    + { normalizeScore( tag.score ) }% + { tag.label } +
    + ) + } ); return ( - + - { taxLabel } + { responseData[ taxSlug ].label } @@ -347,6 +349,6 @@ function AzureOpenAIEmbeddingsResults( { postId } ) { return card.length ? card : null } -function formatScore( score ) { +function normalizeScore( score ) { return ( score * 100 ).toFixed( 2 ); } \ No newline at end of file diff --git a/src/scss/settings.scss b/src/scss/settings.scss index 1f46b10b0..966a20017 100644 --- a/src/scss/settings.scss +++ b/src/scss/settings.scss @@ -51,21 +51,21 @@ } .components-notice { - &, &__content { - margin: 0; - } + &, &__content { + margin: 0; + } &.personalizer-deprecation-notice { margin-bottom: 16px; } - } + } - .components-notice-list { - display: flex; - flex-direction: column; - gap: 8px; - margin-block-end: 16px; - } + .components-notice-list { + display: flex; + flex-direction: column; + gap: 8px; + margin-block-end: 16px; + } .settings-panel { .settings-row { @@ -417,9 +417,16 @@ height: 100%; box-shadow: 0 19px 28px rgba(0,0,0,0.30), 0 15px 12px rgba(0,0,0,0.22); backdrop-filter: blur(15px); - background-color: rgba(0, 0, 0, 0.2); + background-color: rgba(0, 0, 0, 0.2); z-index: 99999; padding: 4rem 1rem; + transform: translateX(500px); + transition: transform 0.2s ease-in-out; + box-sizing: border-box; + + &--open { + transform: translateX(0); + } } .classifai__classification-previewer-search-control { @@ -470,19 +477,37 @@ } .classifai__classification-previewer-result-tag { - display: inline-block; - box-shadow: 0 2px 3px rgba(0, 0, 0, 0.2); - margin-right: 0.5rem; - margin-bottom: 0.5rem; - border: 1px solid #eaeaea; + display: inline-block; + margin-right: 0.5rem; + margin-bottom: 0.5rem; + border: 1px solid #ccc; + border-radius: 4px; + overflow: hidden; } .classifai__classification-previewer-result-tag-score { display: inline-block; padding: 0.25rem 0.5rem; + border-right: 1px solid #ccc; + background-color: #ddd; } .classifai__classification-previewer-result-tag-label { display: inline-block; padding: 0 0.5rem; -} \ No newline at end of file +} + +.classifai__classification-previewer-result-tag--exceeds-threshold { + border-color: #37bb60; + font-weight: bold; + + .classifai__classification-previewer-result-tag-score { + color: #f1f1f1; + background-color: #37bb60; + border-right: 1px solid #37bb60; + } + + .classifai__classification-previewer-result-tag-label { + color: #37bb60; + } +} From ecdbea8e937b28372f65acb1599fb8a2a7749da0 Mon Sep 17 00:00:00 2001 From: Siddharth Thevaril Date: Thu, 29 Aug 2024 21:24:01 +0530 Subject: [PATCH 078/138] add context for previewer --- .../classification.js | 72 +++++++++++++++++-- src/scss/settings.scss | 12 +++- 2 files changed, 76 insertions(+), 8 deletions(-) diff --git a/src/js/settings/components/feature-additional-settings/classification.js b/src/js/settings/components/feature-additional-settings/classification.js index 1e90d2dd4..85925cbc0 100644 --- a/src/js/settings/components/feature-additional-settings/classification.js +++ b/src/js/settings/components/feature-additional-settings/classification.js @@ -8,9 +8,10 @@ import { Card, CardHeader, CardBody, + Spinner, __experimentalHeading as Heading } from '@wordpress/components'; -import { useState, useEffect } from '@wordpress/element'; +import { useState, useEffect, createContext, useContext } from '@wordpress/element'; import { useDebounce } from '@wordpress/compose'; import { __ } from '@wordpress/i18n'; import { SettingsRow } from '../settings-row'; @@ -69,8 +70,23 @@ const ClassificationMethodSettings = () => { ); }; +const PreviewerProviderContext = createContext( { + isPreviewerOpen: false +} ); + +function PreviewerProvider( { children, value } ) { + return ( + + { children } + + ) +} + export const ClassificationSettings = () => { const [ isPreviewerOpen, setIsPreviewerOpen ] = useState( false ); + const [ selectedPostId, setSelectedPostId ] = useState( 0 ); + const [ isPreviewUnderProcess, setPreviewUnderProcess ] = useState( null ); + const featureSettings = useSelect( ( select ) => select( STORE_NAME ).getFeatureSettings() ); @@ -78,13 +94,23 @@ export const ClassificationSettings = () => { const { postTypesSelectOptions } = usePostTypes(); const { postStatusOptions } = usePostStatuses(); + const previewerContextData = { + isPreviewerOpen, + selectedPostId, + setSelectedPostId, + isPreviewUnderProcess, + setPreviewUnderProcess, + }; + return ( <> - + + + { ); }; -function Previewer( { isPreviewerOpen = false } ) { - const [ selectedPostId, setSelectedPostId ] = useState( 0 ); +function Previewer() { + const { + isPreviewerOpen, + selectedPostId, + setSelectedPostId, + } = useContext( PreviewerProviderContext ); return (
    +
    ); } +function PreviewInProcess() { + const { isPreviewUnderProcess } = useContext( PreviewerProviderContext ); + + if ( ! isPreviewUnderProcess ) { + return null; + } + + return ( +
    + +
    + ) +} + function PostSelector( { setSelectedPostId } ) { const [ searchText, setSearchText ] = useState( '' ); const [ searchResults, setSearchResults ] = useState( [] ); @@ -258,7 +308,7 @@ function PostSelector( { setSelectedPostId } ) { ) : null }
    - + {/* */}
    ); } @@ -282,6 +332,10 @@ function PreviewerResults( { selectedPostId } ) { } function AzureOpenAIEmbeddingsResults( { postId } ) { + const { + setPreviewUnderProcess + } = useContext( PreviewerProviderContext ); + const [ responseData, setResponseData ] = useState( [] ); const settings = useSelect( ( select ) => select( STORE_NAME ).getFeatureSettings() ); @@ -290,6 +344,8 @@ function AzureOpenAIEmbeddingsResults( { postId } ) { return; } + setPreviewUnderProcess( true ); + const formData = new FormData(); formData.append( 'post_id', postId ); @@ -314,6 +370,8 @@ function AzureOpenAIEmbeddingsResults( { postId } ) { if ( responseJSON.success ) { setResponseData( responseJSON.data ); } + + setPreviewUnderProcess( false ); } )() }, [ postId ] ); @@ -350,5 +408,5 @@ function AzureOpenAIEmbeddingsResults( { postId } ) { } function normalizeScore( score ) { - return ( score * 100 ).toFixed( 2 ); -} \ No newline at end of file + return Number( ( score * 100 ).toFixed( 2 ) ); +} diff --git a/src/scss/settings.scss b/src/scss/settings.scss index 966a20017..2806f8e0c 100644 --- a/src/scss/settings.scss +++ b/src/scss/settings.scss @@ -454,8 +454,18 @@ background-color: #fff; } +.classifai__classification-previewer-processing { + margin: 2rem auto; + width: 48px; + height: 48px; + + svg { + margin: 0; + } +} + .classifai__classification-previewer-search-result-container { - margin-top: 4rem; + margin-top: 3rem; } .classifai__classification-previewer-search-item { From 2fa4887d898dfacd726610c30c7c4ba461e0782d Mon Sep 17 00:00:00 2001 From: Siddharth Thevaril Date: Thu, 29 Aug 2024 23:00:50 +0530 Subject: [PATCH 079/138] add notice --- .../classification.js | 35 ++++++++++++------- src/scss/settings.scss | 19 +++++++--- 2 files changed, 38 insertions(+), 16 deletions(-) diff --git a/src/js/settings/components/feature-additional-settings/classification.js b/src/js/settings/components/feature-additional-settings/classification.js index 85925cbc0..da5c37113 100644 --- a/src/js/settings/components/feature-additional-settings/classification.js +++ b/src/js/settings/components/feature-additional-settings/classification.js @@ -2,13 +2,14 @@ import { useSelect, useDispatch } from '@wordpress/data'; import { RadioControl, CheckboxControl, - Button, + BaseControl, SearchControl, TextHighlight, Card, CardHeader, CardBody, Spinner, + Notice, __experimentalHeading as Heading } from '@wordpress/components'; import { useState, useEffect, createContext, useContext } from '@wordpress/element'; @@ -96,6 +97,7 @@ export const ClassificationSettings = () => { const previewerContextData = { isPreviewerOpen, + setIsPreviewerOpen, selectedPostId, setSelectedPostId, isPreviewUnderProcess, @@ -105,10 +107,10 @@ export const ClassificationSettings = () => { return ( <> - + + + @@ -201,12 +203,11 @@ function Previewer() { const { isPreviewerOpen, selectedPostId, - setSelectedPostId, } = useContext( PreviewerProviderContext ); return (
    - +
    @@ -232,7 +233,8 @@ function PreviewInProcess() { ) } -function PostSelector( { setSelectedPostId } ) { +function PostSelector( { placeholder = '', showLabel = true } ) { + const { setSelectedPostId } = useContext( PreviewerProviderContext ); const [ searchText, setSearchText ] = useState( '' ); const [ searchResults, setSearchResults ] = useState( [] ); const [ shouldSearch, setShoudlSearch ] = useState( true ); @@ -286,9 +288,10 @@ function PostSelector( { setSelectedPostId } ) {
    { setShoudlSearch( true ); debouncedSearch( text ); @@ -308,7 +311,6 @@ function PostSelector( { setSelectedPostId } ) { ) : null }
    - {/* */}
    ); } @@ -333,7 +335,8 @@ function PreviewerResults( { selectedPostId } ) { function AzureOpenAIEmbeddingsResults( { postId } ) { const { - setPreviewUnderProcess + setPreviewUnderProcess, + setIsPreviewerOpen, } = useContext( PreviewerProviderContext ); const [ responseData, setResponseData ] = useState( [] ); @@ -345,6 +348,7 @@ function AzureOpenAIEmbeddingsResults( { postId } ) { } setPreviewUnderProcess( true ); + setIsPreviewerOpen( true ); const formData = new FormData(); @@ -404,7 +408,14 @@ function AzureOpenAIEmbeddingsResults( { postId } ) { ) } ); - return card.length ? card : null + return card.length ? ( + <> + + { __( 'Results for each taxonomy are sorted in descending order, starting with the term that has the highest score, indicating the best match based on the embedding data.', 'classifai' ) } + + { card } + + ) : null } function normalizeScore( score ) { diff --git a/src/scss/settings.scss b/src/scss/settings.scss index 2806f8e0c..fb65e0eb4 100644 --- a/src/scss/settings.scss +++ b/src/scss/settings.scss @@ -416,9 +416,9 @@ max-width: 500px; height: 100%; box-shadow: 0 19px 28px rgba(0,0,0,0.30), 0 15px 12px rgba(0,0,0,0.22); - backdrop-filter: blur(15px); - background-color: rgba(0, 0, 0, 0.2); - z-index: 99999; + backdrop-filter: blur(10px); + background-color: rgba(0, 0, 0, 0.15); + z-index: 9999; padding: 4rem 1rem; transform: translateX(500px); transition: transform 0.2s ease-in-out; @@ -429,6 +429,10 @@ } } +.classifai__classification-previewer-result-notice { + margin-bottom: 2rem !important; +} + .classifai__classification-previewer-search-control { display: flex; gap: 1rem; @@ -442,16 +446,23 @@ .classifai__classification-previewer-search-and-results { position: relative; background-color: #fff; + + .components-input-control__container { + border: 1px solid rgb(148 148 148); + } } .classifai__classification-previewer-search-results { position: absolute; - top: 32px; + top: 64px; left: 0; width: 100%; padding: 0.25rem 0; height: auto; background-color: #fff; + border: 1px solid rgb(148 148 148); + box-sizing: border-box; + z-index: 999; } .classifai__classification-previewer-processing { From 1186849fef7014ec0ee007851377022ee5c1a51f Mon Sep 17 00:00:00 2001 From: Siddharth Thevaril Date: Fri, 30 Aug 2024 15:29:48 +0530 Subject: [PATCH 080/138] add close previewer button --- .../feature-additional-settings/classification.js | 14 ++++++++++++-- src/scss/settings.scss | 13 ++++++++++++- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/src/js/settings/components/feature-additional-settings/classification.js b/src/js/settings/components/feature-additional-settings/classification.js index da5c37113..37343e939 100644 --- a/src/js/settings/components/feature-additional-settings/classification.js +++ b/src/js/settings/components/feature-additional-settings/classification.js @@ -10,6 +10,7 @@ import { CardBody, Spinner, Notice, + Button, __experimentalHeading as Heading } from '@wordpress/components'; import { useState, useEffect, createContext, useContext } from '@wordpress/element'; @@ -202,6 +203,7 @@ export const ClassificationSettings = () => { function Previewer() { const { isPreviewerOpen, + setIsPreviewerOpen, selectedPostId, } = useContext( PreviewerProviderContext ); @@ -209,7 +211,14 @@ function Previewer() {
    - + +
    ); } @@ -315,7 +324,8 @@ function PostSelector( { placeholder = '', showLabel = true } ) { ); } -function PreviewerResults( { selectedPostId } ) { +function PreviewerResults() { + const { selectedPostId } = useContext( PreviewerProviderContext ); const activeProvider = useSelect( ( select ) => select( STORE_NAME ).getFeatureSettings().provider ); if ( ! selectedPostId ) { diff --git a/src/scss/settings.scss b/src/scss/settings.scss index fb65e0eb4..40a87a86f 100644 --- a/src/scss/settings.scss +++ b/src/scss/settings.scss @@ -420,10 +420,14 @@ background-color: rgba(0, 0, 0, 0.15); z-index: 9999; padding: 4rem 1rem; + padding-bottom: 2.5rem; transform: translateX(500px); transition: transform 0.2s ease-in-out; box-sizing: border-box; + display: flex; + flex-direction: column; + &--open { transform: translateX(0); } @@ -454,7 +458,6 @@ .classifai__classification-previewer-search-results { position: absolute; - top: 64px; left: 0; width: 100%; padding: 0.25rem 0; @@ -477,6 +480,8 @@ .classifai__classification-previewer-search-result-container { margin-top: 3rem; + flex-grow: 1; + overflow-y: auto; } .classifai__classification-previewer-search-item { @@ -532,3 +537,9 @@ color: #37bb60; } } + +.components-button.classifai__classification-previewer-close-button.is-link { + position: relative; + margin: 0 auto; + top: 1rem; +} From 8f1d26a40347c4c3ac1c7e7aba5fae7b6b7bc61c Mon Sep 17 00:00:00 2001 From: Siddharth Thevaril Date: Fri, 30 Aug 2024 15:36:46 +0530 Subject: [PATCH 081/138] modify embeddings result data format --- includes/Classifai/Admin/Settings.php | 7 ++++--- includes/Classifai/Providers/Azure/Embeddings.php | 14 ++++++-------- .../feature-additional-settings/classification.js | 3 ++- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/includes/Classifai/Admin/Settings.php b/includes/Classifai/Admin/Settings.php index bd5146e9c..24c8903f2 100644 --- a/includes/Classifai/Admin/Settings.php +++ b/includes/Classifai/Admin/Settings.php @@ -68,10 +68,11 @@ public function admin_enqueue_scripts( $hook_suffix ) { wp_set_script_translations( 'classifai-settings', 'classifai' ); $data = array( - 'features' => $this->get_features(), - 'services' => get_services_menu(), - 'settings' => $this->get_settings(), + 'features' => $this->get_features(), + 'services' => get_services_menu(), + 'settings' => $this->get_settings(), 'dashboardUrl' => admin_url( '/' ), + 'nonce' => wp_create_nonce( 'classifai-previewer-action' ), ); wp_add_inline_script( diff --git a/includes/Classifai/Providers/Azure/Embeddings.php b/includes/Classifai/Providers/Azure/Embeddings.php index 51de3d252..1939fa2a0 100644 --- a/includes/Classifai/Providers/Azure/Embeddings.php +++ b/includes/Classifai/Providers/Azure/Embeddings.php @@ -571,7 +571,6 @@ function ( $a, $b ) { } // Prepare the results. - $index = 0; $results = []; foreach ( $sorted_results as $tax => $terms ) { @@ -579,23 +578,22 @@ function ( $a, $b ) { $taxonomy = get_taxonomy( $tax ); $tax_name = $taxonomy->labels->singular_name; - // Setup our taxonomy object. - $results[] = new \stdClass(); - - $results[ $index ]->{$tax_name} = []; + // Initialize the taxonomy bucket in results. + $results[ $tax ] = [ + 'label' => $tax_name, + 'data' => [] + ]; foreach ( $terms as $term ) { // Convert $similarity to percentage. $similarity = round( ( 1 - $term['similarity'] ), 10 ); // Store the results. - $results[ $index ]->{$tax_name}[] = [ // phpcs:ignore Squiz.PHP.DisallowMultipleAssignments.Found + $results[ $tax ]['data'][] = [ 'label' => get_term( $term['term_id'] )->name, 'score' => $similarity, ]; } - - ++$index; } return $results; diff --git a/src/js/settings/components/feature-additional-settings/classification.js b/src/js/settings/components/feature-additional-settings/classification.js index 37343e939..35cd8f333 100644 --- a/src/js/settings/components/feature-additional-settings/classification.js +++ b/src/js/settings/components/feature-additional-settings/classification.js @@ -367,7 +367,8 @@ function AzureOpenAIEmbeddingsResults( { postId } ) { 'action', 'get_post_classifier_embeddings_preview_data' ); - // formData.append( 'nonce', previewerNonce ); + + formData.append( 'nonce', classifAISettings.nonce ); ( async () => { const response = await fetch( ajaxurl, { From 8ffd0f09064228f661753ebe475cdc2618de110d Mon Sep 17 00:00:00 2001 From: Siddharth Thevaril Date: Fri, 30 Aug 2024 18:22:55 +0530 Subject: [PATCH 082/138] add support for OpenAIEmbedding --- .../Classifai/Providers/Azure/Embeddings.php | 6 ++++- .../Classifai/Providers/OpenAI/Embeddings.php | 20 +++++++++-------- .../classification.js | 22 ++++++++++++++++++- 3 files changed, 37 insertions(+), 11 deletions(-) diff --git a/includes/Classifai/Providers/Azure/Embeddings.php b/includes/Classifai/Providers/Azure/Embeddings.php index 1939fa2a0..58429bcab 100644 --- a/includes/Classifai/Providers/Azure/Embeddings.php +++ b/includes/Classifai/Providers/Azure/Embeddings.php @@ -358,9 +358,13 @@ public function get_post_classifier_embeddings_preview_data(): array { // Add terms to this item based on embedding data. if ( $embeddings && ! is_wp_error( $embeddings ) ) { $embeddings_terms = $this->get_terms( $embeddings ); + + if ( is_wp_error( $embeddings_terms ) ) { + wp_send_json_error( $embeddings_terms->get_error_message() ); + } } - return wp_send_json_success( $embeddings_terms ); + wp_send_json_success( $embeddings_terms ); } /** diff --git a/includes/Classifai/Providers/OpenAI/Embeddings.php b/includes/Classifai/Providers/OpenAI/Embeddings.php index 51f058ec2..ed0e0dbaa 100644 --- a/includes/Classifai/Providers/OpenAI/Embeddings.php +++ b/includes/Classifai/Providers/OpenAI/Embeddings.php @@ -470,9 +470,13 @@ public function get_post_classifier_embeddings_preview_data(): array { // Add terms to this item based on embedding data. if ( $embeddings && ! is_wp_error( $embeddings ) ) { $embeddings_terms = $this->get_terms( $embeddings ); + + if ( is_wp_error( $embeddings_terms ) ) { + wp_send_json_error( $embeddings_terms->get_error_message() ); + } } - return wp_send_json_success( $embeddings_terms ); + wp_send_json_success( $embeddings_terms ); } /** @@ -683,7 +687,6 @@ function ( $a, $b ) { } // Prepare the results. - $index = 0; $results = []; foreach ( $sorted_results as $tax => $terms ) { @@ -691,23 +694,22 @@ function ( $a, $b ) { $taxonomy = get_taxonomy( $tax ); $tax_name = $taxonomy->labels->singular_name; - // Setup our taxonomy object. - $results[] = new \stdClass(); - - $results[ $index ]->{$tax_name} = []; + // Initialize the taxonomy bucket in results. + $results[ $tax ] = [ + 'label' => $tax_name, + 'data' => [] + ]; foreach ( $terms as $term ) { // Convert $similarity to percentage. $similarity = round( ( 1 - $term['similarity'] ), 10 ); // Store the results. - $results[ $index ]->{$tax_name}[] = [ // phpcs:ignore Squiz.PHP.DisallowMultipleAssignments.Found + $results[ $tax ]['data'][] = [ 'label' => get_term( $term['term_id'] )->name, 'score' => $similarity, ]; } - - ++$index; } return $results; diff --git a/src/js/settings/components/feature-additional-settings/classification.js b/src/js/settings/components/feature-additional-settings/classification.js index 35cd8f333..8117d374f 100644 --- a/src/js/settings/components/feature-additional-settings/classification.js +++ b/src/js/settings/components/feature-additional-settings/classification.js @@ -338,20 +338,29 @@ function PreviewerResults() { return (
    - { 'azure_openai_embeddings' === activeProvider && } + { 'azure_openai_embeddings' === activeProvider || 'openai_embeddings' === activeProvider && }
    ); } function AzureOpenAIEmbeddingsResults( { postId } ) { const { + isPreviewUnderProcess, setPreviewUnderProcess, setIsPreviewerOpen, } = useContext( PreviewerProviderContext ); const [ responseData, setResponseData ] = useState( [] ); + const [ errorMessage, setErrorMessage ] = useState( '' ); const settings = useSelect( ( select ) => select( STORE_NAME ).getFeatureSettings() ); + useEffect( () => { + // Reset previous results. + if ( isPreviewUnderProcess ) { + setResponseData( [] ); + } + }, [ isPreviewUnderProcess ] ); + useEffect( () => { if ( ! postId ) { return; @@ -359,6 +368,7 @@ function AzureOpenAIEmbeddingsResults( { postId } ) { setPreviewUnderProcess( true ); setIsPreviewerOpen( true ); + setErrorMessage( '' ); const formData = new FormData(); @@ -384,6 +394,8 @@ function AzureOpenAIEmbeddingsResults( { postId } ) { if ( responseJSON.success ) { setResponseData( responseJSON.data ); + } else { + setErrorMessage( responseJSON.data ); } setPreviewUnderProcess( false ); @@ -419,6 +431,14 @@ function AzureOpenAIEmbeddingsResults( { postId } ) { ) } ); + if ( errorMessage ) { + return ( + + { errorMessage } + + ); + } + return card.length ? ( <> From b0eb75cfd05ef94b85b0151ea66b1dde166904e7 Mon Sep 17 00:00:00 2001 From: Siddharth Thevaril Date: Sat, 31 Aug 2024 17:08:13 +0530 Subject: [PATCH 083/138] add previewer for IBMWatson --- .../classification.js | 122 +++++++++++++++++- 1 file changed, 120 insertions(+), 2 deletions(-) diff --git a/src/js/settings/components/feature-additional-settings/classification.js b/src/js/settings/components/feature-additional-settings/classification.js index 8117d374f..33b3b5dcc 100644 --- a/src/js/settings/components/feature-additional-settings/classification.js +++ b/src/js/settings/components/feature-additional-settings/classification.js @@ -339,6 +339,7 @@ function PreviewerResults() { return (
    { 'azure_openai_embeddings' === activeProvider || 'openai_embeddings' === activeProvider && } + { 'ibm_watson_nlu' === activeProvider && }
    ); } @@ -411,7 +412,7 @@ function AzureOpenAIEmbeddingsResults( { postId } ) { return (
    - { normalizeScore( tag.score ) }% + { score }% { tag.label }
    ) @@ -425,7 +426,7 @@ function AzureOpenAIEmbeddingsResults( { postId } ) { - { tags.length ? tags : __( `No classification data found for ${ taxLabel }`, 'classifai' ) } + { tags.length ? tags : __( `No classification data found for ${ taxLabel }.`, 'classifai' ) } ) @@ -449,6 +450,123 @@ function AzureOpenAIEmbeddingsResults( { postId } ) { ) : null } +function IBMWatsonNLUResults( { postId } ) { + const { + isPreviewUnderProcess, + setPreviewUnderProcess, + setIsPreviewerOpen, + } = useContext( PreviewerProviderContext ); + + const [ responseData, setResponseData ] = useState( null ); + const [ errorMessage, setErrorMessage ] = useState( '' ); + const settings = useSelect( ( select ) => select( STORE_NAME ).getFeatureSettings() ); + + const taxMap = { + categories: 'category', + concepts: 'concept', + entities: 'entity', + keywords: 'keyword', + }; + + function formatLabel( label ) { + return label + .split( '/' ) + .filter( ( i ) => '' !== i ) + .join( ', ' ); + } + + useEffect( () => { + // Reset previous results. + if ( isPreviewUnderProcess ) { + setResponseData( null ); + } + }, [ isPreviewUnderProcess ] ); + + useEffect( () => { + if ( ! postId ) { + return; + } + + setPreviewUnderProcess( true ); + setIsPreviewerOpen( true ); + + const formData = new FormData(); + + formData.append( 'action', 'get_post_classifier_preview_data' ); + formData.append( 'post_id', postId ); + formData.append( 'nonce', classifAISettings.nonce ); + + ( async () => { + const response = await fetch( ajaxurl, { + method: 'POST', + body: formData, + } ); + + if ( ! response.ok ) { + return; + } + + const responseJSON = await response.json(); + if ( responseJSON.success ) { + setResponseData( responseJSON.data ); + } else { + setErrorMessage( responseJSON.data ); + } + + setPreviewUnderProcess( false ); + } )() + }, [] ); + + if ( ! responseData ) { + return null; + } + + const renderData = { + categories: responseData.categories, + concepts: responseData.concepts, + entities: responseData.entities, + keywords: responseData.keywords, + }; + + const card = Object.keys( renderData ).map( ( taxSlug ) => { + const tags = renderData[ taxSlug ].map( ( tag, _index ) => { + const threshold = settings[ `${ taxMap[ taxSlug ] }_threshold` ]; + const score = normalizeScore( tag.score || tag.relevance ); + + const scoreClass = score >= threshold ? 'classifai__classification-previewer-result-tag--exceeds-threshold' : ''; + + return ( +
    + { score }% + { formatLabel( tag.label || tag.text ) } +
    + ); + } ); + + return ( + + + + { taxSlug } + + + + { tags.length ? tags : __( `No classification data found for ${ taxSlug }.`, 'classifai' ) } + + + ) + } ); + + return card.length ? ( + <> + + { __( 'Results for each category are sorted in descending order, starting with the term that has the highest score, indicating the best match based on the embedding data.', 'classifai' ) } + + { card } + + ) : null +} + function normalizeScore( score ) { return Number( ( score * 100 ).toFixed( 2 ) ); } From 1c0c2859e766fc15e1fce88749619e7d2c374a71 Mon Sep 17 00:00:00 2001 From: Siddharth Thevaril Date: Sun, 1 Sep 2024 08:46:50 +0530 Subject: [PATCH 084/138] move provider previewers into separate components --- .../classification-previewers/context.js | 5 + .../ibm-watson-nlu.js | 131 ++++++++++ .../classification-previewers/index.js | 2 + .../openai-embedding.js | 120 +++++++++ .../classification-previewers/utils.js | 3 + .../classification.js | 238 +----------------- 6 files changed, 263 insertions(+), 236 deletions(-) create mode 100644 src/js/settings/components/feature-additional-settings/classification-previewers/context.js create mode 100644 src/js/settings/components/feature-additional-settings/classification-previewers/ibm-watson-nlu.js create mode 100644 src/js/settings/components/feature-additional-settings/classification-previewers/index.js create mode 100644 src/js/settings/components/feature-additional-settings/classification-previewers/openai-embedding.js create mode 100644 src/js/settings/components/feature-additional-settings/classification-previewers/utils.js diff --git a/src/js/settings/components/feature-additional-settings/classification-previewers/context.js b/src/js/settings/components/feature-additional-settings/classification-previewers/context.js new file mode 100644 index 000000000..62ca4c498 --- /dev/null +++ b/src/js/settings/components/feature-additional-settings/classification-previewers/context.js @@ -0,0 +1,5 @@ +import { createContext } from '@wordpress/element'; + +export const PreviewerProviderContext = createContext( { + isPreviewerOpen: false +} ); diff --git a/src/js/settings/components/feature-additional-settings/classification-previewers/ibm-watson-nlu.js b/src/js/settings/components/feature-additional-settings/classification-previewers/ibm-watson-nlu.js new file mode 100644 index 000000000..71826ec30 --- /dev/null +++ b/src/js/settings/components/feature-additional-settings/classification-previewers/ibm-watson-nlu.js @@ -0,0 +1,131 @@ +import { + Card, + CardHeader, + CardBody, + Notice, + __experimentalHeading as Heading +} from '@wordpress/components'; +import { useState, useEffect, useContext } from '@wordpress/element'; +import { useSelect } from '@wordpress/data'; +import { normalizeScore } from './utils'; +import { PreviewerProviderContext } from './context'; +import { __ } from '@wordpress/i18n'; + +import { STORE_NAME } from '../../../data/store'; + +export function IBMWatsonNLUResults( { postId } ) { + const { + isPreviewUnderProcess, + setPreviewUnderProcess, + setIsPreviewerOpen, + } = useContext( PreviewerProviderContext ); + + const [ responseData, setResponseData ] = useState( null ); + const [ errorMessage, setErrorMessage ] = useState( '' ); + const settings = useSelect( ( select ) => select( STORE_NAME ).getFeatureSettings() ); + + const taxMap = { + categories: 'category', + concepts: 'concept', + entities: 'entity', + keywords: 'keyword', + }; + + function formatLabel( label ) { + return label + .split( '/' ) + .filter( ( i ) => '' !== i ) + .join( ', ' ); + } + + useEffect( () => { + // Reset previous results. + if ( isPreviewUnderProcess ) { + setResponseData( null ); + } + }, [ isPreviewUnderProcess ] ); + + useEffect( () => { + if ( ! postId ) { + return; + } + + setPreviewUnderProcess( true ); + setIsPreviewerOpen( true ); + + const formData = new FormData(); + + formData.append( 'action', 'get_post_classifier_preview_data' ); + formData.append( 'post_id', postId ); + formData.append( 'nonce', classifAISettings.nonce ); + + ( async () => { + const response = await fetch( ajaxurl, { + method: 'POST', + body: formData, + } ); + + if ( ! response.ok ) { + return; + } + + const responseJSON = await response.json(); + if ( responseJSON.success ) { + setResponseData( responseJSON.data ); + } else { + setErrorMessage( responseJSON.data ); + } + + setPreviewUnderProcess( false ); + } )() + }, [] ); + + if ( ! responseData ) { + return null; + } + + const renderData = { + categories: responseData.categories, + concepts: responseData.concepts, + entities: responseData.entities, + keywords: responseData.keywords, + }; + + const card = Object.keys( renderData ).map( ( taxSlug ) => { + const tags = renderData[ taxSlug ].map( ( tag, _index ) => { + const threshold = settings[ `${ taxMap[ taxSlug ] }_threshold` ]; + const score = normalizeScore( tag.score || tag.relevance ); + + const scoreClass = score >= threshold ? 'classifai__classification-previewer-result-tag--exceeds-threshold' : ''; + + return ( +
    + { score }% + { formatLabel( tag.label || tag.text ) } +
    + ); + } ); + + return ( + + + + { taxSlug } + + + + { tags.length ? tags : __( `No classification data found for ${ taxSlug }.`, 'classifai' ) } + + + ) + } ); + + return card.length ? ( + <> + + { __( 'Results for each category are sorted in descending order, starting with the term that has the highest score, indicating the best match based on the embedding data.', 'classifai' ) } + + { card } + + ) : null +} diff --git a/src/js/settings/components/feature-additional-settings/classification-previewers/index.js b/src/js/settings/components/feature-additional-settings/classification-previewers/index.js new file mode 100644 index 000000000..e0b279d0c --- /dev/null +++ b/src/js/settings/components/feature-additional-settings/classification-previewers/index.js @@ -0,0 +1,2 @@ +export * from './ibm-watson-nlu'; +export * from './openai-embedding'; diff --git a/src/js/settings/components/feature-additional-settings/classification-previewers/openai-embedding.js b/src/js/settings/components/feature-additional-settings/classification-previewers/openai-embedding.js new file mode 100644 index 000000000..bab0ee4f1 --- /dev/null +++ b/src/js/settings/components/feature-additional-settings/classification-previewers/openai-embedding.js @@ -0,0 +1,120 @@ +import { + Card, + CardHeader, + CardBody, + Notice, + __experimentalHeading as Heading +} from '@wordpress/components'; +import { useState, useEffect, useContext } from '@wordpress/element'; +import { useSelect } from '@wordpress/data'; +import { normalizeScore } from './utils'; +import { PreviewerProviderContext } from './context' +import { __ } from '@wordpress/i18n'; + +import { STORE_NAME } from '../../../data/store'; + +export function AzureOpenAIEmbeddingsResults( { postId } ) { + const { + isPreviewUnderProcess, + setPreviewUnderProcess, + setIsPreviewerOpen, + } = useContext( PreviewerProviderContext ); + + const [ responseData, setResponseData ] = useState( [] ); + const [ errorMessage, setErrorMessage ] = useState( '' ); + const settings = useSelect( ( select ) => select( STORE_NAME ).getFeatureSettings() ); + + useEffect( () => { + // Reset previous results. + if ( isPreviewUnderProcess ) { + setResponseData( [] ); + } + }, [ isPreviewUnderProcess ] ); + + useEffect( () => { + if ( ! postId ) { + return; + } + + setPreviewUnderProcess( true ); + setIsPreviewerOpen( true ); + setErrorMessage( '' ); + + const formData = new FormData(); + + formData.append( 'post_id', postId ); + formData.append( + 'action', + 'get_post_classifier_embeddings_preview_data' + ); + + formData.append( 'nonce', classifAISettings.nonce ); + + ( async () => { + const response = await fetch( ajaxurl, { + method: 'POST', + body: formData, + } ); + + if ( ! response.ok ) { + return; + } + + const responseJSON = await response.json(); + + if ( responseJSON.success ) { + setResponseData( responseJSON.data ); + } else { + setErrorMessage( responseJSON.data ); + } + + setPreviewUnderProcess( false ); + } )() + }, [ postId ] ); + + const card = Object.keys( responseData ).map( ( taxSlug ) => { + const tags = responseData[ taxSlug ].data.map( ( tag, _index ) => { + const threshold = settings[ `${ taxSlug}_threshold` ]; + const score = normalizeScore( tag.score ); + + const scoreClass = score >= threshold ? 'classifai__classification-previewer-result-tag--exceeds-threshold' : ''; + + return ( +
    + { score }% + { tag.label } +
    + ) + } ); + + return ( + + + + { responseData[ taxSlug ].label } + + + + { tags.length ? tags : __( `No classification data found for ${ taxLabel }.`, 'classifai' ) } + + + ) + } ); + + if ( errorMessage ) { + return ( + + { errorMessage } + + ); + } + + return card.length ? ( + <> + + { __( 'Results for each taxonomy are sorted in descending order, starting with the term that has the highest score, indicating the best match based on the embedding data.', 'classifai' ) } + + { card } + + ) : null +} diff --git a/src/js/settings/components/feature-additional-settings/classification-previewers/utils.js b/src/js/settings/components/feature-additional-settings/classification-previewers/utils.js new file mode 100644 index 000000000..715b1486e --- /dev/null +++ b/src/js/settings/components/feature-additional-settings/classification-previewers/utils.js @@ -0,0 +1,3 @@ +export function normalizeScore( score ) { + return Number( ( score * 100 ).toFixed( 2 ) ); +} diff --git a/src/js/settings/components/feature-additional-settings/classification.js b/src/js/settings/components/feature-additional-settings/classification.js index 33b3b5dcc..3b5d6d333 100644 --- a/src/js/settings/components/feature-additional-settings/classification.js +++ b/src/js/settings/components/feature-additional-settings/classification.js @@ -5,13 +5,8 @@ import { BaseControl, SearchControl, TextHighlight, - Card, - CardHeader, - CardBody, Spinner, - Notice, Button, - __experimentalHeading as Heading } from '@wordpress/components'; import { useState, useEffect, createContext, useContext } from '@wordpress/element'; import { useDebounce } from '@wordpress/compose'; @@ -20,6 +15,8 @@ import { SettingsRow } from '../settings-row'; import { STORE_NAME } from '../../data/store'; import { usePostTypes, usePostStatuses } from '../../utils/utils'; import { NLUFeatureSettings } from './nlu-feature'; +import { AzureOpenAIEmbeddingsResults, IBMWatsonNLUResults } from './classification-previewers'; +import { PreviewerProviderContext } from './classification-previewers/context'; const ClassificationMethodSettings = () => { const featureSettings = useSelect( ( select ) => @@ -72,10 +69,6 @@ const ClassificationMethodSettings = () => { ); }; -const PreviewerProviderContext = createContext( { - isPreviewerOpen: false -} ); - function PreviewerProvider( { children, value } ) { return ( @@ -343,230 +336,3 @@ function PreviewerResults() {
    ); } - -function AzureOpenAIEmbeddingsResults( { postId } ) { - const { - isPreviewUnderProcess, - setPreviewUnderProcess, - setIsPreviewerOpen, - } = useContext( PreviewerProviderContext ); - - const [ responseData, setResponseData ] = useState( [] ); - const [ errorMessage, setErrorMessage ] = useState( '' ); - const settings = useSelect( ( select ) => select( STORE_NAME ).getFeatureSettings() ); - - useEffect( () => { - // Reset previous results. - if ( isPreviewUnderProcess ) { - setResponseData( [] ); - } - }, [ isPreviewUnderProcess ] ); - - useEffect( () => { - if ( ! postId ) { - return; - } - - setPreviewUnderProcess( true ); - setIsPreviewerOpen( true ); - setErrorMessage( '' ); - - const formData = new FormData(); - - formData.append( 'post_id', postId ); - formData.append( - 'action', - 'get_post_classifier_embeddings_preview_data' - ); - - formData.append( 'nonce', classifAISettings.nonce ); - - ( async () => { - const response = await fetch( ajaxurl, { - method: 'POST', - body: formData, - } ); - - if ( ! response.ok ) { - return; - } - - const responseJSON = await response.json(); - - if ( responseJSON.success ) { - setResponseData( responseJSON.data ); - } else { - setErrorMessage( responseJSON.data ); - } - - setPreviewUnderProcess( false ); - } )() - }, [ postId ] ); - - const card = Object.keys( responseData ).map( ( taxSlug ) => { - const tags = responseData[ taxSlug ].data.map( ( tag, _index ) => { - const threshold = settings[ `${ taxSlug}_threshold` ]; - const score = normalizeScore( tag.score ); - - const scoreClass = score >= threshold ? 'classifai__classification-previewer-result-tag--exceeds-threshold' : ''; - - return ( -
    - { score }% - { tag.label } -
    - ) - } ); - - return ( - - - - { responseData[ taxSlug ].label } - - - - { tags.length ? tags : __( `No classification data found for ${ taxLabel }.`, 'classifai' ) } - - - ) - } ); - - if ( errorMessage ) { - return ( - - { errorMessage } - - ); - } - - return card.length ? ( - <> - - { __( 'Results for each taxonomy are sorted in descending order, starting with the term that has the highest score, indicating the best match based on the embedding data.', 'classifai' ) } - - { card } - - ) : null -} - -function IBMWatsonNLUResults( { postId } ) { - const { - isPreviewUnderProcess, - setPreviewUnderProcess, - setIsPreviewerOpen, - } = useContext( PreviewerProviderContext ); - - const [ responseData, setResponseData ] = useState( null ); - const [ errorMessage, setErrorMessage ] = useState( '' ); - const settings = useSelect( ( select ) => select( STORE_NAME ).getFeatureSettings() ); - - const taxMap = { - categories: 'category', - concepts: 'concept', - entities: 'entity', - keywords: 'keyword', - }; - - function formatLabel( label ) { - return label - .split( '/' ) - .filter( ( i ) => '' !== i ) - .join( ', ' ); - } - - useEffect( () => { - // Reset previous results. - if ( isPreviewUnderProcess ) { - setResponseData( null ); - } - }, [ isPreviewUnderProcess ] ); - - useEffect( () => { - if ( ! postId ) { - return; - } - - setPreviewUnderProcess( true ); - setIsPreviewerOpen( true ); - - const formData = new FormData(); - - formData.append( 'action', 'get_post_classifier_preview_data' ); - formData.append( 'post_id', postId ); - formData.append( 'nonce', classifAISettings.nonce ); - - ( async () => { - const response = await fetch( ajaxurl, { - method: 'POST', - body: formData, - } ); - - if ( ! response.ok ) { - return; - } - - const responseJSON = await response.json(); - if ( responseJSON.success ) { - setResponseData( responseJSON.data ); - } else { - setErrorMessage( responseJSON.data ); - } - - setPreviewUnderProcess( false ); - } )() - }, [] ); - - if ( ! responseData ) { - return null; - } - - const renderData = { - categories: responseData.categories, - concepts: responseData.concepts, - entities: responseData.entities, - keywords: responseData.keywords, - }; - - const card = Object.keys( renderData ).map( ( taxSlug ) => { - const tags = renderData[ taxSlug ].map( ( tag, _index ) => { - const threshold = settings[ `${ taxMap[ taxSlug ] }_threshold` ]; - const score = normalizeScore( tag.score || tag.relevance ); - - const scoreClass = score >= threshold ? 'classifai__classification-previewer-result-tag--exceeds-threshold' : ''; - - return ( -
    - { score }% - { formatLabel( tag.label || tag.text ) } -
    - ); - } ); - - return ( - - - - { taxSlug } - - - - { tags.length ? tags : __( `No classification data found for ${ taxSlug }.`, 'classifai' ) } - - - ) - } ); - - return card.length ? ( - <> - - { __( 'Results for each category are sorted in descending order, starting with the term that has the highest score, indicating the best match based on the embedding data.', 'classifai' ) } - - { card } - - ) : null -} - -function normalizeScore( score ) { - return Number( ( score * 100 ).toFixed( 2 ) ); -} From 90a0af1adc2e5bb9ee86e478cce8459d50cd5072 Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Wed, 4 Sep 2024 16:39:21 +0530 Subject: [PATCH 085/138] Adding style to loading spinner. --- .../components/classifai-registration/index.js | 9 ++++++++- .../components/feature-settings/index.js | 9 ++++++++- src/scss/settings.scss | 17 +++++++++++++++++ 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/src/js/settings/components/classifai-registration/index.js b/src/js/settings/components/classifai-registration/index.js index ac9ac48b4..64841fc88 100644 --- a/src/js/settings/components/classifai-registration/index.js +++ b/src/js/settings/components/classifai-registration/index.js @@ -35,7 +35,14 @@ export const ClassifAIRegistrationForm = ( { onSaveSuccess = () => {} } ) => { }, [ setSettings, setIsLoaded ] ); if ( ! isLoaded ) { - return ; + return ( +
    + + + { __( 'Loading settings…', 'classifai' ) } + +
    + ); } return ( diff --git a/src/js/settings/components/feature-settings/index.js b/src/js/settings/components/feature-settings/index.js index d7a32f373..344757e5d 100644 --- a/src/js/settings/components/feature-settings/index.js +++ b/src/js/settings/components/feature-settings/index.js @@ -73,7 +73,14 @@ export const FeatureSettings = ( { onSaveSuccess = () => {} } ) => { const featureTitle = feature?.label || __( 'Feature', 'classifai' ); if ( ! isLoaded ) { - return ; // TODO: Add proper styling for the spinner. + return ( +
    + + + { __( 'Loading settings…', 'classifai' ) } + +
    + ); } return ( diff --git a/src/scss/settings.scss b/src/scss/settings.scss index 40a87a86f..52bc49f33 100644 --- a/src/scss/settings.scss +++ b/src/scss/settings.scss @@ -380,6 +380,23 @@ gap: 0; } } + + .classifai-loading-settings { + text-align: center; + margin: 20vh 30px; + display: flex; + align-items: center; + justify-content: center; + + .components-spinner { + float: none; + } + + .description { + color: #777; + margin-top: 4px; + } + } } .classifai-onboarding { From 6acc9d0b84d6884cf3c0725d0eb55658ba92faee Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Wed, 4 Sep 2024 16:54:49 +0530 Subject: [PATCH 086/138] Some design fixes on onboarding. --- src/scss/settings.scss | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/scss/settings.scss b/src/scss/settings.scss index 52bc49f33..e5cb74ab2 100644 --- a/src/scss/settings.scss +++ b/src/scss/settings.scss @@ -163,6 +163,8 @@ @media screen and (max-width: 782px) { display: block; + width: 100%; + box-sizing: border-box; } } @@ -174,6 +176,7 @@ @media screen and (max-width: 782px) { display: block; gap: 0; + width: 100%; } } @@ -397,6 +400,10 @@ margin-top: 4px; } } + + .classifai-setup__content { + margin-left: 0px; + } } .classifai-onboarding { From 45155637cc7023cc186827f244ca99f9513319d3 Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Wed, 4 Sep 2024 17:10:45 +0530 Subject: [PATCH 087/138] Move notices under header. --- includes/Classifai/Admin/Settings.php | 3 +++ .../components/classifai-settings/index.js | 17 +++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/includes/Classifai/Admin/Settings.php b/includes/Classifai/Admin/Settings.php index 24c8903f2..a0514fc3b 100644 --- a/includes/Classifai/Admin/Settings.php +++ b/includes/Classifai/Admin/Settings.php @@ -43,6 +43,9 @@ public function register_settings_page() { */ public function render_settings_page() { ?> +
    +
    +
    { } )(); }, [ setSettings, setIsLoaded ] ); + // Render admin notices after the header. + useEffect( () => { + const notices = document.querySelectorAll( + 'div.updated, div.error, div.notice' + ); + const target = document.querySelector( '.classifai-admin-notices' ); + + notices.forEach( ( notice ) => { + if ( ! target ) { + return; + } + + target.appendChild( notice ); + } ); + }, [] ); + return (
    +
    Date: Wed, 4 Sep 2024 20:16:48 +0530 Subject: [PATCH 088/138] Add current step stat on configure feature tabs in onboarding. --- .../configure-features.js | 48 ++++++++++++------- src/scss/settings.scss | 7 --- 2 files changed, 30 insertions(+), 25 deletions(-) diff --git a/src/js/settings/components/classifai-onboarding/configure-features.js b/src/js/settings/components/classifai-onboarding/configure-features.js index 5c199c8cc..e111b4b50 100644 --- a/src/js/settings/components/classifai-onboarding/configure-features.js +++ b/src/js/settings/components/classifai-onboarding/configure-features.js @@ -5,18 +5,18 @@ import { __ } from '@wordpress/i18n'; import { FeatureSettings } from '..'; import { FeatureContext } from '../feature-settings/context'; -import { getFeature } from '../../utils/utils'; +import { getFeature, isFeatureActive } from '../../utils/utils'; import { STORE_NAME } from '../../data/store'; import { useSetupPage } from './hooks'; import { useNavigate } from 'react-router-dom'; export const ConfigureFeatures = () => { - const enabledFeatures = useSelect( ( select ) => { - const settings = select( STORE_NAME ).getSettings(); - return Object.keys( settings ).filter( - ( feature ) => settings[ feature ].status === '1' - ); - } ); + const settings = useSelect( ( select ) => + select( STORE_NAME ).getSettings() + ); + const enabledFeatures = Object.keys( settings ).filter( + ( feature ) => settings[ feature ].status === '1' + ); const { nextStepPath } = useSetupPage(); const navigate = useNavigate(); const [ currentFeature, setCurrentFeature ] = useState( @@ -42,17 +42,29 @@ export const ConfigureFeatures = () => {
    - { enabledFeatures.map( ( feature ) => ( - - ) ) } + { enabledFeatures.map( ( feature, index ) => { + let icon = 'clock'; + if ( isFeatureActive( settings[ feature ] ) ) { + icon = 'yes-alt'; + } else if ( + index < enabledFeatures.indexOf( currentFeature ) + ) { + icon = 'warning'; + } + + return ( + + ); + } ) }
    Date: Wed, 4 Sep 2024 20:22:05 +0530 Subject: [PATCH 089/138] Skip registration step if user is already registered. --- .../classifai-onboarding/enable-features.js | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/js/settings/components/classifai-onboarding/enable-features.js b/src/js/settings/components/classifai-onboarding/enable-features.js index e8af2b722..4f6b268bb 100644 --- a/src/js/settings/components/classifai-onboarding/enable-features.js +++ b/src/js/settings/components/classifai-onboarding/enable-features.js @@ -6,6 +6,8 @@ import { Button, } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; +import apiFetch from '@wordpress/api-fetch'; +import { useState, useEffect } from '@wordpress/element'; import { FeatureContext } from '../feature-settings/context'; import { EnableToggleControl } from '../feature-settings/enable-feature'; @@ -14,11 +16,33 @@ import { useSetupPage } from './hooks'; import { useNavigate } from 'react-router-dom'; export const EnableFeatures = () => { + const [ registrationSettings, setRegistrationSettings ] = useState( {} ); const { features, services, dashboardUrl } = window.classifAISettings; const { nextStepPath } = useSetupPage(); const navigate = useNavigate(); + + // Load the settings. + useEffect( () => { + ( async () => { + const regSettings = await apiFetch( { + path: '/classifai/v1/registration', + } ); // TODO: handle error + + setRegistrationSettings( regSettings ); + } )(); + }, [ setRegistrationSettings ] ); + const onSaveSuccess = () => { - navigate( nextStepPath ); + if ( registrationSettings?.valid_license ) { + navigate( + nextStepPath?.replace( + '/classifai_registration', + '/configure_features' + ) + ); + } else { + navigate( nextStepPath ); + } }; const featureToggles = Object.keys( services ).map( From 6a015acf6f710c929ad7868313ce8f7a199c85e8 Mon Sep 17 00:00:00 2001 From: Siddharth Thevaril Date: Thu, 5 Sep 2024 15:26:29 +0530 Subject: [PATCH 090/138] fix embeddings old previewer --- src/js/language-processing.js | 66 +++++++++++++++++++++++++++++++++-- 1 file changed, 64 insertions(+), 2 deletions(-) diff --git a/src/js/language-processing.js b/src/js/language-processing.js index 88a1e9d14..3a3d320d0 100644 --- a/src/js/language-processing.js +++ b/src/js/language-processing.js @@ -262,9 +262,9 @@ import '../scss/language-processing.scss'; return; } - const htmlData = buildPreviewUI( data.data ); + const fragment = buildPreviewUIForEmbeddings( data.data ); previewWrapper.style.display = 'block'; - previewWrapper.innerHTML = htmlData; + previewWrapper.replaceChildren( fragment ); // remove all .tax-row--hide document @@ -283,6 +283,68 @@ import '../scss/language-processing.scss'; }; previewEmbeddings(); + function buildPreviewUIForEmbeddings( filteredItems ) { + // Create a document fragment to hold the generated DOM elements. + const fragment = document.createDocumentFragment(); + + // Iterate over the categories in filteredItems. + Object.keys( filteredItems ).forEach( ( categoryKey ) => { + const category = filteredItems[ categoryKey ]; + + // Create the category row container. + const taxRowDiv = document.createElement( 'div' ); + taxRowDiv.className = `tax-row tax-row--${ categoryKey } ${ + featureStatuses[ `${ categoryKey }Status` ] + ? '' + : 'tax-row--hide' + }`; + + // Create and append the category label div. + const taxTypeDiv = document.createElement( 'div' ); + taxTypeDiv.className = `tax-type tax-type--${ categoryKey }`; + taxTypeDiv.textContent = category.label; + taxRowDiv.appendChild( taxTypeDiv ); + + // Iterate over the items in the category. + category.data.forEach( ( item ) => { + let rating = item?.score || 0; + let name = item?.label || ''; + + const width = 300 + 300 * rating; + rating = ( rating * 100 ).toFixed( 2 ); + name = name + .split( '/' ) + .filter( ( i ) => '' !== i ) + .join( ', ' ); + + // Create the item cell. + const taxCellDiv = document.createElement( 'div' ); + taxCellDiv.className = 'tax-cell'; + taxCellDiv.style.width = `${ width }px`; + + // Create and append the rating span. + const taxScoreSpan = document.createElement( 'span' ); + taxScoreSpan.className = 'tax-score'; + taxScoreSpan.textContent = `${ rating }%`; + taxCellDiv.appendChild( taxScoreSpan ); + + // Create and append the label span. + const taxLabelSpan = document.createElement( 'span' ); + taxLabelSpan.className = 'tax-label'; + taxLabelSpan.textContent = name; + taxCellDiv.appendChild( taxLabelSpan ); + + // Append the cell to the category row. + taxRowDiv.appendChild( taxCellDiv ); + } ); + + // Append the category row to the fragment. + fragment.appendChild( taxRowDiv ); + } ); + + return fragment; + } + /** * Builds user readable HTML data from the response by NLU. * From 3556ab6e067e35b991be2632cdd5896975065392 Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Thu, 5 Sep 2024 16:52:09 +0530 Subject: [PATCH 091/138] remove strict versioning from .nvmrc --- .nvmrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.nvmrc b/.nvmrc index ee09fac75..9a2a0e219 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -v20.11.1 +v20 From 0bba3ec672a35287d2c6b6a830db053b9b095d4d Mon Sep 17 00:00:00 2001 From: Siddharth Thevaril Date: Thu, 5 Sep 2024 17:13:15 +0530 Subject: [PATCH 092/138] fix eslint errors --- .eslintrc.json | 6 +- .../classification-previewers/context.js | 2 +- .../ibm-watson-nlu.js | 83 ++++++++---- .../openai-embedding.js | 81 +++++++---- .../classification.js | 128 ++++++++++++------ src/js/settings/data/hooks.js | 10 +- src/scss/settings.scss | 5 +- 7 files changed, 215 insertions(+), 100 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 58bbb406e..779fe3bb9 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -21,7 +21,11 @@ "Headers": "readonly", "requestAnimationFrame": "readonly", "React": "readonly", - "Block": "readonly" + "Block": "readonly", + "classifAISettings": "readonly" + }, + "rules": { + "react/jsx-no-undef": "off" }, "extends": ["plugin:@wordpress/eslint-plugin/recommended"], "ignorePatterns": ["*.json"] diff --git a/src/js/settings/components/feature-additional-settings/classification-previewers/context.js b/src/js/settings/components/feature-additional-settings/classification-previewers/context.js index 62ca4c498..b126596f0 100644 --- a/src/js/settings/components/feature-additional-settings/classification-previewers/context.js +++ b/src/js/settings/components/feature-additional-settings/classification-previewers/context.js @@ -1,5 +1,5 @@ import { createContext } from '@wordpress/element'; export const PreviewerProviderContext = createContext( { - isPreviewerOpen: false + isPreviewerOpen: false, } ); diff --git a/src/js/settings/components/feature-additional-settings/classification-previewers/ibm-watson-nlu.js b/src/js/settings/components/feature-additional-settings/classification-previewers/ibm-watson-nlu.js index 71826ec30..fb076f7cf 100644 --- a/src/js/settings/components/feature-additional-settings/classification-previewers/ibm-watson-nlu.js +++ b/src/js/settings/components/feature-additional-settings/classification-previewers/ibm-watson-nlu.js @@ -1,15 +1,9 @@ -import { - Card, - CardHeader, - CardBody, - Notice, - __experimentalHeading as Heading -} from '@wordpress/components'; +import { Card, CardHeader, CardBody, Notice } from '@wordpress/components'; import { useState, useEffect, useContext } from '@wordpress/element'; import { useSelect } from '@wordpress/data'; import { normalizeScore } from './utils'; import { PreviewerProviderContext } from './context'; -import { __ } from '@wordpress/i18n'; +import { __, sprintf } from '@wordpress/i18n'; import { STORE_NAME } from '../../../data/store'; @@ -22,7 +16,9 @@ export function IBMWatsonNLUResults( { postId } ) { const [ responseData, setResponseData ] = useState( null ); const [ errorMessage, setErrorMessage ] = useState( '' ); - const settings = useSelect( ( select ) => select( STORE_NAME ).getFeatureSettings() ); + const settings = useSelect( ( select ) => + select( STORE_NAME ).getFeatureSettings() + ); const taxMap = { categories: 'category', @@ -77,8 +73,8 @@ export function IBMWatsonNLUResults( { postId } ) { } setPreviewUnderProcess( false ); - } )() - }, [] ); + } )(); + }, [ postId ] ); if ( ! responseData ) { return null; @@ -96,36 +92,77 @@ export function IBMWatsonNLUResults( { postId } ) { const threshold = settings[ `${ taxMap[ taxSlug ] }_threshold` ]; const score = normalizeScore( tag.score || tag.relevance ); - const scoreClass = score >= threshold ? 'classifai__classification-previewer-result-tag--exceeds-threshold' : ''; + const scoreClass = + score >= threshold + ? 'classifai__classification-previewer-result-tag--exceeds-threshold' + : ''; return ( -
    - { score }% - { formatLabel( tag.label || tag.text ) } +
    + + { score }% + + + { formatLabel( tag.label || tag.text ) } +
    ); } ); return ( - + - +

    { taxSlug } - +

    - { tags.length ? tags : __( `No classification data found for ${ taxSlug }.`, 'classifai' ) } + { tags.length + ? tags + : sprintf( + /* translators: %s: taxonomy label */ + __( + `No classification data found for %s.`, + 'classifai' + ), + taxSlug + ) }
    - ) + ); } ); + if ( errorMessage ) { + return ( + + { errorMessage } + + ); + } + return card.length ? ( <> - - { __( 'Results for each category are sorted in descending order, starting with the term that has the highest score, indicating the best match based on the embedding data.', 'classifai' ) } + + { __( + 'Results for each category are sorted in descending order, starting with the term that has the highest score, indicating the best match based on the embedding data.', + 'classifai' + ) } { card } - ) : null + ) : null; } diff --git a/src/js/settings/components/feature-additional-settings/classification-previewers/openai-embedding.js b/src/js/settings/components/feature-additional-settings/classification-previewers/openai-embedding.js index bab0ee4f1..7276cedfa 100644 --- a/src/js/settings/components/feature-additional-settings/classification-previewers/openai-embedding.js +++ b/src/js/settings/components/feature-additional-settings/classification-previewers/openai-embedding.js @@ -1,15 +1,9 @@ -import { - Card, - CardHeader, - CardBody, - Notice, - __experimentalHeading as Heading -} from '@wordpress/components'; +import { Card, CardHeader, CardBody, Notice } from '@wordpress/components'; import { useState, useEffect, useContext } from '@wordpress/element'; import { useSelect } from '@wordpress/data'; import { normalizeScore } from './utils'; -import { PreviewerProviderContext } from './context' -import { __ } from '@wordpress/i18n'; +import { PreviewerProviderContext } from './context'; +import { __, sprintf } from '@wordpress/i18n'; import { STORE_NAME } from '../../../data/store'; @@ -22,7 +16,9 @@ export function AzureOpenAIEmbeddingsResults( { postId } ) { const [ responseData, setResponseData ] = useState( [] ); const [ errorMessage, setErrorMessage ] = useState( '' ); - const settings = useSelect( ( select ) => select( STORE_NAME ).getFeatureSettings() ); + const settings = useSelect( ( select ) => + select( STORE_NAME ).getFeatureSettings() + ); useEffect( () => { // Reset previous results. @@ -69,41 +65,67 @@ export function AzureOpenAIEmbeddingsResults( { postId } ) { } setPreviewUnderProcess( false ); - } )() + } )(); }, [ postId ] ); const card = Object.keys( responseData ).map( ( taxSlug ) => { const tags = responseData[ taxSlug ].data.map( ( tag, _index ) => { - const threshold = settings[ `${ taxSlug}_threshold` ]; + const threshold = settings[ `${ taxSlug }_threshold` ]; const score = normalizeScore( tag.score ); - const scoreClass = score >= threshold ? 'classifai__classification-previewer-result-tag--exceeds-threshold' : ''; + const scoreClass = + score >= threshold + ? 'classifai__classification-previewer-result-tag--exceeds-threshold' + : ''; return ( -
    - { score }% - { tag.label } +
    + + { score }% + + + { tag.label } +
    - ) + ); } ); return ( - + - +

    { responseData[ taxSlug ].label } - +

    - { tags.length ? tags : __( `No classification data found for ${ taxLabel }.`, 'classifai' ) } + { tags.length + ? tags + : sprintf( + /* translators: %s: taxonomy label */ + __( + `No classification data found for %s.`, + 'classifai' + ), + responseData[ taxSlug ].label + ) }
    - ) + ); } ); if ( errorMessage ) { return ( - + { errorMessage } ); @@ -111,10 +133,17 @@ export function AzureOpenAIEmbeddingsResults( { postId } ) { return card.length ? ( <> - - { __( 'Results for each taxonomy are sorted in descending order, starting with the term that has the highest score, indicating the best match based on the embedding data.', 'classifai' ) } + + { __( + 'Results for each taxonomy are sorted in descending order, starting with the term that has the highest score, indicating the best match based on the embedding data.', + 'classifai' + ) } { card } - ) : null + ) : null; } diff --git a/src/js/settings/components/feature-additional-settings/classification.js b/src/js/settings/components/feature-additional-settings/classification.js index 3b5d6d333..957e55cc5 100644 --- a/src/js/settings/components/feature-additional-settings/classification.js +++ b/src/js/settings/components/feature-additional-settings/classification.js @@ -8,14 +8,17 @@ import { Spinner, Button, } from '@wordpress/components'; -import { useState, useEffect, createContext, useContext } from '@wordpress/element'; +import { useState, useEffect, useContext } from '@wordpress/element'; import { useDebounce } from '@wordpress/compose'; import { __ } from '@wordpress/i18n'; import { SettingsRow } from '../settings-row'; import { STORE_NAME } from '../../data/store'; import { usePostTypes, usePostStatuses } from '../../utils/utils'; import { NLUFeatureSettings } from './nlu-feature'; -import { AzureOpenAIEmbeddingsResults, IBMWatsonNLUResults } from './classification-previewers'; +import { + AzureOpenAIEmbeddingsResults, + IBMWatsonNLUResults, +} from './classification-previewers'; import { PreviewerProviderContext } from './classification-previewers/context'; const ClassificationMethodSettings = () => { @@ -74,7 +77,7 @@ function PreviewerProvider( { children, value } ) { { children } - ) + ); } export const ClassificationSettings = () => { @@ -89,7 +92,7 @@ export const ClassificationSettings = () => { const { postTypesSelectOptions } = usePostTypes(); const { postStatusOptions } = usePostStatuses(); - const previewerContextData = { + const previewerContextData = { isPreviewerOpen, setIsPreviewerOpen, selectedPostId, @@ -102,7 +105,12 @@ export const ClassificationSettings = () => { <> - + @@ -194,21 +202,30 @@ export const ClassificationSettings = () => { }; function Previewer() { - const { - isPreviewerOpen, - setIsPreviewerOpen, - selectedPostId, - } = useContext( PreviewerProviderContext ); + const { isPreviewerOpen, setIsPreviewerOpen } = useContext( + PreviewerProviderContext + ); return ( -
    - +
    + @@ -224,7 +241,7 @@ function PreviewInProcess() { } return ( -
    +
    - ) + ); } function PostSelector( { placeholder = '', showLabel = true } ) { @@ -260,40 +277,58 @@ function PostSelector( { placeholder = '', showLabel = true } ) { } ( async () => { - const response = await wp.apiRequest({ + const response = await wp.apiRequest( { path: '/wp/v2/posts', data: { - search: searchText - } + search: searchText, + }, } ); if ( Array.isArray( response ) ) { setSearchResults( - response.map( post => ( { id: post.id, title: post.title.rendered } ) ) + response.map( ( post ) => ( { + id: post.id, + title: post.title.rendered, + } ) ) ); } - } )() + } )(); }, [ searchText, shouldSearch ] ); - const searchResultsHtml = searchResults.length ? searchResults.map( ( post ) => ( -
    selectPost( post ) } - className='classifai__classification-previewer-search-item' - > - -
    - ) ) : []; + const searchResultsHtml = searchResults.length + ? searchResults.map( ( post ) => ( +
    selectPost( post ) } + onKeyDown={ ( event ) => { + if ( event.key === 'Enter' ) { + selectPost( post ); + } + } } + className="classifai__classification-previewer-search-item" + tabIndex={ 0 } + role="button" + > + +
    + ) ) + : []; return ( -
    -
    +
    +
    { setShoudlSearch( true ); debouncedSearch( text ); @@ -305,13 +340,11 @@ function PostSelector( { placeholder = '', showLabel = true } ) { setShoudlSearch( true ); } } /> - { - searchResults.length ? ( -
    - { searchResultsHtml } -
    - ) : null - } + { searchResults.length ? ( +
    + { searchResultsHtml } +
    + ) : null }
    ); @@ -319,7 +352,9 @@ function PostSelector( { placeholder = '', showLabel = true } ) { function PreviewerResults() { const { selectedPostId } = useContext( PreviewerProviderContext ); - const activeProvider = useSelect( ( select ) => select( STORE_NAME ).getFeatureSettings().provider ); + const activeProvider = useSelect( + ( select ) => select( STORE_NAME ).getFeatureSettings().provider + ); if ( ! selectedPostId ) { return null; @@ -330,9 +365,14 @@ function PreviewerResults() { } return ( -
    - { 'azure_openai_embeddings' === activeProvider || 'openai_embeddings' === activeProvider && } - { 'ibm_watson_nlu' === activeProvider && } +
    + { 'azure_openai_embeddings' === activeProvider || + ( 'openai_embeddings' === activeProvider && ( + + ) ) } + { 'ibm_watson_nlu' === activeProvider && ( + + ) }
    ); } diff --git a/src/js/settings/data/hooks.js b/src/js/settings/data/hooks.js index 2ea1de038..dccc36bc1 100644 --- a/src/js/settings/data/hooks.js +++ b/src/js/settings/data/hooks.js @@ -12,10 +12,12 @@ export const useFeatureSettings = () => { } const { setFeatureSettings } = useDispatch( STORE_NAME ); - const getFeatureSettings = ( key ) => - useSelect( ( select ) => - select( STORE_NAME ).getFeatureSettings( key, featureName ) - ); + + const getFeatureSettings = useSelect( ( select ) => { + const store = select( STORE_NAME ); + + return ( key ) => store.getFeatureSettings( key, featureName ); + } ); return { featureName, diff --git a/src/scss/settings.scss b/src/scss/settings.scss index 40a87a86f..876a14f43 100644 --- a/src/scss/settings.scss +++ b/src/scss/settings.scss @@ -487,7 +487,9 @@ .classifai__classification-previewer-search-item { padding: 0.25rem 0.5rem; - &:hover { + &:hover, + &:active, + &:focus { background-color: #777; color: #fff; cursor: pointer; @@ -500,6 +502,7 @@ .classifai__classification-previewer-result-card-heading { font-size: 1.15rem !important; + margin: 0; } .classifai__classification-previewer-result-tag { From 719a06f6e858652256672c7594503958fb2a09ee Mon Sep 17 00:00:00 2001 From: Siddharth Thevaril Date: Thu, 5 Sep 2024 17:19:32 +0530 Subject: [PATCH 093/138] fix phpcs errors --- includes/Classifai/Admin/Settings.php | 2 +- includes/Classifai/Providers/Azure/Embeddings.php | 6 ++---- includes/Classifai/Providers/OpenAI/Embeddings.php | 6 ++---- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/includes/Classifai/Admin/Settings.php b/includes/Classifai/Admin/Settings.php index a0514fc3b..84c1acebb 100644 --- a/includes/Classifai/Admin/Settings.php +++ b/includes/Classifai/Admin/Settings.php @@ -246,7 +246,7 @@ public function update_settings_callback( $request ) { // Update only status of the feature. $current_settings['status'] = $feature_settings['status'] ?? $current_settings['status']; - $new_settings = $current_settings; + $new_settings = $current_settings; } else { $new_settings = $feature->sanitize_settings( $settings[ $feature_key ] ); if ( is_wp_error( $new_settings ) ) { diff --git a/includes/Classifai/Providers/Azure/Embeddings.php b/includes/Classifai/Providers/Azure/Embeddings.php index 58429bcab..906a7d8b3 100644 --- a/includes/Classifai/Providers/Azure/Embeddings.php +++ b/includes/Classifai/Providers/Azure/Embeddings.php @@ -340,10 +340,8 @@ public function get_threshold( string $taxonomy = '' ): float { /** * Get the data to preview terms. - * - * @return array */ - public function get_post_classifier_embeddings_preview_data(): array { + public function get_post_classifier_embeddings_preview_data(): void { $nonce = isset( $_POST['nonce'] ) ? sanitize_text_field( wp_unslash( $_POST['nonce'] ) ) : false; if ( ! $nonce || ! wp_verify_nonce( $nonce, 'classifai-previewer-action' ) ) { @@ -585,7 +583,7 @@ function ( $a, $b ) { // Initialize the taxonomy bucket in results. $results[ $tax ] = [ 'label' => $tax_name, - 'data' => [] + 'data' => [], ]; foreach ( $terms as $term ) { diff --git a/includes/Classifai/Providers/OpenAI/Embeddings.php b/includes/Classifai/Providers/OpenAI/Embeddings.php index ed0e0dbaa..c6b7487c9 100644 --- a/includes/Classifai/Providers/OpenAI/Embeddings.php +++ b/includes/Classifai/Providers/OpenAI/Embeddings.php @@ -452,10 +452,8 @@ public function regenerate_embeddings() { * Get the data to preview terms. * * @since 2.5.0 - * - * @return array */ - public function get_post_classifier_embeddings_preview_data(): array { + public function get_post_classifier_embeddings_preview_data(): void { $nonce = isset( $_POST['nonce'] ) ? sanitize_text_field( wp_unslash( $_POST['nonce'] ) ) : false; if ( ! $nonce || ! wp_verify_nonce( $nonce, 'classifai-previewer-action' ) ) { @@ -697,7 +695,7 @@ function ( $a, $b ) { // Initialize the taxonomy bucket in results. $results[ $tax ] = [ 'label' => $tax_name, - 'data' => [] + 'data' => [], ]; foreach ( $terms as $term ) { From 6b4af66c77f6a715d8f198d5b5e950afe39b4a6a Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Thu, 5 Sep 2024 18:01:05 +0530 Subject: [PATCH 094/138] Added filter `classifai_use_legacy_settings_panel` to enable legacy settings panel. --- includes/Classifai/Admin/Settings.php | 15 +++++++++++-- .../Admin/templates/classifai-header.php | 4 ++-- includes/Classifai/Features/Feature.php | 7 +++++-- includes/Classifai/Helpers.php | 21 +++++++++++++++++++ includes/Classifai/Plugin.php | 16 +++++++------- includes/Classifai/Services/Service.php | 2 +- .../Classifai/Services/ServicesManager.php | 13 +++++++----- 7 files changed, 59 insertions(+), 19 deletions(-) diff --git a/includes/Classifai/Admin/Settings.php b/includes/Classifai/Admin/Settings.php index a0514fc3b..d5d75f9f1 100644 --- a/includes/Classifai/Admin/Settings.php +++ b/includes/Classifai/Admin/Settings.php @@ -28,10 +28,21 @@ public function init() { * Registers a hidden sub menu page for the onboarding wizard. */ public function register_settings_page() { + $registration_settings = get_option( 'classifai_settings' ); + $page_title = esc_attr__( 'ClassifAI', 'classifai' ); + $menu_title = $page_title; + + if ( ! isset( $registration_settings['valid_license'] ) || ! $registration_settings['valid_license'] ) { + /* + * Translators: Menu title. + */ + $menu_title = sprintf( __( 'ClassifAI %s', 'classifai' ), '!' ); + } + add_submenu_page( 'tools.php', - esc_attr__( 'ClassifAI', 'classifai' ), - esc_attr__( 'ClassifAI', 'classifai' ), + $page_title, + $menu_title, 'manage_options', 'classifai', [ $this, 'render_settings_page' ] diff --git a/includes/Classifai/Admin/templates/classifai-header.php b/includes/Classifai/Admin/templates/classifai-header.php index a129d8040..ea29a7fde 100644 --- a/includes/Classifai/Admin/templates/classifai-header.php +++ b/includes/Classifai/Admin/templates/classifai-header.php @@ -22,7 +22,7 @@ if ( $is_setup_page ) { ?>
    - + @@ -70,7 +70,7 @@ $value ) { ?> - + is_feature_enabled() ) { $this->feature_setup(); diff --git a/includes/Classifai/Helpers.php b/includes/Classifai/Helpers.php index 5968fe69b..73587721d 100644 --- a/includes/Classifai/Helpers.php +++ b/includes/Classifai/Helpers.php @@ -661,3 +661,24 @@ function get_classification_mode(): string { return $value; } + +/** + * Determine if the legacy settings panel should be used. + * + * @since x.x.x + * + * @return bool + */ +function should_use_legacy_settings_panel(): bool { + /** + * Filter to determine if the legacy settings panel should be used.' + * + * @since x.x.x + * @hook classifai_use_legacy_settings_panel + * + * @param {bool} $use_legacy_settings_panel Whether to use the legacy settings panel. + * + * @return {bool} Whether to use the legacy settings panel. + */ + return apply_filters( 'classifai_use_legacy_settings_panel', false ); +} diff --git a/includes/Classifai/Plugin.php b/includes/Classifai/Plugin.php index 7b0ec6f74..4a377d2e2 100644 --- a/includes/Classifai/Plugin.php +++ b/includes/Classifai/Plugin.php @@ -58,13 +58,15 @@ public function init() { // Initialize the services; each service handles their features. $this->init_services(); - // Initialize the ClassifAI Settings. - $settings = new Admin\Settings(); - $settings->init(); - - // Initialize the ClassifAI Onboarding. - $onboarding = new Admin\Onboarding(); - $onboarding->init(); + if ( ! should_use_legacy_settings_panel() ) { + // Initialize the ClassifAI Settings. + $settings = new Admin\Settings(); + $settings->init(); + } else { + // Initialize the ClassifAI Onboarding. This is only used for the legacy settings panel. + $onboarding = new Admin\Onboarding(); + $onboarding->init(); + } // Initialize the ClassifAI User Profile. $user_profile = new Admin\UserProfile(); diff --git a/includes/Classifai/Services/Service.php b/includes/Classifai/Services/Service.php index c01fd0ee7..95d34356d 100644 --- a/includes/Classifai/Services/Service.php +++ b/includes/Classifai/Services/Service.php @@ -137,7 +137,7 @@ public function get_display_name(): string { public function render_settings_page() { $base_url = add_query_arg( array( - 'page' => 'classifai_old', + 'page' => 'classifai', 'tab' => $this->get_menu_slug(), ), admin_url( 'tools.php' ) diff --git a/includes/Classifai/Services/ServicesManager.php b/includes/Classifai/Services/ServicesManager.php index 0fe6f254d..df114fee4 100644 --- a/includes/Classifai/Services/ServicesManager.php +++ b/includes/Classifai/Services/ServicesManager.php @@ -4,6 +4,7 @@ */ namespace Classifai\Services; +use function Classifai\should_use_legacy_settings_panel; class ServicesManager { @@ -52,8 +53,10 @@ public function register() { } } - // Do the settings pages. - $this->do_settings(); + if ( should_use_legacy_settings_panel() ) { + // Do the settings pages. + $this->do_settings(); + } // Register the functionality $this->register_services(); @@ -270,14 +273,14 @@ protected function register_services() { */ protected function get_menu_title() { $registration_settings = get_option( 'classifai_settings' ); - $this->title = esc_html__( 'ClassifAI Old', 'classifai' ); + $this->title = esc_html__( 'ClassifAI', 'classifai' ); $this->menu_title = $this->title; if ( ! isset( $registration_settings['valid_license'] ) || ! $registration_settings['valid_license'] ) { /* * Translators: Main title. */ - $this->menu_title = sprintf( __( 'ClassifAI Old %s', 'classifai' ), '!' ); + $this->menu_title = sprintf( __( 'ClassifAI %s', 'classifai' ), '!' ); } } @@ -299,7 +302,7 @@ public function register_admin_menu_item() { $this->title, $this->menu_title, 'manage_options', - 'classifai_old', + 'classifai', [ $this, 'render_settings_page' ] ); } From 104e7bbde72c000eb3077320e125529b454a4bcc Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Thu, 5 Sep 2024 18:32:22 +0530 Subject: [PATCH 095/138] Fix setup role in rest API settings endpoint. --- includes/Classifai/Features/Feature.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/includes/Classifai/Features/Feature.php b/includes/Classifai/Features/Feature.php index 6553d28f8..cc01183a3 100644 --- a/includes/Classifai/Features/Feature.php +++ b/includes/Classifai/Features/Feature.php @@ -52,6 +52,7 @@ abstract class Feature { */ public function setup() { add_action( 'admin_init', [ $this, 'setup_roles' ] ); + add_action( 'rest_api_init', [ $this, 'setup_roles' ] ); if ( should_use_legacy_settings_panel() ) { add_action( 'admin_init', [ $this, 'register_setting' ] ); add_action( 'admin_init', [ $this, 'setup_fields_sections' ] ); @@ -74,6 +75,10 @@ public function feature_setup() { * Assigns user roles to the $roles array. */ public function setup_roles() { + if ( ! function_exists( 'get_editable_roles' ) ) { + require_once ABSPATH . 'wp-admin/includes/user.php'; + } + $default_settings = $this->get_default_settings(); $this->roles = get_editable_roles() ?? []; $this->roles = array_combine( array_keys( $this->roles ), array_column( $this->roles, 'name' ) ); From b44da16145d0558981497f0848709139c2b22e63 Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Thu, 5 Sep 2024 19:19:16 +0530 Subject: [PATCH 096/138] Fix setup link in plugin links and notice. --- includes/Classifai/Admin/Notifications.php | 8 +++++++- includes/Classifai/Admin/Settings.php | 2 +- includes/Classifai/Plugin.php | 7 ++++++- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/includes/Classifai/Admin/Notifications.php b/includes/Classifai/Admin/Notifications.php index 9bc13ab9a..850eb5a9a 100644 --- a/includes/Classifai/Admin/Notifications.php +++ b/includes/Classifai/Admin/Notifications.php @@ -4,6 +4,7 @@ use Classifai\Features\DescriptiveTextGenerator; use Classifai\Features\Classification; +use function Classifai\should_use_legacy_settings_panel; class Notifications { @@ -84,6 +85,11 @@ public function render_activation_notice() { return; } + $setup_url = admin_url( 'tools.php?page=classifai#/classifai_setup' ); + if ( should_use_legacy_settings_panel() ) { + $setup_url = admin_url( 'admin.php?page=classifai_setup' ); + } + // Prevent showing the default WordPress "Plugin Activated" notice. unset( $_GET['activate'] ); // phpcs:ignore WordPress.Security.NonceVerification ?> @@ -96,7 +102,7 @@ public function render_activation_notice() {

    - +
    diff --git a/includes/Classifai/Admin/Settings.php b/includes/Classifai/Admin/Settings.php index 3e7e72db9..de8503087 100644 --- a/includes/Classifai/Admin/Settings.php +++ b/includes/Classifai/Admin/Settings.php @@ -67,7 +67,7 @@ public function render_settings_page() { * @param string $hook_suffix The current admin page. */ public function admin_enqueue_scripts( $hook_suffix ) { - if ( ! in_array( $hook_suffix, array( 'admin_page_classifai_setup', 'tools_page_classifai' ), true ) ) { + if ( ! in_array( $hook_suffix, array( 'tools_page_classifai' ), true ) ) { return; } diff --git a/includes/Classifai/Plugin.php b/includes/Classifai/Plugin.php index 4a377d2e2..0b96dff6d 100644 --- a/includes/Classifai/Plugin.php +++ b/includes/Classifai/Plugin.php @@ -226,11 +226,16 @@ public function filter_plugin_action_links( $links ): array { return $links; } + $setup_url = admin_url( 'tools.php?page=classifai#/classifai_setup' ); + if ( should_use_legacy_settings_panel() ) { + $setup_url = admin_url( 'admin.php?page=classifai_setup' ); + } + return array_merge( array( 'setup' => sprintf( ' %s ', - esc_url( admin_url( 'admin.php?page=classifai_setup' ) ), + esc_url( $setup_url ), esc_html__( 'Set up', 'classifai' ) ), 'settings' => sprintf( From 2dea03eaf86ff557354d7a1e1bd05737debe021b Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Thu, 5 Sep 2024 19:41:14 +0530 Subject: [PATCH 097/138] Remove unwanted code. --- .eslintignore | 1 + includes/Classifai/Admin/templates/onboarding-header.php | 1 - package.json | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.eslintignore b/.eslintignore index ae25d19ab..109fd6d21 100644 --- a/.eslintignore +++ b/.eslintignore @@ -4,3 +4,4 @@ tests/* vendor/* bin/* hookdocs/* +docs/* diff --git a/includes/Classifai/Admin/templates/onboarding-header.php b/includes/Classifai/Admin/templates/onboarding-header.php index 75247e245..a3932d061 100644 --- a/includes/Classifai/Admin/templates/onboarding-header.php +++ b/includes/Classifai/Admin/templates/onboarding-header.php @@ -28,5 +28,4 @@ ?>
    -
    diff --git a/package.json b/package.json index 94a49f31b..d273be944 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "scripts": { "start": "wp-scripts start", "build": "wp-scripts build", - "lint:js": "wp-scripts lint-js ./src/js/settings", + "lint:js": "wp-scripts lint-js", "lint:js-fix": "wp-scripts lint-js --fix", "install_tests": "./bin/install-wp-tests.sh classifai_unit_tests root password 127.0.0.1", "test": "./vendor/bin/phpunit", From 85239c43ae681a67a44f9f5378d77950bd1da60b Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Fri, 6 Sep 2024 15:08:17 +0530 Subject: [PATCH 098/138] Update regenerate embeddings processing code --- includes/Classifai/Admin/Notifications.php | 57 ++++++++++++++++++- .../Classifai/Providers/OpenAI/Embeddings.php | 56 +++++++++--------- 2 files changed, 85 insertions(+), 28 deletions(-) diff --git a/includes/Classifai/Admin/Notifications.php b/includes/Classifai/Admin/Notifications.php index 850eb5a9a..840376708 100644 --- a/includes/Classifai/Admin/Notifications.php +++ b/includes/Classifai/Admin/Notifications.php @@ -45,6 +45,7 @@ public function maybe_render_notices() { $this->thresholds_update_notice(); $this->v3_migration_completed_notice(); $this->render_embeddings_notice(); + $this->render_notices(); } /** @@ -258,7 +259,7 @@ public function render_embeddings_notice() { sprintf( // translators: %1$s: Feature specific message; %2$s: URL to Feature settings. __( 'ClassifAI has updated to the text-embedding-3-small embeddings model.
    This requires regenerating any stored embeddings for functionality to work properly.
    Click here to do that, noting this will make multiple API requests to OpenAI.', 'classifai' ), - wp_nonce_url( admin_url( 'tools.php?page=classifai&tab=language_processing&feature=feature_classification' ), 'regen_embeddings', 'embeddings_nonce' ) + wp_nonce_url( admin_url( 'admin-post.php?action=classifai_regen_embeddings' ), 'regen_embeddings', 'embeddings_nonce' ) ) ); ?> @@ -337,4 +338,58 @@ public function ajax_maybe_dismiss_notice() { update_user_meta( get_current_user_id(), "classifai_dismissed_{$notice_id}", true ); } + + /** + * Render any saved notices to display. + */ + public function render_notices() { + $notices = $this->get_notices(); + if ( empty( $notices ) ) { + return; + } + + foreach ( $notices as $notice ) { + if ( ! empty( $notice['message'] ) ) { + ?> +
    +

    +
    + $type, + 'message' => $message, + ]; + set_transient( 'classifai_notices', $notices ); + } } diff --git a/includes/Classifai/Providers/OpenAI/Embeddings.php b/includes/Classifai/Providers/OpenAI/Embeddings.php index c6b7487c9..9f59b5a6c 100644 --- a/includes/Classifai/Providers/OpenAI/Embeddings.php +++ b/includes/Classifai/Providers/OpenAI/Embeddings.php @@ -5,6 +5,7 @@ namespace Classifai\Providers\OpenAI; +use Classifai\Admin\Notifications; use Classifai\Providers\Provider; use Classifai\Providers\OpenAI\APIRequest; use Classifai\Providers\OpenAI\EmbeddingCalculations; @@ -13,6 +14,7 @@ use Classifai\Features\Feature; use Classifai\EmbeddingsScheduler; use WP_Error; +use function Classifai\should_use_legacy_settings_panel; class Embeddings extends Provider { @@ -241,32 +243,6 @@ public function render_provider_fields() { ] ); - // If embeddings regeneration is being requested, run that. - if ( - isset( $_GET['feature'] ) && - 'feature_classification' === sanitize_text_field( wp_unslash( $_GET['feature'] ) ) - ) { - if ( isset( $_GET['embedding_regen_completed'] ) ) { - add_action( - 'admin_notices', - function () { - ?> -
    -

    -
    - regenerate_embeddings(); - } - } - do_action( 'classifai_' . static::ID . '_render_provider_fields', $this ); } @@ -311,6 +287,7 @@ public function register() { add_action( 'created_term', [ $this, 'generate_embeddings_for_term' ] ); add_action( 'edited_terms', [ $this, 'generate_embeddings_for_term' ] ); add_action( 'wp_ajax_get_post_classifier_embeddings_preview_data', array( $this, 'get_post_classifier_embeddings_preview_data' ) ); + add_action( 'admin_post_classifai_regen_embeddings', [ $this, 'classifai_regen_embeddings' ] ); } /** @@ -443,8 +420,19 @@ public function regenerate_embeddings() { // Hide the admin notice. update_option( 'classifai_hide_embeddings_notice', true, false ); + // Set a notice to let the user know the embeddings have been regenerated. + $notifications = new Notifications(); + $notifications->set_notice( + esc_html__( 'Embeddings have been regenerated.', 'classifai' ), + 'success', + ); + // Redirect to the same page but remove the nonce so we don't run this again. - wp_safe_redirect( admin_url( 'tools.php?page=classifai&tab=language_processing&feature=feature_classification&embedding_regen_completed' ) ); + $redirect_url = admin_url( 'tools.php?page=classifai#/language_processing/feature_classification' ); + if ( should_use_legacy_settings_panel() ) { + $redirect_url = admin_url( 'tools.php?page=classifai&tab=language_processing&feature=feature_classification' ); + } + wp_safe_redirect( $redirect_url ); exit; } @@ -477,6 +465,20 @@ public function get_post_classifier_embeddings_preview_data(): void { wp_send_json_success( $embeddings_terms ); } + /** + * Regenerate embeddings. + */ + public function classifai_regen_embeddings(): void { + if ( + ! isset( $_GET['embeddings_nonce'] ) || + ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['embeddings_nonce'] ) ), 'regen_embeddings' ) + ) { + wp_die( esc_html__( 'You do not have permission to perform this operation.', 'classifai' ) ); + } + + $this->regenerate_embeddings(); + } + /** * Trigger embedding generation for content being saved. * From 0397fd8471081c75536f204f4014896d1671155c Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Fri, 13 Sep 2024 23:00:08 +0530 Subject: [PATCH 099/138] Fix Sass compile warning. --- src/scss/settings.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/scss/settings.scss b/src/scss/settings.scss index c3875630c..ba31842d8 100644 --- a/src/scss/settings.scss +++ b/src/scss/settings.scss @@ -131,7 +131,7 @@ display: flex; align-items: stretch; flex-direction: row; - justify-content: start; + justify-content: flex-start; border-bottom: 1px solid #ccd0d4; padding-bottom: 0; margin-right: 20px; @@ -139,7 +139,7 @@ &[aria-orientation="vertical"] { flex-direction: column; border: 0px; - justify-content: start; + justify-content: flex-start; flex-basis: 0; flex-grow: 1; margin-right: 0px; From a26ee4cb752dea0595da48a378210dfebd846782 Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Mon, 16 Sep 2024 11:09:36 +0530 Subject: [PATCH 100/138] Fix default feature settings. --- includes/Classifai/Features/Classification.php | 4 ++-- includes/Classifai/Features/DescriptiveTextGenerator.php | 2 +- includes/Classifai/Features/ExcerptGeneration.php | 4 +++- includes/Classifai/Features/Moderation.php | 4 +++- includes/Classifai/Features/TextToSpeech.php | 4 +++- 5 files changed, 12 insertions(+), 6 deletions(-) diff --git a/includes/Classifai/Features/Classification.php b/includes/Classifai/Features/Classification.php index f3b8427d7..ef4102907 100644 --- a/includes/Classifai/Features/Classification.php +++ b/includes/Classifai/Features/Classification.php @@ -853,10 +853,10 @@ public function add_custom_settings_fields() { public function get_feature_default_settings(): array { return [ 'post_statuses' => [ - 'publish' => 1, + 'publish' => 'publish', ], 'post_types' => [ - 'post' => 1, + 'post' => 'post', ], 'classification_mode' => 'manual_review', 'classification_method' => 'recommended_terms', diff --git a/includes/Classifai/Features/DescriptiveTextGenerator.php b/includes/Classifai/Features/DescriptiveTextGenerator.php index 2681ee81e..878d22fbc 100644 --- a/includes/Classifai/Features/DescriptiveTextGenerator.php +++ b/includes/Classifai/Features/DescriptiveTextGenerator.php @@ -371,7 +371,7 @@ public function add_custom_settings_fields() { public function get_feature_default_settings(): array { return [ 'descriptive_text_fields' => [ - 'alt' => 0, + 'alt' => 'alt', 'caption' => 0, 'description' => 0, ], diff --git a/includes/Classifai/Features/ExcerptGeneration.php b/includes/Classifai/Features/ExcerptGeneration.php index 431ec22cb..ee69f6983 100644 --- a/includes/Classifai/Features/ExcerptGeneration.php +++ b/includes/Classifai/Features/ExcerptGeneration.php @@ -343,7 +343,9 @@ public function get_feature_default_settings(): array { 'original' => 1, ], ], - 'post_types' => [], + 'post_types' => [ + 'post' => 'post', + ], 'length' => absint( apply_filters( 'excerpt_length', 55 ) ), 'provider' => ChatGPT::ID, ]; diff --git a/includes/Classifai/Features/Moderation.php b/includes/Classifai/Features/Moderation.php index 6d4729aba..3ce97190f 100644 --- a/includes/Classifai/Features/Moderation.php +++ b/includes/Classifai/Features/Moderation.php @@ -347,7 +347,9 @@ public function add_custom_settings_fields() { */ public function get_feature_default_settings(): array { return [ - 'content_types' => [], + 'content_types' => [ + 'comments' => 'comments', + ], 'provider' => ModerationProvider::ID, ]; } diff --git a/includes/Classifai/Features/TextToSpeech.php b/includes/Classifai/Features/TextToSpeech.php index 9ac3250a1..a090f7175 100644 --- a/includes/Classifai/Features/TextToSpeech.php +++ b/includes/Classifai/Features/TextToSpeech.php @@ -775,7 +775,9 @@ protected function get_post_types_select_options(): array { */ public function get_feature_default_settings(): array { return [ - 'post_types' => [], + 'post_types' => [ + 'post' => 'post' + ], 'provider' => Speech::ID, ]; } From 2220a016c429bc6c65c380f86be71b50365536cf Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Mon, 16 Sep 2024 12:19:19 +0530 Subject: [PATCH 101/138] Fix IBM watson auth saving --- includes/Classifai/Providers/Watson/NLU.php | 2 +- .../components/provider-settings/ibm-watson-nlu.js | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/includes/Classifai/Providers/Watson/NLU.php b/includes/Classifai/Providers/Watson/NLU.php index e38473446..6fd0b1f46 100644 --- a/includes/Classifai/Providers/Watson/NLU.php +++ b/includes/Classifai/Providers/Watson/NLU.php @@ -209,7 +209,7 @@ public function get_default_provider_settings(): array { $common_settings = [ 'endpoint_url' => '', 'apikey' => '', - 'username' => '', + 'username' => 'apikey', 'password' => '', ]; diff --git a/src/js/settings/components/provider-settings/ibm-watson-nlu.js b/src/js/settings/components/provider-settings/ibm-watson-nlu.js index a8095deed..67ab24ac5 100644 --- a/src/js/settings/components/provider-settings/ibm-watson-nlu.js +++ b/src/js/settings/components/provider-settings/ibm-watson-nlu.js @@ -74,7 +74,13 @@ export const IBMWatsonNLUSettings = ( { isConfigured = false } ) => { onChange( { password: value } ) } + onChange={ ( value ) => { + const data = { password: value }; + if ( useAPIkey ) { + data.username = 'apikey'; + } + onChange( data ); + } } /> From faaa5cb90fd14563b97729445d1327e4b38d8b93 Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Mon, 16 Sep 2024 16:38:02 +0530 Subject: [PATCH 102/138] Update admin tests. --- tests/cypress/integration/admin.test.js | 26 ++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/tests/cypress/integration/admin.test.js b/tests/cypress/integration/admin.test.js index bc9677c81..6c4dc3727 100644 --- a/tests/cypress/integration/admin.test.js +++ b/tests/cypress/integration/admin.test.js @@ -18,22 +18,30 @@ describe( 'Admin can login and make sure plugin is activated', () => { // Check Heading cy.visit( '/wp-admin/tools.php?page=classifai' ); - cy.get( '#wpbody h2' ).contains( 'ClassifAI Settings' ); - cy.get( 'label[for="email"]' ).contains( 'Registered Email' ); - cy.get( 'label[for="license_key"]' ).contains( 'Registration Key' ); + cy.get( '#wpbody h2' ).contains( 'Classification Settings' ); + cy.get( '.classifai-tabs' ).should( 'exist' ); + cy.get( '.classifai-tabs a' ).first().contains( 'Language Processing' ); } ); it( 'Can visit "Language Processing" settings page.', () => { - // Check Heading + // Check Selected Navigation menu cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing' + '/wp-admin/tools.php?page=classifai#/language_processing/feature_classification' ); - cy.get( '#wpbody h2' ).contains( 'Language Processing' ); + cy.get( '.classifai-tabs' ).should( 'exist' ); + cy.get( '.classifai-tabs a.active-tab' ) + .first() + .contains( 'Language Processing' ); } ); it( 'Can see "Image Processing" menu and Can visit "Image Processing" settings page.', () => { - // Check Heading - cy.visit( '/wp-admin/tools.php?page=classifai&tab=image_processing' ); - cy.get( '#wpbody h2' ).contains( 'Image Processing' ); + // Check Selected Navigation menu + cy.visit( + '/wp-admin/tools.php?page=classifai#/image_processing/feature_descriptive_text_generator' + ); + cy.get( '.classifai-tabs' ).should( 'exist' ); + cy.get( '.classifai-tabs a.active-tab' ) + .first() + .contains( 'Image Processing' ); } ); } ); From 76455849cc971d304ecb7548ee9bcc135dccd5f7 Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Mon, 16 Sep 2024 16:49:41 +0530 Subject: [PATCH 103/138] Update tests for the azure open AI excerpt generation. --- .../components/allowed-roles/index.js | 1 + .../excerpt-generation.js | 23 ++++++++- .../feature-settings/enable-feature.js | 1 + .../provider-settings/azure-openai.js | 4 ++ .../components/provider-settings/index.js | 1 + .../excerpt-generation-azure-openai.test.js | 48 +++++++------------ 6 files changed, 46 insertions(+), 32 deletions(-) diff --git a/src/js/settings/components/allowed-roles/index.js b/src/js/settings/components/allowed-roles/index.js index ba8549656..686153421 100644 --- a/src/js/settings/components/allowed-roles/index.js +++ b/src/js/settings/components/allowed-roles/index.js @@ -33,6 +33,7 @@ export const AllowedRoles = () => { { Object.keys( featureRoles ).map( ( role ) => { return ( { 'Choose which post types support this feature.', 'classifai' ) } + className="settings-allowed-post-types" > { ( excerptPostTypesOptions || [] ).map( ( option ) => { const { value: key, label } = option; return ( { ); } ) } + + + setFeatureSettings( { length: value } ) + } + /> + ); }; diff --git a/src/js/settings/components/feature-settings/enable-feature.js b/src/js/settings/components/feature-settings/enable-feature.js index 4d49372e5..8c041f8f1 100644 --- a/src/js/settings/components/feature-settings/enable-feature.js +++ b/src/js/settings/components/feature-settings/enable-feature.js @@ -38,6 +38,7 @@ export const EnableToggleControl = ( { children } ) => { description={ enableDescription } > setFeatureSettings( { diff --git a/src/js/settings/components/provider-settings/azure-openai.js b/src/js/settings/components/provider-settings/azure-openai.js index 048188d8f..f2f794860 100644 --- a/src/js/settings/components/provider-settings/azure-openai.js +++ b/src/js/settings/components/provider-settings/azure-openai.js @@ -39,6 +39,7 @@ export const AzureOpenAISettings = ( { description={ } > @@ -48,6 +49,7 @@ export const AzureOpenAISettings = ( { @@ -63,6 +65,7 @@ export const AzureOpenAISettings = ( { ) } > @@ -84,6 +87,7 @@ export const AzureOpenAISettings = ( { ) } > diff --git a/src/js/settings/components/provider-settings/index.js b/src/js/settings/components/provider-settings/index.js index 8ed8a57a3..4dceb9194 100644 --- a/src/js/settings/components/provider-settings/index.js +++ b/src/js/settings/components/provider-settings/index.js @@ -149,6 +149,7 @@ export const ProviderSettings = () => { label={ __( 'Select a provider', 'classifai' ) } > setFeatureSettings( { provider: value } ) } diff --git a/tests/cypress/integration/language-processing/excerpt-generation-azure-openai.test.js b/tests/cypress/integration/language-processing/excerpt-generation-azure-openai.test.js index d01786ea2..2b74de89c 100644 --- a/tests/cypress/integration/language-processing/excerpt-generation-azure-openai.test.js +++ b/tests/cypress/integration/language-processing/excerpt-generation-azure-openai.test.js @@ -4,13 +4,11 @@ describe( '[Language processing] Excerpt Generation Tests', () => { before( () => { cy.login(); cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_excerpt_generation' + '/wp-admin/tools.php?page=classifai#/language_processing/feature_excerpt_generation' ); - cy.get( '#status' ).check(); - cy.get( - '#classifai_feature_excerpt_generation_post_types_post' - ).check(); - cy.get( '#submit' ).click(); + cy.get( '.classifai-enable-feature-toggle input' ).check(); + cy.get( '.settings-allowed-post-types input#post' ).check(); + cy.saveFeatureSettings(); cy.optInAllFeatures(); cy.disableClassicEditor(); } ); @@ -21,32 +19,20 @@ describe( '[Language processing] Excerpt Generation Tests', () => { it( 'Can save Azure OpenAI "Language Processing" settings', () => { cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_excerpt_generation' + '/wp-admin/tools.php?page=classifai#/language_processing/feature_excerpt_generation' ); - cy.get( '#provider' ).select( 'azure_openai' ); - cy.get( - 'input[name="classifai_feature_excerpt_generation[azure_openai][endpoint_url]"]' - ) + cy.selectProvider( 'azure_openai' ); + cy.get( 'input#azure_openai_endpoint_url' ) .clear() .type( 'https://e2e-test-azure-openai.test/' ); - cy.get( - 'input[name="classifai_feature_excerpt_generation[azure_openai][api_key]"]' - ) - .clear() - .type( 'password' ); - cy.get( - 'input[name="classifai_feature_excerpt_generation[azure_openai][deployment]"]' - ) - .clear() - .type( 'test' ); - - cy.get( '#status' ).check(); - cy.get( - '#classifai_feature_excerpt_generation_roles_administrator' - ).check(); - cy.get( '#length' ).clear().type( 35 ); - cy.get( '#submit' ).click(); + cy.get( 'input#azure_openai_api_key' ).clear().type( 'password' ); + cy.get( 'input#azure_openai_deployment' ).clear().type( 'test' ); + + cy.get( '.classifai-enable-feature-toggle input' ).check(); + cy.get( '.settings-allowed-roles input#administrator' ).check(); + cy.get( '#excerpt_length' ).clear().type( 35 ); + cy.saveFeatureSettings(); } ); it( 'Can see the generate excerpt button in a post', () => { @@ -99,10 +85,10 @@ describe( '[Language processing] Excerpt Generation Tests', () => { cy.enableClassicEditor(); cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_excerpt_generation' + '/wp-admin/tools.php?page=classifai#/language_processing/feature_excerpt_generation' ); - cy.get( '#status' ).check(); - cy.get( '#submit' ).click(); + cy.get( '.classifai-enable-feature-toggle input' ).check(); + cy.saveFeatureSettings(); const data = getChatGPTData(); From f32a3011d96d65824cc12b94853004e029ded12f Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Mon, 16 Sep 2024 16:54:36 +0530 Subject: [PATCH 104/138] Update E2E tests for google gemini API - excerpt generation. --- .../provider-settings/google-gemini-api.js | 1 + ...rpt-generation-googleai-gemini-api.test.js | 36 +++++++++---------- 2 files changed, 17 insertions(+), 20 deletions(-) diff --git a/src/js/settings/components/provider-settings/google-gemini-api.js b/src/js/settings/components/provider-settings/google-gemini-api.js index 02dd7b46a..98f93af0c 100644 --- a/src/js/settings/components/provider-settings/google-gemini-api.js +++ b/src/js/settings/components/provider-settings/google-gemini-api.js @@ -38,6 +38,7 @@ export const GoogleAIGeminiAPISettings = ( { isConfigured = false } ) => { description={ } > onChange( { api_key: value } ) } diff --git a/tests/cypress/integration/language-processing/excerpt-generation-googleai-gemini-api.test.js b/tests/cypress/integration/language-processing/excerpt-generation-googleai-gemini-api.test.js index 07555d6ad..f66d7b9bd 100644 --- a/tests/cypress/integration/language-processing/excerpt-generation-googleai-gemini-api.test.js +++ b/tests/cypress/integration/language-processing/excerpt-generation-googleai-gemini-api.test.js @@ -4,13 +4,12 @@ describe( '[Language processing] Excerpt Generation Tests', () => { before( () => { cy.login(); cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_excerpt_generation' + '/wp-admin/tools.php?page=classifai#/language_processing/feature_excerpt_generation' ); - cy.get( '#status' ).check(); - cy.get( - '#classifai_feature_excerpt_generation_post_types_post' - ).check(); - cy.get( '#submit' ).click(); + cy.get( '#classifai-logo' ).should( 'exist' ); + cy.get( '.classifai-enable-feature-toggle input' ).check(); + cy.get( '.settings-allowed-post-types input#post' ).check(); + cy.saveFeatureSettings(); cy.optInAllFeatures(); cy.disableClassicEditor(); } ); @@ -21,21 +20,18 @@ describe( '[Language processing] Excerpt Generation Tests', () => { it( 'Can save Google AI (Gemini API) "Language Processing" settings', () => { cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_excerpt_generation' + '/wp-admin/tools.php?page=classifai#/language_processing/feature_excerpt_generation' ); - cy.get( '#provider' ).select( 'googleai_gemini_api' ); - cy.get( - 'input[name="classifai_feature_excerpt_generation[googleai_gemini_api][api_key]"]' - ) + cy.get( '#classifai-logo' ).should( 'exist' ); + cy.selectProvider( 'googleai_gemini_api' ); + cy.get( 'input#googleai_gemini_api_api_key' ) .clear() .type( 'password' ); - cy.get( '#status' ).check(); - cy.get( - '#classifai_feature_excerpt_generation_roles_administrator' - ).check(); - cy.get( '#length' ).clear().type( 35 ); - cy.get( '#submit' ).click(); + cy.get( '.classifai-enable-feature-toggle input' ).check(); + cy.get( '.settings-allowed-roles input#administrator' ).check(); + cy.get( '#excerpt_length' ).clear().type( 35 ); + cy.saveFeatureSettings(); } ); it( 'Can see the generate excerpt button in a post', () => { @@ -88,10 +84,10 @@ describe( '[Language processing] Excerpt Generation Tests', () => { cy.enableClassicEditor(); cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_excerpt_generation' + '/wp-admin/tools.php?page=classifai#/language_processing/feature_excerpt_generation' ); - cy.get( '#status' ).check(); - cy.get( '#submit' ).click(); + cy.get( '.classifai-enable-feature-toggle input' ).check(); + cy.saveFeatureSettings(); const data = getGeminiAPIData(); From 81626292ddecd51f8c852337013d8fbf8c41cbdd Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Mon, 16 Sep 2024 17:28:56 +0530 Subject: [PATCH 105/138] Update E2E tests for openAI ChatGPT --- .../prompt-repeater.js | 3 + .../provider-settings/openai-chatgpt.js | 2 + .../components/user-permissions/index.js | 1 + .../excerpt-generation-azure-openai.test.js | 3 +- .../excerpt-generation-openai-chatgpt.test.js | 121 +++++++----------- 5 files changed, 52 insertions(+), 78 deletions(-) diff --git a/src/js/settings/components/feature-additional-settings/prompt-repeater.js b/src/js/settings/components/feature-additional-settings/prompt-repeater.js index ddbd7ea18..c218aa600 100644 --- a/src/js/settings/components/feature-additional-settings/prompt-repeater.js +++ b/src/js/settings/components/feature-additional-settings/prompt-repeater.js @@ -62,6 +62,7 @@ export const PromptRepeater = ( props ) => { { prompts.map( ( prompt, index ) => (
    { !! prompt.original && ( @@ -96,6 +97,7 @@ export const PromptRepeater = ( props ) => { 'Short description of prompt to use for identification.', 'classifai' ) } + className="classifai-prompt-title" /> { prompt: value, } ); } } + className="classifai-prompt-text" /> ) } diff --git a/src/js/settings/components/provider-settings/openai-chatgpt.js b/src/js/settings/components/provider-settings/openai-chatgpt.js index d0d048217..6e412c74b 100644 --- a/src/js/settings/components/provider-settings/openai-chatgpt.js +++ b/src/js/settings/components/provider-settings/openai-chatgpt.js @@ -37,6 +37,7 @@ export const OpenAIChatGPTSettings = ( { isConfigured = false } ) => { description={ } > onChange( { api_key: value } ) } @@ -55,6 +56,7 @@ export const OpenAIChatGPTSettings = ( { isConfigured = false } ) => { ) } > diff --git a/src/js/settings/components/user-permissions/index.js b/src/js/settings/components/user-permissions/index.js index 10b72ba75..aeb6b4a28 100644 --- a/src/js/settings/components/user-permissions/index.js +++ b/src/js/settings/components/user-permissions/index.js @@ -68,6 +68,7 @@ export const UserPermissions = () => { user_based_opt_out: value ? '1' : 'no', } ); } } + className="classifai-settings__user-based-opt-out" /> diff --git a/tests/cypress/integration/language-processing/excerpt-generation-azure-openai.test.js b/tests/cypress/integration/language-processing/excerpt-generation-azure-openai.test.js index 2b74de89c..e982c45a6 100644 --- a/tests/cypress/integration/language-processing/excerpt-generation-azure-openai.test.js +++ b/tests/cypress/integration/language-processing/excerpt-generation-azure-openai.test.js @@ -6,6 +6,7 @@ describe( '[Language processing] Excerpt Generation Tests', () => { cy.visit( '/wp-admin/tools.php?page=classifai#/language_processing/feature_excerpt_generation' ); + cy.get( '#classifai-logo' ).should( 'exist' ); cy.get( '.classifai-enable-feature-toggle input' ).check(); cy.get( '.settings-allowed-post-types input#post' ).check(); cy.saveFeatureSettings(); @@ -21,7 +22,7 @@ describe( '[Language processing] Excerpt Generation Tests', () => { cy.visit( '/wp-admin/tools.php?page=classifai#/language_processing/feature_excerpt_generation' ); - + cy.get( '#classifai-logo' ).should( 'exist' ); cy.selectProvider( 'azure_openai' ); cy.get( 'input#azure_openai_endpoint_url' ) .clear() diff --git a/tests/cypress/integration/language-processing/excerpt-generation-openai-chatgpt.test.js b/tests/cypress/integration/language-processing/excerpt-generation-openai-chatgpt.test.js index 335e60353..4c59113a6 100644 --- a/tests/cypress/integration/language-processing/excerpt-generation-openai-chatgpt.test.js +++ b/tests/cypress/integration/language-processing/excerpt-generation-openai-chatgpt.test.js @@ -4,13 +4,12 @@ describe( '[Language processing] Excerpt Generation Tests', () => { before( () => { cy.login(); cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_excerpt_generation' + '/wp-admin/tools.php?page=classifai#/language_processing/feature_excerpt_generation' ); - cy.get( '#status' ).check(); - cy.get( - '#classifai_feature_excerpt_generation_post_types_post' - ).check(); - cy.get( '#submit' ).click(); + cy.get( '#classifai-logo' ).should( 'exist' ); + cy.get( '.classifai-enable-feature-toggle input' ).check(); + cy.get( '.settings-allowed-post-types input#post' ).check(); + cy.saveFeatureSettings(); cy.optInAllFeatures(); cy.disableClassicEditor(); } ); @@ -21,18 +20,16 @@ describe( '[Language processing] Excerpt Generation Tests', () => { it( 'Can save OpenAI ChatGPT "Language Processing" settings', () => { cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_excerpt_generation' + '/wp-admin/tools.php?page=classifai#/language_processing/feature_excerpt_generation' ); - - cy.get( '#provider' ).select( 'openai_chatgpt' ); - cy.get( '#api_key' ).clear().type( 'password' ); - - cy.get( '#status' ).check(); - cy.get( - '#classifai_feature_excerpt_generation_roles_administrator' - ).check(); - cy.get( '#length' ).clear().type( 35 ); - cy.get( '#submit' ).click(); + cy.get( '#classifai-logo' ).should( 'exist' ); + cy.selectProvider( 'openai_chatgpt' ); + cy.get( '#openai_chatgpt_api_key' ).clear().type( 'password' ); + + cy.get( '.classifai-enable-feature-toggle input' ).check(); + cy.get( '.settings-allowed-roles input#administrator' ).check(); + cy.get( '#excerpt_length' ).clear().type( 35 ); + cy.saveFeatureSettings(); } ); it( 'Can see the generate excerpt button in a post', () => { @@ -85,10 +82,10 @@ describe( '[Language processing] Excerpt Generation Tests', () => { cy.enableClassicEditor(); cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_excerpt_generation' + '/wp-admin/tools.php?page=classifai#/language_processing/feature_excerpt_generation' ); - cy.get( '#status' ).check(); - cy.get( '#submit' ).click(); + cy.get( '.classifai-enable-feature-toggle input' ).check(); + cy.saveFeatureSettings(); const data = getChatGPTData(); @@ -116,84 +113,54 @@ describe( '[Language processing] Excerpt Generation Tests', () => { cy.disableClassicEditor(); cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_excerpt_generation' + '/wp-admin/tools.php?page=classifai#/language_processing/feature_excerpt_generation' ); // Add three custom prompts. - cy.get( - '[name="classifai_feature_excerpt_generation[generate_excerpt_prompt][0][default]"]' - ) - .parents( 'td:first' ) - .find( 'button.js-classifai-add-prompt-fieldset' ) + cy.get( 'button.components-button.action__add_prompt' ) .click() .click() .click(); cy.get( - '[name="classifai_feature_excerpt_generation[generate_excerpt_prompt][0][default]"]' - ) - .parents( 'td' ) - .find( '.classifai-field-type-prompt-setting' ) - .should( 'have.length', 4 ); + '.classifai-prompts div.classifai-field-type-prompt-setting' + ).should( 'have.length', 4 ); // Set the data for each prompt. - cy.get( - '[name="classifai_feature_excerpt_generation[generate_excerpt_prompt][1][title]"]' - ) + cy.get( '#classifai-prompt-setting-1 .classifai-prompt-title input' ) .clear() .type( 'First custom prompt' ); - cy.get( - '[name="classifai_feature_excerpt_generation[generate_excerpt_prompt][1][prompt]"]' - ) + cy.get( '#classifai-prompt-setting-1 .classifai-prompt-text textarea' ) .clear() .type( 'This is our first custom excerpt prompt' ); - cy.get( - '[name="classifai_feature_excerpt_generation[generate_excerpt_prompt][2][title]"]' - ) + cy.get( '#classifai-prompt-setting-2 .classifai-prompt-title input' ) .clear() .type( 'Second custom prompt' ); - cy.get( - '[name="classifai_feature_excerpt_generation[generate_excerpt_prompt][2][prompt]"]' - ) + cy.get( '#classifai-prompt-setting-2 .classifai-prompt-text textarea' ) .clear() .type( 'This prompt should be deleted' ); - cy.get( - '[name="classifai_feature_excerpt_generation[generate_excerpt_prompt][3][title]"]' - ) + cy.get( '#classifai-prompt-setting-3 .classifai-prompt-title input' ) .clear() .type( 'Third custom prompt' ); - cy.get( - '[name="classifai_feature_excerpt_generation[generate_excerpt_prompt][3][prompt]"]' - ) + cy.get( '#classifai-prompt-setting-3 .classifai-prompt-text textarea' ) .clear() .type( 'This is a custom excerpt prompt' ); // Set the third prompt as our default. cy.get( - '[name="classifai_feature_excerpt_generation[generate_excerpt_prompt][3][default]"]' - ) - .parent() - .find( 'a.action__set_default' ) - .click( { force: true } ); + '#classifai-prompt-setting-3 .actions-rows button.action__set_default' + ).click( { force: true } ); // Delete the second prompt. cy.get( - '[name="classifai_feature_excerpt_generation[generate_excerpt_prompt][2][default]"]' - ) - .parent() - .find( 'a.action__remove_prompt' ) - .click( { force: true } ); - cy.get( 'div[aria-describedby="js-classifai--delete-prompt-modal"]' ) - .find( '.button-primary' ) - .click(); + '#classifai-prompt-setting-2 .actions-rows button.action__remove_prompt' + ).click( { force: true } ); + cy.get( 'div.components-confirm-dialog button.is-primary' ).click(); cy.get( - '[name="classifai_feature_excerpt_generation[generate_excerpt_prompt][0][default]"]' - ) - .parents( 'td:first' ) - .find( '.classifai-field-type-prompt-setting' ) - .should( 'have.length', 3 ); + '.classifai-prompts div.classifai-field-type-prompt-setting' + ).should( 'have.length', 3 ); - cy.get( '#submit' ).click(); + cy.saveFeatureSettings(); const data = getChatGPTData( 'excerpt' ); @@ -240,20 +207,20 @@ describe( '[Language processing] Excerpt Generation Tests', () => { it( 'Can enable/disable excerpt generation feature', () => { // Disable features. cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_excerpt_generation' + '/wp-admin/tools.php?page=classifai#/language_processing/feature_excerpt_generation' ); - cy.get( '#status' ).uncheck(); - cy.get( '#submit' ).click(); + cy.get( '.classifai-enable-feature-toggle input' ).uncheck(); + cy.saveFeatureSettings(); // Verify that the feature is not available. cy.verifyExcerptGenerationEnabled( false ); // Enable feature. cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_excerpt_generation' + '/wp-admin/tools.php?page=classifai#/language_processing/feature_excerpt_generation' ); - cy.get( '#status' ).check(); - cy.get( '#submit' ).click(); + cy.get( '.classifai-enable-feature-toggle input' ).check(); + cy.saveFeatureSettings(); // Verify that the feature is available. cy.verifyExcerptGenerationEnabled( true ); @@ -261,10 +228,10 @@ describe( '[Language processing] Excerpt Generation Tests', () => { it( 'Can enable/disable excerpt generation feature by role', () => { cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_excerpt_generation' + '/wp-admin/tools.php?page=classifai#/language_processing/feature_excerpt_generation' ); - cy.get( '#status' ).check(); - cy.get( '#submit' ).click(); + cy.get( '.classifai-enable-feature-toggle input' ).check(); + cy.saveFeatureSettings(); // Disable admin role. cy.disableFeatureForRoles( 'feature_excerpt_generation', [ From 643e6a9a9d34264c61ca9b9970ca7bbc4266cc09 Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Mon, 16 Sep 2024 17:50:46 +0530 Subject: [PATCH 106/138] Update E2E tests for moderation feature. --- .../feature-additional-settings/moderation.js | 2 + .../components/provider-settings/openai.js | 1 + .../moderation-openai-moderation.test.js | 49 +++++++++---------- 3 files changed, 27 insertions(+), 25 deletions(-) diff --git a/src/js/settings/components/feature-additional-settings/moderation.js b/src/js/settings/components/feature-additional-settings/moderation.js index cd7cf5b4a..ae70f7e5e 100644 --- a/src/js/settings/components/feature-additional-settings/moderation.js +++ b/src/js/settings/components/feature-additional-settings/moderation.js @@ -21,10 +21,12 @@ export const ModerationSettings = () => { 'Choose what type of content to moderate.', 'classifai' ) } + className="settings-moderation-content-types" > { Object.keys( contentTypes ).map( ( contentType ) => { return ( { description={ } > onChange( { api_key: value } ) } diff --git a/tests/cypress/integration/language-processing/moderation-openai-moderation.test.js b/tests/cypress/integration/language-processing/moderation-openai-moderation.test.js index 8c3dfc3f7..74188aa4e 100644 --- a/tests/cypress/integration/language-processing/moderation-openai-moderation.test.js +++ b/tests/cypress/integration/language-processing/moderation-openai-moderation.test.js @@ -2,9 +2,9 @@ describe( '[Language processing] Moderation Tests', () => { before( () => { cy.login(); cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_moderation' + '/wp-admin/tools.php?page=classifai#/language_processing/feature_moderation' ); - cy.get( '#submit' ).click(); + cy.saveFeatureSettings(); cy.optInAllFeatures(); cy.disableClassicEditor(); } ); @@ -15,16 +15,15 @@ describe( '[Language processing] Moderation Tests', () => { it( 'Can save OpenAI Moderation "Language Processing" settings', () => { cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_moderation' + '/wp-admin/tools.php?page=classifai#/language_processing/feature_moderation' ); - cy.get( '#api_key' ).clear().type( 'password' ); - cy.get( '#status' ).check(); - cy.get( - '#classifai_feature_moderation_content_types_comments' - ).check(); - cy.get( '#classifai_feature_moderation_roles_administrator' ).check(); - cy.get( '#submit' ).click(); + cy.selectProvider( 'openai_moderation' ); + cy.get( '#openai_api_key' ).clear().type( 'password' ); + cy.get( '.classifai-enable-feature-toggle input' ).check(); + cy.get( '.settings-moderation-content-types input#comments' ).check(); + cy.get( '.settings-allowed-roles input#administrator' ).check(); + cy.saveFeatureSettings(); } ); it( 'Can run moderation on a comment', () => { @@ -43,20 +42,20 @@ describe( '[Language processing] Moderation Tests', () => { it( 'Can enable/disable moderation feature', () => { // Disable features. cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_moderation' + '/wp-admin/tools.php?page=classifai#/language_processing/feature_moderation' ); - cy.get( '#status' ).uncheck(); - cy.get( '#submit' ).click(); + cy.get( '.classifai-enable-feature-toggle input' ).uncheck(); + cy.saveFeatureSettings(); // Verify that the feature is not available. cy.verifyModerationEnabled( false ); // Enable feature. cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_moderation' + '/wp-admin/tools.php?page=classifai#/language_processing/feature_moderation' ); - cy.get( '#status' ).check(); - cy.get( '#submit' ).click(); + cy.get( '.classifai-enable-feature-toggle input' ).check(); + cy.saveFeatureSettings(); // Verify that the feature is available. cy.verifyModerationEnabled( true ); @@ -64,10 +63,10 @@ describe( '[Language processing] Moderation Tests', () => { it( 'Can enable/disable moderation feature by role', () => { cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_moderation' + '/wp-admin/tools.php?page=classifai#/language_processing/feature_moderation' ); - cy.get( '#status' ).check(); - cy.get( '#submit' ).click(); + cy.get( '.classifai-enable-feature-toggle input' ).check(); + cy.saveFeatureSettings(); // Disable admin role. cy.disableFeatureForRoles( 'feature_moderation', [ 'administrator' ] ); @@ -84,10 +83,10 @@ describe( '[Language processing] Moderation Tests', () => { it( 'Can enable/disable moderation feature by user', () => { cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_moderation' + '/wp-admin/tools.php?page=classifai#/language_processing/feature_moderation' ); - cy.get( '#status' ).check(); - cy.get( '#submit' ).click(); + cy.get( '.classifai-enable-feature-toggle input' ).check(); + cy.saveFeatureSettings(); // Disable admin role. cy.disableFeatureForRoles( 'feature_moderation', [ 'administrator' ] ); @@ -106,10 +105,10 @@ describe( '[Language processing] Moderation Tests', () => { it( 'User can opt-out of moderation feature', () => { cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_moderation' + '/wp-admin/tools.php?page=classifai#/language_processing/feature_moderation' ); - cy.get( '#status' ).check(); - cy.get( '#submit' ).click(); + cy.get( '.classifai-enable-feature-toggle input' ).check(); + cy.saveFeatureSettings(); // Enable user based opt-out. cy.enableFeatureOptOut( 'feature_moderation' ); From c0565708900aef5b7b190ab0bbae7f506fb0c3b1 Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Mon, 16 Sep 2024 17:51:31 +0530 Subject: [PATCH 107/138] Update E2E tests for resize content feature. --- .../content-resizing.js | 2 + .../resize_content-azure-openai.test.js | 34 ++--- ...resize_content-googleai-gemini-api.test.js | 24 ++- .../resize_content-openai-chatgpt.test.js | 138 +++++++----------- 4 files changed, 76 insertions(+), 122 deletions(-) diff --git a/src/js/settings/components/feature-additional-settings/content-resizing.js b/src/js/settings/components/feature-additional-settings/content-resizing.js index ef4160f11..12cb8e2b7 100644 --- a/src/js/settings/components/feature-additional-settings/content-resizing.js +++ b/src/js/settings/components/feature-additional-settings/content-resizing.js @@ -15,6 +15,7 @@ export const ContentResizingSettings = () => { { { before( () => { cy.login(); cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_content_resizing' + '/wp-admin/tools.php?page=classifai#/language_processing/feature_content_resizing' ); - cy.get( '#status' ).check(); - cy.get( '#provider' ).select( 'azure_openai' ); - cy.get( - 'input[name="classifai_feature_content_resizing[azure_openai][endpoint_url]"]' - ) + cy.get( '.classifai-enable-feature-toggle input' ).check(); + cy.selectProvider( 'azure_openai' ); + cy.get( 'input#azure_openai_endpoint_url' ) .clear() .type( 'https://e2e-test-azure-openai.test/' ); - cy.get( - 'input[name="classifai_feature_content_resizing[azure_openai][api_key]"]' - ) - .clear() - .type( 'password' ); - cy.get( - 'input[name="classifai_feature_content_resizing[azure_openai][deployment]"]' - ) - .clear() - .type( 'test' ); - cy.get( '#submit' ).click(); + cy.get( 'input#azure_openai_api_key' ).clear().type( 'password' ); + cy.get( 'input#azure_openai_deployment' ).clear().type( 'test' ); + cy.saveFeatureSettings(); cy.optInAllFeatures(); cy.disableClassicEditor(); } ); @@ -32,14 +22,12 @@ describe( '[Language processing] Resize Content Tests', () => { it( 'Resize content feature can grow and shrink content', () => { cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_content_resizing' + '/wp-admin/tools.php?page=classifai#/language_processing/feature_content_resizing' ); - cy.get( '#status' ).check(); - cy.get( - '#classifai_feature_content_resizing_roles_administrator' - ).check(); - cy.get( '#submit' ).click(); + cy.get( '.classifai-enable-feature-toggle input' ).check(); + cy.get( '.settings-allowed-roles input#administrator' ).check(); + cy.saveFeatureSettings(); cy.createPost( { title: 'Resize content', diff --git a/tests/cypress/integration/language-processing/resize_content-googleai-gemini-api.test.js b/tests/cypress/integration/language-processing/resize_content-googleai-gemini-api.test.js index 4c21eda01..75c592ef9 100644 --- a/tests/cypress/integration/language-processing/resize_content-googleai-gemini-api.test.js +++ b/tests/cypress/integration/language-processing/resize_content-googleai-gemini-api.test.js @@ -2,16 +2,12 @@ describe( '[Language processing] Resize Content Tests', () => { before( () => { cy.login(); cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_content_resizing' + '/wp-admin/tools.php?page=classifai#/language_processing/feature_content_resizing' ); - cy.get( '#status' ).check(); - cy.get( '#provider' ).select( 'googleai_gemini_api' ); - cy.get( - 'input[name="classifai_feature_content_resizing[googleai_gemini_api][api_key]"]' - ) - .clear() - .type( 'abc123' ); - cy.get( '#submit' ).click(); + cy.get( '.classifai-enable-feature-toggle input' ).check(); + cy.selectProvider( 'googleai_gemini_api' ); + cy.get( 'input#googleai_gemini_api_api_key' ).clear().type( 'abc123' ); + cy.saveFeatureSettings(); cy.optInAllFeatures(); cy.disableClassicEditor(); } ); @@ -22,14 +18,12 @@ describe( '[Language processing] Resize Content Tests', () => { it( 'Resize content feature can grow and shrink content', () => { cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_content_resizing' + '/wp-admin/tools.php?page=classifai#/language_processing/feature_content_resizing' ); - cy.get( '#status' ).check(); - cy.get( - '#classifai_feature_content_resizing_roles_administrator' - ).check(); - cy.get( '#submit' ).click(); + cy.get( '.classifai-enable-feature-toggle input' ).check(); + cy.get( '.settings-allowed-roles input#administrator' ).check(); + cy.saveFeatureSettings(); cy.createPost( { title: 'Resize content', diff --git a/tests/cypress/integration/language-processing/resize_content-openai-chatgpt.test.js b/tests/cypress/integration/language-processing/resize_content-openai-chatgpt.test.js index 9b8c17066..04dc7ae42 100644 --- a/tests/cypress/integration/language-processing/resize_content-openai-chatgpt.test.js +++ b/tests/cypress/integration/language-processing/resize_content-openai-chatgpt.test.js @@ -2,12 +2,12 @@ describe( '[Language processing] Resize Content Tests', () => { before( () => { cy.login(); cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_content_resizing' + '/wp-admin/tools.php?page=classifai#/language_processing/feature_content_resizing' ); - cy.get( '#status' ).check(); - cy.get( '#provider' ).select( 'openai_chatgpt' ); - cy.get( '#api_key' ).type( 'abc123' ); - cy.get( '#submit' ).click(); + cy.get( '.classifai-enable-feature-toggle input' ).check(); + cy.selectProvider( 'openai_chatgpt' ); + cy.get( '#openai_chatgpt_api_key' ).type( 'abc123' ); + cy.saveFeatureSettings(); cy.optInAllFeatures(); cy.disableClassicEditor(); } ); @@ -18,14 +18,12 @@ describe( '[Language processing] Resize Content Tests', () => { it( 'Resize content feature can grow and shrink content', () => { cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_content_resizing' + '/wp-admin/tools.php?page=classifai#/language_processing/feature_content_resizing' ); - cy.get( '#status' ).check(); - cy.get( - '#classifai_feature_content_resizing_roles_administrator' - ).check(); - cy.get( '#submit' ).click(); + cy.get( '.classifai-enable-feature-toggle input' ).check(); + cy.get( '.settings-allowed-roles input#administrator' ).check(); + cy.saveFeatureSettings(); cy.createPost( { title: 'Resize content', @@ -77,153 +75,125 @@ describe( '[Language processing] Resize Content Tests', () => { it( 'Can set multiple custom resize generation prompts, select one as the default and delete one.', () => { cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_content_resizing' + '/wp-admin/tools.php?page=classifai#/language_processing/feature_content_resizing' ); // Add three custom shrink prompts. cy.get( - '[name="classifai_feature_content_resizing[condense_text_prompt][0][default]"]' + '.settings-condense-text-prompt button.components-button.action__add_prompt' ) - .parents( 'td:first' ) - .find( 'button.js-classifai-add-prompt-fieldset' ) .click() .click() .click(); cy.get( - '[name="classifai_feature_content_resizing[condense_text_prompt][0][default]"]' - ) - .parents( 'td:first' ) - .find( '.classifai-field-type-prompt-setting' ) - .should( 'have.length', 4 ); + '.settings-condense-text-prompt .classifai-prompts div.classifai-field-type-prompt-setting' + ).should( 'have.length', 4 ); // Add three custom grow prompts. cy.get( - '[name="classifai_feature_content_resizing[expand_text_prompt][0][default]"]' + '.settings-expand-text-prompt button.components-button.action__add_prompt' ) - .parents( 'td:first' ) - .find( 'button.js-classifai-add-prompt-fieldset:first' ) .click() .click() .click(); cy.get( - '[name="classifai_feature_content_resizing[expand_text_prompt][0][default]"]' - ) - .parents( 'td:first' ) - .find( '.classifai-field-type-prompt-setting' ) - .should( 'have.length', 4 ); + '.settings-expand-text-prompt .classifai-prompts div.classifai-field-type-prompt-setting' + ).should( 'have.length', 4 ); // Set the data for each prompt. cy.get( - '[name="classifai_feature_content_resizing[condense_text_prompt][1][title]"]' + '.settings-condense-text-prompt #classifai-prompt-setting-1 .classifai-prompt-title input' ) .clear() .type( 'First custom prompt' ); cy.get( - '[name="classifai_feature_content_resizing[condense_text_prompt][1][prompt]"]' + '.settings-condense-text-prompt #classifai-prompt-setting-1 .classifai-prompt-text textarea' ) .clear() .type( 'This is our first custom shrink prompt' ); cy.get( - '[name="classifai_feature_content_resizing[condense_text_prompt][2][title]"]' + '.settings-condense-text-prompt #classifai-prompt-setting-2 .classifai-prompt-title input' ) .clear() .type( 'Second custom prompt' ); cy.get( - '[name="classifai_feature_content_resizing[condense_text_prompt][2][prompt]"]' + '.settings-condense-text-prompt #classifai-prompt-setting-2 .classifai-prompt-text textarea' ) .clear() .type( 'This prompt should be deleted' ); + cy.get( - '[name="classifai_feature_content_resizing[condense_text_prompt][3][title]"]' + '.settings-condense-text-prompt #classifai-prompt-setting-3 .classifai-prompt-title input' ) .clear() .type( 'Third custom prompt' ); cy.get( - '[name="classifai_feature_content_resizing[condense_text_prompt][3][prompt]"]' + '.settings-condense-text-prompt #classifai-prompt-setting-3 .classifai-prompt-text textarea' ) .clear() .type( 'This is a custom shrink prompt' ); + + // Expand prompts. cy.get( - '[name="classifai_feature_content_resizing[expand_text_prompt][1][title]"]' + '.settings-expand-text-prompt #classifai-prompt-setting-1 .classifai-prompt-title input' ) .clear() .type( 'First custom prompt' ); cy.get( - '[name="classifai_feature_content_resizing[expand_text_prompt][1][prompt]"]' + '.settings-expand-text-prompt #classifai-prompt-setting-1 .classifai-prompt-text textarea' ) .clear() .type( 'This is our first custom grow prompt' ); cy.get( - '[name="classifai_feature_content_resizing[expand_text_prompt][2][title]"]' + '.settings-expand-text-prompt #classifai-prompt-setting-2 .classifai-prompt-title input' ) .clear() .type( 'Second custom prompt' ); cy.get( - '[name="classifai_feature_content_resizing[expand_text_prompt][2][prompt]"]' + '.settings-expand-text-prompt #classifai-prompt-setting-2 .classifai-prompt-text textarea' ) .clear() .type( 'This prompt should be deleted' ); cy.get( - '[name="classifai_feature_content_resizing[expand_text_prompt][3][title]"]' + '.settings-expand-text-prompt #classifai-prompt-setting-3 .classifai-prompt-title input' ) .clear() .type( 'Third custom prompt' ); cy.get( - '[name="classifai_feature_content_resizing[expand_text_prompt][3][prompt]"]' + '.settings-expand-text-prompt #classifai-prompt-setting-3 .classifai-prompt-text textarea' ) .clear() .type( 'This is a custom grow prompt' ); // Set the third prompt as our default. cy.get( - '[name="classifai_feature_content_resizing[condense_text_prompt][3][default]"]' - ) - .parent() - .find( 'a.action__set_default' ) - .click( { force: true } ); + '.settings-condense-text-prompt #classifai-prompt-setting-3 .actions-rows button.action__set_default' + ).click( { force: true } ); + cy.get( - '[name="classifai_feature_content_resizing[expand_text_prompt][3][default]"]' - ) - .parent() - .find( 'a.action__set_default' ) - .click( { force: true } ); + '.settings-expand-text-prompt #classifai-prompt-setting-3 .actions-rows button.action__set_default' + ).click( { force: true } ); // Delete the second prompt. cy.get( - '[name="classifai_feature_content_resizing[condense_text_prompt][2][default]"]' - ) - .parent() - .find( 'a.action__remove_prompt' ) - .click( { force: true } ); - cy.get( 'div[aria-describedby="js-classifai--delete-prompt-modal"]' ) - .find( '.button-primary' ) - .click(); + '.settings-condense-text-prompt #classifai-prompt-setting-2 .actions-rows button.action__remove_prompt' + ).click( { force: true } ); + cy.get( 'div.components-confirm-dialog button.is-primary' ).click(); cy.get( - '[name="classifai_feature_content_resizing[condense_text_prompt][0][default]"]' - ) - .parents( 'td:first' ) - .find( '.classifai-field-type-prompt-setting' ) - .should( 'have.length', 3 ); + '.settings-condense-text-prompt .classifai-prompts div.classifai-field-type-prompt-setting' + ).should( 'have.length', 3 ); + cy.get( - '[name="classifai_feature_content_resizing[expand_text_prompt][2][default]"]' - ) - .parent() - .find( 'a.action__remove_prompt' ) - .click( { force: true } ); - cy.get( 'div[aria-describedby="js-classifai--delete-prompt-modal"]' ) - .first() - .find( '.button-primary' ) - .click(); + '.settings-expand-text-prompt #classifai-prompt-setting-2 .actions-rows button.action__remove_prompt' + ).click( { force: true } ); + cy.get( 'div.components-confirm-dialog button.is-primary' ).click(); cy.get( - '[name="classifai_feature_content_resizing[expand_text_prompt][0][default]"]' - ) - .parents( 'td:first' ) - .find( '.classifai-field-type-prompt-setting' ) - .should( 'have.length', 3 ); + '.settings-expand-text-prompt .classifai-prompts div.classifai-field-type-prompt-setting' + ).should( 'have.length', 3 ); - cy.get( '#submit' ).click(); + cy.saveFeatureSettings(); cy.createPost( { title: 'Resize content', @@ -276,20 +246,20 @@ describe( '[Language processing] Resize Content Tests', () => { it( 'Can enable/disable resize content feature', () => { // Disable feature. cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_content_resizing' + '/wp-admin/tools.php?page=classifai#/language_processing/feature_content_resizing' ); - cy.get( '#status' ).uncheck(); - cy.get( '#submit' ).click(); + cy.get( '.classifai-enable-feature-toggle input' ).uncheck(); + cy.saveFeatureSettings(); // Verify that the feature is not available. cy.verifyResizeContentEnabled( false ); // Enable feature. cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_content_resizing' + '/wp-admin/tools.php?page=classifai#/language_processing/feature_content_resizing' ); - cy.get( '#status' ).check(); - cy.get( '#submit' ).click(); + cy.get( '.classifai-enable-feature-toggle input' ).check(); + cy.saveFeatureSettings(); // Verify that the feature is available. cy.verifyResizeContentEnabled( true ); From 34df979624c77b958336d43a518c6531bdc59327 Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Mon, 16 Sep 2024 17:53:19 +0530 Subject: [PATCH 108/138] Update E2E tests for speech to text feature. --- .../speech-to-text-openai-whisper.test.js | 36 +++++++++---------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/tests/cypress/integration/language-processing/speech-to-text-openai-whisper.test.js b/tests/cypress/integration/language-processing/speech-to-text-openai-whisper.test.js index 9e04582d5..ce21b563c 100644 --- a/tests/cypress/integration/language-processing/speech-to-text-openai-whisper.test.js +++ b/tests/cypress/integration/language-processing/speech-to-text-openai-whisper.test.js @@ -4,10 +4,10 @@ describe( '[Language processing] Speech to Text Tests', () => { before( () => { cy.login(); cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_audio_transcripts_generation' + '/wp-admin/tools.php?page=classifai#/language_processing/feature_audio_transcripts_generation' ); - cy.get( '#status' ).check(); - cy.get( '#submit' ).click(); + cy.get( '.classifai-enable-feature-toggle input' ).check(); + cy.saveFeatureSettings(); cy.optInAllFeatures(); cy.disableClassicEditor(); } ); @@ -18,16 +18,14 @@ describe( '[Language processing] Speech to Text Tests', () => { it( 'Can save OpenAI Whisper "Language Processing" settings', () => { cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_audio_transcripts_generation' + '/wp-admin/tools.php?page=classifai#/language_processing/feature_audio_transcripts_generation' ); - cy.get( '#api_key' ).clear().type( 'password' ); + cy.get( '#openai_api_key' ).clear().type( 'password' ); - cy.get( '#status' ).check(); - cy.get( - '#classifai_feature_audio_transcripts_generation_roles_administrator' - ).check(); - cy.get( '#submit' ).click(); + cy.get( '.classifai-enable-feature-toggle input' ).check(); + cy.get( '.settings-allowed-roles input#administrator' ).check(); + cy.saveFeatureSettings(); } ); let audioEditLink = ''; @@ -83,20 +81,20 @@ describe( '[Language processing] Speech to Text Tests', () => { // Disable features cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_audio_transcripts_generation' + '/wp-admin/tools.php?page=classifai#/language_processing/feature_audio_transcripts_generation' ); - cy.get( '#status' ).uncheck(); - cy.get( '#submit' ).click(); + cy.get( '.classifai-enable-feature-toggle input' ).uncheck(); + cy.saveFeatureSettings(); // Verify that the feature is not available. cy.verifySpeechToTextEnabled( false, options ); // Enable feature. cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_audio_transcripts_generation' + '/wp-admin/tools.php?page=classifai#/language_processing/feature_audio_transcripts_generation' ); - cy.get( '#status' ).check(); - cy.get( '#submit' ).click(); + cy.get( '.classifai-enable-feature-toggle input' ).check(); + cy.saveFeatureSettings(); // Verify that the feature is available. cy.verifySpeechToTextEnabled( true, options ); @@ -105,10 +103,10 @@ describe( '[Language processing] Speech to Text Tests', () => { it( 'Can enable/disable speech to text feature by role', () => { // Enable feature. cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_audio_transcripts_generation' + '/wp-admin/tools.php?page=classifai#/language_processing/feature_audio_transcripts_generation' ); - cy.get( '#status' ).check(); - cy.get( '#submit' ).click(); + cy.get( '.classifai-enable-feature-toggle input' ).check(); + cy.saveFeatureSettings(); const options = { audioEditLink, From 77f1be8d1d439136a511af99f7a8e1418b1109f1 Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Mon, 16 Sep 2024 18:17:21 +0530 Subject: [PATCH 109/138] Update E2E tests for text to speech feature. --- .../text-to-speech.js | 2 + .../provider-settings/amazon-polly.js | 5 ++ .../provider-settings/azure-text-to-speech.js | 3 + .../openai-text-to-speech.js | 5 ++ .../text-to-speech-amazon-polly.test.js | 33 +++++----- .../text-to-speech-microsoft-azure.test.js | 65 +++++++++---------- ...xt-to-speech-openai-text-to-speech.test.js | 55 +++++++--------- 7 files changed, 86 insertions(+), 82 deletions(-) diff --git a/src/js/settings/components/feature-additional-settings/text-to-speech.js b/src/js/settings/components/feature-additional-settings/text-to-speech.js index c733190b2..c808173bb 100644 --- a/src/js/settings/components/feature-additional-settings/text-to-speech.js +++ b/src/js/settings/components/feature-additional-settings/text-to-speech.js @@ -20,11 +20,13 @@ export const TextToSpeechSettings = () => { 'Choose which post types support this feature.', 'classifai' ) } + className="settings-allowed-post-types" > { postTypesSelectOptions.map( ( option ) => { const { value: key, label } = option; return ( { <> @@ -37,6 +38,7 @@ export const AmazonPollySettings = ( { isConfigured = false } ) => { ) } > @@ -62,6 +64,7 @@ export const AmazonPollySettings = ( { isConfigured = false } ) => { } > @@ -101,6 +104,7 @@ export const AmazonPollySettings = ( { isConfigured = false } ) => { } > onChange( { voice_engine: value } ) } @@ -123,6 +127,7 @@ export const AmazonPollySettings = ( { isConfigured = false } ) => { onChange( { voice: value } ) } value={ providerSettings.voice || '' } options={ ( providerSettings.voices || [] ) diff --git a/src/js/settings/components/provider-settings/azure-text-to-speech.js b/src/js/settings/components/provider-settings/azure-text-to-speech.js index 47eee610d..ae1893e66 100644 --- a/src/js/settings/components/provider-settings/azure-text-to-speech.js +++ b/src/js/settings/components/provider-settings/azure-text-to-speech.js @@ -44,6 +44,7 @@ export const AzureTextToSpeechSettings = ( { isConfigured = false } ) => { description={ } > @@ -53,6 +54,7 @@ export const AzureTextToSpeechSettings = ( { isConfigured = false } ) => { @@ -65,6 +67,7 @@ export const AzureTextToSpeechSettings = ( { isConfigured = false } ) => { { !! providerSettings.voices?.length && ( onChange( { voice: value } ) } value={ providerSettings.voice || '' } options={ ( providerSettings.voices || [] ).map( diff --git a/src/js/settings/components/provider-settings/openai-text-to-speech.js b/src/js/settings/components/provider-settings/openai-text-to-speech.js index c0927481a..5f67ea2ae 100644 --- a/src/js/settings/components/provider-settings/openai-text-to-speech.js +++ b/src/js/settings/components/provider-settings/openai-text-to-speech.js @@ -37,6 +37,7 @@ export const OpenAITextToSpeachSettings = ( { isConfigured = false } ) => { description={ } > onChange( { api_key: value } ) } @@ -64,6 +65,7 @@ export const OpenAITextToSpeachSettings = ( { isConfigured = false } ) => { } > onChange( { tts_model: value } ) } value={ providerSettings.tts_model || 'tts-1' } options={ [ @@ -101,6 +103,7 @@ export const OpenAITextToSpeachSettings = ( { isConfigured = false } ) => { } > onChange( { voice: value } ) } value={ providerSettings.voice || 'alloy' } options={ [ @@ -139,6 +142,7 @@ export const OpenAITextToSpeachSettings = ( { isConfigured = false } ) => { ) } > onChange( { format: value } ) } value={ providerSettings.format || '.mp3' } options={ [ @@ -161,6 +165,7 @@ export const OpenAITextToSpeachSettings = ( { isConfigured = false } ) => { ) } > onChange( { speed: value } ) } value={ providerSettings.speed || 1 } type="number" diff --git a/tests/cypress/integration/language-processing/text-to-speech-amazon-polly.test.js b/tests/cypress/integration/language-processing/text-to-speech-amazon-polly.test.js index 0ce9b95bc..261c90da4 100644 --- a/tests/cypress/integration/language-processing/text-to-speech-amazon-polly.test.js +++ b/tests/cypress/integration/language-processing/text-to-speech-amazon-polly.test.js @@ -2,23 +2,24 @@ describe( '[Language Processing] Text to Speech (Amazon Polly) Tests', () => { before( () => { cy.login(); cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_text_to_speech_generation' + '/wp-admin/tools.php?page=classifai#/language_processing/feature_text_to_speech_generation' ); - cy.get( - '#classifai_feature_text_to_speech_generation_post_types_post' - ).check( 'post' ); - cy.get( '#provider' ).select( 'aws_polly' ); - cy.get( '#access_key_id' ).clear(); - cy.get( '#access_key_id' ).type( 'SAMPLE_ACCESS_KEY' ); - cy.get( '#secret_access_key' ).clear(); - cy.get( '#secret_access_key' ).type( 'SAMPLE_SECRET_ACCESS_KEY' ); - cy.get( '#aws_region' ).clear(); - cy.get( '#aws_region' ).type( 'SAMPLE_SECRET_ACCESS_KEY' ); - cy.get( '#status' ).check(); - cy.get( '#submit' ).click(); - - cy.get( '#voice' ).select( 'Aditi' ); - cy.get( '#submit' ).click(); + cy.get( '#classifai-logo' ).should( 'exist' ); + cy.get( '.settings-allowed-post-types input#post' ).check(); + cy.selectProvider( 'aws_polly' ); + cy.get( '#aws_polly_access_key_id' ).clear(); + cy.get( '#aws_polly_access_key_id' ).type( 'SAMPLE_ACCESS_KEY' ); + cy.get( '#aws_polly_secret_access_key' ).clear(); + cy.get( '#aws_polly_secret_access_key' ).type( + 'SAMPLE_SECRET_ACCESS_KEY' + ); + cy.get( '#aws_polly_aws_region' ).clear(); + cy.get( '#aws_polly_aws_region' ).type( 'SAMPLE_SECRET_ACCESS_KEY' ); + cy.get( '.classifai-enable-feature-toggle input' ).check(); + cy.saveFeatureSettings(); + + cy.get( '#aws_polly_voice' ).select( 'Aditi' ); + cy.saveFeatureSettings(); cy.optInAllFeatures(); cy.disableClassicEditor(); } ); diff --git a/tests/cypress/integration/language-processing/text-to-speech-microsoft-azure.test.js b/tests/cypress/integration/language-processing/text-to-speech-microsoft-azure.test.js index f351ecebc..6b0f1d76d 100644 --- a/tests/cypress/integration/language-processing/text-to-speech-microsoft-azure.test.js +++ b/tests/cypress/integration/language-processing/text-to-speech-microsoft-azure.test.js @@ -2,23 +2,24 @@ describe( '[Language Processing] Text to Speech (Microsoft Azure) Tests', () => before( () => { cy.login(); cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_text_to_speech_generation' + '/wp-admin/tools.php?page=classifai#/language_processing/feature_text_to_speech_generation' ); - cy.get( '#provider' ).select( 'ms_azure_text_to_speech' ); - cy.get( - '#classifai_feature_text_to_speech_generation_post_types_post' - ).check( 'post' ); - cy.get( '#provider' ).select( 'ms_azure_text_to_speech' ); - cy.get( '#endpoint_url' ).clear(); - cy.get( '#endpoint_url' ).type( 'https://service.com' ); - cy.get( '#api_key' ).type( 'password' ); - cy.get( '#status' ).check(); - cy.get( '#submit' ).click(); - - cy.get( - '[name="classifai_feature_text_to_speech_generation[ms_azure_text_to_speech][voice]"]' - ).select( 'en-AU-AnnetteNeural|Female' ); - cy.get( '#submit' ).click(); + cy.get( '#classifai-logo' ).should( 'exist' ); + cy.selectProvider( 'ms_azure_text_to_speech' ); + cy.get( '.settings-allowed-post-types input#post' ).check(); + cy.selectProvider( 'ms_azure_text_to_speech' ); + cy.get( '#ms_azure_text_to_speech_endpoint_url' ).clear(); + cy.get( '#ms_azure_text_to_speech_endpoint_url' ).type( + 'https://service.com' + ); + cy.get( '#ms_azure_text_to_speech_api_key' ).type( 'password' ); + cy.get( '.classifai-enable-feature-toggle input' ).check(); + cy.saveFeatureSettings(); + + cy.get( '#ms_azure_text_to_speech_voice' ).select( + 'en-AU-AnnetteNeural|Female' + ); + cy.saveFeatureSettings(); cy.optInAllFeatures(); cy.disableClassicEditor(); } ); @@ -118,12 +119,10 @@ describe( '[Language Processing] Text to Speech (Microsoft Azure) Tests', () => cy.disableClassicEditor(); cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_text_to_speech_generation' + '/wp-admin/tools.php?page=classifai#/language_processing/feature_text_to_speech_generation' ); - cy.get( - '#classifai_feature_text_to_speech_generation_post_types_post' - ).uncheck( 'post' ); - cy.get( '#submit' ).click(); + cy.get( '.settings-allowed-post-types input#post' ).uncheck(); + cy.saveFeatureSettings(); cy.visit( '/text-to-speech-test/' ); cy.get( '.class-post-audio-controls' ).should( 'not.exist' ); @@ -132,23 +131,21 @@ describe( '[Language Processing] Text to Speech (Microsoft Azure) Tests', () => it( 'Can enable/disable text to speech feature', () => { // Disable feature. cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_text_to_speech_generation' + '/wp-admin/tools.php?page=classifai#/language_processing/feature_text_to_speech_generation' ); - cy.get( '#status' ).uncheck(); - cy.get( '#submit' ).click(); + cy.get( '.classifai-enable-feature-toggle input' ).uncheck(); + cy.saveFeatureSettings(); // Verify that the feature is not available. cy.verifyTextToSpeechEnabled( false ); // Enable feature. cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_text_to_speech_generation' + '/wp-admin/tools.php?page=classifai#/language_processing/feature_text_to_speech_generation' ); - cy.get( '#status' ).check(); - cy.get( - '#classifai_feature_text_to_speech_generation_post_types_post' - ).check( 'post' ); - cy.get( '#submit' ).click(); + cy.get( '.classifai-enable-feature-toggle input' ).check(); + cy.get( '.settings-allowed-post-types input#post' ).check(); + cy.saveFeatureSettings(); // Verify that the feature is available. cy.verifyTextToSpeechEnabled( true ); @@ -157,12 +154,10 @@ describe( '[Language Processing] Text to Speech (Microsoft Azure) Tests', () => it( 'Can enable/disable text to speech feature by role', () => { // Enable feature. cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_text_to_speech_generation' + '/wp-admin/tools.php?page=classifai#/language_processing/feature_text_to_speech_generation' ); - cy.get( - '#classifai_feature_text_to_speech_generation_post_types_post' - ).check( 'post' ); - cy.get( '#submit' ).click(); + cy.get( '.settings-allowed-post-types input#post' ).check(); + cy.saveFeatureSettings(); // Disable admin role. cy.disableFeatureForRoles( 'feature_text_to_speech_generation', [ diff --git a/tests/cypress/integration/language-processing/text-to-speech-openai-text-to-speech.test.js b/tests/cypress/integration/language-processing/text-to-speech-openai-text-to-speech.test.js index 1d6a85bf8..40e00727b 100644 --- a/tests/cypress/integration/language-processing/text-to-speech-openai-text-to-speech.test.js +++ b/tests/cypress/integration/language-processing/text-to-speech-openai-text-to-speech.test.js @@ -2,19 +2,18 @@ describe( '[Language Processing] Text to Speech (OpenAI) Tests', () => { before( () => { cy.login(); cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_text_to_speech_generation' + '/wp-admin/tools.php?page=classifai#/language_processing/feature_text_to_speech_generation' ); - cy.get( - '#classifai_feature_text_to_speech_generation_post_types_post' - ).check( 'post' ); - cy.get( '#provider' ).select( 'openai_text_to_speech' ); - cy.get( '#tts_model' ).select( 'tts-1' ); - cy.get( '[name="classifai_feature_text_to_speech_generation[openai_text_to_speech][api_key]"]' ).type( 'password' ); - cy.get( '#status' ).check(); - cy.get( '#submit' ).click(); - - cy.get( '[name="classifai_feature_text_to_speech_generation[openai_text_to_speech][voice]"]' ).select( 'alloy' ); - cy.get( '#submit' ).click(); + cy.get( '#classifai-logo' ).should( 'exist' ); + cy.get( '.settings-allowed-post-types input#post' ).check(); + cy.selectProvider( 'openai_text_to_speech' ); + cy.get( '#openai_text_to_speech_tts_model' ).select( 'tts-1' ); + cy.get( '#openai_text_to_speech_api_key' ).clear().type( 'password' ); + cy.get( '.classifai-enable-feature-toggle input' ).check(); + cy.saveFeatureSettings(); + + cy.get( '#openai_text_to_speech_voice' ).select( 'alloy' ); + cy.saveFeatureSettings(); cy.optInAllFeatures(); cy.disableClassicEditor(); } ); @@ -112,12 +111,10 @@ describe( '[Language Processing] Text to Speech (OpenAI) Tests', () => { cy.disableClassicEditor(); cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_text_to_speech_generation' + '/wp-admin/tools.php?page=classifai#/language_processing/feature_text_to_speech_generation' ); - cy.get( - '#classifai_feature_text_to_speech_generation_post_types_post' - ).uncheck( 'post' ); - cy.get( '#submit' ).click(); + cy.get( '.settings-allowed-post-types input#post' ).uncheck(); + cy.saveFeatureSettings(); cy.visit( '/text-to-speech-test/' ); cy.get( '.class-post-audio-controls' ).should( 'not.exist' ); @@ -126,23 +123,21 @@ describe( '[Language Processing] Text to Speech (OpenAI) Tests', () => { it( 'Can enable/disable text to speech feature', () => { // Disable feature. cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_text_to_speech_generation' + '/wp-admin/tools.php?page=classifai#/language_processing/feature_text_to_speech_generation' ); - cy.get( '#status' ).uncheck(); - cy.get( '#submit' ).click(); + cy.get( '.classifai-enable-feature-toggle input' ).uncheck(); + cy.saveFeatureSettings(); // Verify that the feature is not available. cy.verifyTextToSpeechEnabled( false ); // Enable feature. cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_text_to_speech_generation' + '/wp-admin/tools.php?page=classifai#/language_processing/feature_text_to_speech_generation' ); - cy.get( '#status' ).check(); - cy.get( - '#classifai_feature_text_to_speech_generation_post_types_post' - ).check( 'post' ); - cy.get( '#submit' ).click(); + cy.get( '.classifai-enable-feature-toggle input' ).check(); + cy.get( '.settings-allowed-post-types input#post' ).check(); + cy.saveFeatureSettings(); // Verify that the feature is available. cy.verifyTextToSpeechEnabled( true ); @@ -151,12 +146,10 @@ describe( '[Language Processing] Text to Speech (OpenAI) Tests', () => { it( 'Can enable/disable text to speech feature by role', () => { // Enable feature. cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_text_to_speech_generation' + '/wp-admin/tools.php?page=classifai#/language_processing/feature_text_to_speech_generation' ); - cy.get( - '#classifai_feature_text_to_speech_generation_post_types_post' - ).check( 'post' ); - cy.get( '#submit' ).click(); + cy.get( '.settings-allowed-post-types input#post' ).check(); + cy.saveFeatureSettings(); // Disable admin role. cy.disableFeatureForRoles( 'feature_text_to_speech_generation', [ From 926ac19564d897467e8807310e5d976ba2b73fe4 Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Mon, 16 Sep 2024 18:28:42 +0530 Subject: [PATCH 110/138] Update tests for the title generation feature. --- .../title-generation-azure-openai.test.js | 43 +++---- ...tle-generation-googleai-gemini-api.test.js | 24 ++-- .../title-generation-openai-chatgpt.test.js | 108 ++++++------------ tests/cypress/support/commands.js | 68 ++++++----- 4 files changed, 101 insertions(+), 142 deletions(-) diff --git a/tests/cypress/integration/language-processing/title-generation-azure-openai.test.js b/tests/cypress/integration/language-processing/title-generation-azure-openai.test.js index 598df389d..d7c0618a6 100644 --- a/tests/cypress/integration/language-processing/title-generation-azure-openai.test.js +++ b/tests/cypress/integration/language-processing/title-generation-azure-openai.test.js @@ -13,36 +13,21 @@ describe( '[Language processing] Title Generation Tests', () => { it( 'Can save Azure OpenAI "Language Processing" title settings', () => { cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_title_generation' + '/wp-admin/tools.php?page=classifai#/language_processing/feature_title_generation' ); + cy.get( '#classifai-logo' ).should( 'exist' ); - cy.get( '#provider' ).select( 'azure_openai' ); - cy.get( - 'input[name="classifai_feature_title_generation[azure_openai][endpoint_url]"]' - ) + cy.selectProvider( 'azure_openai' ); + cy.get( 'input#azure_openai_endpoint_url' ) .clear() .type( 'https://e2e-test-azure-openai.test/' ); - cy.get( - 'input[name="classifai_feature_title_generation[azure_openai][api_key]"]' - ) - .clear() - .type( 'password' ); - cy.get( - 'input[name="classifai_feature_title_generation[azure_openai][deployment]"]' - ) - .clear() - .type( 'test' ); - - cy.get( '#status' ).check(); - cy.get( - '#classifai_feature_title_generation_roles_administrator' - ).check(); - cy.get( - 'input[name="classifai_feature_title_generation[azure_openai][number_of_suggestions]"]' - ) - .clear() - .type( 1 ); - cy.get( '#submit' ).click(); + cy.get( 'input#azure_openai_api_key' ).clear().type( 'password' ); + cy.get( 'input#azure_openai_deployment' ).clear().type( 'test' ); + + cy.get( '.classifai-enable-feature-toggle input' ).check(); + cy.get( '.settings-allowed-roles input#administrator' ).check(); + cy.get( '#azure_openai_number_of_suggestions' ).clear().type( 1 ); + cy.saveFeatureSettings(); } ); it( 'Can see the generate titles button in a post', () => { @@ -131,10 +116,10 @@ describe( '[Language processing] Title Generation Tests', () => { cy.enableClassicEditor(); cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_title_generation' + '/wp-admin/tools.php?page=classifai#/language_processing/feature_title_generation' ); - cy.get( '#status' ).check(); - cy.get( '#submit' ).click(); + cy.get( '.classifai-enable-feature-toggle input' ).check(); + cy.saveFeatureSettings(); const data = getChatGPTData(); diff --git a/tests/cypress/integration/language-processing/title-generation-googleai-gemini-api.test.js b/tests/cypress/integration/language-processing/title-generation-googleai-gemini-api.test.js index c8adb87ca..a1c88f95a 100644 --- a/tests/cypress/integration/language-processing/title-generation-googleai-gemini-api.test.js +++ b/tests/cypress/integration/language-processing/title-generation-googleai-gemini-api.test.js @@ -13,20 +13,16 @@ describe( '[Language processing] Title Generation Tests', () => { it( 'Can save Google AI (Gemini API) "Language Processing" title settings', () => { cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_title_generation' + '/wp-admin/tools.php?page=classifai#/language_processing/feature_title_generation' ); - - cy.get( '#provider' ).select( 'googleai_gemini_api' ); - cy.get( - 'input[name="classifai_feature_title_generation[googleai_gemini_api][api_key]"]' - ) + cy.get( '#classifai-logo' ).should( 'exist' ); + cy.selectProvider( 'googleai_gemini_api' ); + cy.get( 'input#googleai_gemini_api_api_key' ) .clear() .type( 'password' ); - cy.get( '#status' ).check(); - cy.get( - '#classifai_feature_title_generation_roles_administrator' - ).check(); - cy.get( '#submit' ).click(); + cy.get( '.classifai-enable-feature-toggle input' ).check(); + cy.get( '.settings-allowed-roles input#administrator' ).check(); + cy.saveFeatureSettings(); } ); it( 'Can see the generate titles button in a post', () => { @@ -115,10 +111,10 @@ describe( '[Language processing] Title Generation Tests', () => { cy.enableClassicEditor(); cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_title_generation' + '/wp-admin/tools.php?page=classifai#/language_processing/feature_title_generation' ); - cy.get( '#status' ).check(); - cy.get( '#submit' ).click(); + cy.get( '.classifai-enable-feature-toggle input' ).check(); + cy.saveFeatureSettings(); const data = getGeminiAPIData(); diff --git a/tests/cypress/integration/language-processing/title-generation-openai-chatgpt.test.js b/tests/cypress/integration/language-processing/title-generation-openai-chatgpt.test.js index bbaf7b365..68376d30e 100644 --- a/tests/cypress/integration/language-processing/title-generation-openai-chatgpt.test.js +++ b/tests/cypress/integration/language-processing/title-generation-openai-chatgpt.test.js @@ -13,17 +13,15 @@ describe( '[Language processing] Title Generation Tests', () => { it( 'Can save OpenAI ChatGPT "Language Processing" title settings', () => { cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_title_generation' + '/wp-admin/tools.php?page=classifai#/language_processing/feature_title_generation' ); - - cy.get( '#provider' ).select( 'openai_chatgpt' ); - cy.get( '#api_key' ).clear().type( 'password' ); - cy.get( '#status' ).check(); - cy.get( - '#classifai_feature_title_generation_roles_administrator' - ).check(); - cy.get( '#number_of_suggestions' ).type( 1 ); - cy.get( '#submit' ).click(); + cy.get( '#classifai-logo' ).should( 'exist' ); + cy.selectProvider( 'openai_chatgpt' ); + cy.get( '#openai_chatgpt_api_key' ).clear().type( 'password' ); + cy.get( '.classifai-enable-feature-toggle input' ).check(); + cy.get( '.settings-allowed-roles input#administrator' ).check(); + cy.get( '#openai_chatgpt_number_of_suggestions' ).type( 1 ); + cy.saveFeatureSettings(); } ); it( 'Can see the generate titles button in a post', () => { @@ -112,10 +110,10 @@ describe( '[Language processing] Title Generation Tests', () => { cy.enableClassicEditor(); cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_title_generation' + '/wp-admin/tools.php?page=classifai#/language_processing/feature_title_generation' ); - cy.get( '#status' ).check(); - cy.get( '#submit' ).click(); + cy.get( '.classifai-enable-feature-toggle input' ).check(); + cy.saveFeatureSettings(); const data = getChatGPTData(); @@ -138,84 +136,54 @@ describe( '[Language processing] Title Generation Tests', () => { it( 'Can set multiple custom title generation prompts, select one as the default and delete one.', () => { cy.disableClassicEditor(); cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_title_generation' + '/wp-admin/tools.php?page=classifai#/language_processing/feature_title_generation' ); // Add three custom prompts. - cy.get( - '[name="classifai_feature_title_generation[generate_title_prompt][0][default]"]' - ) - .parents( 'td:first' ) - .find( 'button.js-classifai-add-prompt-fieldset' ) + cy.get( 'button.components-button.action__add_prompt' ) .click() .click() .click(); cy.get( - '[name="classifai_feature_title_generation[generate_title_prompt][0][default]"]' - ) - .parents( 'td:first' ) - .find( '.classifai-field-type-prompt-setting' ) - .should( 'have.length', 4 ); + '.classifai-prompts div.classifai-field-type-prompt-setting' + ).should( 'have.length', 4 ); // Set the data for each prompt. - cy.get( - '[name="classifai_feature_title_generation[generate_title_prompt][1][title]"]' - ) + cy.get( '#classifai-prompt-setting-1 .classifai-prompt-title input' ) .clear() .type( 'First custom prompt' ); - cy.get( - '[name="classifai_feature_title_generation[generate_title_prompt][1][prompt]"]' - ) + cy.get( '#classifai-prompt-setting-1 .classifai-prompt-text textarea' ) .clear() .type( 'This is our first custom title prompt' ); - cy.get( - '[name="classifai_feature_title_generation[generate_title_prompt][2][title]"]' - ) + cy.get( '#classifai-prompt-setting-2 .classifai-prompt-title input' ) .clear() .type( 'Second custom prompt' ); - cy.get( - '[name="classifai_feature_title_generation[generate_title_prompt][2][prompt]"]' - ) + cy.get( '#classifai-prompt-setting-2 .classifai-prompt-text textarea' ) .clear() .type( 'This prompt should be deleted' ); - cy.get( - '[name="classifai_feature_title_generation[generate_title_prompt][3][title]"]' - ) + cy.get( '#classifai-prompt-setting-3 .classifai-prompt-title input' ) .clear() .type( 'Third custom prompt' ); - cy.get( - '[name="classifai_feature_title_generation[generate_title_prompt][3][prompt]"]' - ) + cy.get( '#classifai-prompt-setting-3 .classifai-prompt-text textarea' ) .clear() .type( 'This is a custom title prompt' ); // Set the third prompt as our default. cy.get( - '[name="classifai_feature_title_generation[generate_title_prompt][3][default]"]' - ) - .parent() - .find( 'a.action__set_default' ) - .click( { force: true } ); + '#classifai-prompt-setting-3 .actions-rows button.action__set_default' + ).click( { force: true } ); // Delete the second prompt. cy.get( - '[name="classifai_feature_title_generation[generate_title_prompt][2][default]"]' - ) - .parent() - .find( 'a.action__remove_prompt' ) - .click( { force: true } ); - cy.get( 'div[aria-describedby="js-classifai--delete-prompt-modal"]' ) - .find( '.button-primary' ) - .click(); + '#classifai-prompt-setting-2 .actions-rows button.action__remove_prompt' + ).click( { force: true } ); + cy.get( 'div.components-confirm-dialog button.is-primary' ).click(); cy.get( - '[name="classifai_feature_title_generation[generate_title_prompt][0][default]"]' - ) - .parents( 'td:first' ) - .find( '.classifai-field-type-prompt-setting' ) - .should( 'have.length', 3 ); + '.classifai-prompts div.classifai-field-type-prompt-setting' + ).should( 'have.length', 3 ); - cy.get( '#submit' ).click(); + cy.saveFeatureSettings(); const data = getChatGPTData( 'title' ); @@ -301,20 +269,20 @@ describe( '[Language processing] Title Generation Tests', () => { it( 'Can enable/disable title generation feature', () => { // Disable features. cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_title_generation' + '/wp-admin/tools.php?page=classifai#/language_processing/feature_title_generation' ); - cy.get( '#status' ).uncheck(); - cy.get( '#submit' ).click(); + cy.get( '.classifai-enable-feature-toggle input' ).uncheck(); + cy.saveFeatureSettings(); // Verify that the feature is not available. cy.verifyTitleGenerationEnabled( false ); // Enable feature. cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_title_generation' + '/wp-admin/tools.php?page=classifai#/language_processing/feature_title_generation' ); - cy.get( '#status' ).check(); - cy.get( '#submit' ).click(); + cy.get( '.classifai-enable-feature-toggle input' ).check(); + cy.saveFeatureSettings(); // Verify that the feature is available. cy.verifyTitleGenerationEnabled( true ); @@ -323,10 +291,10 @@ describe( '[Language processing] Title Generation Tests', () => { it( 'Can enable/disable title generation feature by role', () => { // Enable feature. cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_title_generation' + '/wp-admin/tools.php?page=classifai#/language_processing/feature_title_generation' ); - cy.get( '#status' ).check(); - cy.get( '#submit' ).click(); + cy.get( '.classifai-enable-feature-toggle input' ).check(); + cy.saveFeatureSettings(); // Disable admin role. cy.disableFeatureForRoles( 'feature_title_generation', [ diff --git a/tests/cypress/support/commands.js b/tests/cypress/support/commands.js index d0b243583..29de4c472 100644 --- a/tests/cypress/support/commands.js +++ b/tests/cypress/support/commands.js @@ -143,12 +143,11 @@ Cypress.Commands.add( 'enableFeatureForRoles', ( feature, roles ) => { if ( imageProcessingFeatures.includes( feature ) ) { tab = 'image_processing'; } - cy.visit( - `/wp-admin/tools.php?page=classifai&tab=${ tab }&feature=${ feature }` - ); + cy.visit( `/wp-admin/tools.php?page=classifai#/${ tab }/${ feature }` ); + cy.get( '#classifai-logo' ).should( 'exist' ); // Disable access for all roles. - cy.get( '.allowed_roles_row input[type="checkbox"]' ).uncheck( { + cy.get( '.settings-allowed-roles input[type="checkbox"]' ).uncheck( { multiple: true, } ); @@ -156,10 +155,9 @@ Cypress.Commands.add( 'enableFeatureForRoles', ( feature, roles ) => { cy.disableFeatureForUsers(); roles.forEach( ( role ) => { - cy.get( `#classifai_${ feature }_roles_${ role }` ).check(); + cy.get( `.settings-allowed-roles input#${ role }` ).check(); } ); - cy.get( '#submit' ).click(); - cy.get( '.notice' ).contains( 'Settings saved.' ); + cy.saveFeatureSettings(); } ); /** @@ -173,20 +171,17 @@ Cypress.Commands.add( 'disableFeatureForRoles', ( feature, roles ) => { if ( imageProcessingFeatures.includes( feature ) ) { tab = 'image_processing'; } - cy.visit( - `/wp-admin/tools.php?page=classifai&tab=${ tab }&feature=${ feature }` - ); - cy.get( '#status' ).check(); + cy.visit( `/wp-admin/tools.php?page=classifai#/${ tab }/${ feature }` ); + cy.get( '.classifai-enable-feature-toggle input' ).check(); roles.forEach( ( role ) => { - cy.get( `#classifai_${ feature }_roles_${ role }` ).uncheck(); + cy.get( `.settings-allowed-roles input#${ role }` ).uncheck(); } ); // Disable access for all users. cy.disableFeatureForUsers(); - cy.get( '#submit' ).click(); - cy.get( '.notice' ).contains( 'Settings saved.' ); + cy.saveFeatureSettings(); } ); /** @@ -200,12 +195,10 @@ Cypress.Commands.add( 'enableFeatureForUsers', ( feature, users ) => { if ( imageProcessingFeatures.includes( feature ) ) { tab = 'image_processing'; } - cy.visit( - `/wp-admin/tools.php?page=classifai&tab=${ tab }&feature=${ feature }` - ); + cy.visit( `/wp-admin/tools.php?page=classifai#/${ tab }/${ feature }` ); // Disable access for all roles. - cy.get( 'tr.allowed_roles_row input[type="checkbox"]' ).uncheck( { + cy.get( '.settings-allowed-roles input[type="checkbox"]' ).uncheck( { multiple: true, } ); @@ -214,13 +207,12 @@ Cypress.Commands.add( 'enableFeatureForUsers', ( feature, users ) => { users.forEach( ( user ) => { cy.get( - `.allowed_users_row input.components-form-token-field__input` + `.classifai-settings__users input.components-form-token-field__input` ).type( user ); cy.get( '[aria-label="admin (admin)"]' ).click(); } ); - cy.get( '#submit' ).click(); - cy.get( '.notice' ).contains( 'Settings saved.' ); + cy.saveFeatureSettings(); } ); /** @@ -228,7 +220,7 @@ Cypress.Commands.add( 'enableFeatureForUsers', ( feature, users ) => { */ Cypress.Commands.add( 'disableFeatureForUsers', () => { // Disable access for all users. - cy.get( '.allowed_users_row' ).then( ( $body ) => { + cy.get( '.classifai-settings__users' ).then( ( $body ) => { if ( $body.find( `.components-form-token-field__remove-token` ).length > 0 @@ -250,14 +242,11 @@ Cypress.Commands.add( 'enableFeatureOptOut', ( feature ) => { if ( imageProcessingFeatures.includes( feature ) ) { tab = 'image_processing'; } - cy.visit( - `/wp-admin/tools.php?page=classifai&tab=${ tab }&feature=${ feature }` - ); - cy.get( `#classifai_${ feature }_roles_administrator` ).check(); - cy.get( `#user_based_opt_out` ).check(); + cy.visit( `/wp-admin/tools.php?page=classifai#/${ tab }/${ feature }` ); + cy.get( '.settings-allowed-roles input#administrator' ).check(); + cy.get( '.classifai-settings__user-based-opt-out input' ).check(); - cy.get( '#submit' ).click(); - cy.get( '.notice' ).contains( 'Settings saved.' ); + cy.saveFeatureSettings(); } ); /** @@ -574,3 +563,24 @@ Cypress.Commands.add( 'enableClassicEditor', () => { } } ); } ); + +/** + * Select feature Provider. + */ +Cypress.Commands.add( 'selectProvider', ( provider ) => { + cy.get( '#classifai-logo' ).should( 'exist' ); + cy.get( '.classifai-loading-settings' ).should( 'not.exist' ); + cy.get( 'body' ).then( ( $body ) => { + if ( $body.find( '.classifai-settings-edit-provider' ).length > 0 ) { + cy.get( '.classifai-settings-edit-provider' ).click(); + } + } ); + cy.get( '.classifai-provider-select select' ).select( provider ); +} ); + +/** + * Save the feature settings. + */ +Cypress.Commands.add( 'saveFeatureSettings', () => { + cy.get( '.classifai-settings-footer button.save-settings-button' ).click(); +} ); From 27a9e0ec171821d1d82700bd97825b6f8af89a69 Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Mon, 16 Sep 2024 19:05:46 +0530 Subject: [PATCH 111/138] Updated E2E tests for the IBM Watson NLU Classification feature. --- .../classification.js | 6 + .../nlu-feature.js | 3 + .../provider-settings/ibm-watson-nlu.js | 4 + .../classify-content-ibm-watson.test.js | 255 ++++++++---------- 4 files changed, 128 insertions(+), 140 deletions(-) diff --git a/src/js/settings/components/feature-additional-settings/classification.js b/src/js/settings/components/feature-additional-settings/classification.js index 957e55cc5..24bb93a8f 100644 --- a/src/js/settings/components/feature-additional-settings/classification.js +++ b/src/js/settings/components/feature-additional-settings/classification.js @@ -60,6 +60,7 @@ const ClassificationMethodSettings = () => { return ( { setFeatureSettings( { classification_method: value, @@ -118,6 +119,7 @@ export const ClassificationSettings = () => { { setFeatureSettings( { classification_mode: value, @@ -147,11 +149,13 @@ export const ClassificationSettings = () => { 'Choose which post statuses are allowed to use this feature.', 'classifai' ) } + className="settings-allowed-post-statuses" > { postStatusOptions.map( ( option ) => { const { value: key, label } = option; return ( { 'Choose which post types are allowed to use this feature.', 'classifai' ) } + className="settings-allowed-post-types" > { postTypesSelectOptions.map( ( option ) => { const { value: key, label } = option; return ( { className={ 'nlu-features' } > { } } /> { /> { 'ibm_watson_nlu' === featureSettings.provider && ( { <> @@ -55,6 +56,7 @@ export const IBMWatsonNLUSettings = ( { isConfigured = false } ) => { { ! useAPIkey && ( @@ -72,6 +74,7 @@ export const IBMWatsonNLUSettings = ( { isConfigured = false } ) => { description={ } > { @@ -85,6 +88,7 @@ export const IBMWatsonNLUSettings = ( { isConfigured = false } ) => { diff --git a/src/js/settings/components/classifai-onboarding/configure-features.js b/src/js/settings/components/classifai-onboarding/configure-features.js index e111b4b50..57bb5e48d 100644 --- a/src/js/settings/components/classifai-onboarding/configure-features.js +++ b/src/js/settings/components/classifai-onboarding/configure-features.js @@ -72,7 +72,7 @@ export const ConfigureFeatures = () => { > - + diff --git a/src/js/settings/components/classifai-registration/index.js b/src/js/settings/components/classifai-registration/index.js index 5490d4b3a..fc93c53c7 100644 --- a/src/js/settings/components/classifai-registration/index.js +++ b/src/js/settings/components/classifai-registration/index.js @@ -130,7 +130,7 @@ export const ClassifAIRegistrationForm = ( { onSaveSuccess = () => {} } ) => {
    - + { ( fills ) => <>{ fills } } { return ( <> - { ( fills ) => <>{ fills } } + + { ( fills ) => <>{ fills } } + { children } - { ( fills ) => <>{ fills } } + + { ( fills ) => <>{ fills } } + ); }; From eea4fd930ecda7669bd7a5169c00d26152dbdd22 Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Fri, 20 Sep 2024 10:44:46 +0530 Subject: [PATCH 136/138] Sanitize status field in onboarding settings save. --- includes/Classifai/Admin/Settings.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/Classifai/Admin/Settings.php b/includes/Classifai/Admin/Settings.php index c47945e6e..08ef6ad7a 100644 --- a/includes/Classifai/Admin/Settings.php +++ b/includes/Classifai/Admin/Settings.php @@ -260,7 +260,7 @@ public function update_settings_callback( $request ) { $current_settings = $feature->get_settings(); // Update only status of the feature. - $current_settings['status'] = $feature_settings['status'] ?? $current_settings['status']; + $current_settings['status'] = sanitize_text_field( $feature_settings['status'] ?? $current_settings['status'] ); $new_settings = $current_settings; } else { $new_settings = $feature->sanitize_settings( $settings[ $feature_key ] ); From 6ecae5dbea9ac21e509351f96828e01c6c032260 Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Fri, 20 Sep 2024 10:45:50 +0530 Subject: [PATCH 137/138] Improved code by moving personalizer notice to separate component. --- .../feature-additional-settings/index.js | 4 ++ .../recommended-content.js | 42 ++++++++++++++++++ .../components/feature-settings/index.js | 43 +++---------------- 3 files changed, 53 insertions(+), 36 deletions(-) create mode 100644 src/js/settings/components/feature-additional-settings/recommended-content.js diff --git a/src/js/settings/components/feature-additional-settings/index.js b/src/js/settings/components/feature-additional-settings/index.js index 58874c3ce..8e0e84495 100644 --- a/src/js/settings/components/feature-additional-settings/index.js +++ b/src/js/settings/components/feature-additional-settings/index.js @@ -18,6 +18,7 @@ import { ExcerptGenerationSettings } from './excerpt-generation'; import { ClassificationSettings } from './classification'; import { ModerationSettings } from './moderation'; import { Smart404Settings } from './smart-404'; +import { RecommendedContentSettings } from './recommended-content'; const AdditionalSettingsFields = () => { const { featureName } = useFeatureContext(); @@ -50,6 +51,9 @@ const AdditionalSettingsFields = () => { case 'feature_smart_404': return ; + case 'feature_recommended_content': + return ; + default: return null; } diff --git a/src/js/settings/components/feature-additional-settings/recommended-content.js b/src/js/settings/components/feature-additional-settings/recommended-content.js new file mode 100644 index 000000000..c21320ada --- /dev/null +++ b/src/js/settings/components/feature-additional-settings/recommended-content.js @@ -0,0 +1,42 @@ +import { Fill, Notice } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; + +const PersonalizerDeprecationNotice = () => ( + +

    + + { __( 'As of September 2023', 'classifai' ) } + + { ', ' } + { __( + 'new Personalizer resources can no longer be created in Azure. This is currently the only provider available for the Recommended Content feature and as such, this feature will not work unless you had previously created a Personalizer resource. The Azure AI Personalizer provider is deprecated and will be removed in a future release. We hope to replace this provider with another one in a coming release to continue to support this feature', + 'classifai' + ) } + { __( '(see ', 'classifai' ) } + + { __( 'issue#392', 'classifai' ) } + + { ').' } +

    +
    +); + +export const RecommendedContentSettings = () => { + return ( + + + + ); +}; diff --git a/src/js/settings/components/feature-settings/index.js b/src/js/settings/components/feature-settings/index.js index 2d747d234..86249c630 100644 --- a/src/js/settings/components/feature-settings/index.js +++ b/src/js/settings/components/feature-settings/index.js @@ -2,7 +2,7 @@ * WordPress dependencies */ import { __, sprintf } from '@wordpress/i18n'; -import { Panel, PanelBody, Spinner, Notice } from '@wordpress/components'; +import { Panel, PanelBody, Spinner, Notice, Slot } from '@wordpress/components'; import { useDispatch, useSelect } from '@wordpress/data'; import { useEffect } from '@wordpress/element'; @@ -19,38 +19,6 @@ import { Notices } from './notices'; import { useFeatureContext } from './context'; import { FeatureAdditionalSettings } from '../feature-additional-settings'; -const PersonalizerDeprecationNotice = () => ( - -

    - - { __( 'As of September 2023', 'classifai' ) } - - { ', ' } - { __( - 'new Personalizer resources can no longer be created in Azure. This is currently the only provider available for the Recommended Content feature and as such, this feature will not work unless you had previously created a Personalizer resource. The Azure AI Personalizer provider is deprecated and will be removed in a future release. We hope to replace this provider with another one in a coming release to continue to support this feature', - 'classifai' - ) } - { __( '(see ', 'classifai' ) } - - { __( 'issue#392', 'classifai' ) } - - { ').' } -

    -
    -); - const ElasticPressRequiredNotice = () => ( {} } ) => { return ( <> - { 'feature_recommended_content' === featureName && ( - - ) } + + { ( fills ) => <>{ fills } } + {} } ) => { + + { ( fills ) => <>{ fills } } +
    From 8a3243f9c2a6a9aa52cde6c57c832ec7a6dc440f Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Fri, 20 Sep 2024 12:45:42 +0530 Subject: [PATCH 138/138] Display notice for the in progress background processing of embedding generation. --- includes/Classifai/Admin/Settings.php | 26 ++++++++ .../Classifai/Features/Classification.php | 14 +++++ .../Classifai/Providers/Azure/Embeddings.php | 9 +++ .../Classifai/Providers/OpenAI/Embeddings.php | 9 +++ .../classification.js | 60 ++++++++++++++++++- .../components/feature-settings/notices.js | 4 +- src/scss/settings.scss | 3 +- 7 files changed, 122 insertions(+), 3 deletions(-) diff --git a/includes/Classifai/Admin/Settings.php b/includes/Classifai/Admin/Settings.php index 08ef6ad7a..17796ea8f 100644 --- a/includes/Classifai/Admin/Settings.php +++ b/includes/Classifai/Admin/Settings.php @@ -2,6 +2,7 @@ namespace Classifai\Admin; +use Classifai\Features\Classification; use Classifai\Services\ServicesManager; use function Classifai\get_asset_info; @@ -209,6 +210,18 @@ public function register_routes() { ], ] ); + + register_rest_route( + 'classifai/v1', + 'embeddings_in_progress', + [ + [ + 'methods' => \WP_REST_Server::READABLE, + 'callback' => [ $this, 'check_embedding_generation_status' ], + 'permission_callback' => [ $this, 'get_settings_permissions_check' ], + ], + ] + ); } /** @@ -381,4 +394,17 @@ public function update_registration_settings_callback( $request ) { public function registration_settings_permissions_check() { return current_user_can( 'manage_options' ); } + + /** + * Callback for getting the registration settings. + * + * @return \WP_REST_Response + */ + public function check_embedding_generation_status() { + $classification = new Classification(); + $response = array( + 'classifAIEmbedInProgress' => $classification->is_embeddings_generation_in_progress(), + ); + return rest_ensure_response( $response ); + } } diff --git a/includes/Classifai/Features/Classification.php b/includes/Classifai/Features/Classification.php index ef4102907..ad434776a 100644 --- a/includes/Classifai/Features/Classification.php +++ b/includes/Classifai/Features/Classification.php @@ -1190,4 +1190,18 @@ public function migrate_settings() { return $new_settings; } + + /** + * Get status of embeddings generation process. + * + * @return bool + */ + public function is_embeddings_generation_in_progress(): bool { + $is_in_progress = false; + $provider_instance = $this->get_feature_provider_instance(); + if ( $provider_instance && method_exists( $provider_instance, 'is_embeddings_generation_in_progress' ) ) { + $is_in_progress = $provider_instance->is_embeddings_generation_in_progress(); + } + return $is_in_progress; + } } diff --git a/includes/Classifai/Providers/Azure/Embeddings.php b/includes/Classifai/Providers/Azure/Embeddings.php index 177bd95aa..1ff44a426 100644 --- a/includes/Classifai/Providers/Azure/Embeddings.php +++ b/includes/Classifai/Providers/Azure/Embeddings.php @@ -1187,4 +1187,13 @@ public function get_debug_information(): array { $this->feature_instance ); } + + /** + * Get embeddings generation status. + * + * @return bool + */ + public function is_embeddings_generation_in_progress() { + return self::$scheduler_instance->is_embeddings_generation_in_progress(); + } } diff --git a/includes/Classifai/Providers/OpenAI/Embeddings.php b/includes/Classifai/Providers/OpenAI/Embeddings.php index c4c8ba0ac..93c999e65 100644 --- a/includes/Classifai/Providers/OpenAI/Embeddings.php +++ b/includes/Classifai/Providers/OpenAI/Embeddings.php @@ -1296,4 +1296,13 @@ public function get_debug_information(): array { $this->feature_instance ); } + + /** + * Get embeddings generation status. + * + * @return bool + */ + public function is_embeddings_generation_in_progress() { + return self::$scheduler_instance->is_embeddings_generation_in_progress(); + } } diff --git a/src/js/settings/components/feature-additional-settings/classification.js b/src/js/settings/components/feature-additional-settings/classification.js index ba61fd35e..be7a30dc4 100644 --- a/src/js/settings/components/feature-additional-settings/classification.js +++ b/src/js/settings/components/feature-additional-settings/classification.js @@ -7,10 +7,15 @@ import { TextHighlight, Spinner, Button, + Fill, + Notice, } from '@wordpress/components'; -import { useState, useEffect, useContext } from '@wordpress/element'; +import apiFetch from '@wordpress/api-fetch'; +import { useState, useEffect, useContext, useRef } from '@wordpress/element'; import { useDebounce } from '@wordpress/compose'; import { __ } from '@wordpress/i18n'; +import { store as noticesStore } from '@wordpress/notices'; + import { SettingsRow } from '../settings-row'; import { STORE_NAME } from '../../data/store'; import { isFeatureActive, usePostTypes } from '../../utils/utils'; @@ -82,6 +87,8 @@ function PreviewerProvider( { children, value } ) { } export const ClassificationSettings = () => { + const [ embedInProgress, setEmbedInProgress ] = useState( false ); + const isEmbeddingInProgress = useRef( false ); const [ isPreviewerOpen, setIsPreviewerOpen ] = useState( false ); const [ selectedPostId, setSelectedPostId ] = useState( 0 ); const [ isPreviewUnderProcess, setPreviewUnderProcess ] = useState( null ); @@ -89,10 +96,14 @@ export const ClassificationSettings = () => { const featureSettings = useSelect( ( select ) => select( STORE_NAME ).getFeatureSettings() ); + const isSaving = useSelect( ( select ) => + select( STORE_NAME ).getIsSaving() + ); const isConfigured = isFeatureActive( featureSettings ); const { setFeatureSettings } = useDispatch( STORE_NAME ); const { postTypesSelectOptions } = usePostTypes(); const { postStatuses } = window.classifAISettings; + const { createSuccessNotice } = useDispatch( noticesStore ); const previewerContextData = { isPreviewerOpen, @@ -103,8 +114,55 @@ export const ClassificationSettings = () => { setPreviewUnderProcess, }; + // Check if embeddings are in progress + useEffect( () => { + if ( ! isSaving ) { + const getEmbeddingsInProgress = async () => { + try { + const res = await apiFetch( { + path: '/classifai/v1/embeddings_in_progress', + } ); + if ( res?.classifAIEmbedInProgress ) { + setEmbedInProgress( true ); + isEmbeddingInProgress.current = true; + } else { + setEmbedInProgress( false ); + clearInterval( intervalId ); + if ( isEmbeddingInProgress.current ) { + createSuccessNotice( + __( + 'Generation of embeddings is completed.', + 'classifai' + ), + { + id: 'success-feature_classification', + } + ); + } + isEmbeddingInProgress.current = false; + } + } catch ( error ) {} + }; + + const intervalId = setInterval( getEmbeddingsInProgress, 10000 ); + getEmbeddingsInProgress(); + + return () => clearInterval( intervalId ); + } + }, [ isSaving, createSuccessNotice ] ); + return ( <> + { embedInProgress && ( + + + { __( + 'Generation of embeddings is in progress.', + 'classifai' + ) } + + + ) } { !! isConfigured && ( diff --git a/src/js/settings/components/feature-settings/notices.js b/src/js/settings/components/feature-settings/notices.js index 95e2e1596..d549ae868 100644 --- a/src/js/settings/components/feature-settings/notices.js +++ b/src/js/settings/components/feature-settings/notices.js @@ -9,7 +9,9 @@ export const Notices = ( { feature } ) => { ); const featureNotices = notices.filter( - ( notice ) => notice.id === `error-${ feature }` + ( notice ) => + notice.id === `error-${ feature }` || + notice.id === `success-${ feature }` ); if ( featureNotices.length === 0 ) { diff --git a/src/scss/settings.scss b/src/scss/settings.scss index cc7420cf7..6cba5f189 100644 --- a/src/scss/settings.scss +++ b/src/scss/settings.scss @@ -59,7 +59,8 @@ margin: 0; } - &.personalizer-deprecation-notice { + &.personalizer-deprecation-notice, + &.is-info { margin-bottom: 16px; } }