| @@ -3,13 +3,118 @@ | ||
| 3 | 3 | |
| 4 | 4 | add_action( 'init', 'ultp_chatgpt_init' ); |
| 5 | 5 | function ultp_chatgpt_init() { |
| 6 | 6 | if ( ultimate_post()->get_setting( 'ultp_chatgpt' ) == 'true' ) { |
| 7 | - add_filter( 'ultp_settings', 'get_chatgpt_settings', 10, 1 ); | |
| 7 | + add_filter( 'ultp_settings', 'ultp_get_chatgpt_settings', 10, 1 ); | |
| 8 | + add_action( 'rest_api_init', 'ultp_chatgpt_register_route' ); | |
| 8 | 9 | } |
| 9 | 10 | } |
| 10 | 11 | |
| 11 | -function get_chatgpt_settings( $config ) { | |
| 12 | +/** | |
| 13 | + * Register the route the editor uses to ask OpenAI. | |
| 14 | + * | |
| 15 | + * The OpenAI key stays on the server: the editor sends only the prompt, and | |
| 16 | + * this route adds the key and the saved model settings. | |
| 17 | + */ | |
| 18 | +function ultp_chatgpt_register_route() { | |
| 19 | + register_rest_route( | |
| 20 | + 'ultp/v2', | |
| 21 | + '/chatgpt/', | |
| 22 | + array( | |
| 23 | + 'methods' => 'POST', | |
| 24 | + 'callback' => 'ultp_chatgpt_request', | |
| 25 | + 'permission_callback' => function () { | |
| 26 | + return current_user_can( 'edit_posts' ); | |
| 27 | + }, | |
| 28 | + 'args' => array( | |
| 29 | + 'prompt' => array( | |
| 30 | + 'type' => 'string', | |
| 31 | + 'required' => true, | |
| 32 | + ), | |
| 33 | + ), | |
| 34 | + ) | |
| 35 | + ); | |
| 36 | +} | |
| 37 | + | |
| 38 | +/** | |
| 39 | + * Send a prompt to OpenAI with the saved key and settings. | |
| 40 | + * | |
| 41 | + * Returns OpenAI's `choices` on success, or `error.code` (plus the message for | |
| 42 | + * a missing model) on failure, which is the shape the editor already reads. | |
| 43 | + * OpenAI's other error messages are not passed on, as some quote part of the key. | |
| 44 | + * | |
| 45 | + * @param WP_REST_Request $request Request. | |
| 46 | + * @return WP_REST_Response|WP_Error | |
| 47 | + */ | |
| 48 | +function ultp_chatgpt_request( $request ) { | |
| 49 | + $api_key = ultimate_post()->get_setting( 'chatgpt_secret_key' ); | |
| 50 | + if ( empty( $api_key ) ) { | |
| 51 | + return rest_ensure_response( array( 'error' => array( 'code' => 'invalid_api_key' ) ) ); | |
| 52 | + } | |
| 53 | + | |
| 54 | + $prompt = trim( wp_check_invalid_utf8( (string) $request->get_param( 'prompt' ) ) ); | |
| 55 | + if ( '' === $prompt ) { | |
| 56 | + return new WP_Error( 'ultp_chatgpt_empty_prompt', __( 'The prompt is empty.', 'ultimate-post' ), array( 'status' => 400 ) ); | |
| 57 | + } | |
| 58 | + $prompt = mb_substr( $prompt, 0, 20000 ); | |
| 59 | + | |
| 60 | + $model = ultimate_post()->get_setting( 'chatgpt_model' ); | |
| 61 | + if ( ! in_array( $model, array( 'gpt-3.5-turbo', 'gpt-4', 'text-davinci-002', 'text-davinci-003' ), true ) ) { | |
| 62 | + $model = 'gpt-3.5-turbo'; | |
| 63 | + } | |
| 64 | + $timeout = absint( ultimate_post()->get_setting( 'chatgpt_response_time' ) ); | |
| 65 | + $timeout = $timeout ? min( max( $timeout, 5 ), 120 ) : 35; | |
| 66 | + | |
| 67 | + if ( 0 === strpos( $model, 'text-davinci' ) ) { | |
| 68 | + $max_tokens = absint( ultimate_post()->get_setting( 'chatgpt_max_tokens' ) ); | |
| 69 | + $url = 'https://api.openai.com/v1/completions'; | |
| 70 | + $body = array( | |
| 71 | + 'model' => $model, | |
| 72 | + 'prompt' => $prompt, | |
| 73 | + 'max_tokens' => $max_tokens ? min( $max_tokens, 4096 ) : 200, | |
| 74 | + ); | |
| 75 | + } else { | |
| 76 | + $url = 'https://api.openai.com/v1/chat/completions'; | |
| 77 | + $body = array( | |
| 78 | + 'model' => $model, | |
| 79 | + 'messages' => array( | |
| 80 | + array( | |
| 81 | + 'role' => 'user', | |
| 82 | + 'content' => $prompt, | |
| 83 | + ), | |
| 84 | + ), | |
| 85 | + ); | |
| 86 | + } | |
| 87 | + | |
| 88 | + $response = wp_safe_remote_post( | |
| 89 | + $url, | |
| 90 | + array( | |
| 91 | + 'timeout' => $timeout, | |
| 92 | + 'headers' => array( | |
| 93 | + 'Content-Type' => 'application/json', | |
| 94 | + 'Authorization' => 'Bearer ' . $api_key, | |
| 95 | + ), | |
| 96 | + 'body' => wp_json_encode( $body ), | |
| 97 | + ) | |
| 98 | + ); | |
| 99 | + if ( is_wp_error( $response ) ) { | |
| 100 | + return rest_ensure_response( array( 'error' => array( 'code' => 'request_failed' ) ) ); | |
| 101 | + } | |
| 102 | + | |
| 103 | + $data = json_decode( wp_remote_retrieve_body( $response ), true ); | |
| 104 | + if ( ! empty( $data['choices'] ) && is_array( $data['choices'] ) ) { | |
| 105 | + return rest_ensure_response( array( 'choices' => $data['choices'] ) ); | |
| 106 | + } | |
| 107 | + | |
| 108 | + $code = isset( $data['error']['code'] ) ? sanitize_key( $data['error']['code'] ) : 'request_failed'; | |
| 109 | + $error = array( 'code' => $code ); | |
| 110 | + if ( 'model_not_found' === $code && isset( $data['error']['message'] ) ) { | |
| 111 | + $error['message'] = sanitize_text_field( $data['error']['message'] ); | |
| 112 | + } | |
| 113 | + return rest_ensure_response( array( 'error' => $error ) ); | |
| 114 | +} | |
| 115 | + | |
| 116 | +function ultp_get_chatgpt_settings( $config ) { | |
| 12 | 117 | $arr = array( |
| 13 | 118 | 'ultp_chatgpt' => array( |
| 14 | 119 | 'label' => __( 'ChatGPT', 'ultimate-post' ), |
| 15 | 120 | 'attr' => array( |
| @@ -20,9 +125,9 @@ | ||
| 20 | 125 | 'chatgpt_secret_key' => array( |
| 21 | 126 | 'type' => 'text', |
| 22 | 127 | 'label' => __( 'OpenAI API Secret Key', 'ultimate-post' ), |
| 23 | 128 | 'desc' => __( 'For using ChatGPT addon, you have to add your OpenAI secret key.', 'ultimate-post' ), |
| 24 | - 'link' => __( 'https://platform.openai.com/account/api-keys' ), | |
| 129 | + 'link' => 'https://platform.openai.com/account/api-keys', | |
| 25 | 130 | 'linkText' => __( 'Get key.', 'ultimate-post' ), |
| 26 | 131 | ), |
| 27 | 132 | 'chatgpt_model' => array( |
| 28 | 133 | 'type' => 'select', |