Commit ae313bb6 authored by Eleo Basili's avatar Eleo Basili
Browse files

Issue #3281814 by eleonel, tanashin-kishimoto, naveenvalecha: Add confirmation...

Issue #3281814 by eleonel, tanashin-kishimoto, naveenvalecha: Add confirmation block when update or rebuild the model
parent 960643ce
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -21,3 +21,11 @@ quickchat_sync.operation:
    _form: '\Drupal\quickchat_sync\Form\OperationForm'
  requirements:
    _permission: 'quickchat kb manage'

quickchat_sync.modal_form:
  path: '/admin/content/kb/{model_name}/operations/{operation}/modal'
  defaults:
    _title: 'Quickchat Sync'
    _controller: '\Drupal\quickchat_sync\Controller\ModalFormController::openModalForm'
  requirements:
    _permission: 'quickchat kb manage'
+72 −0
Original line number Diff line number Diff line
<?php

namespace Drupal\quickchat_sync\Controller;

use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\OpenModalDialogCommand;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Form\FormBuilder;

/**
 * Controller to provide a route so we can insert a modal form in an Ajax call.
 */
class ModalFormController extends ControllerBase {

  /**
   * The form builder.
   *
   * @var \Drupal\Core\Form\FormBuilder
   */
  protected $formBuilder;

  /**
   * The ModalFormController constructor.
   *
   * @param \Drupal\Core\Form\FormBuilder $formBuilder
   *   The form builder.
   */
  public function __construct(FormBuilder $formBuilder) {
    $this->formBuilder = $formBuilder;
  }

  /**
   * {@inheritdoc}
   *
   * @param \Symfony\Component\DependencyInjection\ContainerInterface $container
   *   The Drupal service container.
   *
   * @return static
   */
  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('form_builder')
    );
  }

  /**
   * Callback for opening the modal form.
   */
  public function openModalForm($model_name, $operation) {
    $response = new AjaxResponse();
    $title = 'Are you sure you want to ' . $operation . ' the model?';

    if ($operation == 'update') {
      $title .= ' This will overwrite the knowledge base at Quickchat AI.';
      $operation = 'update_knowledge_base';
    }

    // Get the modal form using the form builder.
    $modal_form = $this->formBuilder->getForm('Drupal\quickchat_sync\Form\ModalForm', $model_name, $operation);

    // Add an AJAX command to open a modal dialog with the form as the content.
    $response->addCommand(new OpenModalDialogCommand(
      $title,
      $modal_form,
      ['width' => '900']
    ));

    return $response;
  }

}
+162 −0
Original line number Diff line number Diff line
<?php

namespace Drupal\quickchat_sync\Form;

use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\OpenModalDialogCommand;
use Drupal\Core\Ajax\ReplaceCommand;
use Drupal\quickchat_sync\QuickchatSyncApiClient;
use Drupal\Core\Config\ConfigFactoryInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Modal form to handle the quickchat sync operations.
 */
class ModalForm extends FormBase {
  /**
   * The configuration factory.
   *
   * @var \Drupal\Core\Config\ConfigFactoryInterface
   */
  protected $configFactory;

  /**
   * The quickchat_sync.api_client service.
   *
   * @var \Drupal\quickchat_sync\QuickchatSyncApiClient
   */
  protected $quickchatSyncApiClient;

