ai_upgrade_assistant-0.2.0-alpha2/src/Service/HuggingFaceService.php

src/Service/HuggingFaceService.php
<?php

namespace Drupal\ai_upgrade_assistant\Service;

use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;
use Drupal\Core\State\StateInterface;
use GuzzleHttp\ClientInterface;
use GuzzleHttp\Exception\GuzzleException;
use Drupal\Core\DependencyInjection\DependencySerializationTrait;

/**
 * Service for interacting with HuggingFace's Hub.
 */
class HuggingFaceService {

  use DependencySerializationTrait;

  /**
   * The config factory.
   *
   * @var \Drupal\Core\Config\ConfigFactoryInterface
   */
  protected $configFactory;

  /**
   * The HTTP client.
   *
   * @var \GuzzleHttp\ClientInterface
   */
  protected $httpClient;

  /**
   * The logger factory.
   *
   * @var \Drupal\Core\Logger\LoggerChannelFactoryInterface
   */
  protected $loggerFactory;

  /**
   * The state service.
   *
   * @var \Drupal\Core\State\StateInterface
   */
  protected $state;

  /**
   * The subscription manager.
   *
   * @var \Drupal\ai_upgrade_assistant\Service\SubscriptionManager
   */
  protected $subscriptionManager;

  /**
   * The HuggingFace API base URL.
   *
   * @var string
   */
  protected const API_BASE_URL = 'https://huggingface.co/api';

  /**
   * The inference API base URL.
   *
   * @var string
   */
  protected const INFERENCE_API_URL = 'https://api-inference.huggingface.co/models';

  /**
   * The organization name.
   *
   * @var string
   */
  protected const ORGANIZATION = 'thronedigital';

  /**
   * The dataset repository name.
   *
   * @var string
   */
  protected const DATASET_REPO = 'thronedigital/drupal-upgrade-patterns';

  /**
   * Available code models.
   *
   * @var array
   */
  protected const CODE_MODELS = [
    'Salesforce/codet5-base' => [
      'name' => 'CodeT5',
      'description' => 'Open source code-aware encoder-decoder model',
      'type' => 'encoder-decoder',
      'license' => 'BSD-3-Clause',
    ],
    'bigcode/starcoder' => [
      'name' => 'StarCoder',
      'description' => 'Open source code model trained on public code',
      'type' => 'decoder',
      'license' => 'Apache-2.0',
    ],
    'codellama/codellama-7b' => [
      'name' => 'Code Llama',
      'description' => 'Meta\'s open source code model',
      'type' => 'decoder',
      'license' => 'LLAMA 2',
    ],
  ];

  /**
   * Constructs a new HuggingFaceService.
   *
   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
   *   The config factory.
   * @param \GuzzleHttp\ClientInterface $http_client
   *   The HTTP client.
   * @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $logger_factory
   *   The logger factory.
   * @param \Drupal\Core\State\StateInterface $state
   *   The state service.
   * @param \Drupal\ai_upgrade_assistant\Service\SubscriptionManager $subscription_manager
   *   The subscription manager.
   */
  public function __construct(
    ConfigFactoryInterface $config_factory,
    ClientInterface $http_client,
    LoggerChannelFactoryInterface $logger_factory,
    StateInterface $state,
    SubscriptionManager $subscription_manager
  ) {
    $this->configFactory = $config_factory;
    $this->httpClient = $http_client;
    $this->loggerFactory = $logger_factory->get('ai_upgrade_assistant');
    $this->state = $state;
    $this->subscriptionManager = $subscription_manager;
  }

  /**
   * Gets the HuggingFace API token from state.
   *
   * @return string|null
   *   The API token if configured, NULL otherwise.
   */
  protected function getApiToken() {
    return $this->state->get('ai_upgrade_assistant.huggingface_token');
  }

  /**
   * Makes an authenticated request to the HuggingFace API.
   *
   * @param string $endpoint
   *   The API endpoint.
   * @param array $options
   *   Request options.
   *
   * @return \Psr\Http\Message\ResponseInterface
   *   The response.
   */
  protected function makeAuthenticatedRequest($endpoint, array $options = []) {
    $options['headers'] = ($options['headers'] ?? []) + [
      'Authorization' => 'Bearer ' . $this->getApiToken(),
      'X-Organization' => static::ORGANIZATION,
    ];

    try {
      return $this->httpClient->request(
        $options['method'] ?? 'GET',
        static::API_BASE_URL . '/' . ltrim($endpoint, '/'),
        $options
      );
    }
    catch (GuzzleException $e) {
      $this->loggerFactory->error('HuggingFace API request failed: @error', [
        '@error' => $e->getMessage(),
      ]);
      throw $e;
    }
  }

  /**
   * Shares an upgrade pattern to the public dataset.
   *
   * @param array $pattern
   *   The upgrade pattern to share.
   *
   * @return bool
   *   TRUE if the pattern was shared successfully, FALSE otherwise.
   */
  public function sharePattern(array $pattern) {
    try {
      $token = $this->getApiToken();
      if (empty($token)) {
        $this->loggerFactory->warning('Cannot share pattern: HuggingFace API token not configured.');
        return FALSE;
      }

      $response = $this->makeAuthenticatedRequest('datasets/' . static::DATASET_REPO . '/push', [
        'method' => 'POST',
        'json' => [
          'commit_message' => 'Add new upgrade pattern',
          'content' => $pattern,
        ],
      ]);

      if ($response->getStatusCode() === 200) {
        $this->loggerFactory->info('Successfully shared upgrade pattern to HuggingFace dataset.');
        return TRUE;
      }
    }
    catch (GuzzleException $e) {
      $this->loggerFactory->error('Error sharing pattern to HuggingFace: @error', ['@error' => $e->getMessage()]);
    }

    return FALSE;
  }

  /**
   * Gets statistics about the public dataset.
   *
   * @return array
   *   Array containing dataset statistics:
   *   - total_patterns: Total number of patterns
   *   - contributors: Number of unique contributors
   *   - last_update: Timestamp of last update
   *   - success_rate: Pattern success rate percentage
   */
  public function getDatasetStats() {
    try {
      $response = $this->makeAuthenticatedRequest('datasets/' . static::DATASET_REPO . '/stats');

      if ($response->getStatusCode() === 200) {
        $stats = json_decode($response->getBody(), TRUE);

        // Cache the stats in state for 1 hour
        $this->state->set('ai_upgrade_assistant.dataset_stats', [
          'data' => $stats,
          'timestamp' => \Drupal::time()->getRequestTime(),
        ]);

        return $stats;
      }
    }
    catch (GuzzleException $e) {
      $this->loggerFactory->error('Error fetching HuggingFace dataset stats: @error', ['@error' => $e->getMessage()]);

      // Return cached stats if available
      $cached = $this->state->get('ai_upgrade_assistant.dataset_stats');
      if ($cached && ($cached['timestamp'] > (\Drupal::time()->getRequestTime() - 3600))) {
        return $cached['data'];
      }
    }

    return [];
  }

  /**
   * Gets a preview of patterns that will be shared.
   *
   * @param int $limit
   *   Maximum number of patterns to return.
   *
   * @return array
   *   Array of patterns ready for sharing.
   */
  public function getPatternPreview($limit = 5) {
    try {
      $response = $this->makeAuthenticatedRequest('datasets/' . static::DATASET_REPO . '/preview', [
        'query' => ['limit' => $limit],
      ]);

      if ($response->getStatusCode() === 200) {
        return json_decode($response->getBody(), TRUE);
      }
    }
    catch (GuzzleException $e) {
      $this->loggerFactory->error('Error fetching pattern preview: @error', ['@error' => $e->getMessage()]);
    }

    return [];
  }

  /**
   * Downloads the latest patterns from the dataset.
   *
   * @return array
   *   Array of downloaded patterns.
   */
  public function downloadPatterns() {
    try {
      $response = $this->makeAuthenticatedRequest('datasets/' . static::DATASET_REPO . '/patterns');

      if ($response->getStatusCode() === 200) {
        $patterns = json_decode($response->getBody(), TRUE);

        // Cache the patterns
        $this->state->set('ai_upgrade_assistant.patterns', [
          'data' => $patterns,
          'timestamp' => \Drupal::time()->getRequestTime(),
        ]);

        return $patterns;
      }
    }
    catch (GuzzleException $e) {
      $this->loggerFactory->error('Error downloading patterns: @error', ['@error' => $e->getMessage()]);

      // Return cached patterns if available
      $cached = $this->state->get('ai_upgrade_assistant.patterns');
      if ($cached && ($cached['timestamp'] > (\Drupal::time()->getRequestTime() - 86400))) {
        return $cached['data'];
      }
    }

    return [];
  }

