Ollama for configuring the Ollama * host URL. * * @since 1.0.0 */ class OllamaSettings { private const OPTION_GROUP = 'ai-provider-for-ollama-settings'; private const OPTION_NAME = 'ai_provider_for_ollama_settings'; private const PAGE_SLUG = 'ai-provider-for-ollama'; private const SECTION_ID = 'ai_provider_for_ollama_main'; private const AJAX_ACTION = 'ai_provider_for_ollama_list_models'; private const NONCE_ACTION = 'ai_provider_for_ollama_nonce'; /** * Option WordPress stores the Ollama API key in. * * @since 1.4.0 * * @var string */ private const API_KEY_OPTION_NAME = 'connectors_ai_ollama_api_key'; /** * Initializes the settings. * * @since 1.0.0 */ public function init(): void { add_action( 'admin_init', array( $this, 'register_settings' ) ); add_action( 'admin_menu', array( $this, 'register_settings_screen' ) ); add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_settings_script' ) ); add_action( 'wp_ajax_' . self::AJAX_ACTION, array( $this, 'ajax_list_models' ) ); add_filter( 'wpai_has_ai_credentials', array( $this, 'filter_has_ai_credentials' ) ); add_filter( 'wpai_has_text_to_speech_support', array( $this, 'filter_has_text_to_speech_support' ) ); add_filter( 'wpai_is_ollama_connector_configured', array( $this, 'is_connected' ) ); foreach ( array( self::OPTION_NAME, self::API_KEY_OPTION_NAME ) as $option_name ) { add_action( 'add_option_' . $option_name, array( $this, 'invalidate_model_cache' ) ); add_action( 'update_option_' . $option_name, array( $this, 'invalidate_model_cache' ) ); add_action( 'delete_option_' . $option_name, array( $this, 'invalidate_model_cache' ) ); } } /** * Registers the setting and settings fields. * * @since 1.0.0 */ public function register_settings(): void { register_setting( self::OPTION_GROUP, self::OPTION_NAME, array( 'type' => 'array', 'default' => array(), 'sanitize_callback' => array( $this, 'sanitize_settings' ), ) ); add_settings_section( self::SECTION_ID, '', '__return_empty_string', self::PAGE_SLUG ); add_settings_field( self::OPTION_NAME . '_host', __( 'Host URL', 'ai-provider-for-ollama' ), array( $this, 'render_host_field' ), self::PAGE_SLUG, self::SECTION_ID, array( 'label_for' => self::OPTION_NAME . '-host' ) ); add_settings_field( self::OPTION_NAME . '_model', __( 'Available Models', 'ai-provider-for-ollama' ), array( $this, 'render_available_models_field' ), self::PAGE_SLUG, self::SECTION_ID, array( 'label_for' => self::OPTION_NAME . '-model' ) ); } /** * Registers the settings screen. * * @since 1.0.0 */ public function register_settings_screen(): void { add_options_page( __( 'Ollama Settings', 'ai-provider-for-ollama' ), __( 'Ollama', 'ai-provider-for-ollama' ), 'manage_options', self::PAGE_SLUG, array( $this, 'render_screen' ) ); } /** * Sanitizes the settings array. * * @since 1.0.0 * * @param mixed $value The input value. * @return array The sanitized settings. */ public function sanitize_settings( $value ): array { if ( ! is_array( $value ) ) { return array(); } $host = isset( $value['host'] ) ? trim( (string) $value['host'] ) : ''; if ( '' !== $host ) { $host = rtrim( esc_url_raw( $host ), '/' ); } return array( 'host' => $host, ); } /** * Renders the settings screen. * * @since 1.0.0 */ public function render_screen(): void { if ( ! current_user_can( 'manage_options' ) ) { return; } ?>

Connectors%4$s screen.', 'ai-provider-for-ollama' ), '', '', '', '' ); ?>

', '' ); ?>

', '' ); ?>

esc_url( admin_url( 'admin-ajax.php' ) . '?action=' . self::AJAX_ACTION . '&_wpnonce=' . wp_create_nonce( self::NONCE_ACTION ) ), ) ); } /** * Handles the AJAX request to list available Ollama models. * * @since 1.0.0 */ public function ajax_list_models(): void { check_ajax_referer( self::NONCE_ACTION ); if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( __( 'Insufficient permissions.', 'ai-provider-for-ollama' ), 403 ); } if ( ! empty( $_GET['refresh'] ) ) { $this->invalidate_model_cache(); } $models = $this->get_models(); if ( is_wp_error( $models ) ) { $status_code = $models->get_error_data(); wp_send_json_error( $models->get_error_message(), is_int( $status_code ) ? $status_code : 500 ); } wp_send_json_success( $models ); } /** * Discards the cached model list. * * @since 1.4.0 */ public function invalidate_model_cache(): void { $provider_id = 'ollama'; $registry = AiClient::defaultRegistry(); if ( ! $registry->hasProvider( $provider_id ) ) { return; } $provider_classname = $registry->getProviderClassName( $provider_id ); try { $model_metadata_directory = $provider_classname::modelMetadataDirectory(); if ( $model_metadata_directory instanceof CachesDataInterface ) { $model_metadata_directory->invalidateCaches(); } } catch ( \Throwable $e ) { // Nothing cached to drop if the directory cannot be built. return; } } /** * Checks if the Ollama provider is connected. * * @since 1.1.0 * * @return bool True if the Ollama provider is connected, false otherwise. */ public function is_connected(): bool { return ! is_wp_error( $this->get_models() ); } /** * Adds Ollama to the AI plugin's credentials check. * * @since 1.4.0 * * @param mixed $has_credentials Whether AI credentials are already available. * @return bool True if AI credentials are available, false otherwise. */ public function filter_has_ai_credentials( $has_credentials ): bool { return (bool) $has_credentials || $this->is_connected(); } /** * Adds Ollama to the AI plugin's text to speech support check. * * @since 1.4.0 * * @param mixed $has_support Whether text to speech conversion is already supported. * @return bool True if text to speech conversion is supported, false otherwise. */ public function filter_has_text_to_speech_support( $has_support ): bool { return (bool) $has_support || $this->has_model_with_capability( CapabilityEnum::textToSpeechConversion() ); } /** * Checks if any connected Ollama model supports the given capability. * * @since 1.4.0 * * @param \WordPress\AiClient\Providers\Models\Enums\CapabilityEnum $capability The capability to look for. * @return bool True if a model supports the capability, false otherwise. */ private function has_model_with_capability( CapabilityEnum $capability ): bool { $models = $this->get_models(); if ( is_wp_error( $models ) ) { return false; } foreach ( $models as $model ) { foreach ( $model->getSupportedCapabilities() as $supported_capability ) { if ( $capability->equals( $supported_capability ) ) { return true; } } } return false; } /** * Gets the models from the Ollama provider. * * @since 1.1.0 * * @return \WP_Error|list<\WordPress\AiClient\Providers\Models\DTO\ModelMetadata> The models. */ public function get_models() { $provider_id = 'ollama'; $registry = AiClient::defaultRegistry(); if ( ! $registry->hasProvider( $provider_id ) ) { return new WP_Error( 'ai_provider_not_found', __( 'AI provider not found.', 'ai-provider-for-ollama' ), 404 ); } $provider_classname = $registry->getProviderClassName( $provider_id ); try { // phpcs:ignore Generic.Commenting.DocComment.MissingShort $provider_availability = $provider_classname::availability(); if ( ! $provider_availability->isConfigured() ) { return new WP_Error( 'ai_provider_not_configured', __( 'AI provider not configured - ensure Ollama is running or you have valid API credentials.', 'ai-provider-for-ollama' ), 400 ); } // phpcs:ignore Generic.Commenting.DocComment.MissingShort $model_metadata_directory = $provider_classname::modelMetadataDirectory(); return $model_metadata_directory->listModelMetadata(); } catch ( \Throwable $e ) { /* translators: %s: Error message. */ return new WP_Error( 'could_not_list_models', sprintf( __( 'Could not list models for provider - is Ollama running or are the API credentials invalid? Error: %s', 'ai-provider-for-ollama' ), $e->getMessage() ), 500 ); } } /** * Gets the settings from the WordPress option. * * @since 1.0.0 * * @return array The settings. */ public static function get_settings(): array { return (array) get_option( self::OPTION_NAME, array() ); } }