Current File : /home/mdkeenpw/public_html/wp-content/plugins/ai-engine/classes/engines/perplexity.php
<?php

class Meow_MWAI_Engines_Perplexity extends Meow_MWAI_Engines_ChatML {
  // Streaming
  protected $streamInTokens = null;
  protected $streamOutTokens = null;
  protected $streamContent = null;
  protected $streamBuffer = null;
  protected $inCitations = null;

  public function __construct( $core, $env ) {
    parent::__construct( $core, $env );
  }

  protected function set_environment() {
    $env = $this->env;
    $this->apiKey = $env['apikey'] ?? null;
  }

  protected function get_service_name() {
    return 'Perplexity';
  }

  public function get_models() {
    return apply_filters( 'mwai_perplexity_models', MWAI_PERPLEXITY_MODELS );
  }

  public static function get_models_static() {
    return MWAI_PERPLEXITY_MODELS;
  }

  protected function build_url( $query, $endpoint = null ) {
    $endpoint = apply_filters( 'mwai_perplexity_endpoint', 'https://api.perplexity.ai', $this->env );
    return rtrim( $endpoint, '/' ) . '/chat/completions';
  }

  protected function build_messages( $query ) {
    $messages = parent::build_messages( $query );
    $filtered = [];
    $haveSeenUser = false;
    foreach ( $messages as $message ) {
      if ( !$haveSeenUser ) {
        if ( $message['role'] === 'assistant' ) {
          continue;
        }
        if ( $message['role'] === 'user' ) {
          $haveSeenUser = true;
        }
        $filtered[] = $message;
      }
      else {
        $filtered[] = $message;
      }
    }

    return $filtered;
  }

  protected function build_headers( $query ) {
    if ( $query->apiKey ) {
      $this->apiKey = $query->apiKey;
    }
    if ( empty( $this->apiKey ) ) {
      throw new Exception( 'No Perplexity API Key provided. Check your settings.' );
    }
    return [
      'Content-Type' => 'application/json',
      'Authorization' => 'Bearer ' . $this->apiKey,
      'User-Agent' => 'AI Engine',
    ];
  }

  protected function build_body( $query, $streamCallback = null, $extra = null ) {
    $body = parent::build_body( $query, $streamCallback, $extra );
    return $body;
  }

  public function reset_stream() {
    $this->inCitations = null;
    return parent::reset_stream();
  }

  // Let's override the stream handler only to capture the citations
  protected function stream_data_handler( $json ) {
    if ( isset( $json['citations'] ) ) {
      $this->inCitations = $json['citations'];
    }
    return parent::stream_data_handler( $json );
  }

  /**
  * In Perplexity, we intercept the final choices and insert citations
  * as ([domain1](https://...), [domain2](https://...))
  * based on [1][2][3] references in the text.
  */
  protected function finalize_choices( $choices, $responseData, $query ) {
    $citations = isset( $responseData['citations'] ) ? $responseData['citations'] : null;
    if ( empty( $citations ) && !empty( $this->inCitations ) ) {
      $citations = $this->inCitations;
    }
    if ( empty( $citations ) ) {
      return parent::finalize_choices( $choices, $responseData, $query );
    }

    foreach ( $choices as &$choice ) {
      if ( isset( $choice['message']['content'] ) && is_string( $choice['message']['content'] ) ) {
        $content = $choice['message']['content'];
        $content = preg_replace_callback( '/\[(\d+)\](?:\s*\[(\d+)\])*/', function ( $matches ) use ( $citations ) {
          preg_match_all( '/\[(\d+)\]/', $matches[0], $refs );
          $links = [];
          foreach ( $refs[1] as $refNumber ) {
            $index = (int) $refNumber - 1;
            if ( isset( $citations[$index] ) ) {
              $url = $citations[$index];
              $domain = parse_url( $url, PHP_URL_HOST );
              if ( !empty( $domain ) ) {
                $domain = str_replace( 'www.', '', $domain );
              }
              $links[] = '[' . $domain . '](' . $url . ')';
            }
          }
          // If we gathered at least one link, return them all in a parenthetical group
          // e.g. " ([google.com](https://google.com), [othersite.io](https://othersite.io))"
          // Otherwise, just return the original matches (fallback).
          return !empty( $links ) ? ' (' . implode( ', ', $links ) . ')' : $matches[0];
        }, $content );
        $choice['message']['content'] = $content;
      }
    }

    return $choices;
  }

  /**
   * Connection check for Perplexity API
   * Tests the API key by listing async chat completions
   */
  public function connection_check() {
    try {
      // Build the URL for async completions list endpoint
      $endpoint = apply_filters( 'mwai_perplexity_endpoint', 'https://api.perplexity.ai', $this->env );
      $url = rtrim( $endpoint, '/' ) . '/async/chat/completions?limit=1';
      
      // Build headers with API key
      if ( empty( $this->apiKey ) ) {
        throw new Exception( 'No Perplexity API Key provided. Check your settings.' );
      }
      
      $headers = [
        'Authorization' => 'Bearer ' . $this->apiKey,
        'User-Agent' => 'AI Engine',
      ];
      
      $options = [
        'headers' => $headers,
        'method' => 'GET',
        'timeout' => 10,
        'sslverify' => false
      ];
      
      // Make the request
      $response = wp_remote_get( $url, $options );
      
      if ( is_wp_error( $response ) ) {
        throw new Exception( $response->get_error_message() );
      }
      
      $body = wp_remote_retrieve_body( $response );
      $data = json_decode( $body, true );
      
      // Check if response has expected structure
      if ( !is_array( $data ) || !array_key_exists( 'requests', $data ) ) {
        throw new Exception( 'Invalid response from Perplexity API' );
      }
      
      // Get available models from our constants
      $models = $this->get_models();
      $modelNames = array_map( function( $model ) {
        return $model['model'] ?? $model['name'] ?? 'unknown';
      }, array_slice( $models, 0, 5 ) );
      
      return [
        'success' => true,
        'service' => 'Perplexity',
        'message' => 'Connection successful',
        'details' => [
          'endpoint' => $endpoint,
          'model_count' => count( $models ),
          'sample_models' => $modelNames
        ]
      ];
    }
    catch ( Exception $e ) {
      throw new Exception( 'Perplexity connection failed: ' . $e->getMessage() );
    }
  }

}