diff --git a/inc/Main.php b/inc/Main.php index 55f2784b..fe0f9a23 100644 --- a/inc/Main.php +++ b/inc/Main.php @@ -158,21 +158,22 @@ function ( $data ) use ( $settings, $post_types_for_js ) { return array_merge( $data, [ - 'api' => $this->api->get_endpoint(), - 'rest_url' => rest_url( $this->api->get_endpoint() ), - 'postTypes' => $post_types_for_js, - 'hasAPIKey' => isset( $settings['api_key'] ) && ! empty( $settings['api_key'] ), - 'chunksLimit' => apply_filters( 'hyve_chunks_limit', 500 ), - 'isQdrantActive' => Qdrant_API::is_active(), - 'assets' => [ + 'api' => $this->api->get_endpoint(), + 'rest_url' => rest_url( $this->api->get_endpoint() ), + 'postTypes' => $post_types_for_js, + 'hasAPIKey' => isset( $settings['api_key'] ) && ! empty( $settings['api_key'] ), + 'isApiKeyConnected' => self::is_api_key_connected( $settings ), + 'chunksLimit' => apply_filters( 'hyve_chunks_limit', 500 ), + 'isQdrantActive' => Qdrant_API::is_active(), + 'assets' => [ 'images' => HYVE_LITE_URL . 'assets/images/', ], - 'stats' => $this->get_stats(), - 'docs' => 'https://docs.themeisle.com/article/2009-hyve-documentation', - 'qdrant_docs' => 'https://docs.themeisle.com/article/2066-integrate-hyve-with-qdrant', - 'pro' => 'https://themeisle.com/plugins/hyve/', - 'chart' => $this->get_chart_data(), - 'hasPro' => apply_filters( 'product_hyve_license_status', false ), + 'stats' => $this->get_stats(), + 'docs' => 'https://docs.themeisle.com/article/2009-hyve-documentation', + 'qdrant_docs' => 'https://docs.themeisle.com/article/2066-integrate-hyve-with-qdrant', + 'pro' => 'https://themeisle.com/plugins/hyve/', + 'chart' => $this->get_chart_data(), + 'hasPro' => apply_filters( 'product_hyve_license_status', false ), ] ); }, @@ -641,6 +642,43 @@ function ( $date ) { ]; } + /** + * Determine whether the saved OpenAI API key is connected. + * + * The key is validated against OpenAI whenever it is saved, and any + * key-related failure during use is stored in the error option. The key is + * considered connected when it is set and the last stored error (if any) is + * not one that invalidates the key itself. + * + * @param array $settings Plugin settings. + * + * @return bool + */ + public static function is_api_key_connected( $settings ) { + if ( empty( $settings['api_key'] ) ) { + return false; + } + + $last_error = get_option( OpenAI::ERROR_OPTION_KEY, false ); + + if ( ! is_array( $last_error ) || empty( $last_error['code'] ) ) { + return true; + } + + $key_error_codes = [ + 'invalid_api_key', + 'invalid_authentication', + 'account_deactivated', + 'billing_not_active', + 'organization_not_found', + 'organization_deactivated', + 'permission_denied', + 'insufficient_quota', + ]; + + return ! in_array( $last_error['code'], $key_error_codes, true ); + } + /** * Append services errors if they exists. * diff --git a/src/backend/parts/settings/Advanced.js b/src/backend/parts/settings/Advanced.js index ff6a99b5..65cabc53 100644 --- a/src/backend/parts/settings/Advanced.js +++ b/src/backend/parts/settings/Advanced.js @@ -9,6 +9,7 @@ import { BaseControl, Button, ExternalLink, + Icon, Panel, PanelRow, TextControl, @@ -20,6 +21,14 @@ import { useDispatch, useSelect } from '@wordpress/data'; import { applyFilters } from '@wordpress/hooks'; +const getInitialApiStatus = () => { + if ( window.hyve?.isApiKeyConnected ) { + return 'connected'; + } + + return window.hyve?.hasAPIKey ? 'error' : 'none'; +}; + const Advanced = () => { const settings = useSelect( ( select ) => select( 'hyve' ).getSettings() ); @@ -29,6 +38,8 @@ const Advanced = () => { const [ isSaving, setIsSaving ] = useState( false ); + const [ apiStatus, setApiStatus ] = useState( getInitialApiStatus ); + const onSave = async () => { setIsSaving( true ); @@ -47,8 +58,10 @@ const Advanced = () => { if ( settings.api_key ) { setHasAPI( true ); + setApiStatus( 'connected' ); } else { setHasAPI( false ); + setApiStatus( 'none' ); } createNotice( 'success', __( 'Settings saved.', 'hyve-lite' ), { @@ -56,6 +69,10 @@ const Advanced = () => { isDismissible: true, } ); } catch ( error ) { + if ( settings.api_key ) { + setApiStatus( 'error' ); + } + createNotice( 'error', error, { type: 'snackbar', isDismissible: true, @@ -86,17 +103,37 @@ const Advanced = () => { featureComponent: 'api-key', featureValue: 'added', } ); + setApiStatus( 'editing' ); setSetting( 'api_key', newValue ); } } /> - - { __( 'Get an API key', 'hyve-lite' ) } - + { 'connected' === apiStatus && ( +

+ + { __( 'Connected', 'hyve-lite' ) } +

+ ) } + + { 'error' === apiStatus && ( +

+ + { __( + 'Not connected. Please check your API key.', + 'hyve-lite' + ) } +

+ ) } + + { 'connected' !== apiStatus && ( + + { __( 'Get an API key', 'hyve-lite' ) } + + ) }