Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat/488: Ability to resize post content using OpenAI's ChatGPT #532

Merged
merged 53 commits into from
Sep 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
bef8d8d
add setting for resizing content
Sidsector9 Jul 11, 2023
9fbc143
add logic to resize content
Sidsector9 Jul 11, 2023
80c37bb
working without state
Sidsector9 Jul 12, 2023
26f0982
add finishing touch to JS and SCSS
Sidsector9 Jul 12, 2023
8e12bc3
update prompt to handle both shrink and grow operations
Sidsector9 Jul 12, 2023
ade2b65
fix documentation
Sidsector9 Jul 12, 2023
f932808
fix eslint errors
Sidsector9 Jul 12, 2023
ee5e308
move resizing controls toblock toolbar
Sidsector9 Jul 14, 2023
4d59dd8
remove redundant imports
Sidsector9 Jul 14, 2023
792ddf3
fix PR review changes
Sidsector9 Jul 18, 2023
9a9f378
revert toolbar code
Sidsector9 Jul 18, 2023
2fc4d91
refactor to move options to Block Control
Sidsector9 Jul 18, 2023
b338b38
fix text selection issue
Sidsector9 Jul 18, 2023
ec77bfd
add arg validation logic
Sidsector9 Jul 18, 2023
34067ac
fix eslint error
Sidsector9 Jul 18, 2023
7f2bf40
Merge branch 'develop' into feat/488
Sidsector9 Jul 27, 2023
0c098fe
add e2e tests
Sidsector9 Jul 27, 2023
df1fa44
change icon color to black
Sidsector9 Jul 27, 2023
d0dc9d2
fix eslint errors
Sidsector9 Jul 27, 2023
02234ff
fix failing tests
Sidsector9 Jul 27, 2023
d1dead7
fix failing test in WP minimum
Sidsector9 Jul 27, 2023
7dc8a63
remove subscriber and contributor role from content resizing
Sidsector9 Jul 27, 2023
09064d3
add documentation
Sidsector9 Jul 28, 2023
c9d7a64
add content resizing to setup wizard
Sidsector9 Aug 3, 2023
26d7e26
Merge branch 'develop' into feat/488
Sidsector9 Aug 3, 2023
50765d5
change role to system
Sidsector9 Aug 3, 2023
b8c5a14
fix failing tests
Sidsector9 Aug 4, 2023
e65a80d
Merge branch 'develop' of github.com:10up/classifai into feat/488
Sidsector9 Aug 11, 2023
023dd3c
remove partial text selection
Sidsector9 Aug 11, 2023
4dc11fc
add button to popup
Sidsector9 Aug 11, 2023
9e3571b
use enqueue_block_assets
Sidsector9 Aug 16, 2023
f1f4d08
add colored loader
Sidsector9 Aug 16, 2023
3ab7cfa
use classifai colors
Sidsector9 Aug 17, 2023
009c7e5
Merge branch 'develop' of github.com:10up/classifai into feat/488
Sidsector9 Aug 18, 2023
47cbacd
Merge branch 'develop' into feat/488
dkotter Aug 24, 2023
627a19d
Minor text tweaks
dkotter Aug 24, 2023
dff6a5e
Fix eslint issues
dkotter Aug 24, 2023
2de2346
Merge branch 'feat/488' of github.com:10up/classifai into feat/488
Sidsector9 Aug 25, 2023
fd891fc
fix eslint errors
Sidsector9 Aug 25, 2023
141ea55
Ensure we're clicking on the button, not the row
dkotter Aug 25, 2023
a5325f0
fix animation that broke due to WP6.3 update
Sidsector9 Aug 30, 2023
9a9a708
Merge branch 'feat/488' of github.com:10up/classifai into feat/488
Sidsector9 Aug 30, 2023
7c3b7f9
fix eslint errors
Sidsector9 Aug 30, 2023
99485aa
use new Toolbar methods
Sidsector9 Aug 30, 2023
4a15a7c
remove extra concat
Sidsector9 Aug 31, 2023
84b1133
fix failing test
Sidsector9 Aug 31, 2023
85c71e1
Add title to modal
dkotter Aug 31, 2023
d583397
Merge branch 'develop' into feat/488
dkotter Aug 31, 2023
0ee40e4
Merge branch 'develop' into feat/488
dkotter Aug 31, 2023
4047161
Ensure we get the proper block editor when interacting with block ele…
dkotter Aug 31, 2023
338d0eb
Update readmes
dkotter Aug 31, 2023
6e68f08
Merge branch 'feat/488' of github.com:10up/classifai into feat/488
Sidsector9 Sep 1, 2023
b0cc25a
bump min WordPress version required from 5.7 to 5.8
Sidsector9 Sep 1, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{

Check warning on line 1 in .eslintrc.json

View workflow job for this annotation

GitHub Actions / eslint

File ignored by default.
"globals": {
"wp": "readonly",
"jQuery": "readonly",
Expand All @@ -18,7 +18,8 @@
"Backbone": "readonly",
"_": "readonly",
"File": "readonly",
"Headers": "readonly"
"Headers": "readonly",
"requestAnimationFrame": "readonly"
},
"extends": ["plugin:@wordpress/eslint-plugin/recommended"],
"ignorePatterns": ["*.json"]
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/cypress.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
matrix:
core:
- {name: 'WP latest', version: 'latest'}
- {name: 'WP minimum', version: 'WordPress/WordPress#5.7'}
- {name: 'WP minimum', version: 'WordPress/WordPress#5.8'}
- {name: 'WP trunk', version: 'WordPress/WordPress#master'}
steps:
- name: Checkout
Expand Down
12 changes: 9 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ Tap into leading cloud-based services like [OpenAI](https://openai.com/), [Micro

* Generate a summary of post content and store it as an excerpt using [OpenAI's ChatGPT API](https://platform.openai.com/docs/guides/chat)
* Generate titles from post content using [OpenAI's ChatGPT API](https://platform.openai.com/docs/guides/chat)
* Expand or condense text content using [OpenAI's ChatGPT API](https://platform.openai.com/docs/guides/chat)
* Generate new images on demand to use in-content or as a featured image using [OpenAI's DALL·E API](https://platform.openai.com/docs/guides/images)
* Generate transcripts of audio files using [OpenAI's Whisper API](https://platform.openai.com/docs/guides/speech-to-text)
* Convert text content into audio and output a "read-to-me" feature on the front-end to play this audio using [Microsoft Azure's Text to Speech API](https://learn.microsoft.com/en-us/azure/cognitive-services/speech-service/text-to-speech)
Expand All @@ -52,9 +53,9 @@ Tap into leading cloud-based services like [OpenAI](https://openai.com/), [Micro
| :-: | :-: | :-: |
| ![Screenshot of ClassifAI post tagging](assets/img/screenshot-1.png "Example of a Block Editor post with Watson Categories, Keywords, Concepts, and Entities.") | ![Screenshot of ClassifAI recommended content](assets/img/screenshot-2.png "Example of a Recommended Content Block with Azure AI Personalizer.") | ![Screenshot of ClassifAI excerpt generation](assets/img/screenshot-7.png "Example of automatic excerpt generation with OpenAI.") |

| Audio Transcripts | Title Generation | Text to Speech |
| :-: | :-: | :-: |
| ![Screenshot of ClassifAI audio transcript generation](assets/img/screenshot-9.png "Example of automatic audio transcript generation with OpenAI.") | ![Screenshot of ClassifAI title generation](assets/img/screenshot-10.png "Example of automatic title generation with OpenAI.") | ![Screenshot of ClassifAI text to speech generation](assets/img/screenshot-11.png "Example of automatic text to speech generation with Azure.") |
| Audio Transcripts | Title Generation | Expand or Condense Text | Text to Speech |
| :-: | :-: | :-: | :-: |
| ![Screenshot of ClassifAI audio transcript generation](assets/img/screenshot-9.png "Example of automatic audio transcript generation with OpenAI.") | ![Screenshot of ClassifAI title generation](assets/img/screenshot-10.png "Example of automatic title generation with OpenAI.") | ![Screenshot of ClassifAI expand/condense text feature](assets/img/screenshot-12.png "Example of expanding or condensing text with OpenAI.") | ![Screenshot of ClassifAI text to speech generation](assets/img/screenshot-11.png "Example of automatic text to speech generation with Azure.") |

### Image Processing

Expand Down Expand Up @@ -219,6 +220,7 @@ IBM Watson's [Categories](https://cloud.ibm.com/docs/natural-language-understand

* Choose to add the ability to generate excerpts.
* Choose to add the ability to generate titles.
* Choose to add the ability to resize content.
* Set the other options as needed.
* Save changes and ensure a success message is shown. An error will show if API authentication fails.

Expand All @@ -230,6 +232,10 @@ IBM Watson's [Categories](https://cloud.ibm.com/docs/natural-language-understand
* To test title generation, edit (or create) an item that supports titles.
* Ensure this item has content saved.
* Open the Summary panel in the sidebar and click on `Generate titles`.
* To test content resizing, edit (or create) an item. Note: only the block editor is supported.
* Add a paragraph block with some content.
* With this block selected, select the AI icon in the toolbar and choose to either expand or condense the text.
* In the modal that pops up, select one of the options.

## Set Up Language Processing (via OpenAI Embeddings)

Expand Down
Binary file added assets/img/screenshot-12.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion classifai.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* Update URI: https://classifaiplugin.com
* Description: Enhance your WordPress content with Artificial Intelligence and Machine Learning services.
* Version: 2.3.0-dev
* Requires at least: 5.7
* Requires at least: 5.8
* Requires PHP: 7.4
* Author: 10up
* Author URI: https://10up.com
Expand Down
235 changes: 218 additions & 17 deletions includes/Classifai/Providers/OpenAI/ChatGPT.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,9 @@ public function __construct( $service ) {
'title' => __( 'OpenAI ChatGPT', 'classifai' ),
'fields' => array( 'api-key' ),
'features' => array(
'enable_excerpt' => __( 'Excerpt generation', 'classifai' ),
'enable_titles' => __( 'Title generation', 'classifai' ),
'enable_excerpt' => __( 'Excerpt generation', 'classifai' ),
'enable_titles' => __( 'Title generation', 'classifai' ),
'enable_resize_content' => __( 'Content resizing', 'classifai' ),
),
);
}
Expand All @@ -72,8 +73,9 @@ public function is_feature_enabled( string $feature = '' ) {
$feature_roles = [];

$role_keys = [
'enable_excerpt' => 'roles',
'enable_titles' => 'title_roles',
'enable_excerpt' => 'roles',
'enable_titles' => 'title_roles',
'enable_resize_content' => 'resize_content_roles',
];

if ( isset( $role_keys[ $feature ] ) ) {
Expand Down Expand Up @@ -108,7 +110,7 @@ public function is_feature_enabled( string $feature = '' ) {
* This only fires if can_register returns true.
*/
public function register() {
add_action( 'enqueue_block_editor_assets', [ $this, 'enqueue_editor_assets' ] );
add_action( 'enqueue_block_assets', [ $this, 'enqueue_editor_assets' ] );
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_admin_assets' ] );
add_action( 'edit_form_before_permalink', [ $this, 'register_generated_titles_template' ] );
}
Expand Down Expand Up @@ -172,6 +174,24 @@ public function enqueue_editor_assets() {
'before'
);
}

if ( $this->is_feature_enabled( 'enable_resize_content' ) ) {
wp_enqueue_script(
'classifai-content-resizing-plugin-js',
CLASSIFAI_PLUGIN_URL . 'dist/content-resizing-plugin.js',
get_asset_info( 'content-resizing-plugin', 'dependencies' ),
get_asset_info( 'content-resizing-plugin', 'version' ),
true
);

wp_enqueue_style(
'classifai-content-resizing-plugin-css',
CLASSIFAI_PLUGIN_URL . 'dist/content-resizing-plugin.css',
[],
CLASSIFAI_PLUGIN_VERSION,
'all'
);
}
}

/**
Expand All @@ -184,10 +204,8 @@ public function enqueue_admin_assets( $hook_suffix = '' ) {
return;
}

$screen = get_current_screen();
$settings = $this->get_settings();
$user_roles = wp_get_current_user()->roles ?? [];
$title_roles = $settings['title_roles'] ?? [];
$screen = get_current_screen();
$settings = $this->get_settings();

// Load the assets for the classic editor.
if ( $screen && ! $screen->is_block_editor() ) {
Expand Down Expand Up @@ -410,6 +428,60 @@ public function setup_fields_sections() {
'description' => __( 'Number of titles that will be generated in one request.', 'classifai' ),
]
);

// Add resizing content fields.
add_settings_section(
$this->get_option_name() . '_resize_content_settings',
esc_html__( 'Resizing content settings', 'classifai' ),
'',
$this->get_option_name()
);

add_settings_field(
'enable-resize-content',
esc_html__( 'Enable content resizing', 'classifai' ),
[ $this, 'render_input' ],
$this->get_option_name(),
$this->get_option_name() . '_resize_content_settings',
[
'label_for' => 'enable_resize_content',
'input_type' => 'checkbox',
'default_value' => $default_settings['enable_resize_content'],
'description' => __( '"Shrink content" and "Grow content" menu items will be added to the paragraph block\'s toolbar menu.', 'classifai' ),
]
);

$content_resize_roles = $roles;

unset( $content_resize_roles['contributor'], $content_resize_roles['subscriber'] );

add_settings_field(
'resize-content-roles',
esc_html__( 'Allowed roles', 'classifai' ),
[ $this, 'render_checkbox_group' ],
$this->get_option_name(),
$this->get_option_name() . '_resize_content_settings',
[
'label_for' => 'resize_content_roles',
'options' => $content_resize_roles,
'default_values' => $default_settings['resize_content_roles'],
'description' => __( 'Choose which roles are allowed to resize content.', 'classifai' ),
]
);

add_settings_field(
'number-resize-content',
esc_html__( 'Number of suggestions', 'classifai' ),
[ $this, 'render_select' ],
$this->get_option_name(),
$this->get_option_name() . '_resize_content_settings',
[
'label_for' => 'number_resize_content',
'options' => array_combine( range( 1, 10 ), range( 1, 10 ) ),
'default_value' => $default_settings['number_resize_content'],
'description' => __( 'Number of suggestions that will be generated in one request.', 'classifai' ),
]
);
}

/**
Expand Down Expand Up @@ -462,6 +534,24 @@ public function sanitize_settings( $settings ) {
$new_settings['number_titles'] = 1;
}

if ( empty( $settings['enable_resize_content'] ) || 1 !== (int) $settings['enable_resize_content'] ) {
$new_settings['enable_resize_content'] = 'no';
} else {
$new_settings['enable_resize_content'] = '1';
}

if ( isset( $settings['resize_content_roles'] ) && is_array( $settings['resize_content_roles'] ) ) {
$new_settings['resize_content_roles'] = array_map( 'sanitize_text_field', $settings['resize_content_roles'] );
} else {
$new_settings['resize_content_roles'] = array_keys( get_editable_roles() ?? [] );
}

if ( isset( $settings['number_resize_content'] ) && is_numeric( $settings['number_resize_content'] ) && (int) $settings['number_resize_content'] >= 1 && (int) $settings['number_resize_content'] <= 10 ) {
$new_settings['number_resize_content'] = absint( $settings['number_resize_content'] );
} else {
$new_settings['number_resize_content'] = 1;
}

return $new_settings;
}

Expand All @@ -484,14 +574,17 @@ public function get_default_settings() {
$editable_roles = get_editable_roles() ?? [];

return [
'authenticated' => false,
'api_key' => '',
'enable_excerpt' => false,
'roles' => array_keys( $editable_roles ),
'length' => (int) apply_filters( 'excerpt_length', 55 ),
'enable_titles' => false,
'title_roles' => array_keys( $editable_roles ),
'number_titles' => 1,
'authenticated' => false,
'api_key' => '',
'enable_excerpt' => false,
'roles' => array_keys( $editable_roles ),
'length' => (int) apply_filters( 'excerpt_length', 55 ),
'enable_titles' => false,
'title_roles' => array_keys( $editable_roles ),
'number_titles' => 1,
'enable_resize_content' => false,
'resize_content_roles' => array_keys( $editable_roles ),
'number_resize_content' => 1,
];
}

Expand Down Expand Up @@ -519,6 +612,8 @@ public function get_provider_debug_information( $settings = null, $configured =
__( 'Generate titles', 'classifai' ) => $enable_titles ? __( 'yes', 'classifai' ) : __( 'no', 'classifai' ),
__( 'Allowed roles (titles)', 'classifai' ) => implode( ', ', $settings['title_roles'] ?? [] ),
__( 'Number of titles', 'classifai' ) => absint( $settings['number_titles'] ?? 1 ),
__( 'Allowed roles (resize)', 'classifai' ) => implode( ', ', $settings['resize_content_roles'] ?? [] ),
__( 'Number of suggestions', 'classifai' ) => absint( $settings['number_resize_content'] ?? 1 ),
__( 'Latest response', 'classifai' ) => $this->get_formatted_latest_response( get_transient( 'classifai_openai_chatgpt_latest_response' ) ),
];
}
Expand Down Expand Up @@ -548,6 +643,9 @@ public function rest_endpoint_callback( $post_id = 0, $route_to_call = '', $args
case 'title':
$return = $this->generate_titles( $post_id, $args );
break;
case 'resize_content':
$return = $this->resize_content( $post_id, $args );
break;
}

return $return;
Expand Down Expand Up @@ -755,6 +853,109 @@ public function generate_titles( int $post_id = 0, array $args = [] ) {
return $return;
}

/**
* Resizes content.
*
* @param int $post_id The Post Id we're processing
* @param array $args Arguments passed in.
* @return string|WP_Error
*/
public function resize_content( int $post_id, array $args = array() ) {
if ( ! $post_id || ! get_post( $post_id ) ) {
return new WP_Error( 'post_id_required', esc_html__( 'Post ID is required to resize content.', 'classifai' ) );
}

$settings = $this->get_settings();
$args = wp_parse_args(
array_filter( $args ),
[
'num' => $settings['number_resize_content'] ?? 1,
]
);

$request = new APIRequest( $settings['api_key'] ?? '' );

if ( 'shrink' === $args['resize_type'] ) {
$prompt = 'Decrease the content length no more than 2 to 4 sentences.';
} else {
$prompt = 'Increase the content length no more than 2 to 4 sentences.';
}

/**
* Filter the resize prompt we will send to ChatGPT.
*
* @since 2.3.0
*
* @param {string} $prompt Resize prompt we are sending to ChatGPT. Gets added as a system prompt.
* @param {int} $post_id ID of post.
* @param {array} $args Arguments passed to endpoint.
*
* @return {string} Prompt.
*/
$prompt = apply_filters( 'classifai_chatgpt_' . $args['resize_type'] . '_content_prompt', $prompt, $post_id, $args );

/**
* Filter the resize request body before sending to ChatGPT.
*
* @since 2.3.0
* @hook classifai_chatgpt_resize_content_request_body
*
* @param {array} $body Request body that will be sent to ChatGPT.
* @param {int} $post_id ID of post.
*
* @return {array} Request body.
*/
$body = apply_filters(
'classifai_chatgpt_resize_content_request_body',
[
'model' => $this->chatgpt_model,
'messages' => [
[
'role' => 'system',
'content' => $prompt,
],
[
'role' => 'user',
dkotter marked this conversation as resolved.
Show resolved Hide resolved
'content' => esc_html( $args['content'] ),
],
],
'temperature' => 0.9,
'n' => absint( $args['num'] ),
],
$post_id
);

// Make our API request.
$response = $request->post(
$this->chatgpt_url,
[
'body' => wp_json_encode( $body ),
]
);

set_transient( 'classifai_openai_chatgpt_latest_response', $response, DAY_IN_SECONDS * 30 );

if ( is_wp_error( $response ) ) {
return $response;
}

if ( empty( $response['choices'] ) ) {
return new WP_Error( 'no_choices', esc_html__( 'No choices were returned from OpenAI.', 'classifai' ) );
}

// Extract out the text response.
$return = [];

foreach ( $response['choices'] as $choice ) {
if ( isset( $choice['message'], $choice['message']['content'] ) ) {
// ChatGPT often adds quotes to strings, so remove those as well as extra spaces.
$return[] = sanitize_text_field( trim( $choice['message']['content'], ' "\'' ) );
}
}

return $return;
}

/**
* Get our content, trimming if needed.
*
Expand Down
Loading
Loading