I built a WordPress AI plugin to make authors more productive. Here's how
Learn how to build WordPress plugin from scratch and integrate it with Amazon Bedrock to build an AI content generator plugin.
- AWS Account: You'll need an active Amazon Web Services (AWS) account to access Amazon Bedrock and its associated resources.
- Docker: The guide will utilize Docker containers to run WordPress and MySQL, so you'll need to have Docker installed on your development machine. You can skip this if you already have a working WordPress environment.
- Log in to your AWS Management Console and navigate to the IAM service page.
- Click on Users in the left-hand menu, then click Create user.
- Enter
wp-ai-user
as User name and click Next - For the Permissions options, choose Attach policies directly
- Enter “bedrock” in the Search box
- Choose AmazonBedrockFullAccess and click Next
- On the Review and create step click Create user
- On the Users list click
wp-ai-user
. - Click Security credentials tab, then click Create access key button
- For the use case, choose Local code and make sure to tick the confirmation and click Next
- Click Create access key
- Log in to your AWS Management Console and change the region to US East (N. Virginia)
- Navigate to Amazon Bedrock service page
- Click Model access in the left-hand menu
- Click Manage model access button in the top right corner
- Activate the base models that you want. In my case I activated all the base models so the WordPress administrator will have a broad of choices of models when generating content.
- Click Save changes to apply
wp-ai-plugin-tutorial
.1
2
mkdir wp-ai-plugin-tutorial
cd wp-ai-plugin-tutorial
my-ai-content-generator
and put it under ./wordpress/wp-content/plugins
. This directory will be mounted to the WordPress container.1
mkdir -p wordpress/wp-content/plugins/my-ai-content-generator
docker-compose.yml
file.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
version: '3.9'
services:
wordpress:
image: wordpress:6-apache
ports:
- 8080:80
volumes:
- ./wordpress/wp-content/plugins/my-ai-content-generator:/var/www/html/wp-content/plugins/my-ai-content-generator
environment:
WORDPRESS_DB_HOST: mysql
WORDPRESS_DB_NAME: wp_demo
WORDPRESS_DB_USER: wp_user
WORDPRESS_DB_PASSWORD: wp-demo-ai
restart: always
mysql:
image: mysql:8
volumes:
- ./mysql:/var/lib/mysql
environment:
MYSQL_DATABASE: wp_demo
MYSQL_USER: wp_user
MYSQL_PASSWORD: wp-demo-ai
MYSQL_ROOT_PASSWORD: root-demo-ai
restart: always
1
docker compose up -d
http://localhost:8080/
where you should see the WordPress installation page. Follow the instructions to complete the installation.
wp-content/plugins/my-ai-content-generator/
directory.1
cd wordpress/wp-content/plugins/my-ai-content-generator
my-ai-content-generator.php
. Run the following command to create the file.1
touch my-ai-content-generator.php
my-ai-content-generator.php
.1
2
3
4
5
/**
* Plugin Name: My AI Content Generator
* Description: An AI content generator plugin powered by Amazon Bedrock.
*/

my-ai-content-generator/
directory.1
2
3
4
docker run --rm --interactive --tty \
-v $(pwd):/app \
-u $(id -u):$(id -g) \
composer require aws/aws-sdk-php
1
ls -1
1
2
3
4
composer.json
composer.lock
my-ai-content-generator.php
vendor
openssl_encrypt
function or similar. However, for the purpose of this post, I will save it as plain text to the database.my-ai-content-generator.php
as shown below.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
/**
* Plugin Name: My AI Content Generator
* Description: An AI content generator plugin powered by Amazon Bedrock
*/
// Exit if accessed directly
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
// Hooks into admin_menu to add our AI content generator settings page:
// AWS credentials page and foundation models selection page
add_action('admin_menu', 'my_ai_settings_menu');
/**
* Function to render AWS credentials page.
*
* @return void
*/
function my_ai_credentials_page() {
// Check user permissions
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
my_ai_save_credentials();
// Get the current values of the access key id from the database
// Option name is 'my_ai_credentials' and has two keys 'access_key_id' and 'secret_access_key'
// (We never display the secret access key to the user)
$credentials = get_option( 'my_ai_credentials', ['access_key_id' => ''] );
$access_key_id = $credentials['access_key_id'];
// If query string updated=true exists then display a success message
$success_message = false;
if ( isset( $_GET['updated'] ) && $_GET['updated'] === 'true' ) {
$success_message = true;
}
require __DIR__ . '/views/aws-credentials-page.php';
}
/**
* Function to save AWS credentials to the database.
*
* @return void
*/
function my_ai_save_credentials() {
// Get the submitted values from the form
$option_page = $_POST['option_page'] ?? '';
$access_key_id = $_POST['access_key_id'] ?? '';
$secret_access_key = $_POST['secret_access_key'] ?? '';
// Only proceed if option_page is my-ai-credentials-page
if ( $option_page !== 'my-ai-credentials-page' ) {
return;
}
// Save the credentials to the database
update_option('my_ai_credentials', [
'access_key_id' => $access_key_id,
'secret_access_key' => $secret_access_key,
]);
}
/**
* Function to add our AI content generator settings page to the admin menu.
*
* @return void
*/
function my_ai_settings_menu() {
// Foundation model selection page
add_menu_page(
'Foundation models', // page title
'My AI Content Generator', // menu title
'manage_options', // capability
'my-ai-models-page', // menu slug
// callback function to render the page content
function() {
return ''; // Temporary output, will be updated later
},
'dashicons-admin-generic',
);
// AWS credentials page
add_submenu_page(
'my-ai-models-page', // parent menu slug
'AWS credentials', // page title
'AWS credentials', // menu title
'manage_options', // capability
'my-ai-credentials-page', // menu slug
// callback function to render the page content
'my_ai_credentials_page',
);
}
views/
to store HTML pages.1
mkdir views
views/aws-credentials-page.php
for displaying AWS Credentials page.1
touch views/aws-credentials-page.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// Exit if accessed directly
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
// If this is a POST request, no need to display the page and redirect using javascript
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
echo '<script>window.location = "' . admin_url('admin.php?page=my-ai-credentials-page&updated=true') . '";</script>';
return;
}
<div class="wrap">
<h1>AWS Credentials</h1>
<?php if ($success_message): ?>
<div class="updated notice notice-success is-dismissible"><p>AWS credentials saved successfully!</p></div>
<?php endif; ?>
<p>Enter your AWS credentials to use <strong>My AI Content Generator</strong>. Make sure to follow IAM best practices such as applying <a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#grant-least-privilege" target="_blank">principle of least-privilege</a>.</p>
<form method="post"><?php
settings_fields('my-ai-credentials-page');
?><table class="form-table">
<tr valign="top">
<th scope="row">Access Key ID</th>
<td><input required type="text" size="30" name="access_key_id" value="<?php echo esc_attr($access_key_id); ?>" /></td>
</tr>
<tr valign="top">
<th scope="row">Secret Access Key</th>
<td><input required type="password" size="30" name="secret_access_key" value="" /></td>
</tr>
</table><?php
submit_button();
?></form>
</div>
my-ai-content-generator/
directory should look like this.1
ls -1 *
1
2
3
4
5
6
7
8
9
10
composer.json
composer.lock
my-ai-content-generator.php
vendor:
autoload.php
...
views:
aws-credentials-page.php
wp-ai-user
that you created in previous steps.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Initialize Amazon Bedrock using AWS SDK for PHP
require __DIR__ . '/vendor/autoload.php';
use Aws\Bedrock\BedrockClient;
use Aws\BedrockRuntime\BedrockRuntimeClient;
$bedrock = new BedrockClient([
'region' => 'us-east-1',
'version' => 'latest',
'credentials' => [
'key' => YOUR_ACCESS_KEY_ID
'secret' => YOUR_SECRET_ACCESS_KEY
]
]);
$bedrock_runtime = new BedrockRuntimeClient([
'region' => 'us-east-1',
'version' => 'latest',
'credentials' => [
'key' => YOUR_ACCESS_KEY_ID
'secret' => YOUR_SECRET_ACCESS_KEY
]
]);
BedrockClient
is used to manage the foundation models e.g list available foundation models. The other, BedrockRuntimeClient
is used to run inference API to the model.listFoundationModels()
method.1
$bedrock->listFoundationModels();
my-ai-content-generator.php
to provide authors the ability to select models which are going to be displayed when creating a post. Replace all the contents of my-ai-content-generator.php
with the one below.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
/**
* Plugin Name: My AI Content Generator
* Description: An AI content generator plugin powered by Amazon Bedrock
*/
// Exit if accessed directly
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
// Load the AWS SDK for PHP from Composer autoload
require __DIR__ . '/vendor/autoload.php';
use Aws\Bedrock\BedrockClient;
use Aws\BedrockRuntime\BedrockRuntimeClient;
// Get current AWS credentials from the database
$my_ai_credentials = get_option('my_ai_credentials', ['access_key_id' => '', 'secret_access_key' => '']);
// Initialize BedrockClient and BedrockRuntimeClient, default to us-east-1
$bedrock = new BedrockClient([
'credentials' => [
'key' => $my_ai_credentials['access_key_id'],
'secret' => $my_ai_credentials['secret_access_key'],
],
'region' => 'us-east-1',
]);
$bedrock_runtime = new BedrockRuntimeClient([
'credentials' => [
'key' => $my_ai_credentials['access_key_id'],
'secret' => $my_ai_credentials['secret_access_key'],
],
'region' => 'us-east-1',
]);
/**
* Function to return instance of BedrockClient.
*
* @return BedrockClient
*/
function my_ai_bedrock_client() {
global $bedrock;
return $bedrock;
}
/**
* Function to return instance of BedrockRuntimeClient.
*
* @return BedrockRuntimeClient
*/
function my_ai_bedrock_runtime_client() {
global $bedrock_runtime;
return $bedrock_runtime;
}
// Hooks into admin_menu to add our AI content generator settings page:
// AWS credentials page and foundation models selection page
add_action('admin_menu', 'my_ai_settings_menu');
/**
* Function to render AWS credentials page.
*
* @return void
*/
function my_ai_credentials_page() {
// Check user permissions
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
my_ai_save_credentials();
// Get the current values of the access key id from the database
// Option name is 'my_ai_credentials' and has two keys 'access_key_id' and 'secret_access_key'
// (We never display the secret access key to the user)
$credentials = get_option( 'my_ai_credentials', ['access_key_id' => ''] );
$access_key_id = $credentials['access_key_id'];
// If query string updated=true exists then display a success message
$success_message = false;
if ( isset( $_GET['updated'] ) && $_GET['updated'] === 'true' ) {
$success_message = true;
}
require __DIR__ . '/views/aws-credentials-page.php';
}
/**
* Function to save AWS credentials to the database.
*
* @return void
*/
function my_ai_save_credentials() {
// Get the submitted values from the form
$option_page = $_POST['option_page'] ?? '';
$access_key_id = $_POST['access_key_id'] ?? '';
$secret_access_key = $_POST['secret_access_key'] ?? '';
// Only proceed if option_page is my-ai-credentials-page
if ( $option_page !== 'my-ai-credentials-page' ) {
return;
}
// Save the credentials to the database
update_option('my_ai_credentials', [
'access_key_id' => $access_key_id,
'secret_access_key' => $secret_access_key,
]);
}
/**
* Function to get list of foundation models from the Bedrock and caches the result to database.
*
* The database option_name should be 'my_ai_foundation_models'. It has two keys:
* 1. 'foundation_models' - an array of foundation models
* 2. 'last_updated' - the timestamp of the last update
*
* When function is called it check the cache expiration (1 day). If it expires then
* call the Bedrock API and update the cache.
*
* Response is associative arrays with 2 keys:
* - 'error' - default to null
* - 'items' - The list of foundation models
*
* @param BedrockClient $client
* @return array - list of foundation models
*/
function my_ai_get_foundation_models($client) {
$foundation_models = get_option('my_ai_foundation_models', ['foundation_models' => [], 'last_updated' => 0]);
// Check if the cache is expired (1 day)
$now = time();
$cache_expiration = 86400; // 1 day
if ( $now - $foundation_models['last_updated'] > $cache_expiration ) {
try {
// Call the Bedrock API to get the list of foundation models
$response = $client->listFoundationModels();
} catch (Exception $e) {
// If there is an error then return an empty array
return ['error' => $e->getMessage(), 'items' => []];
}
// Update the cache
update_option('my_ai_foundation_models', [
'foundation_models' => $response['modelSummaries'],
'last_updated' => $now,
]);
// Return the list of foundation models
return $response['modelSummaries'];
}
// Return the cached list of foundation models
return $foundation_models['foundation_models'];
}
/**
* Function to render foundation model selection page.
*
* @return void
*/
function my_ai_models_page() {
// Check user permissions
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
my_ai_save_selected_foundation_models();
$bedrock = my_ai_bedrock_client();
// Get current values of the foundation models from the database
$foundation_models = my_ai_get_foundation_models($bedrock);
// Get current selected foundation models from the database
$selected_foundation_models = get_option('my_ai_selected_foundation_models', []);
// If query string updated=true exists then display a success message
$success_message = false;
if ( isset( $_GET['updated'] ) && $_GET['updated'] === 'true' ) {
$success_message = true;
}
// Link to AWS credentials page
$aws_credentials_link = admin_url('admin.php?page=my-ai-credentials-page');
require __DIR__ . '/views/foundation-models-page.php';
}
/**
* Function to save selected foundation models to the database.
*
* @return void
*/
function my_ai_save_selected_foundation_models() {
// Get the submitted values from the form
$option_page = $_POST['option_page'] ?? '';
$selected_foundation_models = $_POST['foundation_models'] ?? [];
// Only proceed if option_page is my-ai-models-page
if ( $option_page !== 'my-ai-models-page' ) {
return;
}
// Save the selected foundation models to the database
update_option('my_ai_selected_foundation_models', $selected_foundation_models);
}
/**
* Function to add our AI content generator settings page to the admin menu.
*
* @return void
*/
function my_ai_settings_menu() {
// Foundation model selection page
add_menu_page(
'Foundation models', // page title
'My AI Content Generator', // menu title
'manage_options', // capability
'my-ai-models-page', // menu slug
// callback function to render the page content
'my_ai_models_page',
'dashicons-admin-generic', // menu icon
);
// AWS credentials page
add_submenu_page(
'my-ai-models-page', // parent menu slug
'Setup AWS Credentials', // page title
'AWS Credentials', // menu title
'manage_options', // capability
'my-ai-credentials-page', // menu slug
// callback function to render the page content
'my_ai_credentials_page',
);
}
my_ai_get_foundation_models()
function, it caches the result for 1 day. This is to improves the performance of the page, so it does not need to call listFoundationModels()
API each time the page is loaded.views/foundation-models-page.php
.1
touch views/foundation-models-page.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
// Exit if accessed directly
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
// If this is a POST request, no need to display the page and redirect using javascript
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
echo '<script>window.location = "' . admin_url('admin.php?page=my-ai-models-page&updated=true') . '";</script>';
return;
}
<div class="wrap">
<h1>My AI Content Generator</h1>
<?php if ($success_message) : ?>
<div class="updated notice notice-success is-dismissible"><p>Selected models saved successfully!</p></div>
<?php endif; ?>
<p>My AI Content Generator helps you write content quickly and efficiently using AI.</p>
<h2>Select Foundation Models</h2>
<p>Please select the foundation models you want to use to generate your content. Each foundation model is trained on a specific dataset and can be used to generate content of different types and sizes. Currently the default region is set to <strong>us-east-1</strong>.</p>
<?php if (isset($foundation_models['error'])) : ?>
<p>No foundation models found. </p>
<p>Make sure your <a href="<?php echo esc_url($aws_credentials_link); ?>">AWS credentials</a> is correct and having proper permissions.</p>
<p><strong>Message</strong>:<br><i><?php echo esc_html($foundation_models['error']); ?></i></p>
<?php return; endif; ?>
<form method="post"><?php
settings_fields('my-ai-models-page');
$counter = 0;
?><table class="widefat striped">
<thead>
<tr>
<td id="cb" class="manage-column column-cb check-column"><input id="cb-select-all-1" type="checkbox">
<label for="cb-select-all-1"><span class="screen-reader-text">Select All</span></label></td>
<th>No</th>
<th>Name</th>
<th>Id</th>
<th>Provider</th>
<th>Input</th>
<th>Output</th>
</tr>
</thead>
<tbody>
<?php foreach ($foundation_models as $foundation_model) : ?>
<?php
// Only show model which outputModalities is TEXT
$is_outputmodality_text = in_array('TEXT', $foundation_model['outputModalities']);
if (! $is_outputmodality_text) {
continue;
}
// Only show supported model, which the model id is not end with suffix any number + 'k'
$is_supported_model = ! preg_match('/\d+k$/', $foundation_model['modelId']);
if (! $is_supported_model) {
continue;
}
// Exclude model ids which not support on-demand throughput
$excluded_model_ids = ['meta.llama2-13b-v1', 'meta.llama2-70b-v1'];
if (in_array($foundation_model['modelId'], $excluded_model_ids)) {
continue;
}
// Define checked variable when current foundation_model is selected
$checked = in_array($foundation_model['modelId'], $selected_foundation_models) ? 'checked' : '';
<tr class="iedit">
<td class="check-column" style="padding: 8px 10px"><input <?php echo $checked; ?> type="checkbox" name="foundation_models[]" value="<?php echo esc_attr($foundation_model['modelId']); ?>"></td>
<td><?php echo ++$counter; ?></td>
<td><?php echo esc_html($foundation_model['modelName']); ?></td>
<td><?php echo esc_html($foundation_model['modelId']); ?></td>
<td><?php echo esc_html($foundation_model['providerName']); ?></td>
<td><?php echo esc_html(implode(', ', $foundation_model['inputModalities'])); ?></td>
<td><?php echo esc_html(implode(', ', $foundation_model['outputModalities'])); ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table><?php
submit_button(); ?>
</form>
</div>
my-ai-content-generator/
directory should like this:1
ls -l *
1
2
3
4
5
6
7
8
9
10
11
composer.json
composer.lock
my-ai-content-generator.php <- modified
vendor:
autoload.php
...
views:
aws-credentials-page.php
foundation-models-page.php <- new file

invokeModel()
method from BedrockRuntimeClient object.1
$bedrock_runtime->invokeModel($params);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$body = [
'prompt' => 'write a joke about mathematics',
'max_tokens' => 1024,
'temperature' => 1,
'top_p' => 0.8,
'top_k' => 200,
'stop' => []
];
$model_params = [
'modelId' => 'mistral.mistral-7b-instruct-v0:2',
'contentType' => 'application/json',
'body' => json_encode($body)
];
<s>[INST]Your prompt[/INST]
. So, creating a function to abstract the invoke and response retrieval would be a good move. Following are some functions to abstract those tasks:1
2
my_ai_build_bedrock_body($model_id, $params);
my_ai_parse_bedrock_response($model_id, $response);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
You are an intelligent AI assistant for writing a blog post. You are an expert to generate very long, detailed and SEO optimized article.
You must take into consideration rules below when generating article:
- The first line of your response should be the title of the blog post followed by a blank line.
- Title MUST be put within <my_ai_title></my_ai_title> tags.
- The article content MUST be put within <my_ai_content></my_ai_contents> tags.
- The summary of the content MUST be put within <my_ai_summary></my_ai_summary> tags.
- Take a look at the additional instruction inside <query></query> tags to generate the content of the article.
- Article format MUST be in HTML
- Make sure to wrap each paragraph with tag <p></p>.
- Make sure to wrap each heading with tag <h2></h2> or <h3></h3>. Depending on the heading level.
- Important: Skip the preamble from your response. NEVER generate text before the article.
Here is an example of the format:
<example>
<my_ai_title>This is example title</my_ai_title>
<my_ai_content>
...[cut]...
</my_ai_content>
<my_ai_summary>
This is example of the summary of the article.
</my_ai_summary>
</example>
rest_api_init
and call the register_rest_route()
function. Here’s an example:1
add_action( 'rest_api_init', 'my_ai_register_rest_apis' );
/my-ai-content-generator/v1/contents
as my REST endpoint to generate the AI content. The full endpoint with the hostname should look like this:1
http://localhost:8080/?rest_route=/my-ai-content-generator/v1/contents
1
http://localhost:8080/wp-json/my-ai-content-generator/v1/contents
my-ai-rest-api.php
under my-ai-content-generator/
directory.1
touch my-ai-rest-api.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
// Exit if accessed directly
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
// Hooks into 'rest_api_init' to add new REST API endpoints
add_action( 'rest_api_init', 'my_ai_register_rest_apis' );
/**
* Function to register new REST API endpoints
*
* @return void
*/
function my_ai_register_rest_apis() {
// Create REST API route to geneterate AI content
register_rest_route('my-ai-content-generator/v1', '/contents', [
'methods' => 'POST',
'callback' => function($request) {
$bedrock_runtime = my_ai_bedrock_runtime_client();
$content = my_ai_generate_content($bedrock_runtime, $_POST);
return $content;
},
'permission_callback' => function() {
if (! current_user_can('edit_posts')) {
return new WP_Error('rest_forbidden', esc_html__('You do not have permission to edit posts.'), [
'status' => rest_authorization_required_code(),
]);
}
return true;
},
]);
}
/**
* Function to build parameter body which sent to Amazon Bedrock
*
* @param string $model_id
* @param array $params
* @return array
*/
function my_ai_build_bedrock_body($model_id, $params) {
$param_body = [];
switch (true) {
// Amazon Titan parameters
case strpos($model_id, 'amazon.titan') === 0:
$param_body = [
'inputText' => $params['prompt'],
'textGenerationConfig' => [
'maxTokenCount' => $params['max_tokens'] ? $params['max_tokens'] : 4096,
'temperature' => $params['temperature'] ? $params['temperature'] : 0.8,
'topP' => $params['top_p'] ? $params['top_p'] : 0.9,
'stopSequences' => []
]
];
break;
// AI21 labs Jurassic parameters
case strpos($model_id, 'ai21.j2') === 0:
$param_body = [
'prompt' => $params['prompt'],
'maxTokens' => $params['max_tokens'] ? $params['max_tokens'] : 4096,
'temperature' => $params['temperature'] ? $params['temperature'] : 0.8,
'topP' => $params['top_p'] ? $params['top_p'] : 0.9,
'stopSequences' => [],
'countPenalty' => [
'scale' => 0
],
'presencePenalty' => [
'scale' => 0
],
'frequencyPenalty' => [
'scale' => 0
],
];
break;
// Anthropic Claude parameters
case strpos($model_id, 'anthropic.claude') === 0:
$param_body = [
'anthropic_version' => 'bedrock-2023-05-31',
'max_tokens' => $params['max_tokens'] ? $params['max_tokens'] : 4096,
'temperature' => $params['temperature'] ? $params['temperature'] : 0.8,
'top_k' => $params['top_k'] ? $params['top_k'] : 200,
'top_p' => $params['top_p'] ? $params['top_p'] : 0.9,
'stop_sequences' => ["\\n\\nHuman:"],
'messages' => [
[
'role' => 'user',
'content' => [
[
'type' => 'text',
'text' => $params['prompt'],
]
]
]
]
];
break;
// Cohere Command parameters
case strpos($model_id, 'cohere.command') === 0:
$param_body = [
'prompt' => $params['prompt'],
'max_tokens' => $params['max_tokens'] ? $params['max_tokens'] : 4000,
'temperature' => $params['temperature'] ? $params['temperature'] : 0.8,
'p' => $params['top_p'] ? $params['top_p'] : 0.9,
'k' => $params['top_k'] ? $params['top_k'] : 200,
'stop_sequences' => [],
'return_likelihoods' => 'NONE',
'stream' => false
];
break;
// Meta Llama2 parameters
case strpos($model_id, 'meta.llama') === 0:
$param_body = [
'prompt' => $params['prompt'],
'max_gen_len' => $params['max_tokens'] ? $params['max_tokens'] : 2048,
'temperature' => $params['temperature'] ? $params['temperature'] : 0.8,
'top_p' => $params['top_p'] ? $params['top_p'] : 0.9
];
break;
// Mistral/Mixtral parameters
case strpos($model_id, 'mistral') === 0:
$param_body = [
'prompt' => $params['prompt'],
'max_tokens' => $params['max_tokens'] ? $params['max_tokens'] : 4096,
'temperature' => $params['temperature'] ? $params['temperature'] : 0.8,
'top_p' => $params['top_p'] ? $params['top_p'] : 0.9,
'top_k' => $params['top_k'] ? $params['top_k'] : 200,
'stop' => []
];
break;
}
return $param_body;
}
/**
* Function to parse the response of Amazon Bedrock InvokeModel()
*
* @param string $model_id - Amazon Bedrock model id
* @param array $response - Amazon Bedrock InvokeModel() response
* @return array - ['text' => '', 'error' => '']
*/
function my_ai_parse_bedrock_response($model_id, $response) {
$parsed_response = [];
switch (true) {
// Amazon Titan response
case strpos($model_id, 'amazon.titan') === 0:
$parsed_response = [
'error' => null,
'text' => $response['results'][0]['outputText']
];
break;
// AI21 labs Jurassic response
case strpos($model_id, 'ai21.j2') === 0:
$parsed_response = [
'error' => null,
'text' => $response['completions'][0]['data']['text']
];
break;
// Anthropic Claude response
case strpos($model_id, 'anthropic.claude') === 0:
$parsed_response = [
'error' => null,
'text' => $response['content'][0]['text'] ?? ''
];
break;
// Cohere Command response
case strpos($model_id, 'cohere.command') === 0:
$parsed_response = [
'error' => null,
'text' => $response['generations'][0]['text'] ?? ''
];
break;
// Meta Llama2 response
case strpos($model_id, 'meta.llama') === 0:
$parsed_response = [
'error' => null,
'text' => $response['generation'] ?? ''
];
break;
// Mistral/Mixtral response
case strpos($model_id, 'mistral') === 0:
$parsed_response = [
'error' => null,
'text' => $response['outputs'][0]['text'] ?? ''
];
break;
}
return $parsed_response;
}
/**
* Function to run inference on Amazon Bedrock
*
* @param BedrockRuntimeClient $client
* @param string $model_id
* @param array $params
* @return array
*/
function my_ai_invoke_bedrock($client, $model_id, $params) {
try {
$body = my_ai_build_bedrock_body($model_id, $params);
$invoke_params = [
'modelId' => $model_id,
'contentType' => 'application/json',
'body' => json_encode($body),
];
$response = $client->invokeModel($invoke_params);
$response_body = json_decode($response['body'], true);
// Parse the response based on model id
return my_ai_parse_bedrock_response($model_id, $response_body);
} catch (Exception $e) {
return [
'error' => $e->getMessage(),
];
}
}
/**
* Function to combine our system prompt with user prompt. Some of the models
* has different prompt format.
*
* @param string $model_id
* @param stirng $user_prompt
* @return string
*/
function my_ai_build_prompt($model_id, $user_prompt) {
$system_prompt = <<<SYSTEM_PROMPT
You are an intelligent AI assistant for writing a blog post. You are an expert to generate very long, detailed and SEO optimized article.
You must take into consideration rules below when generating article:
- The first line of your response should be the title of the blog post followed by a blank line.
- Title MUST be put within <my_ai_title></my_ai_title> tags.
- The article content MUST be put within <my_ai_content></my_ai_content> tags.
- The summary of the content MUST be put within <my_ai_summary></my_ai_summary> tags. It must be put outside <my_ai_content></my_ai_content> tags.
- Article format MUST be in HTML
- Make sure to wrap each paragraph with tag <p></p>.
- Make sure to wrap each heading with tag <h2></h2> or <h3></h3>. Depending on the heading level.
- Important: Skip the preamble from your response. NEVER generate text before the article.
Here is an example of the format:
BEGIN_EXAMPLE
<my_ai_title>This is example title</my_ai_title>
<my_ai_content>
<p>This is example of opening paragraph 1.</p>
<p>This is example of opening paragraph 2.</p>
<h2>Sub heading 1</h2>
<p>This is example paragraph 1</p>
<p>This is example paragraph 2</p>
<p>This is example paragraph 3</p>
<h2>Sub heading 2</h2>
<p>This is example paragraph 1</p>
<p>This is example paragraph 2</p>
<p>This is example paragraph 3</p>
<p>This is example paragraph 4</p>
<p>This is example paragraph 5</p>
<h2>Sub heading 3</h2>
<p>This is example paragraph 1</p>
<p>This is example paragraph 2</p>
<p>This is example paragraph 3</p>
<p>This is example paragraph 4</p>
<h2>Sub heading 4</h2>
<p>This is example paragraph 1</p>
<p>This is example paragraph 2</p>
<p>This is example paragraph 3</p>
<h2>Sub heading for conclusion</h2>
<p>This is example conclusion paragraph 1</p>
<p>This is example conclusion paragraph 2</p>
</my_ai_content>
<!-- this is important </my_ai_content> should exists -->
<my_ai_summary>
This is example of the summary of the article.
</my_ai_summary>
END_EXAMPLE
SYSTEM_PROMPT;
// Add prefix or suffix to the prompt based on the value of model id
if (strpos($model_id, 'anthropic.claude-v2') === 0) {
$final_prompt = <<<FINAL_PROMPT
Human: $system_prompt
$user_prompt
Assistant:
FINAL_PROMPT;
}
if (strpos($model_id, 'meta.llama2') === 0) {
return <<<FINAL_PROMPT
<s>[INST]<<SYS>>$system_prompt<</SYS>>
$user_prompt
[/INST]
FINAL_PROMPT;
}
if (strpos($model_id, 'meta.llama3') === 0) {
return <<<FINAL_PROMPT
<|begin_of_text|><|start_header_id|>system<|end_header_id|>
$system_prompt<|eot_id|><|start_header_id|>user<|end_header_id|>
$user_prompt<|eot_id|><|start_header_id|>assistant<|end_header_id|>
FINAL_PROMPT;
}
if (strpos($model_id, 'mistral') === 0) {
return <<<FINAL_PROMPT
<s>[INST]$system_prompt
$user_prompt
[/INST]
FINAL_PROMPT;
}
return $system_prompt . "\n\n" . $user_prompt;
}
/**
* Function to generate AI content in JSON format.
*
* @param BedrockRuntimeClient $client
* @param array $params
* @return string
*/
function my_ai_generate_content($client, $params) {
$model_id = $params['model_id'];
// Build the prompt based on the model id
$prompt = my_ai_build_prompt($model_id, $params['prompt']);
$params['prompt'] = $prompt;
// Make sure to convert numerical parameters from string to integer/float
$params['max_tokens'] = intval($params['max_tokens']);
$params['temperature'] = floatval($params['temperature']);
$params['top_p'] = floatval($params['top_p']);
$params['top_k'] = intval($params['top_k']);
// Invoke the model
$response = my_ai_invoke_bedrock($client, $model_id, $params);
return json_encode($response, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_NUMERIC_CHECK);
}
my-ai-content-generator.php
to include my-ai-rest-api.php
. Put following code at the end of my-ai-content-generator.php
.1
require __DIR__ . '/my-ai-rest-api.php';
my-ai-content-generator/
should like the following:1
ls -1 *
1
2
3
4
5
6
7
8
9
10
11
12
composer.json
composer.lock
my-ai-content-generator.php <- modified
my-ai-rest-api.php <- new file
vendor:
autoload.php
...
views:
aws-credentials-page.php
foundation-models-page.php
window.React
so you can access this object anywhere in your Javascript code.window.wp
which holds many client side functionalities. In this case I am interested in window.wp.plugins
and the method registerPlugin()
. It allows registering the content generator sidebar. Here is an example:1
2
3
4
5
6
7
8
9
10
window.wp.plugins.registerPlugin('my-ai-content-generator', {
render: () => {
// Create a new PluginSidebar element
var sidebarElement = React.createElement(window.wp.editPost.PluginSidebar, {
name: 'my-ai-sidebar-element',
title: 'My AI Content Generator',
icon: 'vault',
}, childElement);
}
});
PluginSidebar
is used./my-ai-content-generator/v1/contents
, I will use the fetch()
API. Fetch API should be available in most modern browsers both on mobile and desktop. I am using standard form data content type application/x-www-form-urlencoded
for the POST body.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var response = await fetch(myAiApiUrl.toString(), {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: new URLSearchParams({
foundation_model: foundationModel,
prompt: params.prompt,
temperature: params.temperature,
top_p: params.top_p,
top_k: params.top_k || null,
max_tokens: params.max_tokens
}),
credentials: 'include'
});
wpApiSettings
provided by WordPress before making the request:1
wpApiSettings.nonce
core/block-editor
for updating the contents and core/editor
for updating title and the excerpt.1
2
3
4
5
6
7
8
// Update the title in the editor
window.wp.data.dispatch('core/editor').editPost({ title: title });
// Reset the Gutenberg content and pass our content as the replacement
window.wp.data.dispatch('core/block-editor').resetBlocks( window.wp.blocks.parse( content ));
// Update the excerpt in the sidebar
window.wp.data.dispatch('core/editor').editPost({ excerpt: summary });
my-ai-sidebar.js
in my-ai-content-generator/js/
directory.1
2
mkdir js
touch js/my-ai-sidebar.js
my-ai-sidebar.js
.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
function my_ai_sidebar_init() {
var wpBaseUrl = window.location.href.split('/wp-admin/')[0];
// myAiSelectedFoundationModels Javascript variable are injected via WordPress hooks 'enqueue_block_editor_assets'
var foundationModels = myAiSelectedFoundationModels;
var generate_my_ai_content = async (foundationModel, params) => {
var queryStringRestRoute = new URLSearchParams({
rest_route: '/my-ai-content-generator/v1/contents',
_wpnonce: wpApiSettings.nonce
});
var myAiApiUrl = wpBaseUrl + '/?' + queryStringRestRoute;
// Call AI content generator REST endpoint
var response = await fetch(myAiApiUrl.toString(), {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: new URLSearchParams({
model_id: foundationModel,
prompt: params.prompt,
temperature: params.temperature,
top_p: params.top_p,
top_k: params.top_k || null,
max_tokens: params.max_tokens
}),
credentials: 'include'
});
var data = await response.json();
// If the data is still in string, we need to convert it to JSON
if (typeof data === 'string') {
try {
var json = JSON.parse(data);
return json;
} catch (e) {}
}
return data;
};
// Object to store foundation model family configuration such as:
// temperature, top_p, top_k and max_tokens.
// Each model family has default value and maximum value of the configuration.
// As an example: 'amazon.titan' family
var foundationModelConfig = {
'amazon.titan': {
temperature: { min: 0, max: 1, default: 0.9 },
top_p: { min: 0, max: 1, default: 1 },
top_k: null,
max_tokens: { min: 0, max: 4096, default: 2048 }
},
'ai21.j2': {
temperature: { min: 0, max: 1, default: 0.7 },
top_p: { min: 0, max: 1, default: 1 },
top_k: null,
max_tokens: { min: 0, max: 8191, default: 2048 }
},
'anthropic.claude': {
temperature: { min: 0, max: 1, default: 1 },
top_p: { min: 0, max: 1, default: 0.99 },
top_k: { min: 0, max: 500, default: 200 },
max_tokens: { min: 0, max: 4096, default: 2048 }
},
'cohere.command': {
temperature: { min: 0, max: 1, default: 0.75 },
top_p: { min: 0, max: 1, default: 0 },
top_k: { min: 0, max: 500, default: 200 },
max_tokens: { min: 0, max: 4000, default: 2048 }
},
'meta.llama': {
temperature: { min: 0, max: 1, default: 0.5 },
top_p: { min: 0, max: 1, default: 0.9 },
top_k: null,
max_tokens: { min: 0, max: 2048, default: 2048 }
},
'mistral': {
temperature: { min: 0, max: 1, default: 0.5 },
top_p: { min: 0, max: 1, default: 0.9 },
top_k: { min: 0, max: 200, default: 200 },
max_tokens: { min: 0, max: 8192, default: 2048 }
}
};
/**
* Get the current short model id name from the selected model
* @param {string} foundationModel
* @returns {string}
*/
var getModelFamily = (foundationModel) => {
// Get the current short model id name from the selected model
var modelId = '';
for (var modelFamily of Object.keys(foundationModelConfig)) {
if (foundationModel.indexOf(modelFamily) > -1) {
modelId = modelFamily;
break;
}
}
return modelId;
}
// Initial short model name used by the foundationModelConfig
var modelFamily = getModelFamily(foundationModels[0]);
// Register the plugin elements into the Gutenberg sidebar
window.wp.plugins.registerPlugin("my-ai-sidebar", {
render: () => {
var [currentModelState, setCurrentModelState] = React.useState(foundationModels[0]);
var [promptState, setPromptState] = React.useState('');
var [temperatureState, setTemperatureState] = React.useState(foundationModelConfig[modelFamily].temperature);
var [topPState, setTopPState] = React.useState(foundationModelConfig[modelFamily].top_p);
var [topKState, setTopKState] = React.useState(foundationModelConfig[modelFamily].top_k);
var [maxTokensState, setMaxTokensState] = React.useState(foundationModelConfig[modelFamily].max_tokens);
var [buttonEnabledState, setButtonEnabledState] = React.useState(false);
var [generatingState, setGeneratingState] = React.useState(false);
// Create array of options element based on foundation models list
var optionsElement = foundationModels.map(modelId => {
return React.createElement('option', { value: modelId }, modelId);
});
var selectElement = React.createElement('select', {
id: 'my_ai_model_id', style: { marginBottom: '10px', display: 'block', width: '95%' },
value: currentModelState,
onChange: (e) => {
// Get the selected foundation model
var foundationModel = e.target.value;
console.log('FM -> ', foundationModel);
var modelId = getModelFamily(foundationModel);
setTemperatureState(foundationModelConfig[modelId].temperature);
setTopPState(foundationModelConfig[modelId].top_p);
setTopKState(foundationModelConfig[modelId].top_k);
setMaxTokensState(foundationModelConfig[modelId].max_tokens);
setCurrentModelState(foundationModel);
}
}, optionsElement);
var labelSelectModelElement = React.createElement('label', { display: 'block' }, 'Select foundation model:');
var labelPromptElement = React.createElement('label', { display: 'block' }, 'Input prompt:');
var inputPromptElement = React.createElement('textarea', {
id: 'my_ai_prompt', style: { marginBottom: '10px', display: 'block', width: '95%', height: '150px' },
placeholder: 'Write an article about the benefits of meditation',
value: promptState,
onChange: (e) => {
// Enable the generate button if the prompt is not empty
if (e.target.value.trim().length === 0) {
setButtonEnabledState(false);
return;
}
setPromptState(e.target.value);
setButtonEnabledState(true);
}
});
var buttonElement = React.createElement('button', {
id: 'my_ai_btn_generate', display: 'block', className: 'components-button is-primary',
disabled: !buttonEnabledState,
onClick: async (e) => {
var foundationModelId = document.getElementById('my_ai_model_id').value;
var prompt = document.getElementById('my_ai_prompt').value;
setGeneratingState(true)
// When the button clicked the label should change to "Generating...", once finished
// it should back to "Generate"
e.target.innerText = 'Generating...';
e.target.disabled = true;
// Call generate_my_ai_content to fetch the generated content via API
// Construct the foundation model parameters to send to the API
var modelParams = {
prompt: prompt,
temperature: temperatureState.default,
top_p: topPState.default,
top_k: topKState ? topKState.default : null,
max_tokens: maxTokensState.default
}
var response = await generate_my_ai_content(foundationModelId, modelParams);
console.log(response);
// The response contains two properties 'error' and 'text'
if (response.error) {
setGeneratingState(false);
alert(response.error);
e.target.innerText = 'Generate';
e.target.disabled = false;
document.getElementById('my_ai_prompt').focus();
return;
}
// If there is no <my_ai_title>, </my_ai_title>, <my_ai_content>, and <my_ai_content> tag
// in the response, then dispatch everything to the block editor.
// Otherwise, extract the title and content from the response.text
var validFormat = response.text.indexOf('<my_ai_title>') !== -1 &&
response.text.indexOf('</my_ai_title>') !== -1 &&
response.text.indexOf('<my_ai_content>') !== -1 &&
response.text.indexOf('</my_ai_content>') !== -1;
if (! validFormat) {
window.wp.data.dispatch('core/editor').editPost({ title: '[Unknown]' });
window.wp.data.dispatch('core/block-editor').resetBlocks( window.wp.blocks.parse( response.text ));
e.target.innerText = 'Generate';
e.target.disabled = false;
document.getElementById('my_ai_prompt').focus();
setGeneratingState(false);
return;
}
// Extract the title from the response.text using substring.
// The title inside the <my_ai_title>THE_TITLE</my_ai_title>
var title = response.text.substring(response.text.indexOf('<my_ai_title>') + '<my_ai_title>'.length, response.text.indexOf('</my_ai_title>'));
title = title.trim();
// Extract the content from the response.text using substring.
// The content inside the <my_ai_content>THE_CONTENT</my_ai_content>
var content = response.text.substring(response.text.indexOf('<my_ai_content>') + '<my_ai_content>'.length, response.text.indexOf('</my_ai_content>'));
content = content.trim();
// Dispatch the title into Gutenberg using wp.data.dispatch('core/editor').editPost()
window.wp.data.dispatch('core/editor').editPost({ title: title });
// Reset the Gutenberg content and pass our content as the replacement
window.wp.data.dispatch('core/block-editor').resetBlocks( window.wp.blocks.parse( content ));
// If the response.text has the summary then dispatch the core/editor excerpt
if (response.text.indexOf('<my_ai_summary>') !== -1) {
var summary = response.text.substring(response.text.indexOf('<my_ai_summary>') + '<my_ai_summary>'.length, response.text.indexOf('</my_ai_summary>'));
summary = summary.trim();
window.wp.data.dispatch('core/editor').editPost({ excerpt: summary });
}
e.target.innerText = 'Generate';
e.target.disabled = false;
document.getElementById('my_ai_prompt').focus();
setGeneratingState(false);
}
}, 'Generate');
var spanTemperatureElement = React.createElement('span', {
id: 'my_ai_temp_span', style: { fontWeight: 'bold' },
}, temperatureState.default);
var labelTemperatureElement = React.createElement('label', { display: 'block' },
'Temperature: ', spanTemperatureElement);
var inputTemperatureElement = React.createElement('input', {
id: 'my_ai_temp', type: 'range',
min: temperatureState.min,
max: temperatureState.max,
step: '0.1',
value: temperatureState.default,
style: { marginBottom: '10px', display: 'block', width: '95%' },
onChange: (e) => {
setTemperatureState({
min: temperatureState.min,
max: temperatureState.max,
default: e.target.value
});
}
});
var spanTopPElement = React.createElement('span', {
id: 'my_ai_top_p_span', style: { fontWeight: 'bold' },
}, topPState.default);
var labelTopPElement = React.createElement('label', { display: 'block' },
'Top P: ', spanTopPElement);
var inputTopPElement = React.createElement('input', {
id: 'my_ai_top_p', type: 'range',
min: topPState.min,
max: topPState.max,
step: '0.1',
value: topPState.default,
style: { marginBottom: '10px', display: 'block', width: '95%' },
onChange: (e) => {
setTopPState({
min: topPState.min,
max: topPState.max,
default: e.target.value
});
}
});
var spanTopKElement = React.createElement('span', {
id: 'my_ai_top_k_span', style: { fontWeight: 'bold', color: topKState ? 'inherit' : 'red' },
}, topKState ? topKState.default : 0);
var labelTopKElement = React.createElement('label', {
style: { 'display': topKState ? 'inline' : 'none' }
}, 'Top K: ', spanTopKElement);
var inputTopKElement = React.createElement('input', {
id: 'my_ai_top_k', type: 'range',
min: topKState ? topKState.min : null,
max: topKState ? topKState.max : null,
step: 1,
value: topKState ? topKState.default : 0,
style: { marginBottom: '10px', display: topKState ? 'block' : 'none' , width: '95%' },
onChange: (e) => {
if (topKState) {
setTopKState({
min: topKState.min,
max: topKState.max,
default: e.target.value
});
}
}
});
var spanMaxTokensElement = React.createElement('span', {
id: 'my_ai_max_tokens_span', style: { fontWeight: 'bold' },
}, maxTokensState.default);
var labelMaxTokensElement = React.createElement('label', { display: 'block' },
'Max Tokens: ', spanMaxTokensElement);
var inputMaxTokensElement = React.createElement('input', {
id: 'my_ai_max_tokens', type: 'range',
min: maxTokensState.min,
max: maxTokensState.max,
step: 1,
value: maxTokensState.default,
style: { marginBottom: '10px', display: 'block', width: '95%' },
onChange: (e) => {
setMaxTokensState({
min: maxTokensState.min,
max: maxTokensState.max,
default: e.target.value
});
}
});
// Array of model config elements (temperature, top p, top k, and max tokens)
var modelConfigElements = [
labelTemperatureElement, inputTemperatureElement,
labelTopPElement, inputTopPElement,
labelTopKElement, inputTopKElement,
labelMaxTokensElement, inputMaxTokensElement
];
var modelConfigElement = React.createElement('div', {
id: 'my_ai_model_config', display: 'block', style: { marginBottom: '10px' }
}, ...modelConfigElements)
var myAiElement = React.createElement('div', {
style: { paddingLeft: '16px', paddingRight: '16px', marginTop: '20px' },
id: 'my_ai_elements_container'
},
labelSelectModelElement,
selectElement,
labelPromptElement,
inputPromptElement,
modelConfigElement,
buttonElement
); // myAiElement
var pluginInfo = React.createElement(window.wp.editPost.PluginSidebar, {
name: 'my-ai-sidebar-element',
title: 'My AI Content Generator',
icon: 'welcome-write-blog'
}, myAiElement);
return pluginInfo;
}
});
}
my_ai_sidebar_init();
enqueue_block_editor_assets
. Modify my-ai-content-generator.php
and add following code at the end of the file.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
add_action('enqueue_block_editor_assets', function() {
// Script dependencies
$dependencies = ['react', 'wp-blocks', 'wp-editor'];
// URL to js/my-ai-sidebar.js
$script_url = plugin_dir_url(__FILE__) . 'js/my-ai-sidebar.js';
// Enqueue script and use version 1.0.0 as a cache buster
wp_enqueue_script('my-ai-sidebar', $script_url, $dependencies, '1.0.0', true);
// Get current selected foundation models from the database
$selected_foundation_models = get_option('my_ai_selected_foundation_models', []);
// Add inline script so wp-ai-sidebar.js can set the selected foundation models
$javascript_line = sprintf('var myAiSelectedFoundationModels = %s;', json_encode($selected_foundation_models));
wp_add_inline_script('my-ai-sidebar', $javascript_line, 'before');
});
my-ai-content-generator
directory should look like following:1
ls -1 *
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
composer.json
composer.lock
my-ai-content-generator.php <- modified
my-ai-rest-api.php
js:
my-ai-sidebar.js <- new file
vendor:
autoload.php
...
views:
aws-credentials-page.php
foundation-models-page.php

- Select the model, e.g
anthropic.claude-3-haiku-[version]
- Enter your input prompt, e.g “Write an extensive, optimized SEO article with a minimum of 2,000 words about starting a career as a web developer. Provide a long overview of the benefits of being a web developer (include stats if possible) and the challenges of being a web developer. Provide pro-tips on how to become a web developer and which programming language to choose as a beginner.”
- Set the temperature to 0.8, Top P to 0.9, Top K to 200 and Max tokens to 2048
- Click Generate
mistral.mistral-large-[version]
and click Generate button. You should have a different result each time.
- IAM user
wp-ai-user
- Deactivate Amazon Bedrock foundation models you don't need
Any opinions in this post are those of the individual author and may not reflect the opinions of AWS.