Skip to content
Snippets Groups Projects
Commit 6edab22a authored by Mike Feranda's avatar Mike Feranda
Browse files

Issue #3477456 by mferanda: Coding standards clean-up

parent 140247d2
No related branches found
No related tags found
No related merge requests found
## AIDmi - AI, describe my image!
AIDmi provides a button that will use an AI API to send an image to return a detailed description.
AIDmi provides a button that will use an AI API
to send an image to return a detailed description.
## Installation
......@@ -9,10 +10,10 @@ Enable aidmi.
drush en aidmi
- Configure the settings under Configure > Web services > AIDmi Settings
- Select API: Only Gemini at the moment.
- API Key Input Method: API key in Settings or File Path
- File Path is recommended. Make sure you save the file outside of web/ (Example on page)
- API Instructions: Default text provided that is sent to AI for image description.
- Select API: Only Gemini at the moment.
- API Key Input Method: API key in Settings or File Path
- File Path is recommended outside of web/ (Example on page)
- API Instructions: Default text provided for AI image description.
## Development of CKEditor5 Button
......
......@@ -11,4 +11,3 @@ aidmi_ckeditor:
- core/drupal.dialog.ajax
- core/jquery
- core/once
\ No newline at end of file
<?php
use Drupal\editor\Entity\Editor;
use Drupal\ckeditor5\Plugin\CKEditor5PluginDefinition;
/**
* @file
* AIDmi - AI, describe my image.
*/
use Drupal\Core\Routing\RouteMatchInterface;
/**
* Implements hook_help().
*
* Provides a help page for the module.
*/
function aidmi_help($route_name, \Drupal\Core\Routing\RouteMatchInterface $route_match) {
function aidmi_help($route_name, RouteMatchInterface $route_match) {
switch ($route_name) {
case 'help.page.aidmi':
return '<p>' . t('The AIDmi module allows users to upload images and get 508-compliant image descriptions generated by AI.') . '</p>';
......
......@@ -25,4 +25,4 @@ aidmi.describe_content_ajax:
requirements:
_permission: 'access content'
options:
_format: 'json'
\ No newline at end of file
_format: 'json'
......@@ -6,4 +6,3 @@ services:
aidmi.aidmicontroller:
class: 'Drupal\aidmi\Controller\AidmiController'
arguments: ['@aidmi.gemini_ai_service']
\ No newline at end of file
.aidmi-dialog {
height: auto;
}
height: auto;
}
.aidmi-dialog button {
margin-right: 10px;
margin-right: 10px;
}
.aidmi-dialog-content-scrollable {
max-height: 300px;
overflow-y: auto;
padding: 10px;
.aidmi-dialog-content-scrollable {
max-height: 300px;
overflow-y: auto;
padding: 10px;
}
.aidmi-dialog-subtitle {
font-size: 0.9em;
color: #666;
margin-top: -10px;
margin-bottom: 10px;
font-size: 0.9em;
color: #666;
margin-top: -10px;
margin-bottom: 10px;
}
.aidmi-dialog-image {
max-width: 100%;
max-height: 400px;
max-width: 100%;
max-height: 400px;
}
.aidmi-highlight-missing {
border: 2px solid red;
padding: 10px;
margin: 10px 0;
border: 2px solid red;
padding: 10px;
margin: 10px 0;
}
.aidmi-dialog-textarea {
resize: vertical;
width: 100%;
resize: vertical;
width: 100%;
}
.ckeditor5-toolbar-button-aidmi {
background-image: url(../icons/aidmi.svg);
background-image: url(../icons/aidmi.svg);
}
/* CKEditor5 AIDmi icon */
.aidmi-icon-str1 {
stroke:#2D2A2B;
stroke-width:0.83;
stroke-miterlimit:2.61313;
stroke: #2d2a2b;
stroke-width: 0.83;
stroke-miterlimit: 2.61313;
}
.aidmi-icon-str0 {
stroke:black;
stroke-width:0.83;
stroke-miterlimit:2.61313;
stroke: black;
stroke-width: 0.83;
stroke-miterlimit: 2.61313;
}
.aidmi-icon-fil2 {
fill:none;
fill: none;
}
.aidmi-icon-fil0 {
fill:none;
fill-rule:nonzero;
fill: none;
fill-rule: nonzero;
}
.aidmi-icon-fil1 {
fill:black;
fill-rule:nonzero;
}
\ No newline at end of file
fill: black;
fill-rule: nonzero;
}
(function ($, Drupal) {
Drupal.behaviors.aidmiDialog = {
attach: function (context, settings) {
Drupal.aidmi = Drupal.aidmi || {};
Drupal.aidmi.aidmiDialog = function (content, callback) {
let aidmiEditedText;
// Create a temporary container to hold the content.
const tempElement = document.createElement('div');
tempElement.setAttribute('role', 'dialog');
tempElement.setAttribute('aria-labelledby', 'aidmi-dialog-title');
tempElement.setAttribute('aria-describedby', 'aidmi-dialog-description');
tempElement.setAttribute('aria-modal', 'true'); // Ensures screen readers treat the dialog as a modal.
Drupal.behaviors.aidmiDialog = {
attach: function (context, settings) {
Drupal.aidmi = Drupal.aidmi || {};
// Set title text.
let tempETitle = 'AIDmi Description';
let tempESubTitle = 'Please evaluate and edit the description as needed.';
Drupal.aidmi.aidmiDialog = function (content, callback) {
let aidmiEditedText;
// Create a temporary container to hold the content.
const tempElement = document.createElement('div');
tempElement.setAttribute('role', 'dialog');
tempElement.setAttribute('aria-labelledby', 'aidmi-dialog-title');
tempElement.setAttribute('aria-describedby', 'aidmi-dialog-description');
tempElement.setAttribute('aria-modal', 'true'); // Ensures screen readers treat the dialog as a modal.
// Create dialog content with a textarea for editing.
tempElement.innerHTML = `
<div>
<h2 id="aidmi-dialog-title">${Drupal.t(tempETitle)}</h2>
<p id="aidmi-dialog-subtitle" class="aidmi-dialog-subtitle">${Drupal.t(tempESubTitle)}</p>
<textarea id="aidmi-dialog-textarea" class="aidmi-dialog-textarea" rows="6" style="width: 100%;">${Drupal.t(content)}</textarea>
</div>`;
// Set title text.
let tempETitle = 'AIDmi Description';
let tempESubTitle = 'Please evaluate and edit the description as needed.';
// Store the element that triggered the dialog, to return focus later.
const previousFocus = document.activeElement;
// Create dialog content with a textarea for editing.
tempElement.innerHTML = `
<div>
<h2 id="aidmi-dialog-title">${Drupal.t(tempETitle)}</h2>
<p id="aidmi-dialog-subtitle" class="aidmi-dialog-subtitle">${Drupal.t(tempESubTitle)}</p>
<textarea id="aidmi-dialog-textarea" class="aidmi-dialog-textarea" rows="6" style="width: 100%;">${Drupal.t(content)}</textarea>
</div>`;
// Use the Drupal off-canvas dialog to show the content.
const options = {
dialogClass: 'aidmi-dialog',
title: tempETitle,
width: '400px',
buttons: [
{
text: Drupal.t('Insert Text'),
click: function () {
// Get the value from the textarea.
aidmiEditedText = document.getElementById('aidmi-dialog-textarea').value;
// Store the element that triggered the dialog, to return focus later.
const previousFocus = document.activeElement;
// Pass the modified text back through the callback.
callback(aidmiEditedText);
// Use the Drupal off-canvas dialog to show the content.
const options = {
dialogClass: 'aidmi-dialog',
title: tempETitle,
width: '400px',
buttons: [
{
text: Drupal.t('Insert Text'),
click: function () {
// Get the value from the textarea.
aidmiEditedText = document.getElementById('aidmi-dialog-textarea').value;
// Close the dialog.
dialogInstance.close();
}
},
{
text: Drupal.t('Cancel'),
click: function () {
// Return false when Cancel is clicked.
callback(false);
// Pass the modified text back through the callback.
callback(aidmiEditedText);
// Close the dialog.
dialogInstance.close();
}
}
],
close: function () {
// Return focus to the original element when the dialog is closed.
previousFocus.focus();
}
};
// Close the dialog.
dialogInstance.close();
}
},
{
text: Drupal.t('Cancel'),
click: function () {
// Return false when Cancel is clicked.
callback(false);
// Open the dialog using Drupal's dialog API.
const dialogInstance = Drupal.dialog(tempElement, options);
dialogInstance.showModal();
// Close the dialog.
dialogInstance.close();
}
}
],
close: function () {
// Return focus to the original element when the dialog is closed.
previousFocus.focus();
}
};
// Set focus on the textarea for screen readers.
const textareaElement = document.getElementById('aidmi-dialog-textarea');
textareaElement.setAttribute('tabindex', '-1');
textareaElement.focus();
// Open the dialog using Drupal's dialog API.
const dialogInstance = Drupal.dialog(tempElement, options);
dialogInstance.showModal();
}
// Set focus on the textarea for screen readers.
const textareaElement = document.getElementById('aidmi-dialog-textarea');
textareaElement.setAttribute('tabindex', '-1');
textareaElement.focus();
};
}
};
})(jQuery, Drupal);
\ No newline at end of file
})(jQuery, Drupal);
This diff is collapsed.
This diff is collapsed.
......@@ -2,13 +2,13 @@
namespace Drupal\aidmi\Controller;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\aidmi\Service\GeminiAiService;
use Drupal\Core\Render\Element;
use Symfony\Component\HttpFoundation\JsonResponse;
use Drupal\file\Entity\File;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Response;
/**
*
*/
class AidmiController {
protected $geminiAiService;
......@@ -16,56 +16,74 @@ class AidmiController {
$this->geminiAiService = $geminiAiService;
}
/**
*
*/
public static function create(ContainerInterface $container): self {
return new static(
$container->get('aidmi.ai_service')
);
}
/**
*
*/
public function analyzeImage(int $fid): Response {
// Set the file ID in drupalSettings so it can be accessed in JavaScript.
$build = [];
$build['#attached']['drupalSettings']['aidmi'] = [
'imageFid' => $fid,
];
$description = $this->geminiAiService->analyzeImage($fid);
// Return a response for the page.
return new Response($description);
}
/**
*
*/
public function analyzeImageAjax(string $uuid): Response {
$fid = $this->getFileIdByUuid($uuid);
if ($fid) {
$description = $this->geminiAiService->analyzeImage($fid);
return new Response($description);
}
return null;
return NULL;
}
/**
*
*/
public function analyzeContentAjax(): Response {
// Get the raw POST data from the request.
$content = \Drupal::request()->request->get('content');
$imagesJSON = \Drupal::request()->request->get('imagesJSON');
$description = null;
$description = NULL;
if (!empty($content)) {
$description = $this->geminiAiService->analyzeContent($content, $imagesJSON);
}
}
return new Response($description);
}
/**
*
*/
public function getFileIdByUuid(string $uuid) {
// Query for the file entity using the UUID.
$file = \Drupal::entityTypeManager()->getStorage('file')->loadByProperties(['uuid' => $uuid]);
if ($file) {
$file = reset($file); // Get the first result.
// Get the first result.
$file = reset($file);
return $file->id();
} else {
return null;
}
else {
return NULL;
}
}
}
<?php
namespace Drupal\aidmi\Form;
use Drupal\Core\Form\ConfigFormBase;
......@@ -40,21 +41,24 @@ class AidmiSettingsForm extends ConfigFormBase {
$file_status_message = '';
$site_root = \Drupal::root();
// Check if the file input method is selected and if the file exists.
if ($api_input_method === 'file_path' && !empty($api_key_file_path)) {
$full_file_path = $site_root . '/' . ltrim($api_key_file_path, '/'); // Ensure no double slashes.
// Ensure no double slashes.
$full_file_path = $site_root . '/' . ltrim($api_key_file_path, '/');
if (file_exists($full_file_path)) {
$file_status_message = $this->t('File found: @path', ['@path' => $full_file_path]);
\Drupal::messenger()->addStatus($file_status_message); // Add as a status message.
// Add as a status message.
\Drupal::messenger()->addStatus($file_status_message);
}
else {
$file_status_message = $this->t('File not found at the provided path: @path', ['@path' => $full_file_path]);
\Drupal::messenger()->addError($file_status_message); // Add as an error message.
// Add as an error message.
\Drupal::messenger()->addError($file_status_message);
}
}
// Radio buttons for API selection
// Radio buttons for API selection.
$form['selected_api'] = [
'#type' => 'radios',
'#title' => $this->t('Select API'),
......@@ -65,7 +69,7 @@ class AidmiSettingsForm extends ConfigFormBase {
'#description' => $this->t('Select which API to use for processing.'),
'#required' => TRUE,
];
// Add a radio button to select the input method.
$form['api_input_method'] = [
'#type' => 'radios',
......@@ -92,9 +96,9 @@ class AidmiSettingsForm extends ConfigFormBase {
'id' => 'api-key-field',
],
];
$recommended_path = $site_root . '/../api_keys/aidmi.key';
$form['api_key_file_path'] = [
'#type' => 'textfield',
'#title' => $this->t('API Key File Path'),
......@@ -118,7 +122,7 @@ class AidmiSettingsForm extends ConfigFormBase {
],
];
// Field for the API instructions
// Field for the API instructions.
$form['api_instructions'] = [
'#type' => 'textarea',
'#title' => $this->t('API Instructions'),
......@@ -135,28 +139,32 @@ class AidmiSettingsForm extends ConfigFormBase {
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
// Prepare the config object for saving.
$config = $this->configFactory()->getEditable('aidmi.settings');
// Get the selected API input method.
$api_input_method = $config->get('api_input_method');
if ($api_input_method === 'file_path') {
// Clear the old API key if the file path is chosen.
$config->set('api_key', '');
} else {
// Save the entered API key if the API key method is chosen.
$config->set('api_key', $form_state->getValue('api_key'));
}
// Save the API input method and the file path (if applicable).
$config->set('api_input_method', $api_input_method)
->set('api_input_method', $form_state->getValue('api_input_method')) // Save selected API Key method
->set('selected_api', $form_state->getValue('selected_api')) // Save selected API
->set('api_key_file_path', $form_state->getValue('api_key_file_path'))
->set('api_instructions', $form_state->getValue('api_instructions'))
->save();
// Prepare the config object for saving.
$config = $this->configFactory()->getEditable('aidmi.settings');
// Get the selected API input method.
$api_input_method = $config->get('api_input_method');
if ($api_input_method === 'file_path') {
// Clear the old API key if the file path is chosen.
$config->set('api_key', '');
}
else {
// Save the entered API key if the API key method is chosen.
$config->set('api_key', $form_state->getValue('api_key'));
}
// Save the API input method and the file path (if applicable).
$config->set('api_input_method', $api_input_method)
// Save selected API Key method.
->set('api_input_method', $form_state->getValue('api_input_method'))
// Save selected API.
->set('selected_api', $form_state->getValue('selected_api'))
->set('api_key_file_path', $form_state->getValue('api_key_file_path'))
->set('api_instructions', $form_state->getValue('api_instructions'))
->save();
parent::submitForm($form, $form_state);
}
}
<?php
declare(strict_types = 1);
namespace Drupal\aidmi\Plugin\CKEditor5Plugin;
use Drupal\ckeditor5\Plugin\CKEditor5PluginDefault;
use Drupal\Core\Url;
use Drupal\editor\EditorInterface;
/**
* Plugin class to add dialog url for embedded content.
*/
class aidmi extends CKEditor5PluginDefault {
/**
* {@inheritdoc}
*/
public function getDynamicPluginConfig(array $static_plugin_config, EditorInterface $editor): array {
$aidmi_dialog_url = Url::fromRoute('ckeditor5_aidmi.dialog')
->toString(TRUE)
->getGeneratedUrl();
$static_plugin_config['aidmi']['dialogURL'] = $aidmi_dialog_url;
$aidmi_preview_url = Url::fromRoute('ckeditor5_aidmi.preview', [
'editor' => $editor->id(),
])
->toString(TRUE)
->getGeneratedUrl();
// $static_plugin_config['aidmi']['previewURL'] = $aidmi_preview_url;
return $static_plugin_config;
}
}
......@@ -2,25 +2,32 @@
namespace Drupal\aidmi\Service;
use Drupal\file\Entity\File; // Correct namespace for Drupal File Entity
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Component\Utility\Html;
use Drupal\media\Entity\Media;
use Drupal\file\Entity\File;
// Correct namespace for Drupal File Entity.
use Drupal\file\FileInterface;
/**
*
*/
class GeminiAiService {
// Gemini Base URL and Header for API Key.
/**
* Gemini Base URL and Header for API Key.
*/
private string $baseUrl = 'https://generativelanguage.googleapis.com';
private string $apiKeyHeader = 'x-goog-api-key';
// Gemini Models.
private string $GeminiPro = 'models/gemini-pro';
private string $GeminiPro10 = 'models/gemini-1.0-pro';
private string $GeminiPro10Latest = 'models/gemini-1.0-pro-latest';
private string $GeminiPro15 = 'models/gemini-1.5-pro';
private string $GeminiPro15Flash = 'models/gemini-1.5-flash';
private string $GeminiProVision = 'models/gemini-pro-vision';
private string $Embedding = 'models/embedding-001';
private string $AQA = 'models/aqa';
/**
* Gemini Models.
*/
private string $geminiPro = 'models/gemini-pro';
private string $geminiPro10 = 'models/gemini-1.0-pro';
private string $geminiPro10Latest = 'models/gemini-1.0-pro-latest';
private string $geminiPro15 = 'models/gemini-1.5-pro';
private string $geminiPro15Flash = 'models/gemini-1.5-flash';
private string $geminiProVision = 'models/gemini-pro-vision';
private string $embedding = 'models/embedding-001';
private string $aqa = 'models/aqa';
/**
* The configuration factory service.
......@@ -49,7 +56,6 @@ class GeminiAiService {
* @var \Drupal\aidmi\Controller\AidmiController
*/
protected $aidmiController;
public function __construct(ConfigFactoryInterface $configFactory) {
$this->configFactory = $configFactory;
......@@ -65,98 +71,115 @@ class GeminiAiService {
if ($apiInputMethod === 'file_path') {
// Get the API key file path from the configuration.
$apiKeyFilePath = $config->get('api_key_file_path');
$siteRoot = \Drupal::root(); // Get the site root.
$fullFilePath = $siteRoot . '/' . ltrim($apiKeyFilePath, '/'); // Full file path.
// Get the site root.
$siteRoot = \Drupal::root();
// Full file path.
$fullFilePath = $siteRoot . '/' . ltrim($apiKeyFilePath, '/');
// Check if the file exists and is readable.
if (file_exists($fullFilePath) && is_readable($fullFilePath)) {
// Read the API key from the file.
$apiKey = trim(file_get_contents($fullFilePath));
} else {
// Log an error and default the API key to an empty string or a default value.
}
else {
// Log an error and default the API key.
\Drupal::logger('aidmi')->error('API key file not found or not readable at @path', ['@path' => $fullFilePath]);
$apiKey = '0'; // Fallback if file is not found or readable.
// Fallback if file is not found or readable.
$apiKey = '0';
}
} else {
// Get the API key from the configuration if the input method is 'api_key'.
}
else {
// Get the API key from the configuration.
$apiKey = $config->get('api_key');
// Default to '0' if the API key is empty.
if (empty($apiKey)) {
$apiKey = '0';
}
}
$this->apiKey = $apiKey;
$this->apiInstructions = $config->get('api_instructions');
}
/**
*
*/
public function analyzeImage(int $fid): string {
try {
$file = File::load($fid);
if (!$file) {
throw new \Exception(t('File not found.'));
}
$imagePath = $file->getFileUri();
$imageContent = file_get_contents($imagePath);
if (!$imageContent) {
throw new \Exception(t('Unable to open image file.'));
}
$output = $this->geminiReviewImage($imagePath);
return $output;
} catch (\Exception $e) {
return 'Error: ' . $e->getMessage();
}
catch (\Exception $e) {
return 'Error: ' . $e->getMessage();
}
}
/**
*
*/
public function analyzeContent(string $content, string $imagesJSON): string {
try {
try {
$output = $this->geminiReviewContent($content, $imagesJSON);
$output = str_replace("```json", "", $output);
$output = str_replace("```", "", $output);
return $output;
} catch (\Exception $e) {
}
catch (\Exception $e) {
return 'Error: ' . $e->getMessage();
}
}
/**
*
*/
public function getFileIdByUuid(string $uuid, string $type) {
$file = null;
$file = NULL;
// If type is drupal-media, load media entity first.
if ($type == 'drupal-media') {
$media = $this->loadMediaByUuid($uuid);
if ($media && $media->bundle() === 'image') {
// Check if the media entity has the 'field_media_image' field and it's not empty.
// Check if the media entity has the field.
if ($media->hasField('field_media_image') && !$media->get('field_media_image')->isEmpty()) {
$file_id = $media->get('field_media_image')->target_id;
// Load the file entity using the file ID.
$file = File::load($file_id);
}
}
} else {
}
else {
// If type is not drupal-media, directly get file entity by UUID.
$files = \Drupal::entityTypeManager()->getStorage('file')->loadByProperties(['uuid' => $uuid]);
if (!empty($files)) {
$file = reset($files);
}
}
// Check if the file is loaded and valid.
if ($file instanceof \Drupal\file\FileInterface) {
if ($file instanceof FileInterface) {
return $file;
}
elseif ($media && $media->bundle() != 'image') {
return null;
return NULL;
}
else {
throw new \Exception(t('File not found for UUID: @uuid', ['@uuid' => $uuid]));
}
}
/**
* Load a media entity by its UUID.
*
......@@ -166,26 +189,28 @@ class GeminiAiService {
* @return \Drupal\media\MediaInterface|null
* The media entity object if found, otherwise null.
*/
function loadMediaByUuid($uuid) {
public function loadMediaByUuid($uuid) {
// Load the media entity using the UUID.
$media_entities = \Drupal::entityTypeManager()
->getStorage('media')
->loadByProperties(['uuid' => $uuid]);
// Check if any media entities are returned.
if (!empty($media_entities)) {
// Get the first media entity from the array.
return reset($media_entities);
}
return null;
return NULL;
}
function geminiReviewImage(string $imagePath):string {
/**
*
*/
public function geminiReviewImage(string $imagePath):string {
// Keeping all models in here for now.
// Connect
$ch = curl_init($this->baseUrl . "/v1/" . $this->GeminiPro15Flash . ":streamGenerateContent");
// Connect.
$ch = curl_init($this->baseUrl . "/v1/" . $this->geminiPro15Flash . ":streamGenerateContent");
// Setup request to send json via POST.
// The text prompt you want to send.
$prompt = $this->apiInstructions;
......@@ -193,43 +218,42 @@ class GeminiAiService {
$imageData = base64_encode(file_get_contents($imagePath));
// Prepare the data payload.
$data = [
'contents' => [
'parts' => [
'contents' => [
'parts' => [
[
'text' => $prompt,
'text' => $prompt,
],
[
'inlineData' => [
'mimeType' => 'image/jpeg',
'data' => $imageData,
]
'mimeType' => 'image/jpeg',
'data' => $imageData,
],
],
],
]
],
],
];
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_HTTPHEADER, array($this->apiKeyHeader .': '. $this->apiKey, 'Content-Type:application/json'));
curl_setopt($ch, CURLOPT_HTTPHEADER, [$this->apiKeyHeader . ': ' . $this->apiKey, 'Content-Type:application/json']);
// Return response instead of printing.
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
// Send request.
$result = curl_exec($ch);
curl_close($ch);
// Decode and handle the response.
$resultData = json_decode($result, true);
//return $result;
$fullDescription = null;
$resultData = json_decode($result, TRUE);
// Return $result;.
$fullDescription = NULL;
// Iterate through each candidate in the response data.
foreach ($resultData as $entry) {
if (isset($entry['candidates'][0]['content']['parts'])) {
// Concatenate all the text parts together.
foreach ($entry['candidates'][0]['content']['parts'] as $part) {
if (isset($part['text'])) {
$fullDescription .= $part['text'];
}
// Concatenate all the text parts together.
foreach ($entry['candidates'][0]['content']['parts'] as $part) {
if (isset($part['text'])) {
$fullDescription .= $part['text'];
}
}
}
}
......@@ -237,90 +261,90 @@ class GeminiAiService {
if (isset($fullDescription)) {
return $fullDescription;
}
else {
else {
return '**Error**';
}
}
function geminiReviewContent(string $content, string $imagesJSON):string {
// Decode JSON string into PHP array
$jsonArray = json_decode($imagesJSON, true);
/**
*
*/
public function geminiReviewContent(string $content, string $imagesJSON):string {
// Decode JSON string into PHP array.
$jsonArray = json_decode($imagesJSON, TRUE);
// Connect
$ch = curl_init($this->baseUrl . "/v1/" . $this->GeminiPro15Flash . ":streamGenerateContent");
// Connect.
$ch = curl_init($this->baseUrl . "/v1/" . $this->geminiPro15Flash . ":streamGenerateContent");
// Setup request to send json via POST.
// The text prompt you want to send.
$prompt = $this->apiInstructions;
$prompt = 'Response can only be in JSON. Do not provide null value if something is empty, just keep it "" blank. Exact JSON output example: [{"images": [{"data-entity-uuid": "", "src": "", "before_alt": "", "recommendation": "", "decorative_image" = ""}]}, {"message": "error or other message if needed"}]. Review this content and describe the image. Actual images from the content are attached and referenced by data-entity-uuid. Provide a json output with each image by data-entity-uuid, image src, the before alt text if available, and your recommendation. If there is a problem, respond with only an error JSON. Provide a true or false for decorative image where just an alt tag alone is decorative, but alt="" is false. ' . $prompt;
$generateContent = [];
$generateContent[] = ['text' => $prompt];
foreach ($jsonArray as $item) {
$imagePath = null;
$imageContent = null;
$imagePath = NULL;
$imageContent = NULL;
// Get file entity by UUID.
$file = $this->getFileIdByUuid($item['data-entity-uuid'], $item['type']);
if ($file instanceof \Drupal\file\FileInterface) {
if ($file instanceof FileInterface) {
$imagePath = \Drupal::service('file_system')->realpath($file->getFileUri());
// Use the file_url_generator service to generate the URL.
$url = \Drupal::service('file_url_generator')->generateAbsoluteString($file->getFileUri());
if (file_exists($imagePath)) { // Check if file path is valid.
// Check if file path is valid.
if (file_exists($imagePath)) {
$imageContent = file_get_contents($imagePath);
}
}
// If there is an image, do it.
if ($imageContent) {
if ($imageContent) {
// Attach images for Gemini AI review.
$generateContent[] = ['text' => "data-entity-uuid: " . $item['data-entity-uuid'] .", src: ". $url];
$generateContent[] = ['text' => "data-entity-uuid: " . $item['data-entity-uuid'] . ", src: " . $url];
$generateContent[] = [
'inlineData' => [
'mimeType' => 'image/jpeg',
'data' => base64_encode($imageContent),
]
];
'mimeType' => 'image/jpeg',
'data' => base64_encode($imageContent),
],
];
}
}
// Read the image file and encode it in base64.
$imageData = base64_encode(file_get_contents($imagePath));
// Prepare the data payload.
$data = [
'contents' => [
'parts' => $generateContent,
]
'contents' => [
'parts' => $generateContent,
],
];
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_HTTPHEADER, array($this->apiKeyHeader .': '. $this->apiKey, 'Content-Type:application/json'));
curl_setopt($ch, CURLOPT_HTTPHEADER, [$this->apiKeyHeader . ': ' . $this->apiKey, 'Content-Type:application/json']);
// Return response instead of printing.
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
// Send request.
$result = curl_exec($ch);
curl_close($ch);
// Decode and handle the response.
$resultData = json_decode($result, true);
//return $result;
$fullDescription = null;
$resultData = json_decode($result, TRUE);
// Return $result;.
$fullDescription = NULL;
// Iterate through each candidate in the response data.
foreach ($resultData as $entry) {
if (isset($entry['candidates'][0]['content']['parts'])) {
// Concatenate all the text parts together.
foreach ($entry['candidates'][0]['content']['parts'] as $part) {
if (isset($part['text'])) {
$fullDescription .= $part['text'];
}
// Concatenate all the text parts together.
foreach ($entry['candidates'][0]['content']['parts'] as $part) {
if (isset($part['text'])) {
$fullDescription .= $part['text'];
}
}
}
}
......@@ -328,9 +352,9 @@ class GeminiAiService {
if (isset($fullDescription)) {
return $fullDescription;
}
else {
else {
return '**Error**';
}
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment