diff --git a/src/js/settings/components/feature-additional-settings/nlu-feature.js b/src/js/settings/components/feature-additional-settings/nlu-feature.js index 7c3027676..abebc8cd3 100644 --- a/src/js/settings/components/feature-additional-settings/nlu-feature.js +++ b/src/js/settings/components/feature-additional-settings/nlu-feature.js @@ -6,6 +6,7 @@ import { CheckboxControl, SelectControl, __experimentalInputControl as InputControl, // eslint-disable-line @wordpress/no-unsafe-wp-apis + __experimentalInputControlSuffixWrapper as InputControlSuffixWrapper, // eslint-disable-line @wordpress/no-unsafe-wp-apis } from '@wordpress/components'; import { __, sprintf } from '@wordpress/i18n'; @@ -14,7 +15,8 @@ import { __, sprintf } from '@wordpress/i18n'; */ import { SettingsRow } from '../settings-row'; import { STORE_NAME } from '../../data/store'; -import { getFeature } from '../../utils/utils'; +import { getFeature, TooltipPopover } from '../../utils/utils'; +import { thresholdInfo, nluHelperText } from '../../utils/helper-text'; /** * Component for render settings fields when IBM Watson NLU is selected as the provider. @@ -36,18 +38,22 @@ export const NLUFeatureSettings = () => { category: { label: __( 'Category', 'classifai' ), defaultThreshold: 70, + helperText: nluHelperText.category, }, keyword: { label: __( 'Keyword', 'classifai' ), defaultThreshold: 70, + helperText: nluHelperText.keyword, }, entity: { label: __( 'Entity', 'classifai' ), defaultThreshold: 70, + helperText: nluHelperText.entity, }, concept: { label: __( 'Concept', 'classifai' ), defaultThreshold: 70, + helperText: nluHelperText.concept, }, }; @@ -94,12 +100,14 @@ export const NLUFeatureSettings = () => { return ( <> { Object.keys( features ).map( ( feature ) => { - const { defaultThreshold, label } = features[ feature ]; + const { defaultThreshold, label, helperText } = + features[ feature ]; return ( { /> { [ `${ feature }_threshold` ]: value, } ); } } + __unstableInputWidth="8em" + suffix={ + + + + } min="0" max="100" step="0.01" /> + { 'ibm_watson_nlu' === featureSettings.provider && ( { /> { }, } ); } } + __unstableInputWidth="8em" + suffix={ + + + + } min="0" max="100" step="0.01" diff --git a/src/js/settings/components/provider-settings/openai-moderation.js b/src/js/settings/components/provider-settings/openai-moderation.js index 5491ccc1b..eec4a3a92 100644 --- a/src/js/settings/components/provider-settings/openai-moderation.js +++ b/src/js/settings/components/provider-settings/openai-moderation.js @@ -8,6 +8,7 @@ import { useSelect, useDispatch } from '@wordpress/data'; */ import { STORE_NAME } from '../../data/store'; import { OpenAISettings } from './openai'; +import { moderationHelperText } from '../../utils/helper-text'; /** * Component for OpenAI Moderation settings. @@ -28,14 +29,20 @@ export const OpenAIModerationSettings = ( { isConfigured = false } ) => { const { setProviderSettings } = useDispatch( STORE_NAME ); const onChange = ( data ) => setProviderSettings( providerName, data ); - if ( isConfigured ) { - return null; - } - return ( - + <> + { ! isConfigured && ( + + ) } + +
+
+
{ moderationHelperText.content_types }
+
+
+ ); }; diff --git a/src/js/settings/components/settings-row/index.js b/src/js/settings/components/settings-row/index.js index 341d65999..d69299f44 100644 --- a/src/js/settings/components/settings-row/index.js +++ b/src/js/settings/components/settings-row/index.js @@ -3,6 +3,11 @@ */ import classNames from 'classnames'; +/** + * Internal dependencies + */ +import { TooltipPopover } from '../../utils/utils'; + /** * Settings row component. * @@ -13,7 +18,12 @@ import classNames from 'classnames'; export const SettingsRow = ( props ) => { return (
-
{ props.label }
+
+ { props.label } + { props.helperText && ( + + ) } +
{ props.children }
diff --git a/src/js/settings/utils/helper-text.js b/src/js/settings/utils/helper-text.js new file mode 100644 index 000000000..2986ed623 --- /dev/null +++ b/src/js/settings/utils/helper-text.js @@ -0,0 +1,154 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; + +/** + * Threshold information. + * + * @type {Object} + */ +export const thresholdInfo = { + helper: ( + <> +

+ { __( + 'Determines how confident the AI must be before suggesting a term.', + 'classifai' + ) } +

+

+ { __( + 'Higher % = More precise (fewer, more accurate terms)', + 'classifai' + ) } +

+

+ { __( + 'Lower % = More verbose (more suggestions, including lower-confidence terms)', + 'classifai' + ) } +

+ + ), +}; + +/** + * NLU helper text. + * + * @type {Object} + */ +export const nluHelperText = { + category: ( + <> +

+ { __( + 'IBM Watson analyzes your content and assigns a broad topic hierarchy that best describes the overall subject.', + 'classifai' + ) } +

+

+ { __( + 'Example: /technology and computing/software', + 'classifai' + ) } +

+

+ { __( + 'Categories are useful for general classification and site-wide content grouping.', + 'classifai' + ) } +

+ + ), + keyword: ( + <> +

+ { __( + 'Keywords represent important terms in your content that are contextually significant.', + 'classifai' + ) } +

+

+ { __( + 'Watson extracts these to help identify core concepts, topics, and SEO-friendly tags.', + 'classifai' + ) } +

+

+ { __( + 'Keywords often map well to WordPress tags.', + 'classifai' + ) } +

+ + ), + entity: ( + <> +

+ { __( + 'Entities are named people, places, brands, and other proper nouns mentioned in your content.', + 'classifai' + ) } +

+

+ { __( + 'Watson identifies and classifies these by type (e.g., Person, Company, Location).', + 'classifai' + ) } +

+

+ { __( + 'Entities are helpful for structured data and enhancing rich snippets or metadata.', + 'classifai' + ) } +

+ + ), + concept: ( + <> +

+ { __( + "Concepts reflect high-level abstract ideas Watson identifies in your content, even if the term isn't explicitly used.", + 'classifai' + ) } +

+

+ { __( + 'For example, an article about "the iPhone" might be linked to the concept of "Apple Inc."', + 'classifai' + ) } +

+

+ { __( + 'Concepts are great for semantic tagging and content recommendation systems.', + 'classifai' + ) } +

+ + ), +}; + +/** + * Moderation helper text. + * + * @type {Object} + */ +export const moderationHelperText = { + content_types: ( + <> +

+ { __( + 'The OpenAI moderation endpoint will check if text is potentially harmful.', + 'classifai' + ) } +

+

+ { __( + 'Text will be checked against certain categories, like hate, threatening, harassment, self-harm, sexual, violence, and more. Each category is scored on a scale of 0 to 1, with 0 indicating no harm and 1 indicating the highest level of harm. If something is found to be harmful, it will be flagged and blocked.', + 'classifai' + ) } +

+ + ), +}; diff --git a/src/js/settings/utils/utils.js b/src/js/settings/utils/utils.js index 8e2deeb4f..012f77a3e 100644 --- a/src/js/settings/utils/utils.js +++ b/src/js/settings/utils/utils.js @@ -2,6 +2,10 @@ * WordPress dependencies */ import { useSelect, useDispatch } from '@wordpress/data'; +import { Icon, Popover } from '@wordpress/components'; +import { useState } from '@wordpress/element'; +import { info } from '@wordpress/icons'; +import { __ } from '@wordpress/i18n'; // Update URL based on the current tab and feature selected export const updateUrl = ( key, value ) => { @@ -205,3 +209,41 @@ export const isProviderConfigurationNeeded = ( feature ) => { return isEnabled && ! authenticated; }; + +/** + * Tooltip Popover component. + * + * @param {Object} props The props object. + * @param {string} props.tooltipContent The tooltip content. + * @return {React.ReactElement} The TooltipPopover component. + */ +export const TooltipPopover = ( { tooltipContent } ) => { + const [ popoverAnchor, setPopoverAnchor ] = useState(); + const [ isVisible, setIsVisible ] = useState( false ); + const toggleVisible = () => { + setIsVisible( ( state ) => ! state ); + }; + + return ( +
+ + { isVisible && ( + +
+ { tooltipContent } +
+
+ ) } +
+ ); +}; diff --git a/src/scss/settings.scss b/src/scss/settings.scss index 336d3bbd4..a9d99e873 100644 --- a/src/scss/settings.scss +++ b/src/scss/settings.scss @@ -127,6 +127,17 @@ .classifai-panel-header-close { margin-right: -10px; } + + .display-container-wrapper { + background-color: #f0f0f1; + margin-top: 8px; + margin-bottom: 8px; + + .helper-text-content { + max-width: 600px; + padding: 4px 20px; + } + } } .classifai-settings-wrapper { diff --git a/tests/bin/initialize.sh b/tests/bin/initialize.sh index c50b4be22..7ccc2716e 100755 --- a/tests/bin/initialize.sh +++ b/tests/bin/initialize.sh @@ -3,3 +3,4 @@ npm run env run tests-wordpress chmod -- -c ugo+w /var/www/html npm run env run tests-cli wp rewrite structure '/%postname%/' -- --hard npm run env run tests-cli wp plugin deactivate classic-editor npm run env run tests-cli wp plugin deactivate woocommerce +npm run env run tests-cli wp plugin deactivate elasticpress diff --git a/tests/test-plugin/e2e-test-plugin.php b/tests/test-plugin/e2e-test-plugin.php index a41a31ecb..5b3270eb7 100644 --- a/tests/test-plugin/e2e-test-plugin.php +++ b/tests/test-plugin/e2e-test-plugin.php @@ -10,6 +10,9 @@ add_filter( 'classifai_aws_polly_pre_connect_to_service', 'classifai_mock_aws_polly_connect_to_service' ); add_filter( 'classifai_aws_polly_pre_synthesize_speech', 'classifai_mock_aws_polly_pre_synthesize_speech' ); +// Disable ElasticPress admin bar. +add_filter( 'ep_admin_bar_should_display', '__return_false' ); + /** * Mock ClassifAI's HTTP requests. *