Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Freelygive/ai recipe #14

Merged
merged 10 commits into from
Aug 29, 2024
2 changes: 2 additions & 0 deletions dxpr_cms_image_media_type/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
"description": "Adds focal point-based cropping to the image media type.",
"version": "dev-main",
"require": {
"drupal/ai": "1.0.x-dev@dev",
"drupal/ai_image_alt_text": "1.0.x-dev@dev",
"drupal/core": ">=10.3",
"drupal/focal_point": "^2"
}
Expand Down
10 changes: 10 additions & 0 deletions dxpr_cms_image_media_type/recipe.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,15 @@ recipes:
- core/recipes/image_media_type
install:
- focal_point
- key
- ai
- provider_openai
- provider_anthropic
- ai_image_alt_text
config:
import:
focal_point: '*'
ai_image_alt_text: '*'
actions:
core.entity_form_display.media.image.media_library:
setComponent:
Expand All @@ -21,3 +27,7 @@ config:
preview_image_style: media_library
preview_link: true
offsets: '50,50'
ai_image_alt_text.settings:
simple_config_update:
autogenerate: true
hide_button: true
137 changes: 136 additions & 1 deletion dxpr_cms_installer/src/Form/ConfigureAPIKeysForm.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\StringTranslation\TranslationInterface;
use Drupal\dxpr_builder\Service\DxprBuilderJWTDecoder;
use Drupal\key\Entity\Key;
use OpenAI;
use Symfony\Component\DependencyInjection\ContainerInterface;
use WpAi\Anthropic\AnthropicAPI;

/**
* Defines form for entering DXPR Builder product key and Google Cloud Translation API key.
Expand Down Expand Up @@ -68,7 +71,7 @@ class ConfigureAPIKeysForm extends FormBase implements ContainerInjectionInterfa
public function __construct($root, InfoParserInterface $info_parser,
TranslationInterface $translator,
ConfigFactoryInterface $config_factory,
DxprBuilderJWTDecoder $jwtDecoder) {
DxprBuilderJWTDecoder $jwtDecoder,) {
ivanboring marked this conversation as resolved.
Show resolved Hide resolved
$this->root = $root;
$this->infoParser = $info_parser;
$this->stringTranslation = $translator;
Expand All @@ -86,6 +89,7 @@ public static function create(ContainerInterface $container) {
$container->get('string_translation'),
$container->get('config.factory'),
$container->get('dxpr_builder.jwt_decoder'),
$container->get('module_installer')
);
}

Expand Down Expand Up @@ -119,6 +123,39 @@ public function buildForm(array $form, FormStateInterface $form_state, array &$i
];
// }

$form['ai_provider'] = [
'#type' => 'select',
'#title' => $this->t('Select AI Provider'),
'#description' => $this->t('If you want to enable AI features like ai powered alt text generation, select the AI provider you want to use for AI features and fill in the API Key.'),
'#empty_option' => $this->t('No AI'),
'#options' => [
'openai' => $this->t('OpenAI'),
'anthropic' => $this->t('Anthropic'),
],
];

$form['openai_key'] = [
'#type' => 'password',
'#title' => $this->t('OpenAI API key'),
'#description' => $this->t('Get a key from <a href="https://platform.openai.com/api-keys" target="_blank">platform.openai.com/api-keys</a>.'),
'#states' => [
'visible' => [
':input[name="ai_provider"]' => ['value' => 'openai'],
],
],
];

$form['anthropic_key'] = [
'#type' => 'password',
'#title' => $this->t('Anthropic API key'),
'#description' => $this->t('Get a key from <a href="https://console.anthropic.com/settings/keys" target="_blank">console.anthropic.com/settings/keys</a>.'),
'#states' => [
'visible' => [
':input[name="ai_provider"]' => ['value' => 'anthropic'],
],
],
];

ivanboring marked this conversation as resolved.
Show resolved Hide resolved
$form['actions'] = [
'continue' => [
'#type' => 'submit',
Expand All @@ -145,6 +182,28 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
if (!empty($google_translation_key)) {
$this->configFactory->getEditable('tmgmt.translator.google')->set('settings.api_key', $google_translation_key)->save();
}

// If the AI provider is set, enable the appropriate modules.
if ($ai_provider = $form_state->getValue('ai_provider')) {
// Setup the key for the provider.
$key_id = $ai_provider . '_key';
$key = Key::create([
'id' => $key_id,
'label' => ucfirst($ai_provider) . ' API Key',
'description' => 'API Key for ' . ucfirst($ai_provider),
'key_type' => 'authentication',
'key_provider' => 'config',
]);
$key->setKeyValue($form_state->getValue($key_id));
$key->save();
// Add the key to the config.
$this->configFactory->getEditable('provider_' . $ai_provider . '.settings')->set('api_key', $key_id)->save();
// Set the default provider.
$this->configFactory->getEditable('ai.settings')->set('default_providers.chat', [
'provider_id' => $ai_provider,
'model_id' => $ai_provider == 'openai' ? 'gpt-4o' : 'claude-3-5-sonnet-20240620',
])->save();
}
ivanboring marked this conversation as resolved.
Show resolved Hide resolved
}

/**
Expand All @@ -164,7 +223,83 @@ public function validateForm(array &$form, FormStateInterface $form_state): void
]));
}
}

// Test the OpenAI key if it is set.
if ($form_state->getValue('ai_provider') === 'openai') {
// It has to be set.
if (empty($form_state->getValue('openai_key'))) {
$form_state->setErrorByName('openai_key', $this->t('OpenAI API key is required, if you want to enable OpenAI.'));
}
// It has to be valid.
elseif ($errorMessage = $this->testOpenAIKey($form_state->getValue('openai_key'))) {
$form_state->setErrorByName('openai_key', $this->t('Your OpenAI API key seems to be invalid with message %message.', [
'%message' => $errorMessage,
]));
}
}

// Test the Anthropic key if it is set.
if ($form_state->getValue('ai_provider') === 'anthropic') {
// It has to be set.
if (empty($form_state->getValue('anthropic_key'))) {
$form_state->setErrorByName('anthropic_key', $this->t('Anthropic API key is required, if you want to enable Anthropic.'));
}
// It has to be valid.
elseif ($errorMessage = $this->testAnthropicKey($form_state->getValue('anthropic_key'))) {
$form_state->setErrorByName('anthropic_key', $this->t('Your Anthropic API key seems to be invalid with message %message.', [
'%message' => $errorMessage,
]));
}
}
}

/**
* Simple test of the OpenAI Key.
*
* @param string $openai_key
* The OpenAI key.
*
* @return string
* If the key is valid, the error message.
*/
protected function testOpenAIKey(string $openai_key): string {
$client = OpenAI::client($openai_key);
try {
$client->models()->list();
}
catch (\Exception $e) {
return $e->getMessage();
}
return '';
}
ivanboring marked this conversation as resolved.
Show resolved Hide resolved

/**
* Simple test of the Anthropic Key.
*
* @param string $anthropic_key
* The Anthropic key.
*
* @return string
* If the key is valid, the message.
*/
protected function testAnthropicKey(string $anthropic_key): string {
$client = new AnthropicAPI($anthropic_key);
$payload = [
'model' => 'claude-3-5-sonnet-20240620',
'messages' => [
[
'role' => 'user',
'content' => 'Say hello!',
],
],
];
try {
$client->messages()->maxTokens(10)->create($payload);
}
catch (\Exception $e) {
return $e->getMessage();
}
return "";
}
ivanboring marked this conversation as resolved.
Show resolved Hide resolved

}
Loading