  /**
   * Creates a new dataset repository if it doesn't exist.
   *
   * @return bool
   *   TRUE if successful, FALSE otherwise.
   */
  public function initializeDataset() {
    try {
      // Check if dataset exists
      try {
        $this->makeAuthenticatedRequest('datasets/' . static::DATASET_REPO);
        $this->loggerFactory->info('Dataset repository already exists');
        return TRUE;
      }
      catch (GuzzleException $e) {
        if ($e->getCode() !== 404) {
          throw $e;
        }
      }

      // Create dataset repository
      $response = $this->makeAuthenticatedRequest('datasets/create', [
        'method' => 'POST',
        'json' => [
          'name' => 'drupal-upgrade-patterns',
          'organization' => static::ORGANIZATION,
          'private' => FALSE,
          'type' => 'code',
          'description' => 'Collection of Drupal upgrade patterns for automated code updates',
          'tags' => ['drupal', 'code-upgrade', 'migration'],
        ],
      ]);

      $this->loggerFactory->info('Created new dataset repository');
      return $response->getStatusCode() === 200;
    }
    catch (GuzzleException $e) {
      $this->loggerFactory->error('Failed to initialize dataset: @error', [
        '@error' => $e->getMessage(),
      ]);
      return FALSE;
    }
  }

  /**
   * Validates a HuggingFace API token.
   *
   * @param string $token
   *   The token to validate.
   *
   * @return bool
   *   TRUE if the token is valid, FALSE otherwise.
   */
  public function validateToken($token) {
    try {
      $response = $this->makeAuthenticatedRequest('whoami', [
        'headers' => [
          'Authorization' => 'Bearer ' . $token,
        ],
      ]);

      return $response->getStatusCode() === 200;
    }
    catch (GuzzleException $e) {
      $this->loggerFactory->error('Error validating HuggingFace token: @error', ['@error' => $e->getMessage()]);
      return FALSE;
    }
  }

  /**
   * Analyzes code using a specified model.
   *
   * @param string $code
   *   The code to analyze.
   * @param string $model_id
   *   The model ID to use (default: Salesforce/codet5-base).
   *
   * @return array
   *   The analysis results.
   *
   * @throws \Exception
   *   If the user doesn't have access to AI features.
   */
  public function analyzeCode($code, $model_id = 'Salesforce/codet5-base') {
    // Check subscription
    if (!$this->subscriptionManager->hasFeature('ai_code_analysis')) {
      throw new \Exception('AI code analysis requires a Pro or Enterprise subscription.');
    }

    if ($this->subscriptionManager->hasExceededLimits()) {
      throw new \Exception('API usage limit exceeded for your subscription tier.');
    }

    if (!isset(static::CODE_MODELS[$model_id])) {
      throw new \InvalidArgumentException("Invalid model ID: $model_id");
    }

    try {
      $response = $this->makeAuthenticatedRequest('models/' . $model_id, [
        'method' => 'POST',
        'json' => [
          'inputs' => $code,
          'options' => [
            'wait_for_model' => true,
            'use_cache' => true,
          ],
        ],
      ]);

      // Track usage
      $this->subscriptionManager->trackUsage('code_analysis');

      return json_decode((string) $response->getBody(), TRUE);
    }
    catch (GuzzleException $e) {
      $this->loggerFactory->error('Code analysis failed: @error', [
        '@error' => $e->getMessage(),
      ]);
      return NULL;
    }
  }

  /**
   * Creates or updates a pattern in the dataset.
   *
   * @param array $pattern
   *   The upgrade pattern data.
   * @param string $version
   *   The Drupal version this pattern applies to.
   * @param array $metadata
   *   Additional metadata about the pattern.
   *
   * @return bool
   *   TRUE if successful, FALSE otherwise.
   *
   * @throws \Exception
   *   If the user doesn't have access to pattern creation.
   */
  public function createPattern(array $pattern, $version, array $metadata = []) {
    // Basic pattern creation is allowed for all tiers
    $canUseAI = $this->subscriptionManager->hasFeature('ai_code_analysis');
    
    try {
      $data = [
        'pattern' => $pattern,
        'drupal_version' => $version,
        'metadata' => $metadata + [
          'timestamp' => time(),
          'validated' => FALSE,
          'success_count' => 0,
          'failure_count' => 0,
          'organization' => static::ORGANIZATION,
          'tier' => $this->subscriptionManager->getCurrentTier(),
        ],
      ];

      // Add AI analysis only for pro/enterprise tiers
      if ($canUseAI && !empty($pattern['before_code'])) {
        $data['analysis'] = [
          'before' => $this->analyzeCode($pattern['before_code']),
          'after' => $this->analyzeCode($pattern['after_code']),
        ];
      }

      $response = $this->makeAuthenticatedRequest('datasets/' . static::DATASET_REPO . '/push', [
        'method' => 'POST',
        'json' => $data,
      ]);

      // Track pattern creation
      $this->subscriptionManager->trackUsage('pattern_created');

      return $response->getStatusCode() === 200;
    }
    catch (GuzzleException $e) {
      $this->loggerFactory->error('Failed to create pattern: @error', [
        '@error' => $e->getMessage(),
      ]);
      return FALSE;
    }
  }

