diff --git a/modules/ai_api_explorer/ai_api_explorer.services.yml b/modules/ai_api_explorer/ai_api_explorer.services.yml new file mode 100644 index 0000000000000000000000000000000000000000..5eb363228ef1b10bf0215efce9b01b6f98735f2d --- /dev/null +++ b/modules/ai_api_explorer/ai_api_explorer.services.yml @@ -0,0 +1,3 @@ +services: + ai_api_explorer.helper: + class: '\Drupal\ai_api_explorer\ExplorerHelper' diff --git a/modules/ai_api_explorer/css/api_explorer.css b/modules/ai_api_explorer/css/api_explorer.css index a4efb1d0ea7b11fcfb3b438a46e2d3517e7f26ad..3899bbbb2e1780df76a2c7ff8ea2b922abbb0682 100644 --- a/modules/ai_api_explorer/css/api_explorer.css +++ b/modules/ai_api_explorer/css/api_explorer.css @@ -16,6 +16,11 @@ clear: both; } +.ai-error { + color: #cc3d3d; + +} + code.ai-code { display: block; white-space: pre-wrap; diff --git a/modules/ai_api_explorer/src/ExplorerHelper.php b/modules/ai_api_explorer/src/ExplorerHelper.php new file mode 100644 index 0000000000000000000000000000000000000000..4b2a1cc178906932f00e1d3ce2cdc7da089812a8 --- /dev/null +++ b/modules/ai_api_explorer/src/ExplorerHelper.php @@ -0,0 +1,32 @@ +<?php + +namespace Drupal\ai_api_explorer; + +/** + * Helper functions for the forms. + * + * @package Drupal\ai_api_explorer + */ +class ExplorerHelper { + + /** + * Function to render an exception in a modal. + * + * @param \Exception $e + * The exception to render. + * + * @return string + * The rendered exception. + */ + public static function renderException(\Exception $e): string { + $message = '<div class="ai-error">'; + $message .= '<h2>' . t('An error occurred') . '</h2>'; + $message .= '<p>' . t('The following error occurred while processing your request:') . '</p>'; + $message .= '<p><em>' . $e->getMessage() . '</em></p>'; + $message .= '<p>' . t('Of the following exception type:') . '</p>'; + $message .= '<p>' . get_class($e) . '</p>'; + $message .= '</div>'; + return $message; + } + +} diff --git a/modules/ai_api_explorer/src/Form/ChatGenerationForm.php b/modules/ai_api_explorer/src/Form/ChatGenerationForm.php index a78354d9e2338e4cba3931ac2b9ab92fbe854151..820f22dc2fb5516d988b8834314a38667ee5e3a7 100644 --- a/modules/ai_api_explorer/src/Form/ChatGenerationForm.php +++ b/modules/ai_api_explorer/src/Form/ChatGenerationForm.php @@ -25,6 +25,13 @@ class ChatGenerationForm extends FormBase { */ protected $aiProviderHelper; + /** + * The Explorer Helper. + * + * @var \Drupal\ai_api_explorer\ExplorerHelper + */ + protected $explorerHelper; + /** * {@inheritdoc} */ @@ -38,6 +45,7 @@ class ChatGenerationForm extends FormBase { public static function create(ContainerInterface $container) { $instance = parent::create($container); $instance->aiProviderHelper = $container->get('ai.form_helper'); + $instance->explorerHelper = $container->get('ai_api_explorer.helper'); return $instance; } @@ -131,17 +139,23 @@ class ChatGenerationForm extends FormBase { } $input = new ChatInput($messages); - $response = $provider->chat($input, $form_state->getValue('chat_ai_model'), ['chat_generation'])->getNormalized(); + $response = NULL; + try { + $response = $provider->chat($input, $form_state->getValue('chat_ai_model'), ['chat_generation'])->getNormalized(); + } + catch (\Exception $e) { + $response = $this->explorerHelper->renderException($e); + } // Generation code for normalization. $code = $this->normalizeCodeExample($provider, $form_state, $messages); $code .= $this->rawCodeExample($provider, $form_state, $messages); - if (get_class($response) == ChatMessage::class) { + if (is_object($response) && get_class($response) == ChatMessage::class) { $form['response']['#context']['texts'] = '<h4>Role: ' . $response->getRole() . "</h4><p>" . $response->getMessage() . '</p>' . $code; } else { - $form['response']['#context']['texts'] = '<p>' . $this->t('Error: Invalid response from the provider.') . '</p>'; + $form['response']['#context']['texts'] = '<p>' . $response . '</p>'; } return $form['response']; } diff --git a/modules/ai_api_explorer/src/Form/EmbeddingsGenerationForm.php b/modules/ai_api_explorer/src/Form/EmbeddingsGenerationForm.php index be7292535c95a55a6de13f277ec708ba80bec7be..26b8c60ae3463aa34a0dee1d5adcc2d6a36a7665 100644 --- a/modules/ai_api_explorer/src/Form/EmbeddingsGenerationForm.php +++ b/modules/ai_api_explorer/src/Form/EmbeddingsGenerationForm.php @@ -28,6 +28,13 @@ class EmbeddingsGenerationForm extends FormBase { */ protected $requestStack; + /** + * The Explorer Helper. + * + * @var \Drupal\ai_api_explorer\ExplorerHelper + */ + protected $explorerHelper; + /** * {@inheritdoc} */ @@ -42,6 +49,7 @@ class EmbeddingsGenerationForm extends FormBase { $instance = parent::create($container); $instance->aiProviderHelper = $container->get('ai.form_helper'); $instance->requestStack = $container->get('request_stack'); + $instance->explorerHelper = $container->get('ai_api_explorer.helper'); return $instance; } @@ -111,8 +119,13 @@ class EmbeddingsGenerationForm extends FormBase { */ public function getResponse(array &$form, FormStateInterface $form_state) { $provider = $this->aiProviderHelper->generateAiProviderFromFormSubmit($form, $form_state, 'embeddings', 'embed'); - $embeddings = $provider->embeddings($form_state->getValue('prompt'), $form_state->getValue('embed_ai_model'), ['ai_api_explorer']); - $response = implode(', ', $embeddings->getNormalized()); + try { + $embeddings = $provider->embeddings($form_state->getValue('prompt'), $form_state->getValue('embed_ai_model'), ['ai_api_explorer']); + $response = implode(', ', $embeddings->getNormalized()); + } + catch (\Exception $e) { + $response = $this->explorerHelper->renderException($e); + } // Generation code. $code = "<details style=\"background: #ccc; padding: 5px;\"><summary>Code Example</summary><code style=\"display: block; white-space: pre-wrap; padding: 20px;\">"; $code .= '$prompt = "' . $form_state->getValue('prompt') . '";<br>'; diff --git a/modules/ai_api_explorer/src/Form/ModerationGenerationForm.php b/modules/ai_api_explorer/src/Form/ModerationGenerationForm.php index cdf8324263a569edd535e197f257e5904817cb33..c613ac4f73477d7078fb122b26ab4cd7bfdc5084 100644 --- a/modules/ai_api_explorer/src/Form/ModerationGenerationForm.php +++ b/modules/ai_api_explorer/src/Form/ModerationGenerationForm.php @@ -24,6 +24,13 @@ class ModerationGenerationForm extends FormBase { */ protected $aiProviderHelper; + /** + * The Explorer Helper. + * + * @var \Drupal\ai_api_explorer\ExplorerHelper + */ + protected $explorerHelper; + /** * {@inheritdoc} */ @@ -37,6 +44,7 @@ class ModerationGenerationForm extends FormBase { public static function create(ContainerInterface $container) { $instance = parent::create($container); $instance->aiProviderHelper = $container->get('ai.form_helper'); + $instance->explorerHelper = $container->get('ai.explorer_helper'); return $instance; } diff --git a/modules/ai_api_explorer/src/Form/SpeechToTextGenerationForm.php b/modules/ai_api_explorer/src/Form/SpeechToTextGenerationForm.php index a0509961a36755901f3092be9e694d972cc81b34..22223cfce4147ac336cfb37172db460ce6ae7613 100644 --- a/modules/ai_api_explorer/src/Form/SpeechToTextGenerationForm.php +++ b/modules/ai_api_explorer/src/Form/SpeechToTextGenerationForm.php @@ -28,6 +28,13 @@ class SpeechToTextGenerationForm extends FormBase { */ protected $requestStack; + /** + * The Explorer Helper. + * + * @var \Drupal\ai_api_explorer\ExplorerHelper + */ + protected $explorerHelper; + /** * {@inheritdoc} */ @@ -42,6 +49,7 @@ class SpeechToTextGenerationForm extends FormBase { $instance = parent::create($container); $instance->aiProviderHelper = $container->get('ai.form_helper'); $instance->requestStack = $container->get('request_stack'); + $instance->explorerHelper = $container->get('ai.explorer_helper'); return $instance; } @@ -115,7 +123,12 @@ class SpeechToTextGenerationForm extends FormBase { $file = reset($files); $raw_file = file_get_contents($file['file']->getPathname()); - $response = $provider->speechToText($raw_file, $form_state->getValue('stt_ai_model'), ['ai_api_explorer'])->getNormalized(); + try { + $response = $provider->speechToText($raw_file, $form_state->getValue('stt_ai_model'), ['ai_api_explorer'])->getNormalized(); + } + catch (\Exception $e) { + $response = $this->explorerHelper->renderException($e); + } // Generation code. $code = "<details style=\"background: #ccc; padding: 5px;\"><summary>Code Example</summary><code style=\"display: block; white-space: pre-wrap; padding: 20px;\">"; diff --git a/modules/ai_api_explorer/src/Form/TextToImageGenerationForm.php b/modules/ai_api_explorer/src/Form/TextToImageGenerationForm.php index d1f06848a03c55665abb105073349858115fdb07..34af9e83836cca18667075a8d7b82dec6e27117e 100644 --- a/modules/ai_api_explorer/src/Form/TextToImageGenerationForm.php +++ b/modules/ai_api_explorer/src/Form/TextToImageGenerationForm.php @@ -42,6 +42,13 @@ class TextToImageGenerationForm extends FormBase { */ protected $entityTypeManager; + /** + * The Explorer Helper. + * + * @var \Drupal\ai_api_explorer\ExplorerHelper + */ + protected $explorerHelper; + /** * {@inheritdoc} */ @@ -58,6 +65,7 @@ class TextToImageGenerationForm extends FormBase { $instance->requestStack = $container->get('request_stack'); $instance->moduleHandler = $container->get('module_handler'); $instance->entityTypeManager = $container->get('entity_type.manager'); + $instance->explorerHelper = $container->get('ai_api_explorer.helper'); return $instance; } @@ -144,13 +152,18 @@ class TextToImageGenerationForm extends FormBase { */ public function getResponse(array &$form, FormStateInterface $form_state) { $provider = $this->aiProviderHelper->generateAiProviderFromFormSubmit($form, $form_state, 'text_to_image', 'image_generator'); - $images = $provider->textToImage($form_state->getValue('prompt'), $form_state->getValue('image_generator_ai_model'), ['ai_api_explorer']); - $response = ''; - foreach ($images->getAsBase64EncodedString() as $image) { - $response .= '<img src="data:image/png;charset=utf-8;base64,' . $image . '" />'; + try { + $images = $provider->textToImage($form_state->getValue('prompt'), $form_state->getValue('image_generator_ai_model'), ['ai_api_explorer']); + $response = ''; + foreach ($images->getAsBase64EncodedString() as $image) { + $response .= '<img src="data:image/png;charset=utf-8;base64,' . $image . '" />'; + } + if ($form_state->getValue('save_as_media')) { + $images->getAsMediaReference($form_state->getValue('save_as_media'), 'image.png'); + } } - if ($form_state->getValue('save_as_media')) { - $images->getAsMediaReference($form_state->getValue('save_as_media'), 'image.png'); + catch (\Exception $e) { + $response = $this->explorerHelper->renderException($e); } // Generation code. diff --git a/modules/ai_api_explorer/src/Form/TextToSpeechGenerationForm.php b/modules/ai_api_explorer/src/Form/TextToSpeechGenerationForm.php index 2995d913e81b5022ba662e48deec0e993ddfa2e2..82da95b426fb1071dc771d2e426ddf7926cb370e 100644 --- a/modules/ai_api_explorer/src/Form/TextToSpeechGenerationForm.php +++ b/modules/ai_api_explorer/src/Form/TextToSpeechGenerationForm.php @@ -57,6 +57,13 @@ class TextToSpeechGenerationForm extends FormBase { */ protected $entityTypeManager; + /** + * The Explorer Helper. + * + * @var \Drupal\ai_api_explorer\ExplorerHelper + */ + protected $explorerHelper; + /** * {@inheritdoc} */ @@ -75,6 +82,7 @@ class TextToSpeechGenerationForm extends FormBase { $instance->fileSystem = $container->get('file_system'); $instance->moduleHandler = $container->get('module_handler'); $instance->entityTypeManager = $container->get('entity_type.manager'); + $instance->explorerHelper = $container->get('ai_api_explorer.helper'); return $instance; } @@ -161,16 +169,21 @@ class TextToSpeechGenerationForm extends FormBase { */ public function getResponse(array &$form, FormStateInterface $form_state) { $provider = $this->aiProviderHelper->generateAiProviderFromFormSubmit($form, $form_state, 'text_to_speech', 'tts_'); - $audio = $provider->textToSpeech($form_state->getValue('prompt'), $form_state->getValue('tts_ai_model'), ['ai_api_explorer']); + $response = ''; - if ($form_state->getValue('save_as_media')) { - $audio->getAsMediaReference($form_state->getValue('save_as_media'), 'text-to-speech.mp3'); + try { + $audio = $provider->textToSpeech($form_state->getValue('prompt'), $form_state->getValue('tts_ai_model'), ['ai_api_explorer']); + if ($form_state->getValue('save_as_media')) { + $audio->getAsMediaReference($form_state->getValue('save_as_media'), 'text-to-speech.mp3'); + } + $audio_normalized = $audio->getNormalized(); + // Save the binary data to a file. + $file_url = $this->fileSystem->saveData($audio_normalized[0], 'public://text-to-speech-test.mp3', FileExists::Replace); + $response .= '<audio controls><source src="' . $this->fileUrlGenerator->generateAbsoluteString($file_url) . '" type="audio/mpeg"></audio>'; + } + catch (\Exception $e) { + $response = $this->explorerHelper->renderException($e); } - $audio_normalized = $audio->getNormalized(); - // Save the binary data to a file. - $file_url = $this->fileSystem->saveData($audio_normalized[0], 'public://text-to-speech-test.mp3', FileExists::Replace); - $response .= '<audio controls><source src="' . $this->fileUrlGenerator->generateAbsoluteString($file_url) . '" type="audio/mpeg"></audio>'; - // Generation code. $code = "<details style=\"background: #ccc; padding: 5px;\"><summary>Code Example</summary><code style=\"display: block; white-space: pre-wrap; padding: 20px;\">"; $code .= '$prompt = "' . $form_state->getValue('prompt') . '";<br>'; diff --git a/modules/ai_external_moderation/config/install/ai_external_moderation.settings.yml b/modules/ai_external_moderation/config/install/ai_external_moderation.settings.yml index ed552b91b6d843b804e8edcc5a7ebddf2a408787..3882592998eee8b523b7fe8d133a85810b110449 100644 --- a/modules/ai_external_moderation/config/install/ai_external_moderation.settings.yml +++ b/modules/ai_external_moderation/config/install/ai_external_moderation.settings.yml @@ -1,2 +1 @@ moderations: - diff --git a/modules/providers/provider_openai/src/Plugin/AiProvider/OpenAiProvider.php b/modules/providers/provider_openai/src/Plugin/AiProvider/OpenAiProvider.php index 81046d3573a8974bd253b0161c0e5f836f53d432..59cf133660f0e2386de9349aab225c737b6a450a 100644 --- a/modules/providers/provider_openai/src/Plugin/AiProvider/OpenAiProvider.php +++ b/modules/providers/provider_openai/src/Plugin/AiProvider/OpenAiProvider.php @@ -406,16 +406,17 @@ class OpenAiProvider extends AiProviderClientBase implements * @throws \Drupal\ai\Exception\AiUnsafePromptException */ public function moderationEndpoints(string $prompt): void { + $this->getClient(); // If moderation is disabled, we skip this. if (!$this->moderation) { return; } - $this->getClient(); $payload = [ 'model' => 'text-moderation-latest', 'input' => $prompt, ] + $this->configuration; $response = $this->client->moderations()->create($payload)->toArray(); + if (!empty($response['results'][0]['flagged'])) { throw new AiUnsafePromptException('The prompt was flagged by the moderation model.'); }