  /**
   * {@inheritdoc}
   */
  public function __construct(ConfigFactoryInterface $config_factory, QuickchatSyncApiClient $quickchat_api_client) {
    $this->configFactory = $config_factory;
    $this->quickchatSyncApiClient = $quickchat_api_client;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('config.factory'),
      $container->get('quickchat_sync.api_client')
    );
  }

  /**
   * {@inheritdoc}
   */
  public function getFormId() {
    return 'quickchat_operation_modal_form';
  }

  /**
   * {@inheritdoc}
   */
  protected function getEditableConfigNames() {}

  /**
   * {@inheritdoc}
   */
  public function buildForm(array $form, FormStateInterface $form_state, $model_name = NULL, $operation = NULL) {
    $form['#prefix'] = '<div id="quickchat_operation_modal">';
    $form['#suffix'] = '</div>';

    $form['status_messages'] = [
      '#type' => 'status_messages',
      '#weight' => -10,
    ];

    $form['operation'] = [
      '#type' => 'hidden',
      '#value' => $operation,
    ];

    $form['model_name'] = [
      '#type' => 'hidden',
      '#value' => $model_name,
    ];

    $form['actions'] = ['#type' => 'actions'];
    $form['actions']['save'] = [
      '#type' => 'submit',
      '#value' => $this->t('Confirm'),
      '#attributes' => [
        'class' => [
          'use-ajax',
        ],
      ],
      '#ajax' => [
        'callback' => [$this, 'submitModalFormAjax'],
        'event' => 'click',
      ],
    ];

    $form['#attached']['library'][] = 'core/drupal.dialog.ajax';

    return $form;
  }

  /**
   * AJAX callback handler that displays any errors or a success message.
   */
  public function submitModalFormAjax(array $form, FormStateInterface $form_state) {
    $response = new AjaxResponse();

    if ($form_state->hasAnyErrors()) {
      $response->addCommand(new ReplaceCommand('#quickchat_operation_modal', $form));
    }
    else {
      $response->addCommand(new OpenModalDialogCommand(
        "Operation completed",
        "The knowledge base has been synced on Quickchat AI.", [
          'width' => 400,
          'height' => 100,
        ])
      );
    }

    return $response;
  }

  /**
   * {@inheritdoc}
   */
  public function validateForm(array &$form, FormStateInterface $form_state) {}

  /**
   * {@inheritdoc}
   */
  public function submitForm(array &$form, FormStateInterface $form_state) {
    if ($operation = $form_state->getValue('operation')) {
      $config = $this->configFactory->get('quickchat_sync.settings');
      $sync = $config->get('sync');
      $model_name = $form_state->getValue('model_name');

      foreach ($sync as $model_id => $properties) {
        if ($properties['label_machine_name'] === $model_name) {
          break;
        }
      }

      $scenario_id = $sync[$model_id]['scenario_id'];
      $token = $sync[$model_id]['token'];

      if (array_key_exists('view_arguments', $sync[$model_id])) {
        $arguments = $sync[$model_id]['view_arguments'];
      }

      $view_name = $sync[$model_id]['view_name'];
      $view_display = $sync[$model_id]['view_display'];
      $view_arguments = explode(',', $arguments);
      $text = $this->quickchatSyncApiClient->getViewHtml($view_name, $view_display, $view_arguments);

      $response = $this->quickchatSyncApiClient->sync($operation, $scenario_id, $token, $text);
    }
  }

}
+26 −168
Original line number Diff line number Diff line
@@ -4,7 +4,6 @@ namespace Drupal\quickchat_sync\Form;