  /**
   * Validates a pattern against existing code samples.
   *
   * @param array $pattern
   *   The pattern to validate.
   *
   * @return array
   *   Validation results with confidence scores.
   */
  public function validatePattern(array $pattern) {
    try {
      // Use CodeBERT to analyze pattern similarity
      $similarity = $this->analyzeCode(
        $pattern['before_code'] . "\n" . $pattern['after_code'],
        'Salesforce/codet5-base'
      );

      // Use GraphCodeBERT for structure analysis
      $structure = $this->analyzeCode(
        $pattern['before_code'] . "\n" . $pattern['after_code'],
        'bigcode/starcoder'
      );

      return [
        'similarity_score' => $similarity['score'] ?? 0,
        'structure_score' => $structure['score'] ?? 0,
        'is_valid' => ($similarity['score'] ?? 0) > 0.8,
        'suggestions' => $this->generateSuggestions($similarity, $structure),
      ];
    }
    catch (\Exception $e) {
      $this->loggerFactory->error('Pattern validation failed: @error', [
        '@error' => $e->getMessage(),
      ]);
      return [
        'similarity_score' => 0,
        'structure_score' => 0,
        'is_valid' => FALSE,
        'error' => $e->getMessage(),
      ];
    }
  }

  /**
   * Generates improvement suggestions based on analysis results.
   *
   * @param array $similarity
   *   Similarity analysis results.
   * @param array $structure
   *   Structure analysis results.
   *
   * @return array
   *   List of suggestions.
   */
  protected function generateSuggestions(array $similarity, array $structure) {
    $suggestions = [];

    if (($similarity['score'] ?? 0) < 0.8) {
      $suggestions[] = 'Pattern may need more specific code context';
    }

    if (($structure['score'] ?? 0) < 0.8) {
      $suggestions[] = 'Consider preserving more of the original code structure';
    }

    return $suggestions;
  }

  /**
   * Updates the organization profile on HuggingFace.
   *
   * @param array $settings
   *   The organization settings.
   *
   * @return bool
   *   TRUE if successful, FALSE otherwise.
   */
  public function updateOrganizationProfile(array $settings) {
    try {
      $data = [
        'username' => $settings['username'],
        'fullName' => $settings['full_name'],
        'type' => $settings['type'],
      ];

      // Add optional fields if they exist
      if (!empty($settings['homepage'])) {
        $data['websiteUrl'] = $settings['homepage'];
      }
      if (!empty($settings['github_username'])) {
        $data['githubUsername'] = $settings['github_username'];
      }
      if (!empty($settings['twitter_username'])) {
        $data['twitterUsername'] = $settings['twitter_username'];
      }
      if (!empty($settings['ai_interests'])) {
        $data['interests'] = $settings['ai_interests'];
      }

      // Handle logo upload if present
      if (!empty($settings['logo'])) {
        $file = \Drupal\file\Entity\File::load($settings['logo'][0]);
        if ($file) {
          $data['logoUrl'] = $file->createFileUrl(FALSE);
        }
      }

      $response = $this->makeAuthenticatedRequest('organizations/' . static::ORGANIZATION, [
        'method' => 'PUT',
        'json' => $data,
      ]);

      $this->loggerFactory->info('Updated organization profile on HuggingFace');
      return $response->getStatusCode() === 200;
    }
    catch (GuzzleException $e) {
      $this->loggerFactory->error('Failed to update organization profile: @error', [
        '@error' => $e->getMessage(),
      ]);
      return FALSE;
    }
  }

}

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

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