Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,31 @@ composer require wp-plugin/alt-text-generator-gpt-vision

## For Developers

### Programmatic usage via Abilities API

**Ability:** `acpl/generate-alt-text`

```php
$ability = wp_get_ability('acpl/generate-alt-text');
if (!$ability) {
return;
}

$result = $ability->execute([
'attachment_id' => 456, // Required. Image attachment ID.
'user_prompt' => 'Write the alt text in Polish.', // Optional. Extra AI instructions.
'save' => false, // Optional. False returns only; true saves to attachment metadata. Default false.
]);

if (is_wp_error($result)) {
// Handle error.
return;
}

$attachment_id = $result['attachment_id']; // 456
$alt_text = $result['alt']; // Generated alt text.
```

### Filters

#### `acpl/ai_alt_generator/system_prompt`
Expand Down
2 changes: 2 additions & 0 deletions alt-text-generator-gpt-vision.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
* @package Acpl\AltGenerator
*/

use Acpl\AltGenerator\Abilities;
use Acpl\AltGenerator\Admin;
use Acpl\AltGenerator\AltGeneratorPlugin;

Expand All @@ -27,6 +28,7 @@
require __DIR__ . '/vendor/autoload.php';

AltGeneratorPlugin::init(__FILE__);
Abilities::init();

