load(); } /** * Get default settings values. * * @return array */ public function get_defaults(): array { $default_provider = \AISupportAssistant\Providers\WordPressAIProvider::is_available() ? 'wp_native_ai' : 'gemini'; return array( 'ai_provider' => $default_provider, 'openai_api_key' => '', 'openai_chat_model' => 'gpt-4o-mini', 'openai_embedding_model' => 'text-embedding-3-small', 'gemini_api_key' => '', 'gemini_chat_model' => 'gemini-3.1-flash-lite', 'gemini_embedding_model' => 'gemini-embedding-001', 'vector_store' => 'mysql', 'temperature' => 0.2, 'max_tokens' => 1000, 'top_k' => 5, 'chunk_size' => 700, 'chunk_overlap' => 100, 'enable_streaming' => 1, 'enable_chat_history' => 1, 'enable_citations' => 1, 'show_powered_by' => 0, 'chat_widget_title' => 'AI Support Assistant', 'chat_widget_greeting' => 'Hello! I am your documentation assistant. Ask me anything, and I will search our guide files to help you.', 'ai_system_instruction' => 'You are a customer support assistant. Answer ONLY using the supplied documentation. Do not invent information. Provide concise and accurate answers.', 'chat_fallback_message' => 'I couldn\'t find that information in the uploaded documentation.', ); } /** * Load settings from database. * * @return void */ private function load(): void { $db_options = get_option( self::OPTION_KEY, array() ); if ( ! is_array( $db_options ) ) { $db_options = array(); } $this->settings = wp_parse_args( $db_options, $this->get_defaults() ); } /** * Get all settings array. * * @return array */ public function get_all(): array { if ( null === $this->settings ) { $this->load(); } return $this->settings; } /** * Get a specific settings value by key. * * @param string $key Settings key. * @param mixed $default Optional default fallback. * @return mixed */ public function get( string $key, $default = null ) { $all = $this->get_all(); if ( array_key_exists( $key, $all ) ) { return $all[ $key ]; } return $default; } /** * Update settings. * * @param array $new_settings Settings to update. * @return bool True if value changed and update succeeded. */ public function save( array $new_settings ): bool { $current = $this->get_all(); $sanitized = array(); foreach ( $this->get_defaults() as $key => $default_val ) { if ( isset( $new_settings[ $key ] ) ) { if ( is_int( $default_val ) ) { $sanitized[ $key ] = (int) $new_settings[ $key ]; } elseif ( is_float( $default_val ) ) { $sanitized[ $key ] = (float) $new_settings[ $key ]; } else { $sanitized[ $key ] = sanitize_text_field( $new_settings[ $key ] ); } } else { $sanitized[ $key ] = $current[ $key ] ?? $default_val; } } if ( ! \AISupportAssistant\Providers\WordPressAIProvider::is_available() && 'wp_native_ai' === $sanitized['ai_provider'] ) { $sanitized['ai_provider'] = 'gemini'; } $this->validate_api_keys_and_models( $sanitized ); $this->settings = $sanitized; return update_option( self::OPTION_KEY, $sanitized ); } /** * Validate models and API key endpoints live before saving settings. * * @param array $sanitized Settings payload to validate. * @throws \Exception If API key validation or model test fails. */ private function validate_api_keys_and_models( array $sanitized ): void { $old_settings = $this->settings; $this->settings = $sanitized; $provider_name = $sanitized['ai_provider'] ?? 'wp_native_ai'; if ( 'wp_native_ai' === $provider_name ) { return; } if ( 'openai' === $provider_name ) { $direct_key = trim( (string) ( $sanitized['openai_api_key'] ?? '' ) ); if ( ! empty( $direct_key ) ) { $provider = new \AISupportAssistant\Providers\OpenAIProvider( $direct_key ); try { $provider->get_embedding( 'test' ); } catch ( \Exception $e ) { $this->settings = $old_settings; /* translators: %s: OpenAI embedding model validation error message. */ throw new \Exception( esc_html( sprintf( __( 'OpenAI Embedding Model validation failed: %s', 'ai-support-assistant' ), $e->getMessage() ) ) ); } } } elseif ( 'gemini' === $provider_name ) { $direct_key = trim( (string) ( $sanitized['gemini_api_key'] ?? '' ) ); if ( ! empty( $direct_key ) ) { $provider = new \AISupportAssistant\Providers\GeminiProvider( $direct_key ); try { $provider->get_embedding( 'test' ); } catch ( \Exception $e ) { $this->settings = $old_settings; /* translators: %s: Gemini embedding model validation error message. */ throw new \Exception( esc_html( sprintf( __( 'Gemini Embedding Model validation failed: %s', 'ai-support-assistant' ), $e->getMessage() ) ) ); } } } } }