-
Notifications
You must be signed in to change notification settings - Fork 88
Expand file tree
/
Copy pathExcerpt_Generation.php
More file actions
265 lines (229 loc) · 6.75 KB
/
Excerpt_Generation.php
File metadata and controls
265 lines (229 loc) · 6.75 KB
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
<?php
/**
* Excerpt generation WordPress Ability implementation.
*
* @package WordPress\AI
*/
declare( strict_types=1 );
namespace WordPress\AI\Abilities\Excerpt_Generation;
use WP_Error;
use WordPress\AI\Abstracts\Abstract_Ability;
use function WordPress\AI\get_post_context;
use function WordPress\AI\get_preferred_models_for_text_generation;
use function WordPress\AI\normalize_content;
/**
* Excerpt generation WordPress Ability.
*
* @since 0.2.0
*/
class Excerpt_Generation extends Abstract_Ability {
/**
* {@inheritDoc}
*
* @since x.x.x
*/
protected function guideline_categories(): array {
return array( 'site', 'copy' );
}
/**
* {@inheritDoc}
*
* @since 0.2.0
*/
protected function input_schema(): array {
return array(
'type' => 'object',
'properties' => array(
'content' => array(
'type' => 'string',
'sanitize_callback' => 'sanitize_text_field',
'description' => esc_html__( 'Content to generate an excerpt suggestion for.', 'ai' ),
),
'context' => array(
'type' => 'string',
'sanitize_callback' => 'sanitize_text_field',
'description' => esc_html__( 'Additional context to use when generating an excerpt suggestion for the content. This can either be a string of additional context or can be a post ID that will then be used to get context from that post (if it exists). If no content is provided but a valid post ID is used here, the content from that post will be used.', 'ai' ),
),
),
);
}
/**
* {@inheritDoc}
*
* @since 0.2.0
*/
protected function output_schema(): array {
return array(
'type' => 'string',
'description' => esc_html__( 'Generated excerpt.', 'ai' ),
);
}
/**
* {@inheritDoc}
*
* @since 0.2.0
*/
protected function execute_callback( $input ) {
// Default arguments.
$args = wp_parse_args(
$input,
array(
'content' => null,
'context' => null,
),
);
// If a post ID is provided, ensure the post exists before using its' content.
if ( is_numeric( $args['context'] ) ) {
$post = get_post( (int) $args['context'] );
if ( ! $post ) {
return new WP_Error(
'post_not_found',
/* translators: %d: Post ID. */
sprintf( esc_html__( 'Post with ID %d not found.', 'ai' ), absint( $args['context'] ) )
);
}
// Get the post context.
$context = get_post_context( $post->ID );
$content = $context['content'] ?? '';
unset( $context['content'] );
// Default to the passed in content if it exists.
if ( $args['content'] ) {
$content = normalize_content( $args['content'] );
}
} else {
$content = normalize_content( $args['content'] ?? '' );
$context = $args['context'] ?? '';
}
// If we have no content, return an error.
if ( empty( $content ) ) {
return new WP_Error(
'content_not_provided',
esc_html__( 'Content is required to generate an excerpt suggestion.', 'ai' )
);
}
// Generate the excerpt.
$result = $this->generate_excerpt( $content, $context );
// If we have an error, return it.
if ( is_wp_error( $result ) ) {
return $result;
}
// If we have no results, return an error.
if ( empty( $result ) ) {
return new WP_Error(
'no_results',
esc_html__( 'No excerpt suggestion was generated.', 'ai' )
);
}
// Return the excerpts in the format the Ability expects.
return sanitize_textarea_field( trim( $result, ' "\'' ) );
}
/**
* {@inheritDoc}
*
* @since 0.2.0
*/
protected function permission_callback( $args ) {
$post_id = isset( $args['context'] ) && is_numeric( $args['context'] ) ? absint( $args['context'] ) : null;
if ( $post_id ) {
$post = get_post( $post_id );
// Ensure the post exists.
if ( ! $post ) {
return new WP_Error(
'post_not_found',
/* translators: %d: Post ID. */
sprintf( esc_html__( 'Post with ID %d not found.', 'ai' ), absint( $post_id ) )
);
}
// Ensure the user has permission to edit this particular post.
if ( ! current_user_can( 'edit_post', $post_id ) ) {
return new WP_Error(
'insufficient_capabilities',
esc_html__( 'You do not have permission to generate excerpts for this post.', 'ai' )
);
}
// Ensure the post type is allowed in REST endpoints.
$post_type = get_post_type( $post_id );
if ( ! $post_type ) {
return false;
}
$post_type_obj = get_post_type_object( $post_type );
if ( ! $post_type_obj || empty( $post_type_obj->show_in_rest ) ) {
return false;
}
} elseif ( ! current_user_can( 'edit_posts' ) ) {
// Ensure the user has permission to edit posts in general.
return new WP_Error(
'insufficient_capabilities',
esc_html__( 'You do not have permission to generate excerpts.', 'ai' )
);
}
return true;
}
/**
* {@inheritDoc}
*
* @since 0.2.0
*/
protected function meta(): array {
return array(
'show_in_rest' => true,
);
}
/**
* Generate an excerpt suggestion from the given content.
*
* @since 0.2.0
*
* @param string $content The content to generate an excerpt from.
* @param string|array<string, string> $context Additional context to use.
* @return string|\WP_Error The generated excerpt, or a WP_Error if there was an error.
*/
protected function generate_excerpt( string $content, $context ) {
// Convert the context to a string if it's an array.
if ( is_array( $context ) ) {
$context = implode(
"\n",
array_map(
static function ( $key, $value ) {
return sprintf(
'%s: %s',
ucwords( str_replace( '_', ' ', $key ) ),
$value
);
},
array_keys( $context ),
$context
)
);
}
$content = '<content>' . $content . '</content>';
// If we have additional context, add it to the content.
if ( $context ) {
$content .= "\n\n<additional-context>" . $context . '</additional-context>';
}
$prompt_builder = $this->get_prompt_builder( $content );
if ( is_wp_error( $prompt_builder ) ) {
return $prompt_builder;
}
// Generate an excerpt using the AI client.
return $prompt_builder->generate_text();
}
/**
* Gets a prompt builder for generating an excerpt.
*
* @since 0.7.0
*
* @param string $prompt The prompt to generate an excerpt from.
* @return \WP_AI_Client_Prompt_Builder|\WP_Error The prompt builder, or a WP_Error on failure.
*/
private function get_prompt_builder( string $prompt ) {
$prompt_builder = wp_ai_client_prompt( $prompt )
->using_system_instruction( $this->get_system_instruction() )
->using_temperature( 0.7 )
->using_model_preference( ...get_preferred_models_for_text_generation() );
return $this->ensure_text_generation_supported(
$prompt_builder,
esc_html__( 'Excerpt generation failed. Please ensure you have a connected provider that supports text generation.', 'ai' )
);
}
}