if (is_admin()) {
Admin::init();
Expand Down
106 changes: 106 additions & 0 deletions includes/Abilities.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
<?php

namespace Acpl\AltGenerator;

use WP_Error;

class Abilities {
public const CATEGORY = 'acpl-alt-generator';
public const GENERATE_ALT_TEXT = 'acpl/generate-alt-text';

public static function init(): void {
add_action('wp_abilities_api_categories_init', self::register_category(...));
add_action('wp_abilities_api_init', self::register(...));
}

private static function register_category(): void {
wp_register_ability_category(self::CATEGORY, [
'label' => __('Image Alt Text Generator', 'alt-text-generator-gpt-vision'),
'description' => __('Tools for generating alt text for images.', 'alt-text-generator-gpt-vision'),
]);
}

private static function register(): void {
wp_register_ability(self::GENERATE_ALT_TEXT, [
'label' => __('Generate image alternative text', 'alt-text-generator-gpt-vision'),
'description' => __(
'Generates alt text for a WordPress image attachment. By default, returns the text only. Set save to true to also update the attachment metadata.',
'alt-text-generator-gpt-vision',
),
'category' => self::CATEGORY,
'execute_callback' => self::execute_generate_alt(...),
'permission_callback' => self::can_generate_alt(...),
'input_schema' => [
'type' => 'object',
'properties' => [
'attachment_id' => [
'type' => 'integer',
'description' => __('WordPress image attachment ID.', 'alt-text-generator-gpt-vision'),
],
'user_prompt' => [
'type' => 'string',
'description' => __(
'Optional extra instructions for the generated alt text.',
'alt-text-generator-gpt-vision',
),
],
'save' => [
'type' => 'boolean',
'default' => false,
'description' => __(
'Whether to save the generated alt text to attachment metadata. Defaults to false.',
'alt-text-generator-gpt-vision',
),
],
],
'required' => ['attachment_id'],
],
'output_schema' => [
'type' => 'object',
'properties' => [
'attachment_id' => [
'type' => 'integer',
'description' => __('Processed attachment ID.', 'alt-text-generator-gpt-vision'),
],
'alt' => [
'type' => 'string',
'description' => __('Generated alt text.', 'alt-text-generator-gpt-vision'),
],
],
'required' => ['attachment_id', 'alt'],
],
'meta' => [
'show_in_rest' => true,
],
]);
}

private static function can_generate_alt(array $args): bool {
if (!empty($args['save'])) {
return current_user_can('edit_post', (int) $args['attachment_id']);
}

return current_user_can('edit_posts');
}

private static function execute_generate_alt(array $args): array|WP_Error {
$attachment_id = (int) $args['attachment_id'];
$save_alt = !empty($args['save']);
$user_prompt = (string) ($args['user_prompt'] ?? '');

if ($save_alt) {
$alt_text = AltGenerator::generate_and_set_alt_text($attachment_id, $user_prompt);
} else {
$alt_text = AltGenerator::generate_alt_text($attachment_id, $user_prompt);
}

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

return [
'attachment_id' => $attachment_id,
'alt' => $alt_text,
];
}
}
47 changes: 0 additions & 47 deletions includes/ApiController.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,65 +3,18 @@
namespace Acpl\AltGenerator;

use WP_Error;
use WP_REST_Request;
use WP_REST_Response;
use WP_REST_Server;

class ApiController {
public static function init(): void {
register_rest_route('acpl/alt-text-generator', '/alt-text', [
'methods' => WP_REST_Server::CREATABLE,
'args' => [
'attachment_id' => [
'required' => true,
'type' => 'integer',
],
'user_prompt' => [
'required' => false,
'type' => 'string',
'sanitize_callback' => 'sanitize_textarea_field',
],
'save' => [
'required' => false,
'type' => 'boolean',
'default' => false,
'description' => esc_html__(
'Saves the generated alt text to the image when enabled.',
'alt-text-generator-gpt-vision',
),
],
],
'callback' => [self::class, 'generate_alt_text'],
'permission_callback' => static fn() => current_user_can('edit_posts'),
]);
register_rest_route('acpl/alt-text-generator', '/vision-models', [
'methods' => WP_REST_Server::READABLE,
'callback' => [self::class, 'get_supported_models'],
'permission_callback' => static fn() => current_user_can('manage_options'),
]);
}

public static function generate_alt_text(WP_REST_Request $request): WP_REST_Response|WP_Error {
$attachment_id = (int) $request->get_param('attachment_id');
$save_alt = (bool) $request->get_param('save');
$user_prompt = (string) ($request->get_param('user_prompt') ?? '');

if ($save_alt) {
$alt_text = AltGenerator::generate_and_set_alt_text($attachment_id, $user_prompt);
} else {
$alt_text = AltGenerator::generate_alt_text($attachment_id, $user_prompt);
}

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

return new WP_REST_Response([
'img_id' => $attachment_id,
'alt' => $alt_text,
]);
}

public static function get_supported_models(): WP_Error|WP_REST_Response {
return new WP_REST_Response(ModelHelper::get_supported_models());
}
Expand Down
3 changes: 2 additions & 1 deletion src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export const GENERATE_API_PATH = 'acpl/alt-text-generator/alt-text';
export const GENERATE_API_PATH =
'wp-abilities/v1/abilities/acpl/generate-alt-text/run';
export const LIST_MODELS_API_PATH = 'acpl/alt-text-generator/vision-models';
export const BULK_ACTION_OPTION_VALUE = 'generate_alt_text';
19 changes: 11 additions & 8 deletions src/utils/generateAltText.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,35 @@
import apiFetch from '@wordpress/api-fetch';
import { GENERATE_API_PATH } from '../constants';

interface Input {
attachment_id: number;
save: boolean;
user_prompt?: string;
}

export default async (
attachmentId: number,
save: boolean = false,
userPrompt?: string,
signal?: AbortSignal | null,
) => {
const requestData: {
attachment_id: number;
save: boolean;
user_prompt?: string;
} = {
const input: Input = {
attachment_id: attachmentId,
save,
};

if (userPrompt?.length) {
requestData.user_prompt = userPrompt;
input.user_prompt = userPrompt;
}

return apiFetch<{ alt: string; img_id: number }>({
// Using apiFetch directly because `executeAbility` from `@wordpress/abilities` lacks `AbortSignal` support.
return apiFetch<{ alt: string; attachment_id: number }>({
path: GENERATE_API_PATH,
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
data: requestData,
data: { input },
signal,
})
.then((response) => {
Expand Down