deepseek-1.x-dev/src/Form/SettingsForm.php

src/Form/SettingsForm.php
<?php

namespace Drupal\deepseek\Form;

use Drupal\Component\Plugin\Exception\PluginNotFoundException;
use Drupal\Component\Serialization\Yaml;
use Drupal\Component\Utility\Crypt;
use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Link;
use Drupal\Core\Routing\RouteProviderInterface;
use Drupal\Core\Url;
use Drupal\deepseek\AiDbVectorsPluginManager;
use Drupal\deepseek\AiProvidersPluginManager;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Configure Deepseek settings for this site.
 */
class SettingsForm extends ConfigFormBase {

  /**
   * Constructs a new setting form object.
   */
  public function __construct(
    protected AiProvidersPluginManager $providers,
    protected AiDbVectorsPluginManager $vectors,
    protected RouteProviderInterface $routeProvider,
  ) {
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('plugin.manager.ai_providers'),
      $container->get('plugin.manager.ai_db_vectors'),
      $container->get('router.route_provider'),
    );
  }

  /**
   * {@inheritdoc}
   */
  public function getFormId(): string {
    return 'deepseek_settings';
  }

  /**
   * {@inheritdoc}
   */
  protected function getEditableConfigNames(): array {
    return ['deepseek.settings'];
  }

  /**
   * {@inheritdoc}
   */
  public function buildForm(array $form, FormStateInterface $form_state): array {
    $config = $this->config('deepseek.settings');
    $values = $form_state->getUserInput();
    $form['provider'] = [
      '#type' => 'select',
      '#title' => $this->t('AI Provider'),
      '#value' => $values['provider'] ?? $config->get('provider'),
      '#options' => $this->providers(),
      '#ajax' => [
        'callback' => [$this, 'reloadForm'],
        'wrapper' => $this->getFormId(),
        'event' => 'change',
      ],
      // Wrap the form in a div for AJAX replacement.
    ];
    $form['#prefix'] = '<div id="' . $this->getFormId() . '">';
    $form['#suffix'] = '</div>';
    $api_key = $values['api_key'] ?? $config->get('api_key');
    $form['api_key'] = [
      '#type' => 'textfield',
      '#title' => $this->t('API key'),
      '#default_value' => $api_key,
    ];
    // Determine base_url.
    $provider = $values['provider'] ?? $config->get('provider');
    $defs = $this->getDefinitions($provider);
    $url = !empty($config->get('base_url')) ? $config->get('base_url') : $defs['url'];
    $model = !empty($config->get('model')) ? $config->get('model') : $defs['model'];
    $embedding = !empty($config->get('embedding')) ? $config->get('embedding') : $defs['embedding'];
    if (!empty($provider) && (empty($url) || $provider != 'self_host' ||  ($values && $values['provider'] != $config->get('provider')))) {
      $url = $defs['url'];
    }
    $form['base_url'] = [
      '#type' => 'url',
      '#title' => $this->t('URL'),
      '#default_value' => $url,
      '#ajax' => [
        'callback' => [$this, 'reloadForm'],
        'wrapper' => $this->getFormId(),
        'event' => 'change',
      ],
      '#description' => implode('<br/>', [
        $this->t('You can set/replace the URL server %url', ['%url' => $defs['url'] ?? '']),
      ]),
    ];
    $form['load_model'] = [
      '#type' => 'checkbox',
      '#title' => $this->t('Click to load all available models'),
      '#description' => $this->t('Make sure you have good url and api key'),
      '#ajax' => [
        'callback' => [$this, 'reloadForm'],
        'wrapper' => $this->getFormId(),
        'options' => ['query' => ['ajax_form' => 1]],
        'event' => 'click',
      ],
      '#attributes' => ['type' => 'button'],
    ];

    $models = [];
    if (!empty($form_state->getValue('load_model'))) {
      $models = $this->loadModel($provider, $url, $api_key);
    }
    if (empty($models)) {
      $form['model'] = [
        '#type' => 'textfield',
        '#title' => $this->t("Set model's API identifier"),
        '#default_value' => $model,
        '#description' => [
          '#markup' => $this->t('Example: %model', ['%model' => $defs['model'] ?? '']),
        ],
      ];
    }
    else {
      $form['model'] = [
        '#type' => 'select',
        '#title' => $this->t("This model's API identifier"),
        '#default_value' => $model,
        '#options' => $models,
        '#empty_option' => $this->t('- Select -'),
      ];
    }
    if (empty($models)) {
      $form['embedding'] = [
        '#type' => 'textfield',
        '#title' => $this->t("RAG This model's embedding"),
        '#default_value' => $embedding,
        '#description' => [
          '#markup' => implode('<br/>', [
            $this->t('Example: %model', ['%model' => $defs['embedding'] ?? '']),
            $this->t('Leave blank if you do not want integration'),
          ]),
        ],
      ];
    }
    else {
      $form['embedding'] = [
        '#type' => 'select',
        '#title' => $this->t("RAG This model's embedding"),
        '#default_value' => $embedding,
        '#options' => $models,
        '#empty_option' => $this->t('- Select -'),
        '#description' => $this->t('Leave blank if you do not want integration'),
      ];
    }
    $dbVectors = $this->dbVectors();
    $dbVector = $values['db_vector'] ?? $config->get('db_vector');
    $description = [$this->t('Leave blank if you do not want integration')];
    $routes = $this->routeProvider->getRoutesByNames(['view.ai.embedding']);
    if (!empty($routes)) {
      $url = Url::fromRoute('view.ai.embedding');
      $linkEmbedding = Link::fromTextAndUrl($this->t('content'), $url)->toString();
    }
    else {
      $linkEmbedding = $this->t('by active VBO with action AI embedding');
    }
    $description[] = $this->t(
      'Embedding manual @embedding', [
        '@embedding' => $linkEmbedding,
      ]
    );
    $routes = $this->routeProvider->getRoutesByNames(['ai_auto_embedding.settings']);
    if (!empty($routes)) {
      $url = Url::fromRoute('ai_auto_embedding.settings');
      $linkEmbedding = Link::fromTextAndUrl($this->t('Ai auto RAG embedding'), $url)->toString();
    }
    else {
      $linkEmbedding = $this->t('by active module auto AI embedding');
    }
    $description[] = $this->t(
      'Active @embedding', [
        '@embedding' => $linkEmbedding,
      ]
    );
    $form['database_vector'] = [
      '#type' => 'details',
      '#title' => $this->t('RAG Database Vector Settings'),
      '#description' => !empty($dbVector) ? $this->getDbVectorDefinitions($dbVector)['description'] : '',
      'db_vector' => [
        '#type' => 'select',
        '#title' => $this->t("Database for embedding"),
        '#default_value' => $dbVector,
        '#options' => $dbVectors,
        '#empty_option' => $this->t('- Select -'),
        '#description' => implode('. ', $description),
        '#ajax' => [
          'callback' => [$this, 'reloadForm'],
          'wrapper' => $this->getFormId(),
          'event' => 'change',
        ],
      ],
    ];
    $defDbVectors = $this->vectors->getDefinitions();
    $url = $defDbVectors[$dbVector]['url'] ?? '';
    $port = $defDbVectors[$dbVector]['port'] ?? '';
    $visible = [
      [':input[name="db_vector"]' => ['!value' => '']],
    ];
    $invisible = [
      [':input[name="db_vector"]' => ['value' => 'mysql']],
      [':input[name="db_vector"]' => ['value' => 'mariadb']],
      [':input[name="db_vector"]' => ['value' => 'pgsql']],
    ];
    $states = [
      'visible' => $visible,
      'invisible' => $invisible,
    ];
    $form['database_vector']['url'] = [
      '#type' => 'url',
      '#title' => $this->t('URL'),
      '#default_value' => $config->get('url'),
      '#description' => $url,
      '#states' => $states,
    ];

    $form['database_vector']['username'] = [
      '#type' => 'textfield',
      '#title' => $this->t('Username'),
      '#default_value' => $config->get('username'),
      '#states' => $states,
    ];

    $form['database_vector']['password'] = [
      '#type' => 'password',
      '#title' => $this->t('Password'),
      '#default_value' => $config->get('password'),
      '#states' => $states,
    ];

    $form['database_vector']['token'] = [
      '#type' => 'textfield',
      '#title' => $this->t('Token/API Key'),
      '#default_value' => $config->get('token'),
      '#states' => $states,
    ];

    $form['database_vector']['db_name'] = [
      '#type' => 'textfield',
      '#title' => $this->t('Database name'),
      '#default_value' => $config->get('db_name'),
      '#states' => $states,
    ];

    $form['database_vector']['table_name'] = [
      '#type' => 'textfield',
      '#title' => $this->t('Table / Collection name'),
      '#description' => $this->t('Lock to name @name', ['@name' => 'ai_embedding']),
      '#default_value' => $config->get('table_name') ?? 'ai_embedding',
      '#states' => $states,
      '#attributes' => [
        'readonly' => TRUE,
      ],
    ];

    $form['database_vector']['port'] = [
      '#type' => 'number',
      '#title' => $this->t('Port'),
      '#description' => $port,
      '#default_value' => $config->get('port'),
      '#states' => $states,
    ];

    $form['database_vector']['dimension'] = [
      '#type' => 'number',
      '#title' => $this->t('Vector dimensionality'),
      '#default_value' => $config->get('dimension'),
      '#description' => $this->t('Default dimension is 768. Some models output higher dimensions (e.g., 1024 or 3072 for Gemini). Make sure your database supports the selected size.'),
    ];

    $form['database_vector']['threshold'] = [
      '#type' => 'number',
      '#title' => $this->t('Threshold'),
      '#default_value' => $config->get('threshold'),
      '#description' => $this->t('Threshold index to get result.'),
    ];

    $form['system'] = [
      '#type' => 'details',
      '#title' => $this->t('Role system set prompt'),
      'system_prompt' => [
        '#type' => 'textarea',
        '#title' => $this->t("System prompt"),
        '#default_value' => $config->get('system_prompt'),
        '#description' => $this->t('It must return markdown format.'),
      ],
    ];
    $form['mcp'] = [
      '#type' => 'details',
      '#title' => $this->t('Model context protocol Settings'),
      '#description' => implode(' ', [
        $this->t('Add url to config, Your mcp server:'),
        Url::fromRoute('deepseek.mcp_server', [], ['absolute' => TRUE])->toString(),
      ]),
      'mcp_key' => [
        '#type' => 'textfield',
        '#title' => $this->t("MCP API key"),
        '#default_value' => !empty($config->get('mcp_key')) ? $config->get('mcp_key') : Crypt::randomBytesBase64(20),
        '#description' => implode(' ', [
          $this->t('Leave blank if you open your mcp server publicly.'),
          $this->t('Add to header "X-API-Key":'),
          '"' . $config->get('mcp_key') . '"',
        ]),
      ],
    ];

    $prompts = $config->get('prompts') ?? '';
    $form['prompts'] = [
      '#type' => 'textarea',
      '#title' => $this->t('Prompts ckeditor example'),
      '#description' => $this->t('Yaml format. Provides prompt terms. Array must have id, title, prompt, role'),
      '#default_value' => is_array($prompts) ? Yaml::encode($prompts) : $prompts,
      '#prefix' => '<div id="' . $this->getFormId() . '-list">',
      '#suffix' => '</div>',
      '#attributes' => [
        'class' => ['yaml-editor'],
      ],
    ];
    $form['voice'] = [
      '#type' => 'select',
      '#title' => $this->t('Language for voice chat'),
      '#default_value' => $config->get('voice'),
      '#options' => $this->languages(),
    ];
    $form['bootstrap'] = [
      '#type' => 'checkbox',
      '#title' => $this->t('Use bootstrap 5'),
      '#default_value' => $config->get('bootstrap'),
      '#description' => $this->t("Do not select if you are using bootstrap 5 theme"),
    ];

    $form['#attached']['library'][] = 'deepseek/yaml_editor';

    return parent::buildForm($form, $form_state);
  }

  /**
   * AJAX callback to reload the form.
   */
  public function reloadForm(array &$form, FormStateInterface $form_state) {
    return $form;
  }

  /**
   * Loads model options from the API.
   */
  private function loadModel($provider, $url = '', $apiKey = '') {
    $options = [];
    try {
      $plugin = $this->providers->createInstance($provider);
      $models = $plugin->models($url, $apiKey);
      foreach ($models as $result) {
        if (is_object($result) && !empty($result->id)) {
          $options[$result->id] = $result->name ?? $result->id;
        }
        elseif (is_array($result)) {
          $options[$result['id']] = $result['name'] ?? $result['id'];
        }
      }
    }
    catch (PluginNotFoundException $e) {

    }
    return $options;
  }

  /**
   * {@inheritdoc}
   */
  protected function providers() {
    $definitions = $this->providers->getDefinitions();
    $provides = [];
    foreach ($definitions as $definition) {
      $provides[$definition['id']] = $definition['label'];
    }
    return $provides;
  }

  /**
   * {@inheritdoc}
   */
  protected function getDefinitions($provider) {
    $definitions = $this->providers->getDefinitions();
    return $definitions[$provider] ?? [];
  }

  /**
   * {@inheritdoc}
   */
  protected function dbVectors() {
    $definitions = $this->vectors->getDefinitions();
    $vectors = [];
    foreach ($definitions as $definition) {
      $vectors[$definition['id']] = $definition['label'];
    }
    return $vectors;
  }

  /**
   * {@inheritdoc}
   */
  protected function getDbVectorDefinitions($dbVector) {
    $definitions = $this->vectors->getDefinitions();
    return $definitions[$dbVector] ?? [];
  }

  /**
   * {@inheritdoc}
   */
  public function submitForm(array &$form, FormStateInterface $form_state): void {
    $db_vector = $form_state->getValue('db_vector');
    $this->config('deepseek.settings')
      ->set('base_url', $form_state->getValue('base_url'))
      ->set('api_key', $form_state->getValue('api_key'))
      ->set('prompts', $form_state->getValue('prompts'))
      ->set('provider', $form_state->getValue('provider'))
      ->set('embedding', $form_state->getValue('embedding'))
      ->set('bootstrap', $form_state->getValue('bootstrap'))
      ->set('voice', $form_state->getValue('voice'))
      ->set('model', $form_state->getValue('model'))
      ->set('url', $form_state->getValue('url'))
      ->set('port', $form_state->getValue('port'))
      ->set('token', $form_state->getValue('token'))
      ->set('dimension', $form_state->getValue('dimension'))
      ->set('threshold', $form_state->getValue('threshold'))
      ->set('username', $form_state->getValue('username'))
      ->set('password', $form_state->getValue('password'))
      ->set('db_name', $form_state->getValue('db_name'))
      ->set('table_name', $form_state->getValue('table_name'))
      ->set('system_prompt', $form_state->getValue('system_prompt'))
      ->set('mcp_key', $form_state->getValue('mcp_key'))
      ->set('db_vector', $db_vector)
      ->save();
    parent::submitForm($form, $form_state);
    if (!empty($db_vector)) {
      $dbVector = $this->vectors->createInstance($db_vector);
      if ($dbVector && !empty($form_state->getValue('table_name'))) {
        $dbVector->createTable($form_state->getValue('table_name'), $form_state->getValue('dimension'));
      }
    }
  }

  /**
   * {@inheritdoc}
   */
  private function languages() {
    return [
      'en-US' => 'English',
      'fr-FR' => 'Français',
      'it-IT' => 'Italiano',
      'es-ES' => 'Español',
      'ca-ES' => 'Català',
      'gl-ES' => 'Galego',
      'pt-PT' => 'Português',
      'pt-BR' => 'Brasil',
      'vi-VN' => 'Tiếng Việt',
      'af-ZA' => 'Afrikaans',
      'bs-BA' => 'Bosanski',
      'id-ID' => 'Bahasa',
      'jv-ID' => 'Basa Java',
      'cy-GB' => 'Cymraeg',
      'da-DK' => 'Dansk',
      'de-DE' => 'Deutsch',
      'et-EE' => 'Eesti',
      'eu-ES' => 'Euskera',
      'fa-IR' => 'Farsi',
      'fil-PH' => 'Filipino',
      'ga-IE' => 'Gaeilge',
      'hr-HR' => 'Hrvatski',
      'sw-KE' => 'Kiswahili',
      'kk-KZ' => 'Қазақ',
      'lt-LT' => 'Lietuvių',
      'lv-LV' => 'Latviešu',
      'mt-MT' => 'Malti',
      'hu-HU' => 'Magyar',
      'nl-NL' => 'Nederlands',
      'uz-UZ' => 'Oʻzbekcha',
      'pl-PL' => 'Polski',
      'sq-AL' => 'Shqip',
      'sv-SE' => 'Svenska',
      'fi-FI' => 'Suomi',
      'cs-CZ' => 'Čeština',
      'is-IS' => 'Íslenska',
      'ro-RO' => 'Română',
      'tr-TR' => 'Türkçe',
      'sk-SK' => 'Slovenčina',
      'el-GR' => 'Ελληνικά',
      'az-AZ' => 'Azərbaycanca',
      'bg-BG' => 'Български',
      'sr-RS' => 'Српски',
      'mk-MK' => 'Македонски',
      'mn-MN' => 'монгол',
      'ru-RU' => 'Русский',
      'uk-UA' => 'Українська',
      'ja-JP' => '日本語',
      'ko-KR' => '한국어',
      'th-TH' => 'ไทย',
      'km-KH' => 'ភាសាខ្មែរ',
      'lo-LA' => 'ພາສາລາວ',
      'ar-SA' => 'العربية',
      'ms-MY' => 'بهاس ملايو',
      'ps-AF' => 'پښتو',
      'am-ET' => 'አማርኛ',
      'bn-IN' => 'বাংলা',
      'he-IL' => 'עברית',
      'hy-AM' => 'Հայերեն',
      'ka-GE' => 'ქართული ენა',
      'my-MM' => 'ဗမာစကား',
      'si-LK' => 'සිංහල',
      'gu-IN' => 'ગુજરાતી',
      'hi-IN' => 'हिन्दी',
      'kn-IN' => 'ಕನ್ನಡ',
      'ml-IN' => 'മലയാളം',
      'mr-IN' => 'मराठी',
      'or-IN' => 'ଓଡିଆ',
      'pa-IN' => 'ਪੰਜਾਬੀ',
      'ta-IN' => 'தமிழ்',
      'te-IN' => 'తెలుగు',
      'ur-IN' => 'اردو',
      'ne-NP' => 'नेपाली',
      'zh-CN' => '中文',
      'zh-HK' => '中文 (香港)',
      'zh-TW' => '繁體中文',
    ];
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc