| 1 |
<?php |
| 2 |
|
| 3 |
namespace WPDeveloper\BetterDocs\AI; |
| 4 |
|
| 5 |
use WPDeveloper\BetterDocs\Core\Settings; |
| 6 |
use WPDeveloper\BetterDocs\AI\Providers\OpenAIProvider; |
| 7 |
use WPDeveloper\BetterDocs\AI\Providers\GeminiProvider; |
| 8 |
use WPDeveloper\BetterDocs\AI\Providers\ClaudeProvider; |
| 9 |
use WPDeveloper\BetterDocs\AI\Providers\DeepSeekProvider; |
| 10 |
use WPDeveloper\BetterDocs\AI\Providers\OpenRouterProvider; |
| 11 |
|
| 12 |
/** |
| 13 |
* Resolves the active AI provider from settings and constructs it with the |
| 14 |
* right per-platform API key and the global model. This is the single entry |
| 15 |
* point the content-suite features use; they never name a concrete provider. |
| 16 |
* |
| 17 |
* @since 4.4.0 |
| 18 |
*/ |
| 19 |
class ProviderFactory { |
| 20 |
|
| 21 |
/** |
| 22 |
* Per-platform API key settings are stored as ai_api_key_<platform>. |
| 23 |
* |
| 24 |
* Deliberately private: OpenAI does NOT follow this scheme (see |
| 25 |
* OPENAI_KEY_FIELD), so hand-concatenating the prefix would silently read |
| 26 |
* the wrong — always empty — setting for the default platform. Go through |
| 27 |
* key_field_for() instead; it is the only sanctioned way to name a key field. |
| 28 |
*/ |
| 29 |
private const KEY_PREFIX = 'ai_api_key_'; |
| 30 |
|
| 31 |
/** |
| 32 |
* OpenAI keeps the original single-provider field name. Write with AI has |
| 33 |
* always been OpenAI-only, so the stored key is already the OpenAI key — |
| 34 |
* reusing the name means existing installs need no migration and no user |
| 35 |
* has to re-enter anything when multi-platform support lands. |
| 36 |
*/ |
| 37 |
const OPENAI_KEY_FIELD = 'ai_autowrite_api_key'; |
| 38 |
|
| 39 |
/** |
| 40 |
* Settings key that holds the API key for a platform. |
| 41 |
* |
| 42 |
* @param string $platform |
| 43 |
* @return string |
| 44 |
*/ |
| 45 |
public static function key_field_for( $platform ) { |
| 46 |
return 'openai' === $platform ? self::OPENAI_KEY_FIELD : self::KEY_PREFIX . $platform; |
| 47 |
} |
| 48 |
|
| 49 |
/** |
| 50 |
* @var Settings |
| 51 |
*/ |
| 52 |
private $settings; |
| 53 |
|
| 54 |
public function __construct( Settings $settings ) { |
| 55 |
$this->settings = $settings; |
| 56 |
} |
| 57 |
|
| 58 |
/** |
| 59 |
* Platform id => provider class. Filterable so Pro / add-ons can register |
| 60 |
* or replace providers without editing core. |
| 61 |
* |
| 62 |
* @return array<string,string> |
| 63 |
*/ |
| 64 |
public function provider_map() { |
| 65 |
return apply_filters( 'betterdocs_ai_providers', array( |
| 66 |
'openai' => OpenAIProvider::class, |
| 67 |
'gemini' => GeminiProvider::class, |
| 68 |
'claude' => ClaudeProvider::class, |
| 69 |
'deepseek' => DeepSeekProvider::class, |
| 70 |
'openrouter' => OpenRouterProvider::class, |
| 71 |
) ); |
| 72 |
} |
| 73 |
|
| 74 |
/** |
| 75 |
* Whether a platform id has a registered provider. |
| 76 |
* |
| 77 |
* @param string $platform |
| 78 |
* @return bool |
| 79 |
*/ |
| 80 |
public function is_supported( $platform ) { |
| 81 |
$map = $this->provider_map(); |
| 82 |
return isset( $map[ $platform ] ); |
| 83 |
} |
| 84 |
|
| 85 |
/** |
| 86 |
* Currently selected platform, defaulting to OpenAI. |
| 87 |
* |
| 88 |
* @return string |
| 89 |
*/ |
| 90 |
public function active_platform() { |
| 91 |
$platform = (string) $this->settings->get( 'ai_platform', 'openai' ); |
| 92 |
return $this->is_supported( $platform ) ? $platform : 'openai'; |
| 93 |
} |
| 94 |
|
| 95 |
/** |
| 96 |
* Stored API key for a platform. |
| 97 |
* |
| 98 |
* @param string $platform |
| 99 |
* @return string |
| 100 |
*/ |
| 101 |
public function api_key_for( $platform ) { |
| 102 |
return (string) $this->settings->get( self::key_field_for( $platform ), '' ); |
| 103 |
} |
| 104 |
|
| 105 |
/** |
| 106 |
* Resolved global model for the active platform. Falls back through the |
| 107 |
* legacy per-feature model and then the platform default, so a not-yet |
| 108 |
* migrated install still resolves a valid model. |
| 109 |
* |
| 110 |
* @param string|null $platform |
| 111 |
* @return string |
| 112 |
*/ |
| 113 |
public function active_model( $platform = null ) { |
| 114 |
$platform = $platform ? $platform : $this->active_platform(); |
| 115 |
$model = (string) $this->settings->get( 'ai_model', '' ); |
| 116 |
|
| 117 |
if ( '' !== $model && ModelRegistry::has_model( $platform, $model ) ) { |
| 118 |
return $model; |
| 119 |
} |
| 120 |
|
| 121 |
$legacy = (string) $this->settings->get( 'write_with_ai_model', '' ); |
| 122 |
if ( '' !== $legacy && ModelRegistry::has_model( $platform, $legacy ) ) { |
| 123 |
return $legacy; |
| 124 |
} |
| 125 |
|
| 126 |
return ModelRegistry::default_model( $platform ); |
| 127 |
} |
| 128 |
|
| 129 |
/** |
| 130 |
* Build the provider for the active platform (or an explicit one). |
| 131 |
* |
| 132 |
* @param string|null $platform Optional platform override. |
| 133 |
* @param string|null $model Optional model override. |
| 134 |
* @return \WPDeveloper\BetterDocs\AI\Contracts\AIProvider |
| 135 |
*/ |
| 136 |
public function make( $platform = null, $model = null ) { |
| 137 |
$platform = $platform ? $platform : $this->active_platform(); |
| 138 |
if ( ! $this->is_supported( $platform ) ) { |
| 139 |
$platform = 'openai'; |
| 140 |
} |
| 141 |
|
| 142 |
$map = $this->provider_map(); |
| 143 |
$class = $map[ $platform ]; |
| 144 |
$key = $this->api_key_for( $platform ); |
| 145 |
|
| 146 |
if ( null === $model ) { |
| 147 |
$model = ( $platform === $this->active_platform() ) |
| 148 |
? $this->active_model( $platform ) |
| 149 |
: ModelRegistry::default_model( $platform ); |
| 150 |
} |
| 151 |
|
| 152 |
return new $class( $key, $model ); |
| 153 |
} |
| 154 |
|
| 155 |
/** |
| 156 |
* Build a provider with an explicit key/model — used by key validation |
| 157 |
* before anything is persisted. |
| 158 |
* |
| 159 |
* @param string $platform |
| 160 |
* @param string $api_key |
| 161 |
* @param string $model |
| 162 |
* @return \WPDeveloper\BetterDocs\AI\Contracts\AIProvider |
| 163 |
*/ |
| 164 |
public function make_with( $platform, $api_key, $model = '' ) { |
| 165 |
if ( ! $this->is_supported( $platform ) ) { |
| 166 |
$platform = 'openai'; |
| 167 |
} |
| 168 |
$map = $this->provider_map(); |
| 169 |
$class = $map[ $platform ]; |
| 170 |
return new $class( $api_key, $model ); |
| 171 |
} |
| 172 |
|
| 173 |
/** |
| 174 |
* Validate a key for a platform. |
| 175 |
* |
| 176 |
* @param string $platform |
| 177 |
* @param string $api_key |
| 178 |
* @return array array( 'valid' => bool, 'message' => string ) |
| 179 |
*/ |
| 180 |
public function validate( $platform, $api_key ) { |
| 181 |
return $this->make_with( $platform, $api_key )->validate_key( $api_key ); |
| 182 |
} |
| 183 |
} |
| 184 |
|