use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\views\Views;
use Drupal\quickchat_sync\QuickchatSyncApiClient;
use Drupal\Core\Config\ConfigFactoryInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -114,7 +113,7 @@ class OperationForm extends FormBase {
    $view_name = $sync[$model_id]['view_name'];
    $view_display = $sync[$model_id]['view_display'];
    $view_arguments = explode(',', $arguments);
    $preview = $this->getViewHtml($view_name, $view_display, $view_arguments);
    $preview = $this->quickchatSyncApiClient->getViewHtml($view_name, $view_display, $view_arguments);
    $preview = array_filter(explode('\n', $preview));
    $preview = $this->generateHtmlList($preview);

@@ -132,58 +131,44 @@ class OperationForm extends FormBase {
    }

    $form[$model_id]['update'] = [
      '#type' => 'submit',
      '#value' => $this->t('Update model'),
      '#name' => $model_id . '_update',
      '#submit' => ['::updateCallback'],
      '#ajax' => [
        'callback' => '::ajaxSycCallback',
        'wrapper' => 'models-fieldset-wrapper',
      '#type' => 'link',
      '#title' => $this->t('Update'),
      '#url' => Url::fromRoute('quickchat_sync.modal_form', [
        'model_name' => $model_name,
        'operation' => 'update',
      ]),
      '#attributes' => [
        'class' => [
          'use-ajax',
          'button',
        ],
      ],
    ];

    $form[$model_id]['rebuild'] = [
      '#type' => 'submit',
      '#value' => $this->t('Rebuild model'),
      '#name' => $model_id . '_rebuild',
      '#submit' => ['::rebuildCallback'],
      '#ajax' => [
        'callback' => '::ajaxSycCallback',
        'wrapper' => 'models-fieldset-wrapper',
      '#type' => 'link',
      '#title' => $this->t('Rebuild'),
      '#url' => Url::fromRoute('quickchat_sync.modal_form', [
        'model_name' => $model_name,
        'operation' => 'retrain',
      ]),
      '#attributes' => [
        'class' => [
          'use-ajax',
          'button',
        ],
      ],
    ];

    // Attach the library for pop-up dialogs/modals.
    $form['#attached']['library'][] = 'core/drupal.dialog.ajax';

    $form[$model_id]['preview'] = [
      '#type' => 'item',
      '#title' => $this->t('Preview'),
      '#markup' => $preview,
    ];

    $form[$model_id]['scenario_id'] = [
      '#type' => 'hidden',
      '#value' => $sync[$model_id]['scenario_id'],
    ];

    $form[$model_id]['token'] = [
      '#type' => 'hidden',
      '#value' => $sync[$model_id]['token'],
    ];

    $form[$model_id]['view_name'] = [
      '#type' => 'hidden',
      '#value' => $sync[$model_id]['view_name'],
    ];

    $form[$model_id]['view_display'] = [
      '#type' => 'hidden',
      '#value' => $sync[$model_id]['view_display'],
    ];

    $form[$model_id]['view_arguments'] = [
      '#type' => 'hidden',
      '#value' => $sync[$model_id]['view_arguments'],
    ];

    return $form;
  }

@@ -192,133 +177,6 @@ class OperationForm extends FormBase {
   */
  public function submitForm(array &$form, FormStateInterface $form_state) {}

  /**
   * Callback for both ajax-enabled buttons.
   *
   * Selects and returns the fieldset with the models sync in it.
   */
  public function ajaxSycCallback(array &$form, FormStateInterface $form_state) {
    return $form;
  }

  /**
   * Submit handler for the "rebuid" button.
   *
   * Rebuilds the corresponding line.
   */
  public function rebuildCallback(array &$form, FormStateInterface $form_state) {
    $trigger = $form_state->getTriggeringElement();
    $indexToRebuild = intval(strtok($trigger['#name'], '_rebuild'));

    if (array_key_exists('confirm', $form[$indexToRebuild])) {
      if ($form[$indexToRebuild]['confirm']['#value']) {
        $config = $this->configFactory->get('quickchat_sync.settings');
        $sync = $config->get('sync');
        $scenario_id = $form[$indexToRebuild]['scenario_id']['#value'];
        $token = $form[$indexToRebuild]['token']['#value'];
        $response = $this->executeOperation('retrain', $scenario_id, $token);

        if ($response) {
          if ($response->getStatusCode() == 200) {
            $this->messenger()->addMessage(
              $this->t('The knowledge base has been rebuilt on Quickchat AI.')
            );
          }
          else {
            $this->messenger()->addError($this->t('@error', [
              '@error' => $response->getReasonPhrase(),
            ]));
          }
        }
        $form_state->set('confirm', FALSE);
      }
    }
    else {
      $form_state->set('confirm', 'rebuild');
    }

    $form_state->setRebuild();
  }

  /**
   * Submit handler for the "update" button.
   *
   * Update the corresponding line.
   */
  public function updateCallback(array &$form, FormStateInterface $form_state) {
    $trigger = $form_state->getTriggeringElement();
    $indexToRebuild = intval(strtok($trigger['#name'], '_rebuild'));
    if (array_key_exists('confirm', $form[$indexToRebuild])) {
      if ($form[$indexToRebuild]['confirm']['#value']) {
        $config = $this->configFactory->get('quickchat_sync.settings');
        $sync = $config->get('sync');
        $scenario_id = $form[$indexToRebuild]['scenario_id']['#value'];
        $token = $form[$indexToRebuild]['token']['#value'];
        $view_name = $form[$indexToRebuild]['view_name']['#value'];
        $view_display = $form[$indexToRebuild]['view_display']['#value'];
        $view_arguments = [];
        $args = str_replace(' ', '', $form[$indexToRebuild]['view_arguments']['#value']);

        if ($args) {
          $view_arguments = explode(',', $args);
        }

        $html = $this->getViewHtml($view_name, $view_display, $view_arguments);
        $response = $this->executeOperation('update_knowledge_base', $scenario_id, $token, $html);

        if ($response) {
          $this->messenger()->deleteAll();
          if ($response->getStatusCode() == 200) {
            $this->messenger()->addMessage($this->t('The knowledge base has been updated on Quickchat AI.'));
          }
          else {
            $this->messenger()->addError($this->t('@error', [
              '@error' => $response->getReasonPhrase(),
            ]));
          }
        }
      }
    }
    else {
      $form_state->set('confirm', 'update');
    }

    $form_state->setRebuild();
  }

  /**
   * Helper function to get the render HTML from a given view.
   */
  private function getViewHtml($view_name, $view_display, $arguments = []) {
    $html = '';

    if ($view = Views::getView($view_name)) {
      $build = $view->buildRenderable($view_display, $arguments);
      $html = strip_tags(\Drupal::service('renderer')->renderPlain($build));
      $html = preg_replace('/\r|\n/', '', trim($html));
    }

    return rtrim($html);
  }

  /**
   * Helper to call the Quickchat API to train the given chatbot model.
   *
   * @param string $action
   *   Action to perform: fetch, retrain, update_name, update_short_description,
   *   update_knowledge_base, update_how_to_reach_you.
   * @param string $scenario_id
   *   ID associated with your Quickchat API implementation.
   *   update_knowledge_base, update_how_to_reach_you.
   * @param string $token
   *   Token to use for API limited access.
   * @param string $text
   *   Optional parameter, only to be provided for update actions.
   */
  private function executeOperation($action, $scenario_id, $token, $text = '') {
    return $this->quickchatSyncApiClient->drupal($action, $scenario_id, $token, $text);
  }

  /**
   * Helper to generate a HTML list using given array.
   *
+26 −2
Original line number Diff line number Diff line
@@ -4,6 +4,7 @@ namespace Drupal\quickchat_sync;

use Drupal\quickchat\QuickchatApiClient;
use Drupal\Component\Utility\Html;
use Drupal\views\Views;

/**
 * Quickchat Sync API client extending QuickchatApiClient base class.
@@ -29,7 +30,7 @@ class QuickchatSyncApiClient extends QuickchatApiClient {
   *
   * @see https://www.quickchat.ai/docs/#send-message-to-ai
   */
  public function drupal($action, $scenario_id, $token, $text = NULL) {
  public function sync($action, $scenario_id, $token, $text = NULL) {
    $data = [
      'token' => $token,
      'action' => $action,
@@ -44,7 +45,15 @@ class QuickchatSyncApiClient extends QuickchatApiClient {
      }
    }

    return $this->request('POST', '/drupal/', $data);
    if ($response = $this->request('POST', '/drupal/', $data)) {
      if ($response->getStatusCode() != 200) {
        $this->messenger->addError($this->t('@error', [
          '@error' => $response->getReasonPhrase(),
        ]));
      }

      return $response;
    }
  }

  /**
@@ -63,4 +72,19 @@ class QuickchatSyncApiClient extends QuickchatApiClient {
    return $value;
  }

  /**
   * Helper function to get the render HTML from a given view.
   */
  public function getViewHtml($view_name, $view_display, $arguments = []) {
    $html = '';

    if ($view = Views::getView($view_name)) {
      $build = $view->buildRenderable($view_display, $arguments);
      $html = strip_tags(\Drupal::service('renderer')->renderPlain($build));
      $html = preg_replace('/\r|\n/', '', trim($html));
    }

    return rtrim($html);
  }

}
Loading