Current File : //home/mdkeenpw/www/wp-content/plugins/trx_addons/addons/ai-helper/support/Gutenberg/Helper.php |
<?php
namespace TrxAddons\AiHelper\Gutenberg;
use TrxAddons\AiHelper\OpenAi;
use TrxAddons\AiHelper\OpenAiAssistants;
use TrxAddons\AiHelper\Lists;
use TrxAddons\AiHelper\Utils;
if ( ! class_exists( 'Helper' ) ) {
/**
* Main class for AI Helper Gutenberg support
*/
class Helper {
/**
* Constructor
*/
function __construct() {
// Register AI Helper block
// 1 way) Used only for static blocks (all functionality is in the block's js-file. No PHP-file is required)
// add_action( 'enqueue_block_editor_assets', array( $this, 'enqueue_block_editor_scripts' ) );
// 2 way) Used for both static and dynamic blocks
add_action( 'init', array( $this, 'register_blocks' ) );
// REST API callbacks - return titles, excerpt and content for the post
add_action( 'rest_api_init', array( $this, 'register_rest_api_callbacks' ) );
// Add messages to js-vars
add_filter( 'trx_addons_filter_localize_script_admin', array( $this, 'localize_script_admin' ) );
}
/**
* Check if AI Helper is allowed for Gutenberg
*/
public static function is_allowed() {
return trx_addons_exists_gutenberg()
&& trx_addons_get_setting( 'allow_gutenberg_blocks' );
}
/**
* Localize script to show messages in the admin mode
*
* @hooked 'trx_addons_filter_localize_script_admin'
*
* @param array $vars Array of variables to be passed to the script
*
* @return array Modified array of variables
*/
function localize_script_admin( $vars ) {
if ( self::is_allowed() ) {
$vars['msg_ai_helper_error'] = esc_html__( "AI Helper error", 'trx_addons' );
$vars['msg_ai_helper_response'] = esc_html__( "Check/Edit a response and apply it", 'trx_addons' );
$vars['msg_ai_helper_response_variations'] = esc_html__( "Please, select a variation", 'trx_addons' );
$vars['msg_ai_helper_bt_caption_replace'] = esc_html__( "Replace", 'trx_addons' );
$vars['msg_ai_helper_bt_caption_prepend'] = esc_html__( "Prepend", 'trx_addons' );
$vars['msg_ai_helper_bt_caption_append'] = esc_html__( "Append", 'trx_addons' );
$vars['ai_helper_list_models'] = Lists::get_list_ai_text_models();
$vars['ai_helper_list_commands'] = Lists::get_list_ai_commands();
$vars['ai_helper_list_bases'] = Lists::get_list_ai_bases();
$vars['ai_helper_list_text_tones'] = Lists::get_list_ai_text_tones();
$vars['ai_helper_list_text_languages'] = Lists::get_list_ai_text_languages();
}
return $vars;
}
// 1st way) Used only for static blocks (all functionality is in the block's js-file. No PHP-file is required)
// function enqueue_block_editor_scripts() {
// if ( trx_addons_exists_gutenberg() && trx_addons_get_setting( 'allow_gutenberg_blocks' ) ) {
// wp_enqueue_script(
// 'trx_addons-ai-helper-gutenberg-editor-panel',
// trx_addons_get_file_url( TRX_ADDONS_PLUGIN_ADDONS . 'ai-helper/support/gutenberg/blocks/build/index.js' ),
// trx_addons_block_editor_dependencis(),
// filemtime( trx_addons_get_file_dir( TRX_ADDONS_PLUGIN_ADDONS . 'ai-helper/support/gutenberg/blocks/build/index.js' ) ),
// true
// );
// }
// }
/**
* Register blocks (2nd way: Used for both static and dynamic blocks)
*/
function register_blocks() {
if ( ! self::is_allowed() ) {
return;
}
global $pagenow;
if ( 'widgets.php' == $pagenow ) {
return;
}
// Register style and script for Editor mode
wp_register_style( 'trx_addons-ai-helper-gutenberg-editor-panel', trx_addons_get_file_url( TRX_ADDONS_PLUGIN_ADDONS . 'ai-helper/support/Gutenberg/blocks/build/index.css' ) );
wp_register_script( 'trx_addons-ai-helper-gutenberg-editor-panel', trx_addons_get_file_url( TRX_ADDONS_PLUGIN_ADDONS . 'ai-helper/support/Gutenberg/blocks/build/index.js' ), array( 'wp-blocks', 'wp-element', 'wp-components', 'wp-plugins', 'wp-edit-post', 'wp-i18n' ) ); //, 'wp-editor'
// Register block
register_block_type( 'trx-addons/ai-helper-panel', apply_filters( 'trx_addons_gb_map', array(
// Style and script files for Editor mode
'editor_style' => 'trx_addons-ai-helper-gutenberg-editor-panel',
'editor_script' => 'trx_addons-ai-helper-gutenberg-editor-panel',
//'attributes' => array(),
//'render_callback' => array( $this, 'render_block' ),
), 'trx-addons/ai-helper-panel' ) );
}
/**
* Register REST API callbacks
*/
function register_rest_api_callbacks() {
if ( ! self::is_allowed() ) {
return;
}
register_rest_route( 'ai-helper/v1', 'get-response', array(
'methods' => \WP_REST_SERVER::CREATABLE, // 'POST'
'callback' => array( $this, 'get_response' ),
'permission_callback' => array( $this, 'get_response_permissions_check' ),
) );
register_rest_route( 'ai-helper/v1', 'fetch-answer', array(
'methods' => \WP_REST_SERVER::CREATABLE, // 'POST'
'callback' => array( $this, 'fetch_answer' ),
'permission_callback' => array( $this, 'get_response_permissions_check' ),
) );
}
/**
* Check if a user has permissions to get response from OpenAi API
*
* @param WP_REST_Request $request Full details about the request.
*/
function get_response_permissions_check() {
// Way 1: Allow access to registered users only (all users who have the edit_posts capability)
return current_user_can( 'edit_posts' );
// Way 2: Restrict endpoint to only users who have the edit_posts capability. For others return an error.
// if ( ! current_user_can( 'edit_posts' ) ) {
// return new WP_Error( 'rest_forbidden', esc_html__( 'OMG you can not edit posts.', 'trx_addons' ), array( 'status' => 401 ) );
// }
// return true;
}
/**
* Send a query to OpenAi API with a post content or a prompt and return response
*
* @param WP_REST_Request $request Full details about the request.
*/
function get_response( $request ) {
$answer = array(
'error' => '',
'data' => array(
'text' => '',
'message' => ''
)
);
if ( current_user_can( 'edit_posts' ) ) {
$params = $request->get_params();
$model = ! empty( $params['model'] ) ? $params['model'] : trx_addons_get_option( 'ai_helper_text_model_default', 'openai/default' );
$commands = Lists::get_list_ai_commands();
$command = ! empty( $params['command'] ) ? $params['command'] : 'write_blog';
$base_on = ! empty( $params['base_on'] ) ? $params['base_on'] : 'prompt';
$prompt = ! empty( $params['prompt'] ) ? trim( $params['prompt'] ) : $commands[ $command ]['prompt'];
$hint = ! empty( $params['hint'] ) ? trim( $params['hint'] ) : '';
$content = ! empty( $params['content'] ) ? trim( $params['content'] ) : '';
$text_tone = ! empty( $params['text_tone'] ) ? trim( $params['text_tone'] ) : 'normal';
$text_language = ! empty( $params['text_language'] ) ? trim( $params['text_language'] ) : 'english';
if ( ! empty( $model ) && ! empty( $command ) && ! empty( $commands[ $command ] )
&& ( $base_on == 'prompt' && ! empty( $prompt ) || $base_on != 'prompt' && ! empty( $content ) )
) {
$msg = '';
if ( ! in_array( $command, array( 'process_tone', 'process_translate' ) ) ) {
$content = strip_tags( $content );
}
if ( $command == 'process_tone' ) {
$prompt = str_replace( '%tone%', $text_tone, $prompt );
}
if ( $command == 'process_translate' ) {
$prompt = str_replace( '%language%', $text_language, $prompt );
}
// Prepage a prompt part for variations
$variations = '';
if ( ! empty( $commands[ $command ]['variations'] ) ) {
$variations = sprintf( __( 'Generate %d variants of the %s based on the post content (as a single sentence for each variant). Start each variant on a new line and enclose it in double curly braces. Return only text without numeration and any other messages.', 'trx_addons' ),
apply_filters( 'trx_addons_filter_ai_helper_variations_total', $commands[ $command ]['variations'], $command ),
$commands[ $command ]['variation_name']
);
}
if ( $base_on == 'prompt' ) {
$msg = trx_addons_strdot( $prompt ) . ' ' . $variations;
} else {
$msg = strpos( $command, 'write_' ) !== false
? $prompt . ': ' . trx_addons_strdot( $content )
. ( ! empty( $hint ) ? ' ' . trx_addons_strdot( $hint ) : '' )
. $this->get_subprompt( $params )
: ( ! empty( $variations ) ? $variations : trx_addons_strdot( $prompt ) )
. ( ! empty( $hint ) ? ' ' . trx_addons_strdot( $hint ) : '' )
. $this->get_subprompt( $params )
. ( $command != 'process_translate'
? ' ' . __( 'Respond in the same language as the text being processed.', 'trx_addons' )
: ''
)
. ' ' . sprintf( __( 'The text to be processed is enclosed to double curly braces: %s', 'trx_addons' ),
'{{ ' . preg_replace( "/(\r?\n){2,}/", '$1', $content ) . ' }}'
);
}
$api = Utils::get_chat_api( $model );
$chat_args = array(
'prompt' => apply_filters( 'trx_addons_filter_ai_helper_prompt', $msg, $params, 'gutenberg' ),
'role' => 'gb_assistant',
'system_prompt' => apply_filters( 'trx_addons_filter_ai_helper_system_prompt', trx_addons_get_option( 'ai_helper_system_prompt_openai' ) ),
'n' => 1,
);
if ( ! empty( $model ) ) {
$chat_args['model'] = $model;
if ( Utils::is_flowise_ai_model( $model ) ) {
$chat_args['override_config'] = ! empty( $params['flowise_override'] ) ? $params['flowise_override'] : '';
}
}
$response = $api->query( $chat_args, compact( 'model', 'command', 'base_on', 'prompt', 'content', 'text_tone', 'text_language' ) );
$answer = $this->parse_response( $response, $answer );
}
}
return rest_ensure_response( apply_filters( 'trx_addons_filter_ai_helper_get_response', $answer ) );
}
/**
* AJAX handler for the 'trx_addons_ajax_sc_tgenerator_fetch' action
*
* @hooked 'wp_ajax_trx_addons_ajax_sc_tgenerator_fetch'
*/
function fetch_answer( $request ) {
$answer = array(
'error' => '',
'data' => array(
'text' => '',
'message' => ''
)
);
if ( current_user_can( 'edit_posts' ) ) {
$params = $request->get_params();
$run_id = ! empty( $params['run_id'] ) ? $params['run_id'] : '';
$thread_id = ! empty( $params['thread_id'] ) ? $params['thread_id'] : '';
$answer['finish_reason'] = 'queued';
$answer['run_id'] = $run_id;
$answer['thread_id'] = $thread_id;
$api = OpenAiAssistants::instance();
if ( $api->get_api_key() != '' ) {
$response = $api->fetch_answer( $thread_id, $run_id );
$answer = $this->parse_response( $response, $answer );
} else {
$answer['error'] = __( 'Error! API key is not specified.', 'trx_addons' );
}
}
// Return response to the AJAX handler
return rest_ensure_response( apply_filters( 'trx_addons_filter_ai_helper_fetch_answer', $answer ) );
}
/**
* Parse response from API
*
* @param string $response Response from API
* @param array $answer Current answer
*
* @return array Modified answer
*/
function parse_response( $response, $answer ) {
if ( ! empty( $response['finish_reason'] ) ) {
$answer['finish_reason'] = $response['finish_reason'];
}
if ( ! empty( $response['thread_id'] ) ) {
$answer['thread_id'] = $response['thread_id'];
}
if ( ! empty( $response['choices'][0]['message']['content'] ) ) {
// Get all variations from the response. Each variation is separated by a new line and encosed in double curly brackets.
if ( substr_count( $response['choices'][0]['message']['content'], '{{' ) > 1 ) {
if ( preg_match_all( '/{{(.*)}}/U', $response['choices'][0]['message']['content'], $matches ) ) {
$answer['data']['text'] = $matches[1];
} else {
$answer['data']['text'] = array( $this->fix_headings_in_content( str_replace( array( '{{', '}}' ), '', $response['choices'][0]['message']['content'] ) ) );
}
// Get whole text from the response as a single variant
} else {
if ( preg_match( '#<body>([\s\S]*)</body>#U', $response['choices'][0]['message']['content'], $matches ) ) {
$answer['data']['text'] = $this->fix_headings_in_content( $matches[1] );
} else {
$answer['data']['text'] = array( $this->fix_headings_in_content( str_replace( array( '{{', '}}' ), '', $response['choices'][0]['message']['content'] ) ) );
}
}
} else if ( ! empty( $response['finish_reason'] ) && $response['finish_reason'] == 'queued' && ! empty( $response['run_id'] ) ) {
$answer['finish_reason'] = $response['finish_reason'];
$answer['run_id'] = $response['run_id'];
} else {
if ( ! empty( $response['error']['message'] ) ) {
$answer['error'] = $response['error']['message'];
} else if ( ! empty( $response['error'] ) && is_string( $response['error'] ) ) {
$answer['error'] = $response['error'];
} else {
$answer['error'] = __( 'Error! Unknown response from the API. Maybe the API server is not available right now.', 'trx_addons' );
}
}
return $answer;
}
/**
* Fix headings in the generated content: add an attribute {"level": N} to the wp:heading block
*
* @param string $content Generated content
*
* @return string Fixed content
*/
function fix_headings_in_content( $content ) {
for ( $i = 1; $i <= 6; $i++ ) {
$content = preg_replace( '/^<!-- wp:heading -->([\s]*<h' . $i . ')/m', '<!-- wp:heading {"level":' . $i . '} -->$1', $content );
}
return $content;
}
/**
* Get subprompt for the some commands
*
* @param array $params Parameters of the request
*
* @return string Subprompt
*/
function get_subprompt( $params ) {
$subprompt = '';
if ( ! empty( $params['command'] ) && strpos( $params['command'], 'write_' ) !== false ) {
$subprompt = ( ! empty( $subprompt ) ? ' ' : '' )
. apply_filters( 'trx_addons_filter_ai_helper_write_post_subprompt', __( 'The text should consist of at least three sections with subheadings.', 'trx_addons' ), $params );
}
return ! empty( $subprompt ) ? ' ' . $subprompt : '';
}
}
}